refactor: eliminate multiple value choices in schemaKey Literals - #437
Open
candleindark wants to merge 2 commits into
Open
refactor: eliminate multiple value choices in schemaKey Literals#437candleindark wants to merge 2 commits into
schemaKey Literals#437candleindark wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #437 +/- ##
=======================================
Coverage 47.96% 47.96%
=======================================
Files 19 19
Lines 2427 2427
=======================================
Hits 1164 1164
Misses 1263 1263
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
`Contributor` and `Activity` admitted their subclasses' keys alongside their own. `DandiBaseModel.ensure_schemakey` pins every instance to its own class name, so the extra values were unreachable at runtime, and in the generated JSON Schema they produced a `const` and a contradicting `enum` for the same property: the post-processing in `__get_pydantic_json_schema__` collapses an `enum` into a `const` only when the `enum` holds a single member. The widened `Literal`s were load-bearing for `mypy` rather than for validation, in that they made each subclass override a subtype of the base annotation. Narrowing them therefore needs `# type: ignore[assignment]` on the five subclass overrides, following the precedent set by `Asset.schemaKey`. Co-Authored-By: Claude Code 2.1.223 / claude-opus-5 <noreply@anthropic.com>
candleindark
force-pushed
the
eliminate-multiple-schemaKey-value-choices
branch
from
August 6, 2026 19:55
652c023 to
75c5301
Compare
candleindark
marked this pull request as ready for review
August 6, 2026 21:28
schemaKey LiteralsschemaKey Literals
1 task
…ypes `CommonModel.wasGeneratedBy` was annotated `Sequence[Activity]`, which reads as "any `Activity` subclass" but reduces to "exactly `Activity`" for JSON input, since `ensure_schemakey` rejects a subclass key on `Activity` itself. It now lists the activity types outright: `Activity`, `Project`, `PublishActivity`, and `Session`. `BareAsset` no longer narrows that set. Its `List[Union[Session, Project, Activity]]` admitted `Activity` yet turned away `PublishActivity`, which is inconsistent, as every `PublishActivity` is an `Activity`. LinkML cannot express such a set either once `schemaKey` serves as the type designator, since a class-valued range expands over the class's descendants, whether that class stands as the range itself or as one of the alternatives within an `any_of`. The field is redeclared only for a `title` and a `description`, reworded after `Dandiset.wasGeneratedBy`. The old pair named a class set that no longer holds and treated the value as names, as in "Name of the session, project or activity." `Sequence` gives way to `list`. 23e5685, in #100, introduced it only to sidestep `list`'s invariance for `Dandiset`'s override, which now takes a `# type: ignore[assignment]` instead. Co-Authored-By: Claude Code 2.1.223 / claude-opus-5 <noreply@anthropic.com>
yarikoptic
force-pushed
the
eliminate-multiple-schemaKey-value-choices
branch
from
August 7, 2026 19:30
75c5301 to
e45422b
Compare
Member
|
I added |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eliminates the multiple value choices in the
schemaKeyLiterals ofContributorandActivity, the last two classes that offered more than one.The extra choices were unreachable at runtime, since
ensure_schemakeypins every instance to its own class name, and in the generated JSON Schema they produced aconstand a contradictingenumon the same property. They existed to keepmypyquiet, so removing them costs# type: ignore[assignment]on five subclass overrides, asAsset.schemaKeyalready does.That exposed
wasGeneratedBy, which the second commit adjusts:CommonModel.wasGeneratedBybecomeslist[Union[Activity, Project, PublishActivity, Session]]. Its formerSequence[Activity]reads as "anyActivitysubclass", but for JSON input it accepted onlyActivityitself.BareAssetstops narrowing that set and keeps the field only for a rewordedtitleanddescription.Sequencegives way tolist.Why the multiple value choices are a problem
The
schemaKeypost-processing in__get_pydantic_json_schema__collapsesenumintoconstonly for a single-memberenum, so both classes emitted the two keys at once. ForActivity:A validator has to satisfy both, so the effective constraint was already just
"Activity"while theenumadvertised three values the schema would never accept. Anything reading the schema to enumerate valid keys, a UI or a code generator, saw the wrong set.Why
BareAsset.wasGeneratedBygoes awayList[Union[Session, Project, Activity]]admittedActivityyet turned awayPublishActivity, which is inconsistent, since everyPublishActivityis anActivity.That set is also inexpressible in LinkML once
schemaKeyserves as the type designator: a class-valued range expands over the class's descendants, whether the class stands as the range itself or as an alternative within anany_of. Seedesignates_type: truemakes generated artifacts resolve a superclass-typed slot value to its concrete subtype in the migration playbook.The consequence for this field is recorded there too: the
BareAsset.wasGeneratedByrange override inmodels_merge.yamlis a deliberate no-op.gen-pydanticalready rendersCommonModel.wasGeneratedByas exactly the union proposed here, and the override renders as a nested union that flattens to the same four members. That finding says the override should be revisited once the Pydantic definition is corrected, which is what this PR does.Separately, the old
title,"Name of the session, project or activity.", described anamefield rather than a list of activities, and both strings named a class set that no longer holds. They now followDandiset.wasGeneratedBy:title="Associated activities",description="Activities that generated this asset."Why
Sequencegives way tolistSequencewas never a modeling decision.23e56855, part of #100, the PR that first addedmypyhere, switched both fields fromListtoSequencepurely to sidesteplist's invariance forDandiset's narrowing override, and the name appears nowhere else inmodels.py.Pydantic's Sequences documentation says "In most cases, you will want to use the built-in types (such as
listortuple) as type coercion will apply", reservingSequencefor "when you want to preserve the input type during serialization", which these models do not need given they round-trip through JSON. Concretely,dandi-clicalls.appendonBareAsset.wasGeneratedBy, whichSequencedoes not provide.Both defects entered together, incidentally:
f224281aintroduced all three multi-valueLiterals and23e56855introducedSequencethree minutes later, both in #100, neither with a validation rationale.Behavior change and downstream check
AssetandBareAssetmetadata now accept aPublishActivityinwasGeneratedBy, which the JSON Schema previously rejected. This is a widening only, so nothing previously valid becomes invalid.dandi-cli'sdandi/metadata/core.pycalls.appendonBareAsset.wasGeneratedBy, which type-checks again now that the field is alist.dandi-archivehas no Python reference towasGeneratedBy; it handles metadata as dicts throughdandischema.metadata.validate/migrate. Its frontendwasGeneratedBy(web/src/types/schema.ts, typedProject[]) is theDandisetfield, whose accepted class set is unchanged here:Dandiset.wasGeneratedByonly moves fromSequence[Project]tolist[Project].