fix(frame): keep reference-valued properties as IRIs - #161
Merged
Merged
Conversation
Contributor
Release previewMerging this PR would release v1.0.1 (current: Changelog preview (truncated)## v1.0.1 (2026-09-19)
### Bug Fixes
- **frame**: Keep reference-valued properties as IRIs
([`0e04a1a`](https://github.com/OO-LD/oold-python/commit/0e04a1a87b3e3db077a3f76d7252e56ee6cbf621))
### Chores
- Classify as Beta
([`c147a05`](https://github.com/OO-LD/oold-python/commit/c147a05f7665f1ede18f6b48c99a5c0540640d9a))
### Documentation
- **examples**: Optional children extension in wiki_data
([`2f372c3`](https://github.com/OO-LD/oold-python/commit/2f372c3f060fa25770b6194642db4eeca905357c))
Preview via python-semantic-release and conventional commits. |
Contributor
📊 Benchmark ResultsClick to see benchmark comparisonThreshold: 1.3x (30% slower triggers a regression warning) Note: Benchmarks are informational only and won't fail the build. 💡 Tip: Download the |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
schema_to_frame emitted no subframe for reference-valued properties, on the assumption that a referenced IRI carries no local triples. Where the target does carry triples in the same graph, framing embedded it as an object and the framed document stopped validating against the schema the frame came from. Reference signals per OOLD-EXT-68fa: x-oold-range, an IRI-family format, or a term mapped "@type": "@id". Embedding wins where both appear. keyword_alias_keys excludes keys aliasing a JSON-LD keyword: id carries an IRI format and so matches, but a subframe there writes {"@id": {...}}, which pyld rejects. Found through allOf, since the base schema declares the convention. Refs OO-LD/oold-schema#160
simontaurus
force-pushed
the
fix/frame-reference-embed-never
branch
from
September 19, 2026 13:46
5fd1cbe to
0e04a1a
Compare
Contributor
📊 Benchmark ResultsClick to see benchmark comparisonThreshold: 1.3x (30% slower triggers a regression warning) Note: Benchmarks are informational only and won't fail the build. 💡 Tip: Download the |
simontaurus
added a commit
that referenced
this pull request
Sep 19, 2026
OOLD-EXT-6ea3 (SHOULD) wants an IRI-valued property to constrain its lexical form, and OOLD-EXT-1f92 recommends iri-reference - it admits absolute IRIs, compact IRIs and context-relative references alike, which is what instances carry. Our own validator warned on our own output: "IRI reference properties without an iri-reference/uri* format". It is also the second of the three reference signals a frame derivation looks for (OOLD-EXT-68fa), so this keeps the schema side and oold.validation.frame.reference_properties in agreement - the same principle #161 applied to framing. A format the declaration already states is left alone; OSW declares `format: autocomplete` on link properties for its UI.
simontaurus
added a commit
that referenced
this pull request
Sep 27, 2026
* fix: publish the document shape, not the code-generation shape A schema this library emits is the published artifact - rc.3's own from-python.md points users at model_json_schema() - so three internal shapes were leaking into it, and it did not validate against the spec we implement. - a link serialises to an IRI, so its property is `type: string` (or an array of strings). The $ref/allOf form is what generator.preprocess builds so datamodel-code-generator emits Optional[Bar]; published, it described a document the library never writes, and the JSON-LD round trip failed on it - with "@type": "@id" an embedded object loses its properties. Union arms keep their union: they genuinely accept a literal, a reference or an inline object - requiredness is stated by `required` alone; x-oold-required-iri and x-oold-link are field annotations and stay internal. Mirrored for v1 in static.export_schema, which pydantic v1 reaches without the v2 hook - a required property no longer also carries `default: null`, which nothing can satisfy - carry $id up to the document when a self-referential model returns a {"$defs": ..., "$ref": ...} wrapper The legacy binding has no emission hook and is skipped: it is the deprecated opt-out, not what publishes. * feat!: a bare Link[T] annotation is optional Closes #159. All three spellings below are optional to supply; only the explicit argument makes a link required: father: Link["Person"] father: Link["Person"] = OoldField() father: Link["Person"] = OoldField(required=True) "No default means required" reads well in plain Python but is wrong for a link, because requiredness propagates into resolution: resolving a link constructs the target, so a required link makes every stored document lacking it unconstructible - and a self-referential link like father could never be satisfied by a real dataset. Links are declared far more often than they are required, so the terse form is the common case. The bare form still gets an injected OoldField() for its default=None; a link cannot be required at the pydantic level, since its value never reaches validation. * fix: a partial export emits one schema level, composed with allOf The schema hierarchy now mirrors the class hierarchy. PARTIAL was configuration that did nothing: it emitted the same monolithic schema as FULL, with every inherited property inlined and no allOf, whether or not cutoff_base_cls was given. - _export_schema_from_dynamic_model built its "model itself" copy from model_fields, which pydantic has already flattened to include inherited fields. A level is the difference against its bases; restating an inherited property can also relax it, which OOLD-CMP-f3c7 forbids - emit allOf for each composable base. The base $ids were already collected and reached @context only, so the document claimed an inheritance it never declared - the inverse of OOLD-CMP-b926, and OOLD-CMP-e4a3 wants the two in the same order - skip this library's own bases: {"$ref": "LinkedBaseModel"} resolves to nothing. A user's base without an $id keeps being named by its class - drop $defs entries nothing references: the dynamic copy left a stale, flattened definition behind that contradicted the level beside it FULL stays the default and keeps its meaning. * docs: correct what a schema says about a link - requiredness reaches the schema as `required` alone; x-oold-required-iri is a field annotation and stays internal - a bare Link[T] annotation is optional, not required - a link property is `type: string` (or an array of strings), not a $ref to the target; the $ref form belongs to code generation - note the @context terms a link needs, including @container on a strictly array-typed property * test: cover def pruning and the v1 requiredness spelling * fix: declare an IRI-family format on emitted link properties OOLD-EXT-6ea3 (SHOULD) wants an IRI-valued property to constrain its lexical form, and OOLD-EXT-1f92 recommends iri-reference - it admits absolute IRIs, compact IRIs and context-relative references alike, which is what instances carry. Our own validator warned on our own output: "IRI reference properties without an iri-reference/uri* format". It is also the second of the three reference signals a frame derivation looks for (OOLD-EXT-68fa), so this keeps the schema side and oold.validation.frame.reference_properties in agreement - the same principle #161 applied to framing. A format the declaration already states is left alone; OSW declares `format: autocomplete` on link properties for its UI. * fix: derive x-oold-range from the target's location, not its identity x-oold-range is dereferenced - code generation fetches the target schema, a form editor renders the targets a property allows - so it must be where the schema lives. get_cls_iri() answers identity: it merges the $id with the type field's default(s), which are the instances' rdf:type. Deriving the range from it published identities with nothing to fetch at them. wiki_data.Person answered ["http://www.wikidata.org/entity/Q5", "Item:Q5"] and publishes no schema at either, so the emitted range pointed at a Wikidata class. The range now comes from $id alone. A class that does not say where its schema lives contributes none; the property is still marked a reference by its format, the second signal in OOLD-EXT-68fa. Where location and identity coincide nothing changes.
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.
Port of OO-LD/oold-js#3; same defect, since this module is a port of
schema_to_frame.mjs.schema_to_frameemitted no subframe for reference-valued properties, on the assumption stated in the module docstring: a referenced IRI with no local triples stays{"id": ...}. Where the target does carry triples in the same graph, framing pulls them in as an object and the framed document stops validating against the schema the frame was derived from, which declares a string there.OOLD-EXT-68faalready requires@embed: @neverhere, and the worked example in the specification's #framing section already prints it, so both implementations were in breach of an existing MUST.Reference signals:
x-oold-range, an IRI-familyformat(the familyOOLD-EXT-6ea3recommends), or a term mapped"@type": "@id". Embedding wins where a property carries both.keyword_alias_keys
Thing.schema.jsondeclaresidwith"format": "iri"while its context aliasesidto@id, so the reference signals match it. A subframe there writes{"@id": {...}}and pyld fails with "@id" value must be a string. Keys aliasing a JSON-LD keyword are excluded.The alias is searched across the composed schema: after dereferencing,
Contactkeeps Thing's@contexton itsallOfmember rather than at the root, so a root-only scan misses it. This surfaced from the committed corpus, not from a unit test, and the same fix went back into oold-js.Verified
502 passed, 7 skippedacrosstests/test_validation/. Removing only the@neverline fails exactly the two new framing tests, so they are not vacuous.