|
10 | 10 | import re |
11 | 11 | import sys |
12 | 12 | import time |
| 13 | +import types |
13 | 14 | import warnings |
14 | 15 | from os import path |
15 | 16 | from typing import IO, TYPE_CHECKING, Any |
|
75 | 76 | ] |
76 | 77 |
|
77 | 78 |
|
78 | | -def get_stable_hash(obj: Any) -> str: |
79 | | - """ |
80 | | - Return a stable hash for a Python data structure. We can't just use |
81 | | - the md5 of str(obj) since for example dictionary items are enumerated |
82 | | - in unpredictable order due to hash randomization in newer Pythons. |
| 79 | +def _stable_hash(obj: Any) -> str: |
| 80 | + """Return a stable hash for a Python data structure. |
| 81 | +
|
| 82 | + We can't just use the md5 of str(obj) as the order of collections |
| 83 | + may be random. |
83 | 84 | """ |
84 | 85 | if isinstance(obj, dict): |
85 | | - return get_stable_hash(list(obj.items())) |
86 | | - elif isinstance(obj, (list, tuple)): |
87 | | - obj = sorted(get_stable_hash(o) for o in obj) |
| 86 | + obj = sorted(map(_stable_hash, obj.items())) |
| 87 | + if isinstance(obj, (list, tuple, set, frozenset)): |
| 88 | + obj = sorted(map(_stable_hash, obj)) |
| 89 | + elif isinstance(obj, (type, types.FunctionType)): |
| 90 | + # The default repr() of functions includes the ID, which is not ideal. |
| 91 | + # We use the fully qualified name instead. |
| 92 | + obj = f'{obj.__module__}.{obj.__qualname__}' |
88 | 93 | return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest() |
89 | 94 |
|
90 | 95 |
|
@@ -132,10 +137,10 @@ def __init__( |
132 | 137 |
|
133 | 138 | if config: |
134 | 139 | values = {c.name: c.value for c in config.filter(config_categories)} |
135 | | - self.config_hash = get_stable_hash(values) |
| 140 | + self.config_hash = _stable_hash(values) |
136 | 141 |
|
137 | 142 | if tags: |
138 | | - self.tags_hash = get_stable_hash(sorted(tags)) |
| 143 | + self.tags_hash = _stable_hash(sorted(tags)) |
139 | 144 |
|
140 | 145 | def __eq__(self, other: BuildInfo) -> bool: # type: ignore[override] |
141 | 146 | return (self.config_hash == other.config_hash and |
|
0 commit comments