Skip to content

Commit 49ac4af

Browse files
committed
ensure that hashlib.<name> does not raise AttributeError
1 parent 1e67293 commit 49ac4af

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,17 @@ difflib
230230
(Contributed by Jiahao Li in :gh:`134580`.)
231231

232232

233+
hashlib
234+
-------
235+
236+
* Ensure that hash functions guaranteed to be always *available* exist as
237+
attributes of :mod:`hashlib` even if they will not work at runtime due to
238+
missing backend implementations. For instance, ``hashlib.md5`` will no
239+
longer raise :exc:`AttributeError` if OpenSSL is not available and Python
240+
has been built without MD5 support.
241+
(Contributed by Bénédikt Tran in :gh:`136929`.)
242+
243+
233244
http.client
234245
-----------
235246

Lib/hashlib.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,16 +261,42 @@ def file_digest(fileobj, digest, /, *, _bufsize=2**18):
261261
return digestobj
262262

263263

264+
__logging = None
265+
__logger = None
264266
for __func_name in __always_supported:
265267
# try them all, some may not work due to the OpenSSL
266268
# version not supporting that algorithm.
267269
try:
268270
globals()[__func_name] = __get_hash(__func_name)
269271
except ValueError:
270-
import logging
271-
logging.exception('code for hash %s was not found.', __func_name)
272-
272+
import logging as __logging
273+
if __logger is None:
274+
__logger = __logging.getLogger(__name__)
275+
__logger.warning('hash algorithm %s will not be supported at runtime',
276+
__func_name)
277+
# The following code can be simplified in Python 3.19
278+
# once "string" is removed from the signature.
279+
__code = f'''\
280+
def {__func_name}(data=__UNSET, *, usedforsecurity=True, string=__UNSET):
281+
if data is __UNSET and string is not __UNSET:
282+
import warnings
283+
warnings.warn(
284+
"the 'string' keyword parameter is deprecated since "
285+
"Python 3.15 and slated for removal in Python 3.19; "
286+
"use the 'data' keyword parameter or pass the data "
287+
"to hash as a positional argument instead",
288+
DeprecationWarning, stacklevel=2)
289+
if data is not __UNSET and string is not __UNSET:
290+
raise TypeError("'data' and 'string' are mutually exclusive "
291+
"and support for 'string' keyword parameter "
292+
"is slated for removal in a future version.")
293+
raise ValueError("unsupported hash algorithm {__func_name}")
294+
'''
295+
exec(__code, {"__UNSET": object()}, __locals := {})
296+
globals()[__func_name] = __locals[__func_name]
297+
del __code, __locals
273298

274299
# Cleanup locals()
275300
del __always_supported, __func_name, __get_hash
276301
del __py_new, __hash_new, __get_openssl_constructor
302+
del __logger, __logging
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Ensure that hash functions guaranteed to be always *available* exist as
2+
attributes of :mod:`hashlib` even if they will not work at runtime due to
3+
missing backend implementations. For instance, ``hashlib.md5`` will no
4+
longer raise :exc:`AttributeError` if OpenSSL is not available and Python
5+
has been built without MD5 support. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)