From e5df455087e3b4869b6aafff03121ee2cecef249 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:40:38 +0100 Subject: [PATCH] Move ``stable_hash()`` to a new private module (sphinx.util._serialise) (#12947) --- sphinx/builders/html/_build_info.py | 25 +++---------------------- sphinx/util/_serialise.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 sphinx/util/_serialise.py diff --git a/sphinx/builders/html/_build_info.py b/sphinx/builders/html/_build_info.py index 5b364c0d9..281ad5ab9 100644 --- a/sphinx/builders/html/_build_info.py +++ b/sphinx/builders/html/_build_info.py @@ -2,16 +2,14 @@ from __future__ import annotations -import hashlib -import types from typing import TYPE_CHECKING from sphinx.locale import __ +from sphinx.util._serialise import stable_hash if TYPE_CHECKING: from collections.abc import Set from pathlib import Path - from typing import Any from sphinx.config import Config, _ConfigRebuild from sphinx.util.tags import Tags @@ -57,10 +55,10 @@ class BuildInfo: if config: values = {c.name: c.value for c in config.filter(config_categories)} - self.config_hash = _stable_hash(values) + self.config_hash = stable_hash(values) if tags: - self.tags_hash = _stable_hash(sorted(tags)) + self.tags_hash = stable_hash(sorted(tags)) def __eq__(self, other: BuildInfo) -> bool: # type: ignore[override] return (self.config_hash == other.config_hash and @@ -75,20 +73,3 @@ class BuildInfo: f'tags: {self.tags_hash}\n' ) filename.write_text(build_info, encoding="utf-8") - - -def _stable_hash(obj: Any) -> str: - """Return a stable hash for a Python data structure. - - We can't just use the md5 of str(obj) as the order of collections - may be random. - """ - if isinstance(obj, dict): - obj = sorted(map(_stable_hash, obj.items())) - if isinstance(obj, list | tuple | set | frozenset): - obj = sorted(map(_stable_hash, obj)) - elif isinstance(obj, type | types.FunctionType): - # The default repr() of functions includes the ID, which is not ideal. - # We use the fully qualified name instead. - obj = f'{obj.__module__}.{obj.__qualname__}' - return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest() diff --git a/sphinx/util/_serialise.py b/sphinx/util/_serialise.py new file mode 100644 index 000000000..e6627a8e6 --- /dev/null +++ b/sphinx/util/_serialise.py @@ -0,0 +1,27 @@ +"""Serialise objects to a stable representation.""" + +from __future__ import annotations + +import hashlib +import types +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + + +def stable_hash(obj: Any) -> str: + """Return a stable hash for a Python data structure. + + We can't just use the md5 of str(obj) as the order of collections + may be random. + """ + if isinstance(obj, dict): + obj = sorted(map(stable_hash, obj.items())) + if isinstance(obj, list | tuple | set | frozenset): + obj = sorted(map(stable_hash, obj)) + elif isinstance(obj, type | types.FunctionType): + # The default repr() of functions includes the ID, which is not ideal. + # We use the fully qualified name instead. + obj = f'{obj.__module__}.{obj.__qualname__}' + return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest()