From b6f818f6001523998345db2c07f72ff5a8760265 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:54:54 +0100 Subject: [PATCH] Add ``stable_str()`` (#12948) Co-authored-by: Eric Larson --- sphinx/util/_serialise.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/sphinx/util/_serialise.py b/sphinx/util/_serialise.py index e6627a8e6..11d95ee1f 100644 --- a/sphinx/util/_serialise.py +++ b/sphinx/util/_serialise.py @@ -3,6 +3,7 @@ from __future__ import annotations import hashlib +import json import types from typing import TYPE_CHECKING @@ -25,3 +26,28 @@ def stable_hash(obj: Any) -> str: # We use the fully qualified name instead. obj = f'{obj.__module__}.{obj.__qualname__}' return hashlib.md5(str(obj).encode(), usedforsecurity=False).hexdigest() + + +def stable_str(obj: Any, *, indent: int | None = None) -> str: + """Return a stable string representation of a Python data structure. + + We can't just use ``str(obj)`` as the order of collections may be random. + """ + return json.dumps(_stable_str_prep(obj), indent=indent) + + +def _stable_str_prep(obj: Any) -> dict[str, Any] | list[Any] | str: + if isinstance(obj, dict): + # Convert to a sorted dict + obj = [(_stable_str_prep(k), _stable_str_prep(v)) for k, v in obj.items()] + obj.sort() + return dict(obj) + if isinstance(obj, list | tuple | set | frozenset): + # Convert to a sorted list + return sorted(map(_stable_str_prep, obj)) + if isinstance(obj, type | types.FunctionType): + # The default repr() of functions includes the ID, which is not ideal. + # We use the fully qualified name instead. + return f'{obj.__module__}.{obj.__qualname__}' + # We can't do any better, just use the string representation + return str(obj)