You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
load_entity regenerates a class for a category that a packaged class already registers, and overwrites it in the type lookup #138
When load_entity autofetches a schema, it compiles a new class into osw.model.entity even if a packaged class is already registered for
that category IRI. The generated class then takes over the entry in
oold's type lookup, so every later resolution by IRI returns the
generated class instead of the packaged one.
Both classes claim the same category IRI, and the generated one holds
the slot.
Why it matters
Packaged behaviour is lost. The packaged classes carry typed
fields and helpers (unit enums, to_base(), and so on) that the
generated ones do not necessarily reproduce. Code that resolves a
reference gets the generated class and silently loses them.
References are dropped on assignment. A field declared as the
packaged class receives an instance of the generated class, so isinstance fails, and pydantic re-constructs the value from dict(value) rather than accepting it. For oold range fields the
value in __dict__ is None (the IRI lives in __iris__), so the
reference is lost with no error. Details and a reproduction in Reference is lost when a resolved node is re-validated into a different class for the same category OO-LD/oold-python#106.
Before fetching and compiling a schema, look the category IRI up in
oold's type registry and reuse the registered class if there is one.
Roughly:
cls=lookup_registered_type(category_iri) # oold _typesifclsisNone:
cls=fetch_and_compile(category_iri) # current path
Two details worth deciding explicitly:
Do not silently overwrite. If a class is generated for an IRI that
is already registered, that is a conflict. Either keep the first
registration, or log it loudly. Silent last-one-wins is what makes
this hard to spot.
autofetch_schema=False is not a workaround. Passing it for the
top-level load does not help, because the resolver used for nested
references calls load_entity with default parameters.
Workaround
Pass model_to_use explicitly for every load, including anything reached
by following a reference, or re-load second-level entities directly by
IRI.
(1) asks "does osw.model.entity have a class with this name", which is
the wrong question twice over: it is keyed by class name rather than by
category IRI, and it only ever looks in osw.model.entity, so a packaged
class registered for the category is invisible. (2) then always takes the
generated class, even when a packaged one exists.
Patch
Consult oold's registry by category IRI before deciding to compile:
fromoold.modelimport_typesas_oold_types_v2fromoold.model.v1import_typesas_oold_types_v1def_registered_model(category_iri: str):
"""Return a class already registered for this category IRI, if any. osw models are pydantic v1, so prefer that registry. """return_oold_types_v1.get(category_iri) or_oold_types_v2.get(category_iri)
in the per-category loop:
cls_by_category= {}
forcategoryinjsondata["type"]:
schema= ... # unchangedschemas.append(schema)
cls_name=schema["title"]
ifparam.model_to_use:
continueregistered=_registered_model(category)
ifregisteredisnotNone:
cls_by_category[category] =registeredcontinue# already have a class: do not compileifnothasattr(model, cls_name):
ifparam.autofetch_schema:
self.fetch_schema(...)
ifnothasattr(model, cls_name):
schemas_fetched=Falseprint(...)
Independently of the above, fetch_schema should refuse to take a
registry slot that is already claimed by a different class, or at least
log it. Silent last-one-wins is what makes this hard to find: the
packaged class is imported, then a later autofetch replaces it, and
nothing in the program reports that the class identity for a category
changed.
Two side benefits
One less page fetch per category, since the jsonschema slot only
needs fetching when no class is registered. Worth doing, but keep it
separate from the correctness fix, since schemas is also used to
build the multiple-inheritance base list.
The name-keyed check goes away, so two categories whose schema titles
happen to collide stop aliasing each other.
Test
Register a class for a category, then load_entity an instance of it
with autofetch_schema=True, and assert type(entity) is the_registered_class and that osw.model.entity was not given a new class for it.
Activity
simontaurus commentedon Aug 7, 2026
Proposed fix
Where it goes wrong
core.py, inload_entity:(1) asks "does
osw.model.entityhave a class with this name", which isthe wrong question twice over: it is keyed by class name rather than by
category IRI, and it only ever looks in
osw.model.entity, so a packagedclass registered for the category is invisible. (2) then always takes the
generated class, even when a packaged one exists.
Patch
Consult oold's registry by category IRI before deciding to compile:
in the per-category loop:
and at construction:
Do not overwrite an existing registration
Independently of the above,
fetch_schemashould refuse to take aregistry slot that is already claimed by a different class, or at least
log it. Silent last-one-wins is what makes this hard to find: the
packaged class is imported, then a later autofetch replaces it, and
nothing in the program reports that the class identity for a category
changed.
Two side benefits
jsonschemaslot onlyneeds fetching when no class is registered. Worth doing, but keep it
separate from the correctness fix, since
schemasis also used tobuild the multiple-inheritance base list.
happen to collide stop aliasing each other.
Test
Register a class for a category, then
load_entityan instance of itwith
autofetch_schema=True, and asserttype(entity) is the_registered_classand thatosw.model.entitywas not given a new class for it.fix(core): prefer a registered class over a generated one