oold 2.3.0__tar.gz → 2.3.1__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.
Files changed (282) hide show
  1. {oold-2.3.0 → oold-2.3.1}/CHANGELOG.md +19 -0
  2. {oold-2.3.0 → oold-2.3.1}/CITATION.cff +1 -1
  3. {oold-2.3.0 → oold-2.3.1}/PKG-INFO +1 -1
  4. {oold-2.3.0 → oold-2.3.1}/docs/how-to/controller.md +2 -0
  5. {oold-2.3.0 → oold-2.3.1}/pyproject.toml +1 -1
  6. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/frame.py +75 -9
  7. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_jsonld.py +135 -2
  8. {oold-2.3.0 → oold-2.3.1}/uv.lock +1 -6
  9. {oold-2.3.0 → oold-2.3.1}/.devcontainer/devcontainer.json +0 -0
  10. {oold-2.3.0 → oold-2.3.1}/.devcontainer/postCreateCommand.sh +0 -0
  11. {oold-2.3.0 → oold-2.3.1}/.editorconfig +0 -0
  12. {oold-2.3.0 → oold-2.3.1}/.gitattributes +0 -0
  13. {oold-2.3.0 → oold-2.3.1}/.github/ISSUE_TEMPLATE.md +0 -0
  14. {oold-2.3.0 → oold-2.3.1}/.github/actions/setup-python-env/action.yml +0 -0
  15. {oold-2.3.0 → oold-2.3.1}/.github/workflows/main.yml +0 -0
  16. {oold-2.3.0 → oold-2.3.1}/.github/workflows/on-release-main.yml +0 -0
  17. {oold-2.3.0 → oold-2.3.1}/.github/workflows/validate-codecov-config.yml +0 -0
  18. {oold-2.3.0 → oold-2.3.1}/.gitignore +0 -0
  19. {oold-2.3.0 → oold-2.3.1}/.mcp.json +0 -0
  20. {oold-2.3.0 → oold-2.3.1}/.pre-commit-config.yaml +0 -0
  21. {oold-2.3.0 → oold-2.3.1}/CLAUDE.md +0 -0
  22. {oold-2.3.0 → oold-2.3.1}/CONTRIBUTING.md +0 -0
  23. {oold-2.3.0 → oold-2.3.1}/Dockerfile +0 -0
  24. {oold-2.3.0 → oold-2.3.1}/LICENSE +0 -0
  25. {oold-2.3.0 → oold-2.3.1}/Makefile +0 -0
  26. {oold-2.3.0 → oold-2.3.1}/README.md +0 -0
  27. {oold-2.3.0 → oold-2.3.1}/codecov.yaml +0 -0
  28. {oold-2.3.0 → oold-2.3.1}/docs/about.md +0 -0
  29. {oold-2.3.0 → oold-2.3.1}/docs/api.md +0 -0
  30. {oold-2.3.0 → oold-2.3.1}/docs/architecture.md +0 -0
  31. {oold-2.3.0 → oold-2.3.1}/docs/assets/oold_concept.png +0 -0
  32. {oold-2.3.0 → oold-2.3.1}/docs/contributing.md +0 -0
  33. {oold-2.3.0 → oold-2.3.1}/docs/design/downstream-migration.md +0 -0
  34. {oold-2.3.0 → oold-2.3.1}/docs/design/graph-object-binding.md +0 -0
  35. {oold-2.3.0 → oold-2.3.1}/docs/get-started.md +0 -0
  36. {oold-2.3.0 → oold-2.3.1}/docs/how-to/backends.md +0 -0
  37. {oold-2.3.0 → oold-2.3.1}/docs/how-to/codegen.md +0 -0
  38. {oold-2.3.0 → oold-2.3.1}/docs/how-to/index.md +0 -0
  39. {oold-2.3.0 → oold-2.3.1}/docs/how-to/object-graph-mapping.md +0 -0
  40. {oold-2.3.0 → oold-2.3.1}/docs/how-to/rdf-export.md +0 -0
  41. {oold-2.3.0 → oold-2.3.1}/docs/how-to/validation.md +0 -0
  42. {oold-2.3.0 → oold-2.3.1}/docs/index.md +0 -0
  43. {oold-2.3.0 → oold-2.3.1}/docs/maintaining-meta-schemas.md +0 -0
  44. {oold-2.3.0 → oold-2.3.1}/docs/stylesheets/extra.css +0 -0
  45. {oold-2.3.0 → oold-2.3.1}/examples/backend_auth.py +0 -0
  46. {oold-2.3.0 → oold-2.3.1}/examples/bench_attribute_access.py +0 -0
  47. {oold-2.3.0 → oold-2.3.1}/examples/bench_binding_variants.py +0 -0
  48. {oold-2.3.0 → oold-2.3.1}/examples/check_binding_features.py +0 -0
  49. {oold-2.3.0 → oold-2.3.1}/examples/linked_data_editor.ipynb +0 -0
  50. {oold-2.3.0 → oold-2.3.1}/examples/linked_data_editor.py +0 -0
  51. {oold-2.3.0 → oold-2.3.1}/examples/notation_example.py +0 -0
  52. {oold-2.3.0 → oold-2.3.1}/examples/wiki_data.py +0 -0
  53. {oold-2.3.0 → oold-2.3.1}/pytest.benchmark.ini +0 -0
  54. {oold-2.3.0 → oold-2.3.1}/scripts/check_markdown_extensions.py +0 -0
  55. {oold-2.3.0 → oold-2.3.1}/scripts/compare_benchmarks.py +0 -0
  56. {oold-2.3.0 → oold-2.3.1}/src/__init__.py +0 -0
  57. {oold-2.3.0 → oold-2.3.1}/src/oold/__init__.py +0 -0
  58. {oold-2.3.0 → oold-2.3.1}/src/oold/backend/__init__.py +0 -0
  59. {oold-2.3.0 → oold-2.3.1}/src/oold/backend/auth.py +0 -0
  60. {oold-2.3.0 → oold-2.3.1}/src/oold/backend/document_store.py +0 -0
  61. {oold-2.3.0 → oold-2.3.1}/src/oold/backend/interface.py +0 -0
  62. {oold-2.3.0 → oold-2.3.1}/src/oold/backend/sparql.py +0 -0
  63. {oold-2.3.0 → oold-2.3.1}/src/oold/cli.py +0 -0
  64. {oold-2.3.0 → oold-2.3.1}/src/oold/drafts/field_extras.py +0 -0
  65. {oold-2.3.0 → oold-2.3.1}/src/oold/drafts/ldc_generator.py +0 -0
  66. {oold-2.3.0 → oold-2.3.1}/src/oold/drafts/magic_field.py +0 -0
  67. {oold-2.3.0 → oold-2.3.1}/src/oold/experimental/__init__.py +0 -0
  68. {oold-2.3.0 → oold-2.3.1}/src/oold/experimental/codegen_spike.py +0 -0
  69. {oold-2.3.0 → oold-2.3.1}/src/oold/generator.py +0 -0
  70. {oold-2.3.0 → oold-2.3.1}/src/oold/model/__init__.py +0 -0
  71. {oold-2.3.0 → oold-2.3.1}/src/oold/model/_compat.py +0 -0
  72. {oold-2.3.0 → oold-2.3.1}/src/oold/model/_descriptor.py +0 -0
  73. {oold-2.3.0 → oold-2.3.1}/src/oold/model/_notation.py +0 -0
  74. {oold-2.3.0 → oold-2.3.1}/src/oold/model/_ref.py +0 -0
  75. {oold-2.3.0 → oold-2.3.1}/src/oold/model/v1/__init__.py +0 -0
  76. {oold-2.3.0 → oold-2.3.1}/src/oold/model/v1/_descriptor.py +0 -0
  77. {oold-2.3.0 → oold-2.3.1}/src/oold/static.py +0 -0
  78. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/__init__.py +0 -0
  79. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/anywidget/JsonEditorWidget.ipynb +0 -0
  80. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/anywidget/__init__.py +0 -0
  81. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/anywidget/jsoneditor.py +0 -0
  82. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/nicegui/__init__.py +0 -0
  83. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/nicegui/jsoneditor.py +0 -0
  84. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/nicegui/main.py +0 -0
  85. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/panel/__init__.py +0 -0
  86. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/panel/anywidget_vite/jsoneditor.py +0 -0
  87. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/panel/demo.py +0 -0
  88. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/README.md +0 -0
  89. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/package-lock.json +0 -0
  90. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/package.json +0 -0
  91. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/src/jsoneditor.vue +0 -0
  92. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/src/jsoneditor_component.js +0 -0
  93. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/src/jsoneditor_component.less +0 -0
  94. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/vite.config.anywidget.mjs +0 -0
  95. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/vite.config.js +0 -0
  96. {oold-2.3.0 → oold-2.3.1}/src/oold/ui/vue/vite.config.nicegui.js +0 -0
  97. {oold-2.3.0 → oold-2.3.1}/src/oold/utils/codegen.py +0 -0
  98. {oold-2.3.0 → oold-2.3.1}/src/oold/utils/environment.py +0 -0
  99. {oold-2.3.0 → oold-2.3.1}/src/oold/utils/json_tools.py +0 -0
  100. {oold-2.3.0 → oold-2.3.1}/src/oold/utils/transform.py +0 -0
  101. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/__init__.py +0 -0
  102. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/check_registry.py +0 -0
  103. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/cli.py +0 -0
  104. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/compliance.py +0 -0
  105. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/context_graph.py +0 -0
  106. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/context_resolution.py +0 -0
  107. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/formats.py +0 -0
  108. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/generate.py +0 -0
  109. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/instance_checks.py +0 -0
  110. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/loader.py +0 -0
  111. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/mcp_server.py +0 -0
  112. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.7.0/oold-meta-schema.json +0 -0
  113. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.7.0/oold-pattern-lint.schema.json +0 -0
  114. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.7.0/oold-ui-meta-schema.json +0 -0
  115. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.8.0/oold-meta-schema.json +0 -0
  116. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.8.0/oold-pattern-lint.schema.json +0 -0
  117. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/0.8.0/oold-ui-meta-schema.json +0 -0
  118. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-meta-schema-base.json +0 -0
  119. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-meta-schema.json +0 -0
  120. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-pattern-lint.schema.json +0 -0
  121. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-rules.json +0 -0
  122. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-rules.schema.json +0 -0
  123. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0/oold-ui-meta-schema.json +0 -0
  124. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.1/oold-meta-schema.json +0 -0
  125. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.1/oold-pattern-lint.schema.json +0 -0
  126. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.1/oold-rules.json +0 -0
  127. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.1/oold-rules.schema.json +0 -0
  128. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.1/oold-ui-meta-schema.json +0 -0
  129. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-meta-schema-base.json +0 -0
  130. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-meta-schema.json +0 -0
  131. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-pattern-lint.schema.json +0 -0
  132. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-rules.json +0 -0
  133. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-rules.schema.json +0 -0
  134. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.2/oold-ui-meta-schema.json +0 -0
  135. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-meta-schema-base.json +0 -0
  136. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-meta-schema.json +0 -0
  137. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-pattern-lint.schema.json +0 -0
  138. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-rules.json +0 -0
  139. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-rules.schema.json +0 -0
  140. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.3/oold-ui-meta-schema.json +0 -0
  141. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-meta-schema-base.json +0 -0
  142. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-meta-schema.json +0 -0
  143. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-pattern-lint.schema.json +0 -0
  144. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-rules.json +0 -0
  145. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-rules.schema.json +0 -0
  146. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.4/oold-ui-meta-schema.json +0 -0
  147. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-meta-schema-base.json +0 -0
  148. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-meta-schema.json +0 -0
  149. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-pattern-lint.schema.json +0 -0
  150. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-rules.json +0 -0
  151. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-rules.schema.json +0 -0
  152. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/1.0.0-rc.5/oold-ui-meta-schema.json +0 -0
  153. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta/index.json +0 -0
  154. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta_store.py +0 -0
  155. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/meta_vendor.py +0 -0
  156. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/pattern_lint.py +0 -0
  157. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/pipeline.py +0 -0
  158. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/predicates.py +0 -0
  159. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/report.py +0 -0
  160. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/resolve.py +0 -0
  161. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/roundtrip.py +0 -0
  162. {oold-2.3.0 → oold-2.3.1}/src/oold/validation/schema_checks.py +0 -0
  163. {oold-2.3.0 → oold-2.3.1}/tests/conftest.py +0 -0
  164. {oold-2.3.0 → oold-2.3.1}/tests/data/__init__.py +0 -0
  165. {oold-2.3.0 → oold-2.3.1}/tests/data/class_hierarchy/__init__.py +0 -0
  166. {oold-2.3.0 → oold-2.3.1}/tests/data/class_hierarchy/model_v1.py +0 -0
  167. {oold-2.3.0 → oold-2.3.1}/tests/data/class_hierarchy/model_v2.py +0 -0
  168. {oold-2.3.0 → oold-2.3.1}/tests/data/enum_docstrings/__init__.py +0 -0
  169. {oold-2.3.0 → oold-2.3.1}/tests/data/enum_docstrings/model_v1.py +0 -0
  170. {oold-2.3.0 → oold-2.3.1}/tests/data/enum_docstrings/model_v2.py +0 -0
  171. {oold-2.3.0 → oold-2.3.1}/tests/data/format_parity.json +0 -0
  172. {oold-2.3.0 → oold-2.3.1}/tests/data/oneof_subschema/__init__.py +0 -0
  173. {oold-2.3.0 → oold-2.3.1}/tests/data/oneof_subschema/model_v1.py +0 -0
  174. {oold-2.3.0 → oold-2.3.1}/tests/data/oneof_subschema/model_v2.py +0 -0
  175. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Address.schema.json +0 -0
  176. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Contact.schema.json +0 -0
  177. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/ContactSeparateKeys.schema.json +0 -0
  178. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Minimal.schema.json +0 -0
  179. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Organization.schema.json +0 -0
  180. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/OwlOrganization.instance.json +0 -0
  181. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/OwlOrganization.schema.json +0 -0
  182. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Person.schema.json +0 -0
  183. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/PersonWithPet.instance.json +0 -0
  184. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/PersonWithPet.schema.json +0 -0
  185. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Pet.schema.json +0 -0
  186. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/RdfPerson.instance.json +0 -0
  187. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/RdfPerson.schema.json +0 -0
  188. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Researcher.schema.json +0 -0
  189. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/Thing.schema.json +0 -0
  190. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/UiAnnotations.schema.json +0 -0
  191. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/UiOverlay.json +0 -0
  192. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/Gauge.schema.json +0 -0
  193. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/NarrowBase.schema.json +0 -0
  194. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/Sensor.schema.json +0 -0
  195. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/array_without_container.schema.json +0 -0
  196. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/base_uri_misaligned.schema.json +0 -0
  197. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/branch_context_conflict.schema.json +0 -0
  198. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/closed_object_rejects_metadata.schema.json +0 -0
  199. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/context_array_order_mismatch.schema.json +0 -0
  200. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/inline_type_disagrees.schema.json +0 -0
  201. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/invalid_meta.schema.json +0 -0
  202. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/iri_reference_without_format.schema.json +0 -0
  203. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/legacy_dialect.schema.json +0 -0
  204. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/missing_context_term.schema.json +0 -0
  205. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/narrow_only_relaxation.schema.json +0 -0
  206. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/no_context.schema.json +0 -0
  207. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/root_ref_not_reflected.schema.json +0 -0
  208. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/undefined_prefix.schema.json +0 -0
  209. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/unresolvable_context_ref.schema.json +0 -0
  210. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/versioned_id_missing_version.schema.json +0 -0
  211. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/vocab_covers_the_remainder.schema.json +0 -0
  212. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/broken/xsd_string_coercion.schema.json +0 -0
  213. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/compliance/jsonld-features.json +0 -0
  214. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/compliance/oold-vocab.json +0 -0
  215. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/compliance/roundtrip-patterns.json +0 -0
  216. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/remote_context/Leaf.schema.json +0 -0
  217. {oold-2.3.0 → oold-2.3.1}/tests/data/oold/x_oold_context/SkosSynonym.schema.json +0 -0
  218. {oold-2.3.0 → oold-2.3.1}/tests/data/quantities/__init__.py +0 -0
  219. {oold-2.3.0 → oold-2.3.1}/tests/data/quantities/model.py +0 -0
  220. {oold-2.3.0 → oold-2.3.1}/tests/data/resolver_cache/66a1009a2cf528dd525ec0c0150488fe.json +0 -0
  221. {oold-2.3.0 → oold-2.3.1}/tests/data/subclass_inheritance/__init__.py +0 -0
  222. {oold-2.3.0 → oold-2.3.1}/tests/data/subclass_inheritance/model_v1.py +0 -0
  223. {oold-2.3.0 → oold-2.3.1}/tests/data/subclass_inheritance/model_v2.py +0 -0
  224. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/__init__.py +0 -0
  225. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/model_v1.py +0 -0
  226. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/model_v1_nested.py +0 -0
  227. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/model_v2.py +0 -0
  228. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/model_v2_nested.py +0 -0
  229. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/src/Bar.json +0 -0
  230. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/src/Foo.json +0 -0
  231. {oold-2.3.0 → oold-2.3.1}/tests/data/test_core/src/bar2/Bar2.json +0 -0
  232. {oold-2.3.0 → oold-2.3.1}/tests/query_test.py +0 -0
  233. {oold-2.3.0 → oold-2.3.1}/tests/static.py +0 -0
  234. {oold-2.3.0 → oold-2.3.1}/tests/test_auth.py +0 -0
  235. {oold-2.3.0 → oold-2.3.1}/tests/test_auto_descriptor_binding.py +0 -0
  236. {oold-2.3.0 → oold-2.3.1}/tests/test_backends.py +0 -0
  237. {oold-2.3.0 → oold-2.3.1}/tests/test_base_controller.py +0 -0
  238. {oold-2.3.0 → oold-2.3.1}/tests/test_binding_switch.py +0 -0
  239. {oold-2.3.0 → oold-2.3.1}/tests/test_code_generation.py +0 -0
  240. {oold-2.3.0 → oold-2.3.1}/tests/test_compat_parity.py +0 -0
  241. {oold-2.3.0 → oold-2.3.1}/tests/test_compat_parity_v1.py +0 -0
  242. {oold-2.3.0 → oold-2.3.1}/tests/test_downstream_shapes.py +0 -0
  243. {oold-2.3.0 → oold-2.3.1}/tests/test_link_annotation.py +0 -0
  244. {oold-2.3.0 → oold-2.3.1}/tests/test_notation.py +0 -0
  245. {oold-2.3.0 → oold-2.3.1}/tests/test_oold.py +0 -0
  246. {oold-2.3.0 → oold-2.3.1}/tests/test_rdf.py +0 -0
  247. {oold-2.3.0 → oold-2.3.1}/tests/test_ref.py +0 -0
  248. {oold-2.3.0 → oold-2.3.1}/tests/test_register_controller_type.py +0 -0
  249. {oold-2.3.0 → oold-2.3.1}/tests/test_register_controller_type_v1.py +0 -0
  250. {oold-2.3.0 → oold-2.3.1}/tests/test_register_type.py +0 -0
  251. {oold-2.3.0 → oold-2.3.1}/tests/test_registry_precedence.py +0 -0
  252. {oold-2.3.0 → oold-2.3.1}/tests/test_schema_generation.py +0 -0
  253. {oold-2.3.0 → oold-2.3.1}/tests/test_serialization.py +0 -0
  254. {oold-2.3.0 → oold-2.3.1}/tests/test_sparql_query.py +0 -0
  255. {oold-2.3.0 → oold-2.3.1}/tests/test_transform.py +0 -0
  256. {oold-2.3.0 → oold-2.3.1}/tests/test_typing.py +0 -0
  257. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/__init__.py +0 -0
  258. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/conftest.py +0 -0
  259. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_check_registry.py +0 -0
  260. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_check_registry_drift.py +0 -0
  261. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_checks.py +0 -0
  262. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_cli.py +0 -0
  263. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_compliance.py +0 -0
  264. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_emitted_schema_conformance.py +0 -0
  265. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_formats.py +0 -0
  266. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_generate.py +0 -0
  267. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_mcp_server.py +0 -0
  268. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_meta_store.py +0 -0
  269. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_meta_vendor.py +0 -0
  270. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_parity_live.py +0 -0
  271. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_pipeline.py +0 -0
  272. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_resolve.py +0 -0
  273. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_rules.py +0 -0
  274. {oold-2.3.0 → oold-2.3.1}/tests/test_validation/test_schema_levels.py +0 -0
  275. {oold-2.3.0 → oold-2.3.1}/tests/typing/links.py +0 -0
  276. {oold-2.3.0 → oold-2.3.1}/tests/typing/pyrightconfig.json +0 -0
  277. {oold-2.3.0 → oold-2.3.1}/tests/typing/query_dsl.py +0 -0
  278. {oold-2.3.0 → oold-2.3.1}/tests/ui/panel/conftest.py +0 -0
  279. {oold-2.3.0 → oold-2.3.1}/tests/ui/panel/panel_app.py +0 -0
  280. {oold-2.3.0 → oold-2.3.1}/tests/ui/panel/panel_frontend_test.py +0 -0
  281. {oold-2.3.0 → oold-2.3.1}/tox.ini +0 -0
  282. {oold-2.3.0 → oold-2.3.1}/zensical.toml +0 -0
@@ -7,6 +7,25 @@ not edit released sections by hand.
7
7
 
8
8
  <!-- version list -->
9
9
 
10
+ ## v2.3.1 (2026-11-15)
11
+
12
+ ### Bug Fixes
13
+
14
+ - **frame**: Anchor the frame on a type pinned as a default
15
+ ([`fa22022`](https://github.com/OO-LD/oold-python/commit/fa220221e48feb83c26879748c317457d2484012))
16
+
17
+ - **frame**: Keep a $ref-valued embed out of the reference list
18
+ ([`a7ac1d1`](https://github.com/OO-LD/oold-python/commit/a7ac1d1d153719258fb299c3f706a58238bbe9c4))
19
+
20
+ - **frame**: One reverse subframe per IRI, not per term
21
+ ([`74a3adf`](https://github.com/OO-LD/oold-python/commit/74a3adf44fb647aa544c7432c0e5fd78f44949c1))
22
+
23
+ ### Documentation
24
+
25
+ - **controller**: Name controller members for what they do
26
+ ([`648018f`](https://github.com/OO-LD/oold-python/commit/648018fc480934b5b5185c6d0702860392e90c25))
27
+
28
+
10
29
  ## v2.3.0 (2026-10-15)
11
30
 
12
31
  ### Bug Fixes
@@ -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.3.0
6
+ version: 2.3.1
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/"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: oold
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: Linked data class python package
5
5
  Project-URL: Homepage, https://OO-LD.github.io/oold-python/
6
6
  Project-URL: Documentation, https://OO-LD.github.io/oold-python/
@@ -147,6 +147,8 @@ This is useful for checkpointing state (e.g. saving `joint_count` after a calibr
147
147
 
148
148
  ## Guidelines
149
149
 
150
+ - Name controller members for what they *do*: `connect()`, `download()`, `store()`, not `connection`, `url`, `storage`. A controller extends a model whose field names come from a schema, and a noun-named method or property competes with a field that the schema may gain at any regeneration - it shadows the field, or the field shadows it, and the two are hard to tell apart because both answer to attribute access. A verb is almost never a field name.
151
+ - The risk is worst for a property: it reads like data, so callers reach for it with `getattr()`, and anything it does on the way - opening a session, touching the filesystem - surfaces where a plain attribute read was expected. If a controller has to expose derived data, give it a method.
150
152
  - Prefix controller-only attributes with `_` to make the distinction explicit
151
153
  - Keep `__init__` arguments limited to data model fields - controllers should be constructed from data, not from runtime handles
152
154
  - If a controller needs runtime resources (sockets, threads), initialize them in a dedicated `connect()` / `start()` method, not in `__init__`
@@ -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.3.0"
12
+ version = "2.3.1"
13
13
  classifiers = [
14
14
  "Development Status :: 4 - Beta",
15
15
  "Intended Audience :: Developers",
@@ -41,14 +41,17 @@ IRI_FORMATS = frozenset({"iri", "iri-reference", "uri", "uri-reference"})
41
41
  def is_embed(node: Any) -> bool:
42
42
  """True when a property's schema describes an embedded *object* value.
43
43
 
44
- A ``$ref`` alone is not enough: it may point at a scalar DataType leaf, which is a literal
45
- rather than an embed. After dereferencing, a real embed is inlined as an object with its own
46
- properties anyway, so the object shape is the reliable signal.
44
+ After dereferencing an embed is inlined as an object with its own properties, so the object
45
+ shape is the primary signal. On an undereferenced schema it is a ``$ref``, which therefore
46
+ counts too. A ``$ref`` pointing at a scalar DataType leaf is a literal rather than an embed,
47
+ but it costs only an empty subframe, and framing leaves a literal value alone.
47
48
  """
48
49
  if not isinstance(node, dict):
49
50
  return False
50
51
  if node.get("type") == "object" and node.get("properties"):
51
52
  return True
53
+ if isinstance(node.get("$ref"), str):
54
+ return True
52
55
  if node.get("items") is not None:
53
56
  return is_embed(node["items"])
54
57
  for keyword in ("anyOf", "oneOf", "allOf"):
@@ -97,6 +100,25 @@ def embedded_properties(schema: dict[str, Any]) -> list[str]:
97
100
  return list(dict.fromkeys([*structural, *scoped]))
98
101
 
99
102
 
103
+ def pinned_type_default(schema: Any) -> list[str] | None:
104
+ """The instance type a schema pins through ``properties.type.default``.
105
+
106
+ ``OOLD-INS-4b5c`` only fires where ``x-oold-instance-rdf-type`` is declared, so pinning the
107
+ type as a default on the ``@type``-aliased property is a permitted spelling, and the frame
108
+ needs that anchor as much as it needs the keyword's.
109
+ """
110
+ if not isinstance(schema, dict):
111
+ return None
112
+ properties = schema.get("properties")
113
+ declaration = properties.get("type") if isinstance(properties, dict) else None
114
+ default = declaration.get("default") if isinstance(declaration, dict) else None
115
+ if isinstance(default, str):
116
+ return [default]
117
+ if isinstance(default, list) and default and all(isinstance(entry, str) for entry in default):
118
+ return list(default)
119
+ return None
120
+
121
+
100
122
  def instance_rdf_types(schema: Any) -> list[str] | None:
101
123
  """The instance ``rdf:type``(s) a schema declares, most-derived-wins.
102
124
 
@@ -105,12 +127,18 @@ def instance_rdf_types(schema: Any) -> list[str] | None:
105
127
  inference rather than being materialised. A subclass that wants supertypes in the data lists
106
128
  them explicitly. After dereferencing the most-derived value sits at the top level; the
107
129
  ``allOf`` walk is the fallback for a subclass that omits its own declaration.
130
+
131
+ ``properties.type.default`` is read where the keyword is absent, and it is read before the
132
+ ``allOf`` walk, because a schema's own spelling of its type is nearer than an inherited one.
108
133
  """
109
134
  if not isinstance(schema, dict):
110
135
  return None
111
136
  own = schema.get("x-oold-instance-rdf-type")
112
137
  if isinstance(own, list) and own:
113
138
  return own
139
+ pinned = pinned_type_default(schema)
140
+ if pinned:
141
+ return pinned
114
142
  if isinstance(schema.get("allOf"), list):
115
143
  for sub in schema["allOf"]:
116
144
  types = instance_rdf_types(sub)
@@ -162,6 +190,22 @@ def _is_reference_term(definition: Any) -> bool:
162
190
  return definition.get("@type") == "@id" or "@reverse" in definition
163
191
 
164
192
 
193
+ def reverse_predicates(schema: dict[str, Any]) -> dict[str, list[str]]:
194
+ """The reverse IRI each reverse-mapped property compacts to, keyed by that IRI.
195
+
196
+ Several terms may share one reverse IRI, since nothing stops a schema from naming the same
197
+ relation twice. The IRI is taken as the term writes it, compact or absolute: the frame
198
+ resolves it against the same context the term was read from.
199
+ """
200
+ properties = collect_composed_properties(schema)
201
+ grouped: dict[str, list[str]] = {}
202
+ for name, definition in context_terms(schema.get("@context")).items():
203
+ predicate = definition.get("@reverse")
204
+ if name in properties and isinstance(predicate, str):
205
+ grouped.setdefault(predicate, []).append(name)
206
+ return grouped
207
+
208
+
165
209
  def reference_properties(schema: dict[str, Any]) -> list[str]:
166
210
  """Properties whose value is a reference, so framing must leave it an IRI.
167
211
 
@@ -173,8 +217,9 @@ def reference_properties(schema: dict[str, Any]) -> list[str]:
173
217
  (JSON-LD 1.1 4.1.10), so ``"@type": "@id"`` beside it is redundant and authors omit it;
174
218
  keying only on ``@type`` misses the idiomatic spelling and embeds the targets.
175
219
 
176
- Embedding takes precedence where a property carries both: a property shaped like an object
177
- is an embed whatever its term says.
220
+ Embedding takes precedence where a property carries both, and the test is membership in
221
+ :func:`embedded_properties` rather than the shape alone, so the two lists cannot both claim a
222
+ property that embeds through a scoped ``@context``.
178
223
  """
179
224
 
180
225
  def is_reference(node: Any) -> bool:
@@ -195,11 +240,12 @@ def reference_properties(schema: dict[str, Any]) -> list[str]:
195
240
  properties = collect_composed_properties(schema)
196
241
  terms = context_terms(schema.get("@context"))
197
242
  aliases = keyword_alias_keys(schema)
243
+ embedded = set(embedded_properties(schema))
198
244
  return [
199
245
  name
200
246
  for name, prop in properties.items()
201
247
  if name not in aliases
202
- and not is_embed(prop)
248
+ and name not in embedded
203
249
  and (is_reference(prop) or _is_reference_term(terms.get(name, {})))
204
250
  ]
205
251
 
@@ -210,14 +256,34 @@ def schema_to_frame(schema: dict[str, Any], context_ref: Any = _UNSET) -> dict[s
210
256
  ``context_ref``, when given, is used as the frame's ``@context`` in place of the schema's
211
257
  inline one. Pass the schema's URL so the document loader resolves inherited and scoped
212
258
  contexts rather than losing them.
259
+
260
+ The reference subframes are written before the embedded ones, so a property both lists claim
261
+ keeps the ``{}`` that preserves its nested keys.
262
+
263
+ A reverse-mapped property is written under ``@reverse``, by its reverse IRI rather than by its
264
+ term name. Term names expand, so two terms sharing one reverse IRI would otherwise produce two
265
+ subframes under that IRI; a frame must be a single object, and a processor rejects the pair.
213
266
  """
214
267
  frame: dict[str, Any] = {"@embed": "@once"}
215
268
  frame["@context"] = schema.get("@context") if context_ref is _UNSET else context_ref
216
269
  types = instance_rdf_types(schema)
217
270
  if types:
218
271
  frame["@type"] = types[0] if len(types) == 1 else types
219
- for name in embedded_properties(schema):
220
- frame[name] = {}
272
+
273
+ predicate_of = {name: predicate for predicate, names in reverse_predicates(schema).items() for name in names}
274
+ grouped: dict[str, dict[str, Any]] = {}
275
+
276
+ def place(name: str, subframe: dict[str, Any]) -> None:
277
+ predicate = predicate_of.get(name)
278
+ if predicate is None:
279
+ frame[name] = subframe
280
+ else:
281
+ grouped[predicate] = subframe
282
+
221
283
  for name in reference_properties(schema):
222
- frame[name] = {"@embed": "@never"}
284
+ place(name, {"@embed": "@never"})
285
+ for name in embedded_properties(schema):
286
+ place(name, {})
287
+ if grouped:
288
+ frame["@reverse"] = grouped
223
289
  return frame
@@ -14,6 +14,7 @@ from oold.validation.frame import (
14
14
  is_embed,
15
15
  keyword_alias_keys,
16
16
  reference_properties,
17
+ reverse_predicates,
17
18
  schema_to_frame,
18
19
  )
19
20
  from oold.validation.loader import DocumentLoader, describe_jsonld_error
@@ -98,6 +99,61 @@ def test_instance_rdf_types_is_most_derived_wins():
98
99
  assert instance_rdf_types({"type": "object"}) is None
99
100
 
100
101
 
102
+ def test_a_type_pinned_as_a_default_anchors_the_frame():
103
+ """OO-LD/oold-python#177.
104
+
105
+ Without an anchor the root is only the frame root while every edge runs forward; the first
106
+ backwards edge leaves nothing matched and framing returns a bare ``@graph``.
107
+ """
108
+ context = {
109
+ "ex": "https://example.org/",
110
+ "obo": "http://purl.obolibrary.org/obo/",
111
+ "id": "@id",
112
+ "type": {"@id": "@type", "@container": "@set"},
113
+ "Material": "ex:Material",
114
+ "components": "ex:components",
115
+ "chemical_composition": {"@reverse": "obo:RO_0000080"},
116
+ }
117
+ schema = {
118
+ "type": "object",
119
+ "@context": context,
120
+ "properties": {
121
+ "type": {"type": "array", "items": {"type": "string"}, "default": ["Material"]},
122
+ "chemical_composition": {"type": "object", "properties": {"components": {}}},
123
+ },
124
+ }
125
+ assert instance_rdf_types(schema) == ["Material"]
126
+ frame = schema_to_frame(schema)
127
+ assert frame["@type"] == "Material"
128
+
129
+ rdf_type = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
130
+ nq = "\n".join([
131
+ f"<https://example.org/m1> {rdf_type} <https://example.org/Material> .",
132
+ f"_:c {rdf_type} <https://example.org/ChemicalComposition> .",
133
+ "_:c <http://purl.obolibrary.org/obo/RO_0000080> <https://example.org/m1> .",
134
+ '_:c <https://example.org/components> "Fe" .',
135
+ "",
136
+ ])
137
+ graph = jsonld.from_rdf(nq, {"format": "application/n-quads", "useNativeTypes": True})
138
+ framed = jsonld.frame(graph, frame, {"omitDefault": True})
139
+
140
+ assert "@graph" not in framed
141
+ assert framed["chemical_composition"]["components"] == "Fe"
142
+
143
+
144
+ def test_the_keyword_wins_over_a_type_pinned_as_a_default():
145
+ schema = {
146
+ "x-oold-instance-rdf-type": ["ex:B"],
147
+ "properties": {"type": {"default": ["ex:A"]}},
148
+ }
149
+ assert instance_rdf_types(schema) == ["ex:B"]
150
+ # A schema's own default is nearer than a type inherited through allOf.
151
+ assert instance_rdf_types({
152
+ "allOf": [{"x-oold-instance-rdf-type": ["ex:A"]}],
153
+ "properties": {"type": {"default": ["ex:B"]}},
154
+ }) == ["ex:B"]
155
+
156
+
101
157
  def test_schema_to_frame_carries_type_context_and_subframes():
102
158
  schema = {
103
159
  "x-oold-instance-rdf-type": ["schema:Person"],
@@ -175,6 +231,35 @@ def test_embedding_wins_where_a_property_carries_both_signals():
175
231
  assert reference_properties(schema) == []
176
232
 
177
233
 
234
+ def test_a_ref_valued_embed_keeps_its_subframe_before_dereferencing():
235
+ """OO-LD/oold-python#175.
236
+
237
+ ``$ref`` is the only trace of the embed while the schema is undereferenced, and the
238
+ ``"@type": "@id"`` that OO-LD requires on such a term makes it look reference-valued at the
239
+ same time. With both lists claiming it, ``{}`` was overwritten by ``@embed: @never`` and the
240
+ framed node came back empty.
241
+ """
242
+ schema = {
243
+ "type": "object",
244
+ "@context": {
245
+ "type": {"@id": "@type", "@container": "@set"},
246
+ "chemical_composition": {
247
+ "@id": "https://example.org/chemical_composition",
248
+ "@type": "@id",
249
+ "@context": "ChemicalComposition.schema.json",
250
+ },
251
+ },
252
+ "properties": {
253
+ "type": {"type": "array", "items": {"type": "string"}, "default": ["Material"]},
254
+ "chemical_composition": {"$ref": "ChemicalComposition.schema.json"},
255
+ },
256
+ }
257
+ assert is_embed({"$ref": "ChemicalComposition.schema.json"})
258
+ assert embedded_properties(schema) == ["chemical_composition"]
259
+ assert reference_properties(schema) == []
260
+ assert schema_to_frame(schema, "Material.schema.json")["chemical_composition"] == {}
261
+
262
+
178
263
  def test_a_bare_reverse_term_is_reference_valued():
179
264
  """A reverse term's values are node references by definition (JSON-LD 1.1 4.1.10).
180
265
 
@@ -187,7 +272,55 @@ def test_a_bare_reverse_term_is_reference_valued():
187
272
  "properties": {"employees": {"type": "array", "items": {"type": "string"}}},
188
273
  }
189
274
  assert reference_properties(schema) == ["employees"]
190
- assert schema_to_frame(schema)["employees"] == {"@embed": "@never"}
275
+ assert schema_to_frame(schema)["@reverse"] == {"schema:worksFor": {"@embed": "@never"}}
276
+
277
+
278
+ def test_two_terms_on_one_reverse_iri_share_a_single_subframe():
279
+ """OO-LD/oold-python#176.
280
+
281
+ Term names expand, so a subframe per term became two subframes under one IRI. A frame must be
282
+ a single object, and pyld raised ``invalid frame`` with ``{'frame': [{}, {}]}``.
283
+ """
284
+ context = {
285
+ "ex": "https://example.org/",
286
+ "obo": "http://purl.obolibrary.org/obo/",
287
+ "id": "@id",
288
+ "type": {"@id": "@type", "@container": "@set"},
289
+ "Material": "ex:Material",
290
+ "components": "ex:components",
291
+ "chemical_composition": {"@reverse": "obo:RO_0000080"},
292
+ "material_composition": {"@reverse": "obo:RO_0000080"},
293
+ }
294
+ schema = {
295
+ "x-oold-instance-rdf-type": ["ex:Material"],
296
+ "type": "object",
297
+ "@context": context,
298
+ "properties": {
299
+ "chemical_composition": {"type": "object", "properties": {"components": {}}},
300
+ "material_composition": {"type": "object", "properties": {"components": {}}},
301
+ },
302
+ }
303
+ assert reverse_predicates(schema) == {"obo:RO_0000080": ["chemical_composition", "material_composition"]}
304
+ frame = schema_to_frame(schema)
305
+ assert frame["@reverse"] == {"obo:RO_0000080": {}}
306
+ assert "chemical_composition" not in frame
307
+
308
+ rdf_type = "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
309
+ nq = "\n".join([
310
+ f"<https://example.org/m1> {rdf_type} <https://example.org/Material> .",
311
+ f"_:c {rdf_type} <https://example.org/ChemicalComposition> .",
312
+ "_:c <http://purl.obolibrary.org/obo/RO_0000080> <https://example.org/m1> .",
313
+ '_:c <https://example.org/components> "Fe" .',
314
+ f"_:m {rdf_type} <https://example.org/MaterialComposition> .",
315
+ "_:m <http://purl.obolibrary.org/obo/RO_0000080> <https://example.org/m1> .",
316
+ '_:m <https://example.org/components> "steel" .',
317
+ "",
318
+ ])
319
+ graph = jsonld.from_rdf(nq, {"format": "application/n-quads", "useNativeTypes": True})
320
+ framed = jsonld.frame(graph, frame, {"omitDefault": True})
321
+
322
+ compositions = framed["chemical_composition"]
323
+ assert sorted(entry["components"] for entry in compositions) == ["Fe", "steel"]
191
324
 
192
325
 
193
326
  def test_a_keyword_alias_never_gets_a_subframe():
@@ -234,7 +367,7 @@ def test_schema_to_frame_keeps_reference_valued_properties_as_iris():
234
367
  }
235
368
  frame = schema_to_frame(schema, "Organization.schema.json")
236
369
  assert frame["address"] == {}
237
- assert frame["employees"] == {"@embed": "@never"}
370
+ assert frame["@reverse"] == {"schema:worksFor": {"@embed": "@never"}}
238
371
 
239
372
 
240
373
  def test_framing_leaves_a_reference_whose_target_has_triples_as_an_iri():
@@ -2730,7 +2730,7 @@ wheels = [
2730
2730
 
2731
2731
  [[package]]
2732
2732
  name = "oold"
2733
- version = "2.3.0"
2733
+ version = "2.3.1"
2734
2734
  source = { editable = "." }
2735
2735
  dependencies = [
2736
2736
  { name = "datamodel-code-generator" },
@@ -5613,13 +5613,8 @@ wheels = [
5613
5613
  { url = "https://files.pythonhosted.org/packages/ad/fd/6b84115e3bbe6b76ebb1265e8ff2161c0bc88dcd6499eaf29c61a66421e9/zensical-0.0.45-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c4cb2e11132f02ae824e246e016e073458e12e9de1eaf86fd39f01890d41204c", size = 12698844, upload-time = "2026-06-09T11:22:56.537Z" },
5614
5614
  { url = "https://files.pythonhosted.org/packages/e2/dc/4ddf05d77c1455c32cb26da71f2a19d355927a45a3db5b26fb258a07ce8f/zensical-0.0.45-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:799a01de2102b5f731744ad31bdbc464d0c07d484e67ba148f6923679afa6ce6", size = 12571590, upload-time = "2026-06-09T11:23:00.192Z" },
5615
5615
  { url = "https://files.pythonhosted.org/packages/4c/53/60c6cc7b2ce8b1a83eb87bff3f7289447995552fd9a30ca76ffba22ca9d5/zensical-0.0.45-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6201e79ea8a64bd3ced3f05ef4b1529da0e675d67b1395987c0ba942e4e10dc4", size = 12939590, upload-time = "2026-06-09T11:23:02.721Z" },
5616
- { url = "https://files.pythonhosted.org/packages/9f/1e/e9217ed75dba323a6f9a4eee28eb40416eff99932cd0ee6c394bf07b9ead/zensical-0.0.45-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:854aaf500e4a3ce64adea1faa7a1820c7cf9a4f66be1043e4e9ba727fe9cf2b5", size = 12911669, upload-time = "2026-06-09T11:23:05.407Z" },
5617
- { url = "https://files.pythonhosted.org/packages/71/3c/6fc9fe2334bb4460a8a8d732e23a30d2ddc2ecf63c2eb3487d9e7405e70d/zensical-0.0.45-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a80c57fd50fc60415914388286ac10a7d8b6f70b8ca7235597d09fb12c3171b0", size = 13267643, upload-time = "2026-06-09T11:23:07.915Z" },
5618
5616
  { url = "https://files.pythonhosted.org/packages/be/f9/5696114af4ede5f1bd01e641a4ff24ee8ca49810bfaa28e5be12d930c0ef/zensical-0.0.45-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c3510f69e08b6ed8bb9596fc9393e4687f90394aa0ef2d6118b1375ad97be5", size = 12972147, upload-time = "2026-06-09T11:23:12.069Z" },
5619
5617
  { url = "https://files.pythonhosted.org/packages/a3/9e/5c6acde480c43f8c993b13260925df8db31d51ab8a9977618e9efdd98d45/zensical-0.0.45-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:01c484bb2ee85e98e21e24b397ff52ffc31101f7485935eee5d3afa6cca6cc08", size = 13117360, upload-time = "2026-06-09T11:23:15.155Z" },
5620
- { url = "https://files.pythonhosted.org/packages/3d/31/ea21f102049b35a8fe5218c5331857a15eeb60deb1bb21823a4c0701e274/zensical-0.0.45-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:3654b708830303759e866a58a60c483cd2a1c56a44acdaae5bbb341a3f40ebce", size = 13185593, upload-time = "2026-06-09T11:23:18.166Z" },
5621
- { url = "https://files.pythonhosted.org/packages/b4/97/6ded39fe27fa8a292d17d9af713b018e4919315233b60fa4b4b0aca737a6/zensical-0.0.45-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c4da1c37eca1474b487def0ef40d7ac2aff31a9d7a029cb7479ef7c354437361", size = 13326882, upload-time = "2026-06-09T11:23:21.027Z" },
5622
5618
  { url = "https://files.pythonhosted.org/packages/79/80/075975032a9e20f319c0134f8ca659d295ee4908f15ab212702a2728247f/zensical-0.0.45-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f8a1966c186feebd3b795f9d420000bfd582e16eefdd9bc7a286d878faabae52", size = 13253961, upload-time = "2026-06-09T11:23:23.99Z" },
5623
- { url = "https://files.pythonhosted.org/packages/3f/6a/0eab0eb311af6a07cde15ca58d5d720cbfa02cd509e4c7fb5fa20cda0b46/zensical-0.0.45-cp310-abi3-win32.whl", hash = "sha256:a1dd63a5efb8d0e5f2fadf862f02771a279dc5cbe9a982700194650065758f01", size = 12257083, upload-time = "2026-06-09T11:23:26.769Z" },
5624
5619
  { url = "https://files.pythonhosted.org/packages/1f/cd/b117e749c60b1d1e16b8450db1355f69f38376f783b8c6c8815202988933/zensical-0.0.45-cp310-abi3-win_amd64.whl", hash = "sha256:1f2c0e69839ce4274bde34d18139d3b0d96bbf02b245ada46243590c9eedebc1", size = 12498335, upload-time = "2026-06-09T11:23:29.702Z" },
5625
5620
  ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes