Skip to content

fix: sort LicenseType members by value for reproducible schemas - #434

Merged
yarikoptic merged 1 commit into
masterfrom
fix-license-type-order
Jul 31, 2026
Merged

fix: sort LicenseType members by value for reproducible schemas#434
yarikoptic merged 1 commit into
masterfrom
fix-license-type-order

Conversation

@candleindark

Copy link
Copy Markdown
Member

Fixes #433.

Problem

LicenseType is built by iterating Config.licenses, which is a set. The order of iterating a set of Enum members depends on PYTHONHASHSEED, which CPython randomizes per process, so the member order of LicenseType varied from process to process.

That member order is what fixes the order of the elements in the enum array of the LicenseType definition under $defs, which is emitted into all four generated JSON Schemas (dandiset.json, published-dandiset.json, asset.json, published-asset.json). Two invocations of tools/pubschemata.py on the same commit could therefore emit different files.

Fix

Sort the members by value when building the enum. Config.licenses stays a set, with the sort applied at the single point where the order becomes observable. Validation behavior is unchanged, since JSON Schema enum is order-insensitive.

Merge order

Recommend merging to master before #419 is released. This PR carries no DANDI_SCHEMA_VERSION bump, since #419 already bumps it to 0.8.0. The fix settles the order on ["spdx:CC-BY-4.0", "spdx:CC0-1.0"], which differs from what dandi/schema releases/0.7.0/ holds, so merging ahead of #419's release lets 0.8.0 be published once with the sorted order.

Test plan

  • LicenseType member order is identical across PYTHONHASHSEED in {0, 1, 2, 3, 4, 5, 42, 1234}
  • tools/pubschemata.py output is byte-identical between seeds 0 and 3; before the fix, all four schema files differed
  • pytest dandischema: 258 passed, 19 skipped
  • pre-commit and mypy clean

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 47.48%. Comparing base (cd26d54) to head (0114c13).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #434   +/-   ##
=======================================
  Coverage   47.48%   47.48%           
=======================================
  Files          19       19           
  Lines        2367     2367           
=======================================
  Hits         1124     1124           
  Misses       1243     1243           
Flag Coverage Δ
unittests 47.48% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@candleindark candleindark added bug Something isn't working patch Increment the patch version when merged labels Jul 29, 2026
Comment thread dandischema/models.py Outdated
Comment on lines +123 to +128
# The `sorted()` call is required, not cosmetic. `Config.licenses` is a `set`,
# and the order of iterating a `set` of `Enum` members depends on
# `PYTHONHASHSEED`, which CPython randomizes per process. Without sorting, the
# member order, and hence the `enum` array of every JSON schema generated from
# these models, varies from run to run. Pydantic emits member values rather
# than names into the `enum` array, which is why `value` is the sort key.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# The `sorted()` call is required, not cosmetic. `Config.licenses` is a `set`,
# and the order of iterating a `set` of `Enum` members depends on
# `PYTHONHASHSEED`, which CPython randomizes per process. Without sorting, the
# member order, and hence the `enum` array of every JSON schema generated from
# these models, varies from run to run. Pydantic emits member values rather
# than names into the `enum` array, which is why `value` is the sort key.
# `sorted()` since `Config.licenses` is a `set`.

is likely the absolute maximum needed here, the rest is too AIsloppy to my taste, or am I missing some detail which might be crucial if omitted?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to point out why ordering is needed here not that Config.licenses is unordered. I agree that the original one is too wordy. I have shorten it to just one line. Additionally, I have removed the change to the following docstring.

Please take another look.

@candleindark
candleindark force-pushed the fix-license-type-order branch from 8333f7b to 1a304c4 Compare July 29, 2026 06:06
`LicenseType` was built by iterating `Config.licenses`, which is a `set`.
The order of iterating a `set` of `Enum` members depends on
`PYTHONHASHSEED`, which CPython randomizes per process, so the member
order of `LicenseType` varied from process to process.

That member order is what fixes the order of the elements in the `enum`
array of the `LicenseType` definition under `$defs`, which is emitted
into all four generated JSON Schemas (`dandiset.json`,
`published-dandiset.json`, `asset.json`, `published-asset.json`). Two
invocations of `tools/pubschemata.py` on the same commit could therefore
emit different files.

Sorting the members by value at the point where the order becomes
observable makes the generated schemas reproducible. `Config.licenses`
remains a `set`, which is the right type for a configuration value that
should be deduplicated and compared order-insensitively.

Validation behavior is unchanged, since JSON Schema `enum` is
order-insensitive and nothing in the code depends on the member order.

Closes #433

Co-Authored-By: Claude Code 2.1.220 / Claude Opus 5 claude-opus-5 <noreply@anthropic.com>
@candleindark
candleindark force-pushed the fix-license-type-order branch from 1a304c4 to 0114c13 Compare July 29, 2026 06:12
@candleindark
candleindark requested a review from yarikoptic July 29, 2026 06:15
@yarikoptic
yarikoptic merged commit 569e279 into master Jul 31, 2026
83 checks passed
@yarikoptic
yarikoptic deleted the fix-license-type-order branch July 31, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working patch Increment the patch version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LicenseType member order is non-deterministic, making generated JSON Schemas unreproducible

2 participants