oold 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {oold-2.2.0 → oold-2.3.0}/CHANGELOG.md +25 -0
- {oold-2.2.0 → oold-2.3.0}/CITATION.cff +1 -1
- {oold-2.2.0 → oold-2.3.0}/PKG-INFO +1 -1
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/controller.md +27 -1
- {oold-2.2.0 → oold-2.3.0}/pyproject.toml +1 -1
- {oold-2.2.0 → oold-2.3.0}/src/oold/backend/auth.py +10 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/__init__.py +185 -13
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/_compat.py +4 -2
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/_descriptor.py +60 -12
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/v1/__init__.py +129 -8
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/v1/_descriptor.py +11 -6
- {oold-2.2.0 → oold-2.3.0}/src/oold/static.py +50 -22
- {oold-2.2.0 → oold-2.3.0}/tests/conftest.py +19 -4
- {oold-2.2.0 → oold-2.3.0}/tests/test_auth.py +16 -0
- oold-2.3.0/tests/test_register_controller_type.py +114 -0
- oold-2.3.0/tests/test_register_controller_type_v1.py +96 -0
- oold-2.3.0/tests/test_registry_precedence.py +302 -0
- {oold-2.2.0 → oold-2.3.0}/uv.lock +1 -1
- {oold-2.2.0 → oold-2.3.0}/.devcontainer/devcontainer.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/.devcontainer/postCreateCommand.sh +0 -0
- {oold-2.2.0 → oold-2.3.0}/.editorconfig +0 -0
- {oold-2.2.0 → oold-2.3.0}/.gitattributes +0 -0
- {oold-2.2.0 → oold-2.3.0}/.github/ISSUE_TEMPLATE.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/.github/actions/setup-python-env/action.yml +0 -0
- {oold-2.2.0 → oold-2.3.0}/.github/workflows/main.yml +0 -0
- {oold-2.2.0 → oold-2.3.0}/.github/workflows/on-release-main.yml +0 -0
- {oold-2.2.0 → oold-2.3.0}/.github/workflows/validate-codecov-config.yml +0 -0
- {oold-2.2.0 → oold-2.3.0}/.gitignore +0 -0
- {oold-2.2.0 → oold-2.3.0}/.mcp.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/.pre-commit-config.yaml +0 -0
- {oold-2.2.0 → oold-2.3.0}/CLAUDE.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/CONTRIBUTING.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/Dockerfile +0 -0
- {oold-2.2.0 → oold-2.3.0}/LICENSE +0 -0
- {oold-2.2.0 → oold-2.3.0}/Makefile +0 -0
- {oold-2.2.0 → oold-2.3.0}/README.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/codecov.yaml +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/about.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/api.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/architecture.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/assets/oold_concept.png +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/contributing.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/design/downstream-migration.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/design/graph-object-binding.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/get-started.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/backends.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/codegen.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/index.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/object-graph-mapping.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/rdf-export.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/how-to/validation.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/index.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/maintaining-meta-schemas.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/docs/stylesheets/extra.css +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/backend_auth.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/bench_attribute_access.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/bench_binding_variants.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/check_binding_features.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/linked_data_editor.ipynb +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/linked_data_editor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/notation_example.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/examples/wiki_data.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/pytest.benchmark.ini +0 -0
- {oold-2.2.0 → oold-2.3.0}/scripts/check_markdown_extensions.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/scripts/compare_benchmarks.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/backend/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/backend/document_store.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/backend/interface.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/backend/sparql.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/cli.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/drafts/field_extras.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/drafts/ldc_generator.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/drafts/magic_field.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/experimental/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/experimental/codegen_spike.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/generator.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/_notation.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/model/_ref.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/anywidget/JsonEditorWidget.ipynb +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/anywidget/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/anywidget/jsoneditor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/nicegui/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/nicegui/jsoneditor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/nicegui/main.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/panel/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/panel/anywidget_vite/jsoneditor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/panel/demo.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/README.md +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/package-lock.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/package.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/src/jsoneditor.vue +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/src/jsoneditor_component.js +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/src/jsoneditor_component.less +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/vite.config.anywidget.mjs +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/vite.config.js +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/ui/vue/vite.config.nicegui.js +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/utils/codegen.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/utils/environment.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/utils/json_tools.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/utils/transform.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/check_registry.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/cli.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/compliance.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/context_graph.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/context_resolution.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/formats.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/frame.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/generate.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/instance_checks.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/loader.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/mcp_server.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.7.0/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.7.0/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.7.0/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.8.0/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.8.0/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/0.8.0/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-meta-schema-base.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.1/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.1/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.1/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.1/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.1/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-meta-schema-base.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.2/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-meta-schema-base.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.3/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-meta-schema-base.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.4/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-meta-schema-base.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-pattern-lint.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-rules.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-rules.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/1.0.0-rc.5/oold-ui-meta-schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta/index.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta_store.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/meta_vendor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/pattern_lint.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/pipeline.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/predicates.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/report.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/resolve.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/roundtrip.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/src/oold/validation/schema_checks.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/class_hierarchy/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/class_hierarchy/model_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/class_hierarchy/model_v2.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/enum_docstrings/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/enum_docstrings/model_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/enum_docstrings/model_v2.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/format_parity.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oneof_subschema/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oneof_subschema/model_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oneof_subschema/model_v2.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Address.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Contact.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/ContactSeparateKeys.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Minimal.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Organization.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/OwlOrganization.instance.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/OwlOrganization.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Person.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/PersonWithPet.instance.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/PersonWithPet.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Pet.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/RdfPerson.instance.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/RdfPerson.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Researcher.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/Thing.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/UiAnnotations.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/UiOverlay.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/Gauge.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/NarrowBase.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/Sensor.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/array_without_container.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/base_uri_misaligned.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/branch_context_conflict.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/closed_object_rejects_metadata.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/context_array_order_mismatch.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/inline_type_disagrees.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/invalid_meta.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/iri_reference_without_format.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/legacy_dialect.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/missing_context_term.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/narrow_only_relaxation.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/no_context.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/root_ref_not_reflected.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/undefined_prefix.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/unresolvable_context_ref.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/versioned_id_missing_version.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/vocab_covers_the_remainder.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/broken/xsd_string_coercion.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/compliance/jsonld-features.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/compliance/oold-vocab.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/compliance/roundtrip-patterns.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/remote_context/Leaf.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/oold/x_oold_context/SkosSynonym.schema.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/quantities/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/quantities/model.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/resolver_cache/66a1009a2cf528dd525ec0c0150488fe.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/subclass_inheritance/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/subclass_inheritance/model_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/subclass_inheritance/model_v2.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/model_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/model_v1_nested.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/model_v2.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/model_v2_nested.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/src/Bar.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/src/Foo.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/data/test_core/src/bar2/Bar2.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/query_test.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/static.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_auto_descriptor_binding.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_backends.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_base_controller.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_binding_switch.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_code_generation.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_compat_parity.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_compat_parity_v1.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_downstream_shapes.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_link_annotation.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_notation.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_oold.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_rdf.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_ref.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_register_type.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_schema_generation.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_serialization.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_sparql_query.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_transform.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_typing.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/__init__.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/conftest.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_check_registry.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_check_registry_drift.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_checks.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_cli.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_compliance.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_emitted_schema_conformance.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_formats.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_generate.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_jsonld.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_mcp_server.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_meta_store.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_meta_vendor.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_parity_live.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_pipeline.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_resolve.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_rules.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/test_validation/test_schema_levels.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/typing/links.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/typing/pyrightconfig.json +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/typing/query_dsl.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/ui/panel/conftest.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/ui/panel/panel_app.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tests/ui/panel/panel_frontend_test.py +0 -0
- {oold-2.2.0 → oold-2.3.0}/tox.ini +0 -0
- {oold-2.2.0 → oold-2.3.0}/zensical.toml +0 -0
|
@@ -7,6 +7,31 @@ not edit released sections by hand.
|
|
|
7
7
|
|
|
8
8
|
<!-- version list -->
|
|
9
9
|
|
|
10
|
+
## v2.3.0 (2026-10-15)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- One precedence rule for controllers, isolated per pydantic layer
|
|
15
|
+
([`2b3f70d`](https://github.com/OO-LD/oold-python/commit/2b3f70dc27d42a508a90ebaa4eeca1dae0fd16a2))
|
|
16
|
+
|
|
17
|
+
- Quieter registry logging, and controllers for inline documents
|
|
18
|
+
([`37c578e`](https://github.com/OO-LD/oold-python/commit/37c578e7b81f91075e5c6ef2ce9ed993ad0e4dda))
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Let a class opt out of the type registries
|
|
23
|
+
([`e501f01`](https://github.com/OO-LD/oold-python/commit/e501f0103e488d2354cd3b34a0561e1fd146c125))
|
|
24
|
+
|
|
25
|
+
- Public lookup for the controller type registry
|
|
26
|
+
([`e777b70`](https://github.com/OO-LD/oold-python/commit/e777b7091484f6aad4edbe3cc3e0419deccc23df))
|
|
27
|
+
|
|
28
|
+
- Public registry lookup for the pydantic v1 models
|
|
29
|
+
([`84b76cb`](https://github.com/OO-LD/oold-python/commit/84b76cb003e9560dc1c6c2d2262c0e363dfcc85b))
|
|
30
|
+
|
|
31
|
+
- **auth**: Expose the registered credentials
|
|
32
|
+
([`12e979b`](https://github.com/OO-LD/oold-python/commit/12e979b65e2acfe1a5342c0118df6ef6206108b2))
|
|
33
|
+
|
|
34
|
+
|
|
10
35
|
## v2.2.0 (2026-09-27)
|
|
11
36
|
|
|
12
37
|
### Features
|
|
@@ -3,7 +3,7 @@ message: "If you use this software, please cite it as below."
|
|
|
3
3
|
type: software
|
|
4
4
|
title: "oold-python: Object-Oriented Linked Data for Python"
|
|
5
5
|
# Managed by python-semantic-release (version_variables). Do not edit by hand.
|
|
6
|
-
version: 2.
|
|
6
|
+
version: 2.3.0
|
|
7
7
|
abstract: "A Python package for abstract and object-oriented access to knowledge graphs, bridging semantic web technologies (RDF, JSON-LD, SPARQL) with Pydantic type safety."
|
|
8
8
|
repository-code: "https://github.com/oo-ld/oold-python"
|
|
9
9
|
url: "https://OO-LD.github.io/oold-python/"
|
|
@@ -74,7 +74,33 @@ print(ctrl.to_json())
|
|
|
74
74
|
|
|
75
75
|
## Type registry behavior
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
A controller is kept out of `_types`, the data model registry, and collected in `_controller_types` instead. The data model therefore stays the class a type IRI maps to, and `to_json()` keeps producing controller-free documents.
|
|
78
|
+
|
|
79
|
+
Resolution still hands out the controller: `from_json()`, `from_jsonld()` and anything else going through `resolve_type()` return `RobotController` for `"ex:robot-1"`, so behaviour is available wherever a model is read back. Pass `prefer_controller=False` to `resolve_type()` to get the pure `Robot` class.
|
|
80
|
+
|
|
81
|
+
When several controllers answer to the same IRI, the one registered **last** wins - a package imported later overrides an implementation without the earlier one knowing about it. Every registration is logged at `INFO` under the `oold.model` logger, and an override at `WARNING`, so an unexpected winner is visible:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import logging
|
|
85
|
+
logging.getLogger("oold.model").setLevel(logging.INFO)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Resolution never crosses pydantic versions: `oold.model` and `oold.model.v1` keep separate registries, and a lookup is answered from the one the model registry belongs to.
|
|
89
|
+
|
|
90
|
+
To pick a controller explicitly, construct it from the data model:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
ctrl = RobotController(robot, connection_url="tcp://192.168.1.10:5000")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A controller that reuses a model without being the handler for it - a simulator over `Robot`, an in-memory buffer over a local file - sets `__oold_register__ = False` and stays out of the lookup. It is still constructed from the model as above:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
class SimulatedRobot(BaseController, Robot):
|
|
100
|
+
__oold_register__ = False
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The flag is inherited, so a subclass of `SimulatedRobot` stays out of the registries too; set `__oold_register__ = True` on it to opt back in. `register_controller_type()` registers an opted-out class regardless - the flag governs the automatic registration on class creation, not an explicit request.
|
|
78
104
|
|
|
79
105
|
---
|
|
80
106
|
|
|
@@ -9,7 +9,7 @@ keywords = ['python', 'linked-data', 'json-ld', 'pydantic', 'oo-ld']
|
|
|
9
9
|
requires-python = ">=3.10,<4.0"
|
|
10
10
|
# Version is managed by python-semantic-release (see [tool.semantic_release]).
|
|
11
11
|
# Do not edit by hand; it is bumped automatically on merge to main.
|
|
12
|
-
version = "2.
|
|
12
|
+
version = "2.3.0"
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 4 - Beta",
|
|
15
15
|
"Intended Audience :: Developers",
|
|
@@ -105,6 +105,16 @@ def set_credential(credential: BaseCredential):
|
|
|
105
105
|
_credentials.set(creds)
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def registered_credentials() -> dict[str, BaseCredential]:
|
|
109
|
+
"""The credentials registered in this context, keyed by IRI.
|
|
110
|
+
|
|
111
|
+
A caller that has to build a connection without being told which one - a
|
|
112
|
+
file controller that was handed no session - needs to see what is on offer
|
|
113
|
+
before it can pick, or report that the choice is ambiguous.
|
|
114
|
+
"""
|
|
115
|
+
return dict(_credentials.get())
|
|
116
|
+
|
|
117
|
+
|
|
108
118
|
def get_credential(iri: str, exact: bool = False) -> BaseCredential:
|
|
109
119
|
"""Retrieve the credential for the given IRI from the global store.
|
|
110
120
|
|
|
@@ -107,10 +107,108 @@ class FieldProxy:
|
|
|
107
107
|
_types: dict[str, pydantic.main._model_construction.ModelMetaclass] = {}
|
|
108
108
|
_controller_types: dict[str, list] = {}
|
|
109
109
|
|
|
110
|
+
_logger = logging.getLogger(__name__)
|
|
110
111
|
|
|
111
112
|
M = TypeVar("M", bound="_LinkedBaseModelLegacy")
|
|
112
113
|
|
|
113
114
|
|
|
115
|
+
def _cls_name(cls: type) -> str:
|
|
116
|
+
return f"{cls.__module__}.{cls.__qualname__}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _usable_iris(cls: type, iri) -> list[str]:
|
|
120
|
+
"""The IRIs to register ``cls`` under, flattened and validated.
|
|
121
|
+
|
|
122
|
+
``get_cls_iri()`` returns a bare string or a list, and the list may hold
|
|
123
|
+
the default of the type field, which is a list again. Flattening here is
|
|
124
|
+
what keeps the public API in step with the metaclass; silently skipping a
|
|
125
|
+
nested or empty shape left a class unregistered and unreachable by type.
|
|
126
|
+
"""
|
|
127
|
+
if iri is None:
|
|
128
|
+
iri = cls.get_cls_iri() if hasattr(cls, "get_cls_iri") else None
|
|
129
|
+
if iri is None:
|
|
130
|
+
raise ValueError(f"{_cls_name(cls)} has no type IRI: pass iri= or define get_cls_iri()")
|
|
131
|
+
# filter before sorting: get_cls_iri() may return the default of a required
|
|
132
|
+
# type field, which is PydanticUndefined, and sorting that against a string
|
|
133
|
+
# raises - throwing away the usable IRI next to it
|
|
134
|
+
usable = {value for value in _iri_set(iri) if isinstance(value, str) and value.strip()}
|
|
135
|
+
if not usable:
|
|
136
|
+
raise ValueError(f"{_cls_name(cls)}: no usable type IRI in {iri!r}")
|
|
137
|
+
return sorted(usable)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def registers_itself(cls: type) -> bool:
|
|
141
|
+
"""Whether a class wants to be in the registries.
|
|
142
|
+
|
|
143
|
+
A class opts out with ``__oold_register__ = False``. The case for it is a
|
|
144
|
+
controller that reuses another model without being the handler for it - an
|
|
145
|
+
in-memory buffer over a local-file model, say: it is asked for by name and
|
|
146
|
+
constructed from the model, so having it compete for the model's type IRI
|
|
147
|
+
only makes resolution ambiguous.
|
|
148
|
+
|
|
149
|
+
The flag is a plain class attribute, so it is **inherited**: a subclass of
|
|
150
|
+
an opted-out class stays out too, unless it sets ``__oold_register__ =
|
|
151
|
+
True`` itself.
|
|
152
|
+
"""
|
|
153
|
+
return bool(getattr(cls, "__oold_register__", True))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _put_type(iri: str, cls: type, registry: dict | None = None) -> None:
|
|
157
|
+
"""Write one model registration, reporting what it replaces.
|
|
158
|
+
|
|
159
|
+
``registry`` selects the pydantic layer; it defaults to the v2 one. The two
|
|
160
|
+
layers keep separate registries, and a class of the wrong flavour handed to
|
|
161
|
+
deserialization does not validate.
|
|
162
|
+
"""
|
|
163
|
+
registry = _types if registry is None else registry
|
|
164
|
+
previous = registry.get(iri)
|
|
165
|
+
if previous is cls:
|
|
166
|
+
return
|
|
167
|
+
if previous is None:
|
|
168
|
+
_logger.info("Type %s: %s", iri, _cls_name(cls))
|
|
169
|
+
elif _cls_name(previous) == _cls_name(cls):
|
|
170
|
+
# the same class from a reloaded module: not an override, and saying
|
|
171
|
+
# "X replaces X" for every class of a regenerated model module is noise
|
|
172
|
+
_logger.debug("Type %s: %s re-registered", iri, _cls_name(cls))
|
|
173
|
+
else:
|
|
174
|
+
_logger.warning(
|
|
175
|
+
"Type %s: %s replaces %s",
|
|
176
|
+
iri,
|
|
177
|
+
_cls_name(cls),
|
|
178
|
+
_cls_name(previous),
|
|
179
|
+
)
|
|
180
|
+
registry[iri] = cls
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _put_controller(iri: str, cls: type, registry: dict | None = None) -> None:
|
|
184
|
+
"""Append one controller registration, reporting what it takes over from.
|
|
185
|
+
|
|
186
|
+
A class registered again - re-imported, or defined by a reloaded module -
|
|
187
|
+
moves to the front rather than piling up: resolution takes the last entry,
|
|
188
|
+
so a stale copy left behind would only ever be dead weight.
|
|
189
|
+
"""
|
|
190
|
+
registry = _controller_types if registry is None else registry
|
|
191
|
+
entries = registry.setdefault(iri, [])
|
|
192
|
+
if entries and entries[-1] is cls:
|
|
193
|
+
return
|
|
194
|
+
name = _cls_name(cls)
|
|
195
|
+
superseded = [c for c in entries if c is cls or _cls_name(c) == name]
|
|
196
|
+
for stale in superseded:
|
|
197
|
+
entries.remove(stale)
|
|
198
|
+
if entries:
|
|
199
|
+
_logger.warning(
|
|
200
|
+
"Controller %s: %s takes precedence over %s",
|
|
201
|
+
iri,
|
|
202
|
+
name,
|
|
203
|
+
", ".join(_cls_name(c) for c in entries),
|
|
204
|
+
)
|
|
205
|
+
elif superseded:
|
|
206
|
+
_logger.debug("Controller %s: %s re-registered", iri, name)
|
|
207
|
+
else:
|
|
208
|
+
_logger.info("Controller %s: %s", iri, name)
|
|
209
|
+
entries.append(cls)
|
|
210
|
+
|
|
211
|
+
|
|
114
212
|
def register_type(cls: type, iri: str | list[str] | None = None) -> None:
|
|
115
213
|
"""Register a model class under the type IRI(s) it answers to.
|
|
116
214
|
|
|
@@ -122,6 +220,11 @@ def register_type(cls: type, iri: str | list[str] | None = None) -> None:
|
|
|
122
220
|
Without it callers reach for the private ``_types`` mapping and write into
|
|
123
221
|
it directly, which couples them to an internal and offers no validation.
|
|
124
222
|
|
|
223
|
+
A class may only claim the IRIs it introduces itself: a subclass that
|
|
224
|
+
merely narrows a field reports its parent's IRI, and registering it would
|
|
225
|
+
make every parent document deserialize as the subclass. Pass ``iri``
|
|
226
|
+
explicitly to override that.
|
|
227
|
+
|
|
125
228
|
Parameters
|
|
126
229
|
----------
|
|
127
230
|
cls
|
|
@@ -129,13 +232,18 @@ def register_type(cls: type, iri: str | list[str] | None = None) -> None:
|
|
|
129
232
|
iri
|
|
130
233
|
The IRI(s) to register under. Defaults to ``cls.get_cls_iri()``.
|
|
131
234
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
235
|
+
explicit = iri is not None
|
|
236
|
+
iris = _usable_iris(cls, iri)
|
|
237
|
+
inherited = frozenset() if explicit else _inherited_cls_iris(cls)
|
|
238
|
+
for value in iris:
|
|
239
|
+
if value in inherited:
|
|
240
|
+
_logger.info(
|
|
241
|
+
"%s: %s is inherited, not registering it. Pass iri= to claim it anyway.",
|
|
242
|
+
_cls_name(cls),
|
|
243
|
+
value,
|
|
244
|
+
)
|
|
245
|
+
continue
|
|
246
|
+
_put_type(value, cls)
|
|
139
247
|
|
|
140
248
|
|
|
141
249
|
def get_registered_type(iri: str) -> type | None:
|
|
@@ -152,7 +260,69 @@ def registered_types() -> dict:
|
|
|
152
260
|
return _types
|
|
153
261
|
|
|
154
262
|
|
|
155
|
-
|
|
263
|
+
def register_controller_type(cls: type, iri: str | list[str] | None = None) -> None:
|
|
264
|
+
"""Register a controller class under the type IRI(s) it can act on.
|
|
265
|
+
|
|
266
|
+
Controller subclasses register themselves on creation, so this is only
|
|
267
|
+
needed for controllers built dynamically or registered under an extra IRI.
|
|
268
|
+
Several controllers may answer to the same IRI; the one registered last
|
|
269
|
+
wins, so a package imported later overrides an implementation.
|
|
270
|
+
|
|
271
|
+
Parameters
|
|
272
|
+
----------
|
|
273
|
+
cls
|
|
274
|
+
The controller class to register.
|
|
275
|
+
iri
|
|
276
|
+
The IRI(s) to register under. Defaults to ``cls.get_cls_iri()``.
|
|
277
|
+
"""
|
|
278
|
+
for value in _usable_iris(cls, iri):
|
|
279
|
+
_put_controller(value, cls)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def get_registered_controller_types(iri: str) -> list:
|
|
283
|
+
"""The controller classes registered for a type IRI, last registered first.
|
|
284
|
+
|
|
285
|
+
The order resolution uses: :func:`find_controller_type` and
|
|
286
|
+
:func:`~oold.static.resolve_type` both take the most recent registration.
|
|
287
|
+
"""
|
|
288
|
+
return list(reversed(_controller_types.get(iri, [])))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def registered_controller_types() -> dict:
|
|
292
|
+
"""The live controller registry.
|
|
293
|
+
|
|
294
|
+
Prefer :func:`register_controller_type` over writing to it directly.
|
|
295
|
+
"""
|
|
296
|
+
return _controller_types
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def find_controller_type(type_iris, base: type | None = None) -> type | None:
|
|
300
|
+
"""A registered controller for any of ``type_iris``.
|
|
301
|
+
|
|
302
|
+
Lets a caller act on an entity without knowing which concrete controller
|
|
303
|
+
implements it: pass the entity's type IRIs and, optionally, the interface
|
|
304
|
+
the controller has to provide. The IRIs are tried in order; within one IRI
|
|
305
|
+
the most recently registered controller wins, so a downstream package can
|
|
306
|
+
override an implementation by importing its own.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
type_iris
|
|
311
|
+
A type IRI or a list of them, tried in order.
|
|
312
|
+
base
|
|
313
|
+
If given, only controllers that are a subclass of it are considered.
|
|
314
|
+
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
The controller class, or ``None`` if nothing matches.
|
|
318
|
+
"""
|
|
319
|
+
if isinstance(type_iris, str):
|
|
320
|
+
type_iris = [type_iris]
|
|
321
|
+
for iri in type_iris or []:
|
|
322
|
+
for cls in reversed(_controller_types.get(iri, [])):
|
|
323
|
+
if base is None or issubclass(cls, base):
|
|
324
|
+
return cls
|
|
325
|
+
return None
|
|
156
326
|
|
|
157
327
|
|
|
158
328
|
def _iri_set(value) -> frozenset:
|
|
@@ -214,7 +384,7 @@ class _LinkedBaseModelMetaClassLegacy(pydantic.main._model_construction.ModelMet
|
|
|
214
384
|
# (they extend data models but should not replace them in
|
|
215
385
|
# the type lookup table).
|
|
216
386
|
_is_ctrl = any(b.__module__ == "oold.model" and b.__name__ == "BaseController" for b in cls.__mro__)
|
|
217
|
-
if hasattr(cls, "get_cls_iri"):
|
|
387
|
+
if hasattr(cls, "get_cls_iri") and registers_itself(cls):
|
|
218
388
|
iri = cls.get_cls_iri()
|
|
219
389
|
if iri is not None:
|
|
220
390
|
iris = iri if isinstance(iri, list) else [iri]
|
|
@@ -224,10 +394,12 @@ class _LinkedBaseModelMetaClassLegacy(pydantic.main._model_construction.ModelMet
|
|
|
224
394
|
# IRIs it introduces itself.
|
|
225
395
|
inherited = frozenset() if _is_ctrl else _inherited_cls_iris(cls)
|
|
226
396
|
for i in iris:
|
|
397
|
+
if not isinstance(i, str):
|
|
398
|
+
continue
|
|
227
399
|
if _is_ctrl:
|
|
228
|
-
|
|
400
|
+
_put_controller(i, cls)
|
|
229
401
|
elif i not in inherited:
|
|
230
|
-
|
|
402
|
+
_put_type(i, cls)
|
|
231
403
|
return cls
|
|
232
404
|
|
|
233
405
|
# override operators, see https://docs.python.org/3/library/operator.html
|
|
@@ -1039,7 +1211,7 @@ class _LinkedBaseModelLegacy(BaseModel, GenericLinkedBaseModel, metaclass=_Linke
|
|
|
1039
1211
|
@classmethod
|
|
1040
1212
|
def from_jsonld(cls, jsonld: dict) -> "_LinkedBaseModelLegacy":
|
|
1041
1213
|
"""Constructs a model instance from a JSON-LD representation."""
|
|
1042
|
-
return import_jsonld(BaseModel, _LinkedBaseModelLegacy, cls, jsonld, _types)
|
|
1214
|
+
return import_jsonld(BaseModel, _LinkedBaseModelLegacy, cls, jsonld, _types, _controller_types)
|
|
1043
1215
|
|
|
1044
1216
|
def to_json(self, exclude_defaults: bool = False) -> dict:
|
|
1045
1217
|
"""Return the JSON representation of the object.
|
|
@@ -1071,7 +1243,7 @@ class _LinkedBaseModelLegacy(BaseModel, GenericLinkedBaseModel, metaclass=_Linke
|
|
|
1071
1243
|
@classmethod
|
|
1072
1244
|
def from_json(cls, data: dict) -> "_LinkedBaseModelLegacy":
|
|
1073
1245
|
"""Constructs a model instance from a JSON representation."""
|
|
1074
|
-
return import_json(BaseModel, _LinkedBaseModelLegacy, cls, data, _types)
|
|
1246
|
+
return import_json(BaseModel, _LinkedBaseModelLegacy, cls, data, _types, _controller_types)
|
|
1075
1247
|
|
|
1076
1248
|
# @classmethod
|
|
1077
1249
|
# def model_json_schema(
|
|
@@ -270,21 +270,23 @@ class LinkedApiMixin(GenericLinkedBaseModel):
|
|
|
270
270
|
|
|
271
271
|
@classmethod
|
|
272
272
|
def from_json(cls, data: dict[str, Any]) -> Any:
|
|
273
|
+
from oold.model import _controller_types
|
|
273
274
|
from oold.model._descriptor import _TYPE_REGISTRY
|
|
274
275
|
|
|
275
276
|
# root must be the binding base: import_json only falls back to the
|
|
276
277
|
# given class when the two differ, which is how a payload without a
|
|
277
278
|
# type IRI still constructs.
|
|
278
|
-
return import_json(BaseModel, cls._root_cls(), cls, data, _TYPE_REGISTRY)
|
|
279
|
+
return import_json(BaseModel, cls._root_cls(), cls, data, _TYPE_REGISTRY, _controller_types)
|
|
279
280
|
|
|
280
281
|
def to_jsonld(self) -> dict[str, Any]:
|
|
281
282
|
return export_jsonld(self, BaseModel)
|
|
282
283
|
|
|
283
284
|
@classmethod
|
|
284
285
|
def from_jsonld(cls, jsonld: dict[str, Any]) -> Any:
|
|
286
|
+
from oold.model import _controller_types
|
|
285
287
|
from oold.model._descriptor import _TYPE_REGISTRY
|
|
286
288
|
|
|
287
|
-
return import_jsonld(BaseModel, cls._root_cls(), cls, jsonld, _TYPE_REGISTRY)
|
|
289
|
+
return import_jsonld(BaseModel, cls._root_cls(), cls, jsonld, _TYPE_REGISTRY, _controller_types)
|
|
288
290
|
|
|
289
291
|
def store_jsonld(self) -> None:
|
|
290
292
|
from oold.backend.interface import GetBackendParam, StoreParam, get_backend
|
|
@@ -40,6 +40,7 @@ behaviour off entirely and leaves plain pydantic.
|
|
|
40
40
|
from __future__ import annotations
|
|
41
41
|
|
|
42
42
|
import contextlib
|
|
43
|
+
import logging
|
|
43
44
|
import os
|
|
44
45
|
import sys
|
|
45
46
|
import types
|
|
@@ -649,6 +650,8 @@ def _is_link_annotation(annotation: Any) -> bool:
|
|
|
649
650
|
return False
|
|
650
651
|
|
|
651
652
|
|
|
653
|
+
_logger = logging.getLogger(__name__)
|
|
654
|
+
|
|
652
655
|
_TYPE_REGISTRY: dict[str, type] = {}
|
|
653
656
|
"""Maps a ``type`` field default (the class IRI) to its model class.
|
|
654
657
|
|
|
@@ -675,17 +678,61 @@ def use_type_registry(registry: dict) -> None:
|
|
|
675
678
|
|
|
676
679
|
|
|
677
680
|
def _resolve_cls(data: dict[str, Any], target: Any) -> Any:
|
|
678
|
-
"""Pick the most specific class for a document, by its type IRI.
|
|
681
|
+
"""Pick the most specific class for a document, by its type IRI.
|
|
682
|
+
|
|
683
|
+
A registered controller wins over the pure model, the same way
|
|
684
|
+
``from_json`` resolves it - otherwise the same document would come back as
|
|
685
|
+
a controller when the link carries an IRI and as a plain model when it is
|
|
686
|
+
inlined.
|
|
687
|
+
"""
|
|
688
|
+
from oold.model import _controller_types
|
|
689
|
+
|
|
679
690
|
type_iri = data.get("type")
|
|
680
691
|
if isinstance(type_iri, list):
|
|
681
692
|
type_iri = type_iri[0] if type_iri else None
|
|
682
693
|
if isinstance(type_iri, str):
|
|
694
|
+
controllers = _controller_types.get(type_iri)
|
|
695
|
+
if controllers:
|
|
696
|
+
return controllers[-1]
|
|
683
697
|
found = _TYPE_REGISTRY.get(type_iri)
|
|
684
698
|
if found is not None:
|
|
685
699
|
return found
|
|
686
700
|
return target
|
|
687
701
|
|
|
688
702
|
|
|
703
|
+
def _pure_cls(data: dict[str, Any]) -> Any:
|
|
704
|
+
"""The data model for a document's type IRI, ignoring controllers."""
|
|
705
|
+
type_iri = data.get("type")
|
|
706
|
+
if isinstance(type_iri, list):
|
|
707
|
+
type_iri = type_iri[0] if type_iri else None
|
|
708
|
+
return _TYPE_REGISTRY.get(type_iri) if isinstance(type_iri, str) else None
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _construct_resolved(data: dict[str, Any], target: Any) -> Any:
|
|
712
|
+
"""Build a linked document, falling back to its pure model.
|
|
713
|
+
|
|
714
|
+
A controller may declare fields the document does not carry. Resolution
|
|
715
|
+
prefers it, so without this a document that used to build as a plain model
|
|
716
|
+
would start raising; ``import_json`` makes the same trade.
|
|
717
|
+
"""
|
|
718
|
+
cls = _resolve_cls(data, target)
|
|
719
|
+
if cls is None:
|
|
720
|
+
raise ValueError(f"Cannot construct link from {data!r}: unknown target")
|
|
721
|
+
try:
|
|
722
|
+
return _construct(cls, data)
|
|
723
|
+
except Exception:
|
|
724
|
+
pure = _pure_cls(data)
|
|
725
|
+
if pure is None or pure is cls:
|
|
726
|
+
raise
|
|
727
|
+
_logger.warning(
|
|
728
|
+
"Controller %s could not be built from a linked document "
|
|
729
|
+
"(e.g. missing mandatory fields), falling back to %s",
|
|
730
|
+
cls.__name__,
|
|
731
|
+
pure.__name__,
|
|
732
|
+
)
|
|
733
|
+
return _construct(pure, data)
|
|
734
|
+
|
|
735
|
+
|
|
689
736
|
class LinkResultList(list[T]):
|
|
690
737
|
"""List returned by a to-many link.
|
|
691
738
|
|
|
@@ -896,9 +943,7 @@ def _batch_resolve(refs: list[Ref | None], target: Any) -> list[Any]:
|
|
|
896
943
|
# a second request whose result was then built against the declared
|
|
897
944
|
# target, losing the original error and silently mis-constructing.
|
|
898
945
|
fetched = resolver.resolve_iris(iris)
|
|
899
|
-
nodes = {
|
|
900
|
-
iri: (_construct(_resolve_cls(d, target), d) if d is not None else None) for iri, d in fetched.items()
|
|
901
|
-
}
|
|
946
|
+
nodes = {iri: (_construct_resolved(d, target) if d is not None else None) for iri, d in fetched.items()}
|
|
902
947
|
for r in group:
|
|
903
948
|
r._obj = nodes.get(r.iri)
|
|
904
949
|
return [None if r is None else r._obj for r in refs]
|
|
@@ -915,10 +960,7 @@ def _to_ref(value: Any, target: Any) -> Ref | None:
|
|
|
915
960
|
return Ref(iri=value, target=target)
|
|
916
961
|
if isinstance(value, dict):
|
|
917
962
|
# construct through the model so the linked object is validated
|
|
918
|
-
|
|
919
|
-
if cls is None:
|
|
920
|
-
raise ValueError(f"Cannot construct link from {value!r}: unknown target")
|
|
921
|
-
return Ref(obj=_construct(cls, value), target=target)
|
|
963
|
+
return Ref(obj=_construct_resolved(value, target), target=target)
|
|
922
964
|
return Ref(obj=value, target=target)
|
|
923
965
|
|
|
924
966
|
|
|
@@ -930,10 +972,16 @@ def _register_class(cls: type) -> None:
|
|
|
930
972
|
only claim the IRIs it introduces itself - a subclass that merely narrows a
|
|
931
973
|
field reports its parent's IRI and would otherwise replace it.
|
|
932
974
|
"""
|
|
933
|
-
from oold.model import
|
|
975
|
+
from oold.model import (
|
|
976
|
+
_controller_types,
|
|
977
|
+
_inherited_cls_iris,
|
|
978
|
+
_put_controller,
|
|
979
|
+
_put_type,
|
|
980
|
+
registers_itself,
|
|
981
|
+
)
|
|
934
982
|
|
|
935
983
|
iri = cls.get_cls_iri() if hasattr(cls, "get_cls_iri") else None
|
|
936
|
-
if iri is None:
|
|
984
|
+
if iri is None or not registers_itself(cls):
|
|
937
985
|
return
|
|
938
986
|
is_ctrl = any(b.__module__ == "oold.model" and b.__name__ == "BaseController" for b in cls.__mro__)
|
|
939
987
|
inherited = frozenset() if is_ctrl else _inherited_cls_iris(cls)
|
|
@@ -941,9 +989,9 @@ def _register_class(cls: type) -> None:
|
|
|
941
989
|
if not isinstance(value, str):
|
|
942
990
|
continue
|
|
943
991
|
if is_ctrl:
|
|
944
|
-
|
|
992
|
+
_put_controller(value, cls, _controller_types)
|
|
945
993
|
elif value not in inherited:
|
|
946
|
-
|
|
994
|
+
_put_type(value, cls, _TYPE_REGISTRY)
|
|
947
995
|
|
|
948
996
|
|
|
949
997
|
class _AutoLink:
|