Skip to content

feat: public controller registry lookup, with one precedence rule - #173

Merged
simontaurus merged 6 commits into
mainfrom
feat/controller-type-lookup
Oct 15, 2026
Merged

simontaurus merged 6 commits into
mainfrom
feat/controller-type-lookup

Conversation

@simontaurus

Copy link
Copy Markdown
Contributor

Makes the controller registry usable from outside oold, and gives resolution one
precedence rule instead of three.

Public lookup

A caller holding an entity could not find the controller that implements it
without knowing the concrete class, so downstream packages reached into
_controller_types directly. Added, for both pydantic layers:

  • register_type() / register_controller_type()
  • get_registered_type() / get_registered_controller_types()
  • registered_types() / registered_controller_types()
  • find_controller_type(type_iris, base=None)
  • oold.backend.auth.registered_credentials()

One precedence rule

Previously the three accessors disagreed: get_registered_controller_types
returned earliest-first, find_controller_type last-first, and resolve_type
refused to resolve at all once a second controller was registered - which turned
the documented override mechanism into a way of disabling controllers.

  • The controller registered last wins, in every accessor.
  • A registered controller replaces its pure data model during resolution, in
    from_json / from_jsonld and for inline linked documents, falling back to
    the pure model when the controller cannot be built from the document.
  • The two layers never mix: a lookup is answered from the registry of the layer
    the model registry belongs to. resolve_type took the v2 controller registry
    even when resolving a v1 type.

Registration

  • __oold_register__ = False keeps a class out of both registries. For a
    controller that reuses another model without being its handler - an in-memory
    buffer over a local-file model - which would otherwise compete for that
    model's type IRI.
  • The v1 register_type gained the inherited-IRI guard the v2 path had: a
    subclass that only narrows a field reported its parent's IRI and replaced it,
    so every parent document deserialized as the subclass.
  • register_type / register_controller_type flatten nested IRI lists and
    raise on an unusable IRI instead of silently doing nothing.
  • Every registration logs at INFO, an override at WARNING. A module reloaded by
    importlib.reload re-registers rather than piling up a second copy.

Notes

  • Rebased onto v2.2.0; 800 tests pass, also with OOLD_DESCRIPTOR_BINDING=0.
  • tests/conftest.py now restores the controller registries between tests,
    deep-copying the per-IRI lists.
  • Two packages depend on this and pin the branch until it is released:
    opensemantic.core-python
    (file controllers) and
    osw-python
    (registered_credentials). Both unpin once this lands.

- register_controller_type, get_registered_controller_types and
  registered_controller_types mirror the type-registry API
- find_controller_type maps an entity's type IRIs to a controller,
  optionally filtered by the interface it has to provide
- lets a caller act on an entity without importing the concrete
  controller class
- mirrors register_type, get_registered_type and registered_types onto
  the v1 type registry
- adds the controller counterparts and find_controller_type
- the v1 models keep their own registries, so a caller has to reach the
  matching one
- resolve_type takes the controller registry of the model registry's layer
- several controllers for one IRI: the last registered wins, instead of
  falling back to the pure model and disabling controller resolution
- get_registered_controller_types returns last-first, like the other two
  accessors
- register_type / register_controller_type flatten nested IRI lists and
  raise on an unusable IRI instead of no-op
- v1 register_type gets the inherited-IRI guard the v2 path has
- every registration logged at info, overrides at warning
- conftest restores the controller registries, deep-copying the lists
__oold_register__ = False keeps a controller that reuses another model
out of the lookup, so it stops competing for that model's type IRI. It
stays constructible from the model and explicitly registerable.
A caller that has to build a connection it was not handed needs to see
which credentials are available, or report the choice as ambiguous.
- resolve_type logs a multi-controller IRI at debug; the warning belongs
  to registration, which happens once
- a reloaded module re-registers rather than piling up a second copy and
  reporting it as an override
- an unusable IRI next to a usable one no longer breaks registration
- an inline linked document resolves to the controller too, falling back
  to the pure model when the controller cannot be built from it
- say that __oold_register__ is inherited, and report an inherited-only
  registration instead of silently doing nothing
- tests for the v1 accessors, the v1 opt-out and re-registration
@github-actions

Copy link
Copy Markdown
Contributor

Release preview

Merging this PR would release v2.3.0 (current: v2.2.0).

Changelog preview (truncated)
## v2.3.0 (2026-10-15)

### Bug Fixes

- One precedence rule for controllers, isolated per pydantic layer
  ([`2b3f70d`](https://github.com/OO-LD/oold-python/commit/2b3f70dc27d42a508a90ebaa4eeca1dae0fd16a2))

- Quieter registry logging, and controllers for inline documents
  ([`37c578e`](https://github.com/OO-LD/oold-python/commit/37c578e7b81f91075e5c6ef2ce9ed993ad0e4dda))

### Features

- Let a class opt out of the type registries
  ([`e501f01`](https://github.com/OO-LD/oold-python/commit/e501f0103e488d2354cd3b34a0561e1fd146c125))

- Public lookup for the controller type registry
  ([`e777b70`](https://github.com/OO-LD/oold-python/commit/e777b7091484f6aad4edbe3cc3e0419deccc23df))

- Public registry lookup for the pydantic v1 models
  ([`84b76cb`](https://github.com/OO-LD/oold-python/commit/84b76cb003e9560dc1c6c2d2262c0e363dfcc85b))

- **auth**: Expose the registered credentials
  ([`12e979b`](https://github.com/OO-LD/oold-python/commit/12e979b65e2acfe1a5342c0118df6ef6206108b2))

Preview via python-semantic-release and conventional commits.

@github-actions

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

Click to see benchmark comparison
📊 Benchmark Comparison (threshold: 1.3x)
============================================================

➖ Unchanged (within threshold):
  ➖ test_simple_dict_document_store: 0.0020s → 0.0020s (-0.5%)
  ➖ test_sqlite_document_store: 0.0021s → 0.0021s (+0.1%)
  ➖ test_local_sparql_store: 0.0418s → 0.0396s (-5.2%)
  ➖ test_oneof_subschema: 0.0636s → 0.0630s (-0.9%)
  ➖ test_enum_docstrings: 0.0542s → 0.0527s (-2.8%)
  ➖ test_subclass_inheritance: 0.0591s → 0.0584s (-1.2%)
  ➖ test_class_hierarchy: 0.0555s → 0.0561s (+1.1%)
  ➖ test_core[v1]: 0.0384s → 0.0384s (+0.0%)
  ➖ test_core[v2]: 0.0456s → 0.0470s (+3.0%)
  ➖ test_schema_generation[v1]: 0.0017s → 0.0017s (-0.4%)
  ➖ test_schema_generation[v2]: 0.0038s → 0.0038s (-0.7%)
  ➖ test_simple_json: 0.0007s → 0.0007s (+4.6%)
  ➖ test_complex_graph: 0.0017s → 0.0016s (-2.7%)

============================================================
Summary: 0 regressions, 0 improvements, 13 unchanged
============================================================

✅ No significant performance regressions

Threshold: 1.3x (30% slower triggers a regression warning)

Note: Benchmarks are informational only and won't fail the build.

💡 Tip: Download the benchmark-results artifact for detailed JSON data

@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.64706% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/oold/model/_descriptor.py 65.5% 6 Missing and 4 partials ⚠️
src/oold/model/v1/__init__.py 82.5% 3 Missing and 4 partials ⚠️
src/oold/model/__init__.py 94.2% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@simontaurus
simontaurus merged commit d6cd3c7 into main Oct 15, 2026
21 checks passed
@simontaurus
simontaurus deleted the feat/controller-type-lookup branch October 15, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant