File: 0010-Make-matplotlib-reproducible.patch

package info (click to toggle)
sphinx-needs 5.1.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 12,056 kB
  • sloc: python: 21,148; javascript: 187; makefile: 93; sh: 29; xml: 10
file content (103 lines) | stat: -rw-r--r-- 4,065 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
From: Christian Bayle <bayle@debian.org>
Date: Sat, 20 Sep 2025 19:40:29 +0200
Subject: Make matplotlib reproducible

---
 sphinx_needs/directives/needbar.py | 12 ++++++++++--
 sphinx_needs/utils.py              | 18 +++++++++++++++++-
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/sphinx_needs/directives/needbar.py b/sphinx_needs/directives/needbar.py
index 833d1a0..838f360 100644
--- a/sphinx_needs/directives/needbar.py
+++ b/sphinx_needs/directives/needbar.py
@@ -472,7 +472,9 @@ def process_needbar(
         # 9. final storage
 
         # We need to calculate an unique bar-image file name
-        hash_value = hashlib.sha256(node.attributes["ids"][0].encode()).hexdigest()[:5]
+        #hash_value = hashlib.sha256(node.attributes["ids"][0].encode()).hexdigest()[:5]
+        from sphinx_needs.utils import stable_hash
+        hash_value = stable_hash(node.attributes["ids"][0], length=8)
         image_node = save_matplotlib_figure(
             app, figure, f"need_bar_{hash_value}", fromdocname
         )
@@ -488,7 +490,13 @@ def process_needbar(
 
         # 10. cleanup matplotlib
         # Reset the style configuration:
-        matplotlib.rcParams = style_previous_to_script_execution
+        #matplotlib.rcParams = style_previous_to_script_execution
+        matplotlib.rcParams.update(style_previous_to_script_execution)
+
+        # Ensure SVG outputs reproducible IDs
+        if hasattr(matplotlib, "rcParams"):
+            matplotlib.rcParams["svg.id"] = "needbar"
+            matplotlib.rcParams["svg.hashsalt"] = "needbar"
 
         # close the figure, to free consumed memory. Otherwise we will get:
         # RuntimeWarning from matplotlib: More than 20 figures have been opened.
diff --git a/sphinx_needs/utils.py b/sphinx_needs/utils.py
index a9398bc..52e159f 100644
--- a/sphinx_needs/utils.py
+++ b/sphinx_needs/utils.py
@@ -5,6 +5,7 @@ import importlib
 import operator
 import os
 import re
+import hashlib
 from dataclasses import dataclass
 from functools import lru_cache, reduce, wraps
 from typing import TYPE_CHECKING, Any, Callable, Protocol, TypeVar
@@ -24,6 +25,9 @@ from sphinx_needs.views import NeedsAndPartsListView, NeedsView
 
 if TYPE_CHECKING:
     import matplotlib
+    if hasattr(matplotlib, "rcParams"):
+        matplotlib.rcParams["svg.hashsalt"] = "sphinx-needs"
+        matplotlib.rcParams["svg.id"] = "sphinx-needs"
     from matplotlib.figure import FigureBase
 
 
@@ -45,6 +49,11 @@ MONTH_NAMES = [
     "December",
 ]
 
+def stable_hash(value: str, length: int = 8) -> str:
+    """
+    Deterministic replacement for Python's built-in hash().
+    """
+    return hashlib.sha1(str(value).encode("utf-8")).hexdigest()[:length]
 
 def split_need_id(need_id_full: str) -> tuple[str, str | None]:
     """A need id can be a combination of a main id and a part id,
@@ -386,6 +395,7 @@ def import_matplotlib() -> matplotlib | None:
         return None
     if not os.environ.get("DISPLAY"):
         matplotlib.use("Agg")
+    matplotlib.pyplot.rcParams['svg.hashsalt'] = 'reproducible-salt'
     return matplotlib
 
 
@@ -398,6 +408,12 @@ def save_matplotlib_figure(
     image_folder = os.path.join(builder.outdir, builder.imagedir)
     os.makedirs(image_folder, exist_ok=True)
 
+    # Ensure reproducible SVG output (only matters for savefig)
+    import matplotlib
+    if hasattr(matplotlib, "rcParams"):
+        matplotlib.rcParams["svg.hashsalt"] = "sphinx-needs"
+        matplotlib.rcParams["svg.id"] = "sphinx-needs"
+
     # Determine a common mimetype between matplotlib and the builder.
     matplotlib_types = {
         "image/svg+xml": "svg",
@@ -418,7 +434,7 @@ def save_matplotlib_figure(
 
     abs_file_path = os.path.join(image_folder, f"{basename}.{ext}")
     if abs_file_path not in env.images:
-        figure.savefig(os.path.join(env.app.srcdir, abs_file_path))
+        figure.savefig(os.path.join(env.app.srcdir, abs_file_path), metadata={'Date': None})
         env.images.add_file(fromdocname, abs_file_path)
 
     image_node = nodes.image()