diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 4d4fb77ad4f030..8068977375177f 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -230,6 +230,17 @@ difflib (Contributed by Jiahao Li in :gh:`134580`.) +hashlib +------- + +* Ensure that hash functions guaranteed to be always *available* exist as + attributes of :mod:`hashlib` even if they will not work at runtime due to + missing backend implementations. For instance, ``hashlib.md5`` will no + longer raise :exc:`AttributeError` if OpenSSL is not available and Python + has been built without MD5 support. + (Contributed by Bénédikt Tran in :gh:`136929`.) + + http.client ----------- diff --git a/Lib/hashlib.py b/Lib/hashlib.py index a7db778b716537..8e7083ba692348 100644 --- a/Lib/hashlib.py +++ b/Lib/hashlib.py @@ -261,16 +261,39 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18): return digestobj +__logging = None for __func_name in __always_supported: # try them all, some may not work due to the OpenSSL # version not supporting that algorithm. try: globals()[__func_name] = __get_hash(__func_name) - except ValueError: - import logging - logging.exception('code for hash %s was not found.', __func_name) - + except ValueError as __exc: + import logging as __logging + __logging.error('hash algorithm %s will not be supported at runtime ' + '[reason: %s]', __func_name, __exc) + # The following code can be simplified in Python 3.19 + # once "string" is removed from the signature. + __code = f'''\ +def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET): + if data is __UNSET and string is not __UNSET: + import warnings + warnings.warn( + "the 'string' keyword parameter is deprecated since " + "Python 3.15 and slated for removal in Python 3.19; " + "use the 'data' keyword parameter or pass the data " + "to hash as a positional argument instead", + DeprecationWarning, stacklevel=2) + if data is not __UNSET and string is not __UNSET: + raise TypeError("'data' and 'string' are mutually exclusive " + "and support for 'string' keyword parameter " + "is slated for removal in a future version.") + raise ValueError("unsupported hash algorithm {__func_name}") +''' + exec(__code, {"__UNSET": object()}, __locals := {}) + globals()[__func_name] = __locals[__func_name] + del __exc, __code, __locals # Cleanup locals() del __always_supported, __func_name, __get_hash del __py_new, __hash_new, __get_openssl_constructor +del __logging diff --git a/Misc/NEWS.d/next/Library/2025-07-21-16-13-20.gh-issue-136929.obKZ2S.rst b/Misc/NEWS.d/next/Library/2025-07-21-16-13-20.gh-issue-136929.obKZ2S.rst new file mode 100644 index 00000000000000..31b8563f9d8523 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-21-16-13-20.gh-issue-136929.obKZ2S.rst @@ -0,0 +1,5 @@ +Ensure that hash functions guaranteed to be always *available* exist as +attributes of :mod:`hashlib` even if they will not work at runtime due to +missing backend implementations. For instance, ``hashlib.md5`` will no +longer raise :exc:`AttributeError` if OpenSSL is not available and Python +has been built without MD5 support. Patch by Bénédikt Tran.