metaobjects 0.15.13__tar.gz → 0.17.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.
Files changed (394) hide show
  1. {metaobjects-0.15.13 → metaobjects-0.17.0}/PKG-INFO +1 -1
  2. {metaobjects-0.15.13 → metaobjects-0.17.0}/pyproject.toml +1 -1
  3. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/KNOWN_GAPS.md +8 -0
  4. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/entity_model.py +308 -32
  5. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +6 -1
  6. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/router_generator.py +100 -10
  7. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/core_types.py +72 -7
  8. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/errors.py +21 -0
  9. metaobjects-0.17.0/src/metaobjects/loader/validate_source_escapes.py +205 -0
  10. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/validation_passes.py +536 -9
  11. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/attr/attr_constants.py +5 -0
  12. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/attr/meta_attr.py +12 -0
  13. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/field/meta_field.py +15 -0
  14. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/meta_object.py +22 -0
  15. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/object_constants.py +6 -0
  16. metaobjects-0.17.0/src/metaobjects/meta/persistence/origin/origin_constants.py +49 -0
  17. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/source/meta_source.py +20 -0
  18. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/source/source_constants.py +10 -0
  19. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/extract.py +6 -0
  20. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/types.py +31 -0
  21. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/runtime/object_manager.py +176 -11
  22. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/attr.json +6 -0
  23. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/db.json +2 -0
  24. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/object.json +4 -1
  25. metaobjects-0.17.0/src/metaobjects/spec_metamodel/origin.json +67 -0
  26. metaobjects-0.17.0/tests/codegen/golden/extends/expected/BaseEntity.py +23 -0
  27. metaobjects-0.17.0/tests/codegen/golden/extends/expected/Program.py +27 -0
  28. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +2 -2
  29. metaobjects-0.17.0/tests/codegen/golden/vanilla/expected/Subscriber.py +26 -0
  30. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_entity_model.py +128 -9
  31. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_extends_abstract_field_inheritance.py +6 -3
  32. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_inherit_without_restate_gate.py +8 -3
  33. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_inheritance_conformance.py +6 -2
  34. metaobjects-0.17.0/tests/codegen/test_write_through.py +112 -0
  35. metaobjects-0.17.0/tests/integration/api_contract_jsonb_server.py +317 -0
  36. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/api_contract_server.py +29 -0
  37. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/generated_jsonb_app.py +25 -6
  38. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/generated_m2m_app.py +7 -0
  39. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/generated_router_app.py +6 -0
  40. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/generated_tph_app.py +7 -0
  41. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_generated_router_uuid_pk.py +3 -0
  42. metaobjects-0.17.0/tests/unit/test_object_manager_autoset_stamping.py +239 -0
  43. metaobjects-0.17.0/tests/unit/test_object_manager_write_through.py +167 -0
  44. metaobjects-0.17.0/tests/unit/test_source_escapes.py +336 -0
  45. metaobjects-0.17.0/tests/unit/test_validation_origin_195.py +312 -0
  46. metaobjects-0.15.13/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -28
  47. metaobjects-0.15.13/src/metaobjects/spec_metamodel/origin.json +0 -43
  48. metaobjects-0.15.13/tests/codegen/golden/extends/expected/BaseEntity.py +0 -11
  49. metaobjects-0.15.13/tests/codegen/golden/extends/expected/Program.py +0 -11
  50. metaobjects-0.15.13/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -12
  51. metaobjects-0.15.13/tests/integration/api_contract_jsonb_server.py +0 -160
  52. {metaobjects-0.15.13 → metaobjects-0.17.0}/.gitignore +0 -0
  53. {metaobjects-0.15.13 → metaobjects-0.17.0}/LICENSE +0 -0
  54. {metaobjects-0.15.13 → metaobjects-0.17.0}/README.md +0 -0
  55. {metaobjects-0.15.13 → metaobjects-0.17.0}/hatch_build.py +0 -0
  56. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/__init__.py +0 -0
  57. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/agent_context/__init__.py +0 -0
  58. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/agent_context/scaffold.py +0 -0
  59. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/__init__.py +0 -0
  60. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/api_model.py +0 -0
  61. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/builder.py +0 -0
  62. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/naming.py +0 -0
  63. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/paths.py +0 -0
  64. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/apidocs/renderer.py +0 -0
  65. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/attr_class_map.py +0 -0
  66. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/cli.py +0 -0
  67. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/__init__.py +0 -0
  68. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/config.py +0 -0
  69. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/constants.py +0 -0
  70. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
  71. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
  72. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/format.py +0 -0
  73. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
  74. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generator.py +0 -0
  75. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generator_registry.py +0 -0
  76. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/__init__.py +0 -0
  77. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
  78. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
  79. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
  80. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
  81. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
  82. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
  83. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
  84. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/template_generator.py +0 -0
  85. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
  86. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
  87. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/instance_artifacts.py +0 -0
  88. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
  89. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/overwrite_policy.py +0 -0
  90. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/runner.py +0 -0
  91. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/runtime/__init__.py +0 -0
  92. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
  93. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
  94. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
  95. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
  96. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
  97. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/codegen/type_map.py +0 -0
  98. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/datatype.py +0 -0
  99. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/documentation/__init__.py +0 -0
  100. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/documentation/doc_constants.py +0 -0
  101. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/documentation/doc_provider.py +0 -0
  102. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/documentation/doc_schema.py +0 -0
  103. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/__init__.py +0 -0
  104. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/merge.py +0 -0
  105. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/meta_data_loader.py +0 -0
  106. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/registered_validation.py +0 -0
  107. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/sources/__init__.py +0 -0
  108. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/sources/directory_source.py +0 -0
  109. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/sources/file_source.py +0 -0
  110. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
  111. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/sources/uri_source.py +0 -0
  112. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/validate_discriminator.py +0 -0
  113. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/validate_field_readonly.py +0 -0
  114. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
  115. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
  116. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/__init__.py +0 -0
  117. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/__init__.py +0 -0
  118. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/attr/__init__.py +0 -0
  119. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/field/__init__.py +0 -0
  120. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/field/field_constants.py +0 -0
  121. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/identity/__init__.py +0 -0
  122. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
  123. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
  124. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/index/__init__.py +0 -0
  125. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/index/index_constants.py +0 -0
  126. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/index/meta_index.py +0 -0
  127. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/__init__.py +0 -0
  128. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
  129. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
  130. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/object_extract.py +0 -0
  131. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/object/value_object.py +0 -0
  132. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
  133. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
  134. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
  135. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
  136. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/validator/__init__.py +0 -0
  137. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
  138. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/meta_data.py +0 -0
  139. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/meta_root.py +0 -0
  140. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/__init__.py +0 -0
  141. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
  142. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
  143. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
  144. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
  145. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
  146. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
  147. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/__init__.py +0 -0
  148. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
  149. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
  150. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
  151. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
  152. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
  153. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
  154. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
  155. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
  156. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/provider_extends.py +0 -0
  157. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/template/__init__.py +0 -0
  158. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/template/meta_template.py +0 -0
  159. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/template/prompt_provider.py +0 -0
  160. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/meta/template/template_constants.py +0 -0
  161. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/naming_refs.py +0 -0
  162. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/parser.py +0 -0
  163. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/parser_yaml.py +0 -0
  164. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/provider.py +0 -0
  165. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/py.typed +0 -0
  166. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/registry.py +0 -0
  167. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/registry_manifest.py +0 -0
  168. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/__init__.py +0 -0
  169. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/email_document.py +0 -0
  170. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/escapers.py +0 -0
  171. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
  172. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/__init__.py +0 -0
  173. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/coerce.py +0 -0
  174. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/extract_map.py +0 -0
  175. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
  176. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/locate.py +0 -0
  177. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/normalize.py +0 -0
  178. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/strip.py +0 -0
  179. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
  180. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/filesystem_provider.py +0 -0
  181. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/__init__.py +0 -0
  182. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
  183. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
  184. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/prompt_field.py +0 -0
  185. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
  186. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/prompt/prompt_style.py +0 -0
  187. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/renderer.py +0 -0
  188. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/render/verify.py +0 -0
  189. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/runtime/__init__.py +0 -0
  190. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/runtime/llm_recorder.py +0 -0
  191. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/runtime/n2m_resolver.py +0 -0
  192. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/runtime/tph.py +0 -0
  193. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/serializer_json.py +0 -0
  194. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/shared/__init__.py +0 -0
  195. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/shared/base_types.py +0 -0
  196. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/shared/separators.py +0 -0
  197. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/shared/structural.py +0 -0
  198. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/source/__init__.py +0 -0
  199. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/source/error_source.py +0 -0
  200. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/source/json_path.py +0 -0
  201. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/source/semantic_diff.py +0 -0
  202. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/source/yaml_positions.py +0 -0
  203. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/__init__.py +0 -0
  204. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/documentation.json +0 -0
  205. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/field.json +0 -0
  206. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/identity.json +0 -0
  207. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/index.json +0 -0
  208. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/layout.json +0 -0
  209. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/prompt.json +0 -0
  210. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/relationship.json +0 -0
  211. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/source.json +0 -0
  212. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/template.json +0 -0
  213. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/ui.json +0 -0
  214. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/validator.json +0 -0
  215. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/spec_metamodel/view.json +0 -0
  216. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/super_resolve.py +0 -0
  217. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/validation_types.py +0 -0
  218. {metaobjects-0.15.13 → metaobjects-0.17.0}/src/metaobjects/yaml_desugar.py +0 -0
  219. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/__init__.py +0 -0
  220. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/__init__.py +0 -0
  221. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/extends/meta.json +0 -0
  222. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
  223. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/nested-array/meta.json +0 -0
  224. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
  225. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/scalars/expected/Report.py +0 -0
  226. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/scalars/meta.json +0 -0
  227. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/golden/vanilla/meta.json +0 -0
  228. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_abstract_conformance.py +0 -0
  229. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_api_docs_builder.py +0 -0
  230. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_api_docs_paths.py +0 -0
  231. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli.py +0 -0
  232. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli_providers.py +0 -0
  233. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli_registry.py +0 -0
  234. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli_staleness_nudge.py +0 -0
  235. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli_verify_strict.py +0 -0
  236. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_cli_verify_subverbs.py +0 -0
  237. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_constants_config.py +0 -0
  238. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_enum_conformance.py +0 -0
  239. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_extractor_generator.py +0 -0
  240. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_filter_allowlist_generator.py +0 -0
  241. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_format.py +0 -0
  242. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_fr010_nested_object_prompt.py +0 -0
  243. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_fr010_output_codegen.py +0 -0
  244. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
  245. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_generator.py +0 -0
  246. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_generator_extension_seams.py +0 -0
  247. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_golden.py +0 -0
  248. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_instance_artifacts.py +0 -0
  249. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_m2m_codegen.py +0 -0
  250. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_output_parser_generator.py +0 -0
  251. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_overwrite_policy.py +0 -0
  252. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_payload_vo_generator.py +0 -0
  253. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_projection_compile.py +0 -0
  254. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_render_helper_conformance.py +0 -0
  255. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_render_helper_generator.py +0 -0
  256. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_reverse_finders.py +0 -0
  257. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_router_generator.py +0 -0
  258. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_runner.py +0 -0
  259. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_data.py +0 -0
  260. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_generator.py +0 -0
  261. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_output_pattern.py +0 -0
  262. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_scope_helpers.py +0 -0
  263. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_scope_walk.py +0 -0
  264. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_template_spec.py +0 -0
  265. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_tph_codegen.py +0 -0
  266. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_trace_helper_generator.py +0 -0
  267. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_type_map.py +0 -0
  268. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/codegen/test_validation_conformance.py +0 -0
  269. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/__init__.py +0 -0
  270. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/capabilities.py +0 -0
  271. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/conformance-expected-failures.json +0 -0
  272. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/conformance_adapter.py +0 -0
  273. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/corpus.py +0 -0
  274. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/expected_failures.py +0 -0
  275. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/fixture_discovery.py +0 -0
  276. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/navigator.py +0 -0
  277. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
  278. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_conformance.py +0 -0
  279. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_extract_conformance.py +0 -0
  280. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_extract_object_verdict.py +0 -0
  281. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_fr010_loader_attrs.py +0 -0
  282. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_fr011_attrs.py +0 -0
  283. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_generator_registry_conformance.py +0 -0
  284. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_object_model_conformance.py +0 -0
  285. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_provider_composition_conformance.py +0 -0
  286. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_registry_conformance.py +0 -0
  287. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_runner_hardfail.py +0 -0
  288. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_spec_metamodel_embed.py +0 -0
  289. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_strict_attr_load.py +0 -0
  290. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_template_codegen_conformance.py +0 -0
  291. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_template_generator_conformance.py +0 -0
  292. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/test_yaml_conformance.py +0 -0
  293. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
  294. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/__init__.py +0 -0
  295. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/api_contract_assertions.py +0 -0
  296. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/api_contract_m2m_server.py +0 -0
  297. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/meta_ai_trace.yaml +0 -0
  298. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/normalization.py +0 -0
  299. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/postgres_container.py +0 -0
  300. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/query_runner.py +0 -0
  301. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/scenarios.py +0 -0
  302. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract.py +0 -0
  303. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract_generated.py +0 -0
  304. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract_jsonb.py +0 -0
  305. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract_m2m.py +0 -0
  306. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract_m2m_generated.py +0 -0
  307. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_api_contract_tph_generated.py +0 -0
  308. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_llm_call_trace.py +0 -0
  309. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_normalization.py +0 -0
  310. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_query_scenarios.py +0 -0
  311. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/integration/test_runtime_return_types.py +0 -0
  312. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/open_closed_proof_test.py +0 -0
  313. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/__init__.py +0 -0
  314. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/__init__.py +0 -0
  315. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_coerce.py +0 -0
  316. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_extract.py +0 -0
  317. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_extract_map.py +0 -0
  318. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_json_forgiving_reader.py +0 -0
  319. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_locate.py +0 -0
  320. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_model.py +0 -0
  321. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_normalize.py +0 -0
  322. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_strip.py +0 -0
  323. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
  324. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/prompt/__init__.py +0 -0
  325. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/prompt/test_output_format_renderer.py +0 -0
  326. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_email_document.py +0 -0
  327. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_filesystem_provider.py +0 -0
  328. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_output_format_renderer_nested.py +0 -0
  329. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_output_prompt_conformance.py +0 -0
  330. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_render_conformance.py +0 -0
  331. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_render_max_chars.py +0 -0
  332. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_verify.py +0 -0
  333. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/render/test_verify_conformance.py +0 -0
  334. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/__init__.py +0 -0
  335. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_fr5c_merge_attribution.py +0 -0
  336. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_fr5d_reference_resolution.py +0 -0
  337. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_fr5e_database_source_shape.py +0 -0
  338. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_json_path.py +0 -0
  339. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_semantic_diff.py +0 -0
  340. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_source_on_node.py +0 -0
  341. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/source/test_yaml_positions.py +0 -0
  342. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/test_api_docs_accuracy.py +0 -0
  343. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/__init__.py +0 -0
  344. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_agent_context_staleness.py +0 -0
  345. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_capabilities.py +0 -0
  346. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_common_attrs.py +0 -0
  347. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_core_types.py +0 -0
  348. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_effective_package.py +0 -0
  349. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_errors.py +0 -0
  350. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_field_enum.py +0 -0
  351. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_field_map_validation.py +0 -0
  352. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
  353. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
  354. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_index_lookup.py +0 -0
  355. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_llm_recorder.py +0 -0
  356. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_loader.py +0 -0
  357. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_loader_bom.py +0 -0
  358. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_loader_class.py +0 -0
  359. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_merge.py +0 -0
  360. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_meta_attr.py +0 -0
  361. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_meta_data.py +0 -0
  362. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_meta_source.py +0 -0
  363. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_module_shortcuts.py +0 -0
  364. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_n2m_resolver.py +0 -0
  365. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_n2m_resolver_inherited.py +0 -0
  366. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
  367. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_one_primary_source.py +0 -0
  368. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_parser.py +0 -0
  369. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_parser_yaml.py +0 -0
  370. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_provider.py +0 -0
  371. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_provider_extension.py +0 -0
  372. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_registry.py +0 -0
  373. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_registry_completeness.py +0 -0
  374. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_registry_extend.py +0 -0
  375. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_registry_sealed.py +0 -0
  376. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_relationship_referential_actions.py +0 -0
  377. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_resolution_key.py +0 -0
  378. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
  379. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_serializer.py +0 -0
  380. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_shared_constants.py +0 -0
  381. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_smoke.py +0 -0
  382. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_sources.py +0 -0
  383. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_strict_child_placement.py +0 -0
  384. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_super_resolve.py +0 -0
  385. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_template_toolcall.py +0 -0
  386. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
  387. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_validation_attr_schema.py +0 -0
  388. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_validation_filter_values.py +0 -0
  389. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_validation_origin_paths.py +0 -0
  390. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_validation_sort_field.py +0 -0
  391. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_validation_warnings.py +0 -0
  392. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_write_coercion_arrays.py +0 -0
  393. {metaobjects-0.15.13 → metaobjects-0.17.0}/tests/unit/test_yaml_desugar.py +0 -0
  394. {metaobjects-0.15.13 → metaobjects-0.17.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaobjects
3
- Version: 0.15.13
3
+ Version: 0.17.0
4
4
  Summary: Cross-language metadata standard: declare typed entities once, generate idiomatic drift-checked code across languages — Python port.
5
5
  Project-URL: Homepage, https://metaobjects.dev
6
6
  Project-URL: Repository, https://github.com/metaobjectsdev/metaobjects
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metaobjects"
3
- version = "0.15.13"
3
+ version = "0.17.0"
4
4
  description = "Cross-language metadata standard: declare typed entities once, generate idiomatic drift-checked code across languages — Python port."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -32,6 +32,14 @@ The generated router takes `dto: dict[str, Any]` for `POST` / `PATCH` /
32
32
  `PUT` request bodies and returns `Any` for responses. The repository
33
33
  `Protocol` likewise uses `Any` for the row type.
34
34
 
35
+ **FR-036 update:** the router now DOES import the generated Pydantic models
36
+ (`from .<Entity> import <Entity>, <Entity>Patch`) and constructs them to enforce
37
+ field constraints over HTTP — POST validates `<Entity>(**dto)`, PATCH validates
38
+ the all-optional `<Entity>Patch(**dto)` (present values only) → `400 {"error":"validation"}`.
39
+ The repository seam still receives the original `dict` (preserving the FR-035
40
+ present-key tristate + wire values), so the strong-typing simplification below
41
+ remains for the repo/response types; only the validation import was added.
42
+
35
43
  This keeps the router module decoupled from sibling generated files
36
44
  (the entity-model generator emits one `@dataclass`/Pydantic model per
37
45
  entity in a separate file — wiring router→entity-model would require
@@ -5,9 +5,23 @@ import re
5
5
 
6
6
  from metaobjects.meta.core.field.meta_field import MetaField
7
7
  from metaobjects.meta.core.object.meta_object import MetaObject
8
+ from metaobjects.meta.core.object.object_constants import OBJECT_SUBTYPE_ENTITY
8
9
  from metaobjects.meta.core.field import field_constants as fc
10
+ from metaobjects.meta.core.identity.identity_constants import (
11
+ GENERATION_INCREMENT,
12
+ GENERATION_UUID,
13
+ IDENTITY_ATTR_FIELDS,
14
+ IDENTITY_ATTR_GENERATION,
15
+ )
9
16
  from metaobjects.meta.core.validator import validator_constants as vc
10
- from metaobjects.shared.base_types import TYPE_VALIDATOR
17
+ from metaobjects.meta.persistence.origin.origin_constants import (
18
+ AGG_ALL,
19
+ AGG_ANY,
20
+ AGG_COLLECT,
21
+ ORIGIN_ATTR_AGG,
22
+ ORIGIN_SUBTYPE_AGGREGATE,
23
+ )
24
+ from metaobjects.shared.base_types import TYPE_ORIGIN, TYPE_VALIDATOR
11
25
  from metaobjects.shared.separators import PACKAGE_SEP
12
26
  from metaobjects.codegen.config import GenConfig
13
27
  from metaobjects.codegen.constants import generated_header
@@ -98,7 +112,10 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
98
112
  max_len = _first_attr(field, vc.VALIDATOR_SUBTYPE_LENGTH, vc.VALIDATOR_ATTR_MAX)
99
113
  field_max = field.attrs().get(fc.FIELD_ATTR_MAX_LENGTH)
100
114
  if _is_int(field_max):
101
- max_len = field_max
115
+ # FR-036 A3 — strictest-wins: when BOTH a validator.length @max and a field
116
+ # @maxLength cap a string, the smaller bound governs (never the field attr
117
+ # unconditionally, which could loosen a tighter validator).
118
+ max_len = field_max if max_len is None else min(max_len, field_max)
102
119
  if min_len is not None:
103
120
  kwargs["min_length"] = min_len
104
121
  if max_len is not None:
@@ -127,7 +144,11 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
127
144
  # located via the resolving field.children() in _validators().
128
145
  pattern = v.attr(vc.VALIDATOR_ATTR_PATTERN)
129
146
  if isinstance(pattern, str):
130
- kwargs["pattern"] = pattern
147
+ # FR-036 Pin 2 — a validator.regex is a FULL match. Pydantic v2 `pattern=`
148
+ # is search/partial (un-anchored), so wrap the authored expression in an
149
+ # anchored non-capturing group so alternation still spans the whole value
150
+ # (a redundant anchor on an already-anchored pattern matches identically).
151
+ kwargs["pattern"] = f"^(?:{pattern})$"
131
152
  break
132
153
 
133
154
  # ADR-0036/0037 Wave 3 — @stringFormat: hostname -> a codegen-owned canonical
@@ -137,60 +158,103 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
137
158
  if field.attrs().get(fc.FIELD_ATTR_STRING_FORMAT) == fc.STRING_FORMAT_HOSTNAME:
138
159
  kwargs["pattern"] = _HOSTNAME_REGEX
139
160
 
161
+ # FR-036 Pin 1 — a @required non-array string is non-empty: reject null / "" but
162
+ # ACCEPT whitespace-only. Emit an implicit min_length of 1, keeping the stricter
163
+ # floor if a validator.length @min already set one.
164
+ if (
165
+ field.sub_type == fc.FIELD_SUBTYPE_STRING
166
+ and not field_is_array(field)
167
+ and field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
168
+ ):
169
+ existing_min = kwargs.get("min_length")
170
+ kwargs["min_length"] = max(1, existing_min) if isinstance(existing_min, int) else 1
171
+
140
172
  return kwargs
141
173
 
142
174
 
143
- def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple[str, bool]:
144
- """Return (source line, uses_field). Collects required imports into *imports*."""
145
- # FR-019: a field resolving to a package-level shared enum is typed by the materialized
146
- # standalone class (imported from the shared `enums` module) or — when @provided — an
147
- # external module from per-port config; inline enums keep their `Literal[...]` (py_type_for).
175
+ # Constraint kwargs are emitted in this fixed order so generated output is deterministic.
176
+ _CONSTRAINT_ORDER = ("pattern", "ge", "le", "min_length", "max_length")
177
+
178
+
179
+ def _constraint_parts(constraints: dict[str, object]) -> list[str]:
180
+ """The ``Field(...)`` constraint kwargs rendered in the stable, deterministic order."""
181
+ return [f"{k}={constraints[k]!r}" for k in _CONSTRAINT_ORDER if k in constraints]
182
+
183
+
184
+ def _type_expr_for_field(
185
+ field: MetaField, imports: set[str], config: GenConfig
186
+ ) -> tuple[str, str | None]:
187
+ """The Python annotation text for *field* (arrays wrapped in ``list[...]``) plus the
188
+ shared-enum class name when the field resolves to one (used to render an enum
189
+ default). Collects any required imports into *imports*. Shared by the create-model
190
+ and patch-model emitters so both carry byte-identical typing.
191
+
192
+ FR-019: a field resolving to a package-level shared enum is typed by the materialized
193
+ standalone class (imported from the shared ``enums`` module) or — when @provided — an
194
+ external module; inline enums keep their ``Literal[...]`` (py_type_for)."""
148
195
  shared = (
149
196
  fr019.shared_enum_for_field(field)
150
197
  if field.sub_type == fc.FIELD_SUBTYPE_ENUM
151
198
  else None
152
199
  )
200
+ enum_type_name: str | None = None
153
201
  if shared is not None:
154
202
  type_name, import_line = fr019.enum_type_and_import(shared, config)
155
203
  imports.add(import_line)
204
+ enum_type_name = type_name
156
205
  type_expr = f"list[{type_name}]" if field_is_array(field) else type_name
157
206
  else:
158
207
  pt = py_type_for(field)
159
208
  imports.update(pt.imports)
160
209
  type_expr = pt.expr
161
- # ADR-0036/0037 Wave 3 — @stringFormat: email narrows a plain string to a
162
- # validated email. Bind Pydantic's idiomatic EmailStr (the Python analogue of
163
- # Zod .email()); hostname stays a plain str with a codegen-owned canonical
164
- # pattern= (handled in _validator_constraints). The field stays a string.
210
+ # ADR-0036/0037 Wave 3 — @stringFormat: email narrows a plain string to a validated
211
+ # email (Pydantic EmailStr, the Python analogue of Zod .email()); hostname stays a
212
+ # plain str with a codegen-owned canonical pattern= (in _validator_constraints).
165
213
  if (
166
214
  field.sub_type == fc.FIELD_SUBTYPE_STRING
167
215
  and field.attrs().get(fc.FIELD_ATTR_STRING_FORMAT) == fc.STRING_FORMAT_EMAIL
168
216
  ):
169
- base_expr = "EmailStr"
170
217
  imports.add("from pydantic import EmailStr")
171
- type_expr = f"list[{base_expr}]" if field_is_array(field) else base_expr
218
+ type_expr = "list[EmailStr]" if field_is_array(field) else "EmailStr"
172
219
  if field.sub_type in (fc.FIELD_SUBTYPE_OBJECT, fc.FIELD_SUBTYPE_MAP):
173
- # A field.object (-> VO) and a field.map with @objectRef (-> dict[str, VO])
174
- # both reference a value-object by name; import it so the annotation
175
- # py_type_for emitted resolves.
220
+ # A field.object (-> VO) and a field.map with @objectRef (-> dict[str, VO]) both
221
+ # reference a value-object by name; import it so the emitted annotation resolves.
222
+ # @objectRef is FQN-expanded at load; the generated VOs live flat in one package,
223
+ # so import by the bare class name.
176
224
  ref = field.attrs().get(fc.FIELD_ATTR_OBJECT_REF)
177
225
  if ref:
178
- # @objectRef is FQN-expanded at load time; the generated VOs live
179
- # flat in one package, so import by the bare class name.
180
226
  ref_name = str(ref).split("::")[-1]
181
227
  imports.add(f"from .{ref_name} import {ref_name}")
228
+ return type_expr, enum_type_name
229
+
230
+
231
+ def origin_guaranteed_non_null(field: MetaField) -> bool:
232
+ """#195 — a field derived by an origin.aggregate ``@agg:any|all|collect`` is
233
+ COALESCE-guaranteed non-null in the synthesized view (any→false, all→true,
234
+ collect→[]), so its read type is non-null even when the field is not ``@required``.
235
+ origin.first is deliberately NOT here — an empty related set selects no row (→ null);
236
+ origin.computed nullability is expression-dependent, so it stays the conservative
237
+ nullable default. Mirrors the TS originGuaranteedNonNull."""
238
+ # ADR-0039 sanctioned own — origin.* never inherits (ADR-0029), so own is correct.
239
+ for child in field.own_children():
240
+ if child.type == TYPE_ORIGIN and child.sub_type == ORIGIN_SUBTYPE_AGGREGATE:
241
+ agg = child.attr(ORIGIN_ATTR_AGG) # ADR-0039 own — origin attr
242
+ if agg in (AGG_ANY, AGG_ALL, AGG_COLLECT):
243
+ return True
244
+ return False
245
+
246
+
247
+ def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple[str, bool]:
248
+ """Return (source line, uses_field). Collects required imports into *imports*."""
249
+ type_expr, enum_type_name = _type_expr_for_field(field, imports, config)
182
250
  required = field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
183
251
  default_raw = field.attrs().get(fc.FIELD_ATTR_DEFAULT)
184
252
  # A server-side expression default (now(), gen_random_uuid(), CURRENT_TIMESTAMP)
185
253
  # is filled by the DB — the Python model carries no default for it (the field keeps
186
254
  # its required/optional shape). Only literal defaults become Pydantic defaults.
187
255
  has_default = default_raw is not None and not _is_sql_expr_default(default_raw)
188
- enum_type_name = type_name if shared is not None else None
189
256
 
190
- constraints = _validator_constraints(field)
191
- # Emit kwargs in a stable order so generated output is deterministic.
192
- _order = ["pattern", "ge", "le", "min_length", "max_length"]
193
- parts = [f"{k}={constraints[k]!r}" for k in _order if k in constraints]
257
+ parts = _constraint_parts(_validator_constraints(field))
194
258
  uses_field = bool(parts)
195
259
 
196
260
  # @default — render an enum member (Type.MEMBER, UPPER_CASE) or a Python literal.
@@ -202,15 +266,20 @@ def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple
202
266
  else:
203
267
  default_expr = None
204
268
 
205
- # A field is Optional only when it is neither required nor carries a @default.
206
- annotation = type_expr if (required or has_default) else f"{type_expr} | None"
269
+ # #195 a field derived by an origin.aggregate any|all|collect always COALESCEs
270
+ # to a value (any→false, all→true, collect→[]), so its read type is mandatory
271
+ # non-null exactly like a @required field (no `| None`, no default). origin.first /
272
+ # origin.computed stay the conservative nullable default.
273
+ mandatory = required or origin_guaranteed_non_null(field)
274
+ # A field is Optional only when it is neither mandatory nor carries a @default.
275
+ annotation = type_expr if (mandatory or has_default) else f"{type_expr} | None"
207
276
  if has_default and uses_field:
208
277
  assignment = f" = Field(default={default_expr}, {', '.join(parts)})"
209
278
  elif has_default:
210
279
  assignment = f" = {default_expr}"
211
- elif required and uses_field:
280
+ elif mandatory and uses_field:
212
281
  assignment = f" = Field({', '.join(parts)})"
213
- elif required:
282
+ elif mandatory:
214
283
  assignment = ""
215
284
  elif uses_field:
216
285
  assignment = f" = Field(default=None, {', '.join(parts)})"
@@ -226,6 +295,106 @@ def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple
226
295
  return f" {py_name}: {annotation}{assignment}", uses_field
227
296
 
228
297
 
298
+ def _primary_identity_field_names(entity: MetaObject) -> list[str]:
299
+ """The field names in the entity's primary identity (``identity.primary @fields``),
300
+ normalized to a list. Empty when there is no primary identity. Mirrors the TS
301
+ ``primaryIdentityFieldNames``."""
302
+ primary = entity.primary_identity()
303
+ if primary is None:
304
+ return []
305
+ # ADR-0039: resolving (identity attr) — an identity.primary may be inherited via extends.
306
+ fields = primary.get_meta_attr(IDENTITY_ATTR_FIELDS)
307
+ if isinstance(fields, (list, tuple)):
308
+ return [str(f) for f in fields]
309
+ if isinstance(fields, str) and fields:
310
+ return [s.strip() for s in fields.split(",") if s.strip()]
311
+ return []
312
+
313
+
314
+ def _auto_gen_pk_field_names(entity: MetaObject) -> set[str]:
315
+ """Primary-key field names that the SERVER generates (``identity.primary
316
+ @generation: increment|uuid``) — excluded from the create-validation shape because
317
+ the caller never supplies them. An ``assigned`` (or ungenerated) PK is NOT excluded
318
+ (the caller provides it). Mirrors the TS ``autoGenPkFieldNames``."""
319
+ primary = entity.primary_identity()
320
+ if primary is None:
321
+ return set()
322
+ # ADR-0039: resolving (identity attr).
323
+ generation = primary.get_meta_attr(IDENTITY_ATTR_GENERATION)
324
+ if generation not in (GENERATION_INCREMENT, GENERATION_UUID):
325
+ return set()
326
+ return set(_primary_identity_field_names(entity))
327
+
328
+
329
+ def _create_field_line(
330
+ field: MetaField, imports: set[str], config: GenConfig
331
+ ) -> tuple[str, bool]:
332
+ """A CREATE-model field line (FR-036): keyed by the WIRE name (``field.name``) and
333
+ carrying the same per-field constraints as the read model. A ``@required`` field
334
+ that is SERVER-FILLED — it has ANY ``@default`` (a literal OR a SQL expression such
335
+ as ``now()`` / ``gen_random_uuid()`` / ``CURRENT_TIMESTAMP``) or an ``@autoSet`` — is
336
+ emitted OPTIONAL, because the DB / trigger provides it and a POST that omits it must
337
+ not 400 (mirrors the TS InsertSchema ``fieldWillBeOptional``). A literal ``@default``
338
+ is preserved as the Python default; a SQL-expression default / ``@autoSet`` carries
339
+ no Python default (the server fills it). Returns (source line, uses_field)."""
340
+ type_expr, enum_type_name = _type_expr_for_field(field, imports, config)
341
+ required = field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
342
+ default_raw = field.attrs().get(fc.FIELD_ATTR_DEFAULT)
343
+ literal_default = default_raw is not None and not _is_sql_expr_default(default_raw)
344
+ auto_set = field.attrs().get(fc.FIELD_ATTR_AUTO_SET)
345
+ is_auto_set = auto_set in (fc.AUTO_SET_ON_CREATE, fc.AUTO_SET_ON_UPDATE)
346
+ # A field is required in the CREATE shape only when @required AND not server-filled:
347
+ # ANY @default (literal or SQL-expr) or @autoSet means the DB fills it, so the wire
348
+ # body may omit it (the FR-036 #1 regression fix).
349
+ server_filled = default_raw is not None or is_auto_set
350
+ create_required = required and not server_filled
351
+
352
+ parts = _constraint_parts(_validator_constraints(field))
353
+ uses_field = bool(parts)
354
+
355
+ py_name = field.name # FR-036 #3 — validation models key by the WIRE name (field.name)
356
+
357
+ if literal_default:
358
+ # Preserve the literal @default (enum member or Python literal) exactly as the
359
+ # read model does — the caller may omit the field and the model fills the default.
360
+ if enum_type_name is not None and not field_is_array(field):
361
+ default_expr = f"{enum_type_name}.{str(default_raw).upper()}"
362
+ else:
363
+ default_expr = repr(default_raw)
364
+ annotation = type_expr
365
+ assignment = (
366
+ f" = Field(default={default_expr}, {', '.join(parts)})"
367
+ if uses_field
368
+ else f" = {default_expr}"
369
+ )
370
+ elif create_required:
371
+ annotation = type_expr
372
+ assignment = f" = Field({', '.join(parts)})" if uses_field else ""
373
+ else:
374
+ annotation = f"{type_expr} | None"
375
+ assignment = (
376
+ f" = Field(default=None, {', '.join(parts)})" if uses_field else " = None"
377
+ )
378
+
379
+ return f" {py_name}: {annotation}{assignment}", uses_field
380
+
381
+
382
+ def _patch_field_line(
383
+ field: MetaField, imports: set[str], config: GenConfig
384
+ ) -> tuple[str, bool]:
385
+ """A PATCH-model field line: the field is made Optional (default ``None``) yet
386
+ carries the SAME per-field constraints as the create model, so a present, non-null
387
+ value is validated with the create rules (FR-036) while an absent field is left
388
+ untouched. A ``@default`` is never emitted (a patch does not default a column).
389
+ Returns (source line, uses_field)."""
390
+ type_expr, _enum = _type_expr_for_field(field, imports, config)
391
+ parts = _constraint_parts(_validator_constraints(field))
392
+ py_name = field.name # FR-036 #3 — validation models key by the WIRE name (field.name)
393
+ if parts:
394
+ return f" {py_name}: {type_expr} | None = Field(default=None, {', '.join(parts)})", True
395
+ return f" {py_name}: {type_expr} | None = None", False
396
+
397
+
229
398
  def _effective_fqn(entity: MetaObject) -> str:
230
399
  """`package::name` via the canonical :meth:`MetaData.resolution_key` — own
231
400
  package, else the file-default package (captured at parse), else the nearest
@@ -295,6 +464,80 @@ class EntityModelGenerator:
295
464
  lines.append(f" {d.relation_name}: list[{element}] = []")
296
465
  return lines, uses_field
297
466
 
467
+ def _emit_create_lines(
468
+ self,
469
+ entity: MetaObject,
470
+ imports: set[str],
471
+ config: GenConfig | None = None,
472
+ ) -> tuple[list[str], bool]:
473
+ """The ``<Name>Create`` body — the CREATE-validation shape the generated router
474
+ validates a POST body against (FR-036). Restates every EFFECTIVE field (own +
475
+ inherited via ``extends``) EXCEPT the ones the server owns: an auto-generated PK
476
+ (``identity.primary @generation``; FR-036 #2) and any ``@readOnly`` field
477
+ (FR-013) are omitted, and a ``@required`` server-filled field (``@default`` /
478
+ ``@autoSet``; FR-036 #1) is made optional. Keyed by the wire name; a TPH
479
+ subtype's discriminator is pinned to its ``Literal`` value (the route injects it
480
+ from the URL, so it stays optional with a default). M:N navigations are omitted
481
+ (a create body targets columns, not relations). Returns ``(lines, uses_field)``.
482
+ Override to add/transform create-model lines."""
483
+ cfg = config if config is not None else GenConfig(out_dir="")
484
+ auto_gen_pk = _auto_gen_pk_field_names(entity)
485
+ binding = tph_subtype_binding(entity)
486
+ disc_field = binding[0] if binding is not None else None
487
+ uses_field = False
488
+ lines: list[str] = []
489
+ # RESOLVING (fields()): the flat Create model subclasses BaseModel (not the entity
490
+ # base), so it restates inherited fields too — unlike the read model's own_fields
491
+ # subclass-emit.
492
+ for f in entity.fields():
493
+ if disc_field is not None and f.name == disc_field:
494
+ continue # pinned below (prepended), never as a normal field line
495
+ if f.name in auto_gen_pk:
496
+ continue # FR-036 #2 — a server-generated PK is never in the create body
497
+ if f.attrs().get(fc.FIELD_ATTR_READ_ONLY) is True:
498
+ continue # FR-013 — a read-only column is DB/owner-written, not create input
499
+ if f.is_derived():
500
+ continue # FR-024 §7 (#214) — a derived (origin.*) field is view-computed, not create input
501
+ line, used = _create_field_line(f, imports, cfg)
502
+ uses_field = uses_field or used
503
+ lines.append(line)
504
+ # FR-017 TPH: pin the inherited discriminator to this subtype's value (with a
505
+ # default so the URL-injected create body may omit it) — mirrors the read model.
506
+ if binding is not None:
507
+ disc_name, disc_value = binding
508
+ lines = [f' {disc_name}: Literal["{disc_value}"] = "{disc_value}"', *lines]
509
+ imports.add("from typing import Literal")
510
+ return lines, uses_field
511
+
512
+ def _emit_patch_lines(
513
+ self,
514
+ entity: MetaObject,
515
+ imports: set[str],
516
+ config: GenConfig | None = None,
517
+ ) -> tuple[list[str], bool]:
518
+ """The ``<Name>Patch`` body: every EFFECTIVE field (own + inherited via
519
+ ``extends``) made Optional while retaining its create-model constraints, so the
520
+ generated PATCH handler can validate present, non-null values without
521
+ bean-validating absent ones (FR-036). The primary-key field(s) are excluded
522
+ (route-authoritative; FR-036 #4) and M:N navigations are omitted (a patch
523
+ targets columns, not relations). Returns ``(lines, uses_field)``. Override to
524
+ add/transform patch-model lines."""
525
+ cfg = config if config is not None else GenConfig(out_dir="")
526
+ pk_fields = set(_primary_identity_field_names(entity))
527
+ uses_field = False
528
+ lines: list[str] = []
529
+ # RESOLVING (fields()): the flat Patch model is self-contained (subclasses
530
+ # BaseModel, not the entity base), so it restates inherited fields too.
531
+ for f in entity.fields():
532
+ if f.name in pk_fields:
533
+ continue # FR-036 #4 — the PK is route-authoritative, never patched
534
+ if f.is_derived():
535
+ continue # FR-024 §7 (#214) — a derived (origin.*) field is view-computed, never patched
536
+ line, used = _patch_field_line(f, imports, cfg)
537
+ uses_field = uses_field or used
538
+ lines.append(line)
539
+ return lines, uses_field
540
+
298
541
  def render_entity_model(
299
542
  self,
300
543
  entity: MetaObject,
@@ -333,11 +576,29 @@ class EntityModelGenerator:
333
576
  imports.add("from typing import Literal")
334
577
  body = lines if lines else [" pass"]
335
578
 
336
- # Import only the pydantic names actually referenced.
579
+ # FR-036 an object.entity also emits two flat validation models the generated
580
+ # router binds: a ``<Name>Create`` (POST-body shape: auto-gen PK / @readOnly
581
+ # omitted, @default/@autoSet optional, other @required required) and an
582
+ # all-optional ``<Name>Patch`` (PATCH-body shape: PK excluded). Both key by the
583
+ # WIRE name and carry the field constraints, so present values are validated with
584
+ # the create rules. Value objects / projections are never routed, so they emit
585
+ # neither (only the read model). See findings #1–#4.
586
+ emit_validation_models = entity.sub_type == OBJECT_SUBTYPE_ENTITY
587
+ create_lines: list[str] = []
588
+ create_uses_field = False
589
+ patch_lines: list[str] = []
590
+ patch_uses_field = False
591
+ if emit_validation_models:
592
+ create_lines, create_uses_field = self._emit_create_lines(entity, imports, config)
593
+ patch_lines, patch_uses_field = self._emit_patch_lines(entity, imports, config)
594
+
595
+ # Import only the pydantic names actually referenced. The main model imports
596
+ # BaseModel only when it has no super, but the flat Create/Patch models always
597
+ # subclass BaseModel, so their presence forces the import regardless.
337
598
  pyd_names: list[str] = []
338
- if entity.super_data is None:
599
+ if entity.super_data is None or emit_validation_models:
339
600
  pyd_names.append("BaseModel")
340
- if uses_field:
601
+ if uses_field or create_uses_field or patch_uses_field:
341
602
  pyd_names.append("Field")
342
603
 
343
604
  parts: list[str] = [
@@ -350,7 +611,22 @@ class EntityModelGenerator:
350
611
  parts += [*extra_imports, ""]
351
612
  if pyd_names:
352
613
  parts += [f"from pydantic import {', '.join(pyd_names)}", ""]
353
- parts += ["", self._emit_class_header(entity, base_class), *body, ""]
614
+ parts += ["", self._emit_class_header(entity, base_class), *body]
615
+ if emit_validation_models:
616
+ parts += [
617
+ "",
618
+ "",
619
+ f"class {entity.name}Create(BaseModel):",
620
+ ' """GENERATED — CREATE input: auto-gen PK / @readOnly omitted; '
621
+ '@default/@autoSet optional; present values validated (FR-036)."""',
622
+ *create_lines,
623
+ "",
624
+ "",
625
+ f"class {entity.name}Patch(BaseModel):",
626
+ ' """GENERATED — PATCH input: all fields optional (PK excluded); present values validated (FR-036)."""',
627
+ *patch_lines,
628
+ ]
629
+ parts += [""]
354
630
  return "\n".join(parts)
355
631
 
356
632
  def _render_shared_enums_module(self, entities: list[MetaObject]) -> EmittedFile | None:
@@ -250,7 +250,12 @@ class FilterAllowlistGenerator:
250
250
  src = _primary_source_rdb(entity)
251
251
  if src is None:
252
252
  return None
253
- if src.effective_kind() != SOURCE_KIND_TABLE:
253
+ # FR-024 §7 (#214): a write-through entity read-view is writable and its generated
254
+ # router imports this allowlist — so it MUST be emitted for write-through regardless of
255
+ # source declaration order (matching the router gate; else a view-source-first
256
+ # write-through router imports a never-generated module and FastAPI startup crashes). A
257
+ # projection (read-only source only) is not write-through, so it still skips.
258
+ if not entity.is_write_through() and src.effective_kind() != SOURCE_KIND_TABLE:
254
259
  return None
255
260
 
256
261
  short_name = entity.name