diff --git a/pip_requirements.txt b/pip_requirements.txt index c57ddd86..964a7866 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -11,5 +11,11 @@ mkdocs-literate-nav mkdocs-autorefs mkdocs-redirects +# Pinned so the committed diagram figures stay byte-reproducible: pydot's SVG +# writer changed between 3.x and 4.x (XML prolog, attribute order, cluster +# titles), which reshuffles every generated SVG without changing the picture. +# See scripts/gen_pipeline_diagrams.py. +pydot==3.0.4 + # DataJoint from master for tutorials git+https://github.com/datajoint/datajoint-python.git@master diff --git a/scripts/gen_pipeline_diagrams.py b/scripts/gen_pipeline_diagrams.py index 16c145ec..4d5819c2 100644 --- a/scripts/gen_pipeline_diagrams.py +++ b/scripts/gen_pipeline_diagrams.py @@ -49,14 +49,11 @@ by the pydot that produced the committed figures and as literal spaces by 4.0.1, which shows up as a whole-file diff with no visual change. Compare rendered content, not bytes, when the pydot version moves. Nothing pins pydot. -- **One collapsed edge is traversal-order dependent.** A collapsed edge inherits - the attributes of whichever foreign key in its bundle is visited first - (``diagram.py``, ``_collapse_graph``: ``if not new_graph.has_edge(...)``), with - no aggregation over the bundle. Where a bundle mixes a primary and a secondary - foreign key — ``lab -> session`` here, which bundles ``Subject -> Session`` - (primary) and ``User -> Session`` (secondary) — the edge renders solid or - dashed depending on order alone. The committed figure has it solid; this script - produces dashed. Both are outputs of the same renderer. +- **Node emission order is hash-seeded.** ``dj.Diagram`` iterates sets of + table-name strings when emitting, so node and cluster order follows the + per-process string hash. This script therefore re-execs itself with + ``PYTHONHASHSEED=0``; without that, two identical runs disagree on order while + rendering the same picture. Upstream: datajoint-python#1551. A non-empty diff after a DataJoint upgrade is the signal to review the notation and the surrounding prose together — see issue #246. @@ -68,6 +65,21 @@ import tempfile from pathlib import Path +# Re-exec with a fixed string-hash seed before anything imports datajoint. +# +# dj.Diagram iterates sets of table-name strings on its emission path, so the +# order nodes and clusters land in the SVG follows str.__hash__, which Python +# salts per process (PEP 456). The rendered picture is identical either way — +# same coordinates, colors and edges — but the bytes are not, which makes +# --check unusable and buries real notation changes in reordering noise. +# Upstream: datajoint/datajoint-python#1551. Remove this block once that lands. +# +# PYTHONHASHSEED only takes effect at interpreter start, so it cannot be set +# from inside the process. +if os.environ.get("PYTHONHASHSEED") != "0": + os.environ["PYTHONHASHSEED"] = "0" + os.execv(sys.executable, [sys.executable, *sys.argv]) + import datajoint as dj sys.path.insert(0, str(Path(__file__).resolve().parent)) diff --git a/src/images/imaging-schema.svg b/src/images/imaging-schema.svg index 7bda33a1..99fc25ef 100644 --- a/src/images/imaging-schema.svg +++ b/src/images/imaging-schema.svg @@ -1,11 +1,4 @@ - - - - - + + \ No newline at end of file diff --git a/src/images/pipeline-modules-collapsed.svg b/src/images/pipeline-modules-collapsed.svg index 66ab8dc9..d1f2e8a6 100644 --- a/src/images/pipeline-modules-collapsed.svg +++ b/src/images/pipeline-modules-collapsed.svg @@ -1,11 +1,4 @@ - - - - - + + \ No newline at end of file diff --git a/src/images/pipeline-modules.svg b/src/images/pipeline-modules.svg index 213c7c77..b89778a4 100644 --- a/src/images/pipeline-modules.svg +++ b/src/images/pipeline-modules.svg @@ -1,11 +1,4 @@ - - - - - + + \ No newline at end of file