metaobjects 0.15.13__tar.gz → 0.16.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.
- {metaobjects-0.15.13 → metaobjects-0.16.0}/PKG-INFO +1 -1
- {metaobjects-0.15.13 → metaobjects-0.16.0}/pyproject.toml +1 -1
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/KNOWN_GAPS.md +8 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/entity_model.py +271 -27
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/router_generator.py +94 -9
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/extract.py +6 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/types.py +31 -0
- metaobjects-0.16.0/tests/codegen/golden/extends/expected/BaseEntity.py +23 -0
- metaobjects-0.16.0/tests/codegen/golden/extends/expected/Program.py +27 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +2 -2
- metaobjects-0.16.0/tests/codegen/golden/vanilla/expected/Subscriber.py +26 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_entity_model.py +128 -9
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_extends_abstract_field_inheritance.py +6 -3
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_inherit_without_restate_gate.py +8 -3
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_inheritance_conformance.py +6 -2
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/api_contract_server.py +29 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/generated_jsonb_app.py +3 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/generated_m2m_app.py +7 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/generated_router_app.py +6 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/generated_tph_app.py +7 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_generated_router_uuid_pk.py +3 -0
- metaobjects-0.15.13/tests/codegen/golden/extends/expected/BaseEntity.py +0 -11
- metaobjects-0.15.13/tests/codegen/golden/extends/expected/Program.py +0 -11
- metaobjects-0.15.13/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -12
- {metaobjects-0.15.13 → metaobjects-0.16.0}/.gitignore +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/LICENSE +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/README.md +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/hatch_build.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/agent_context/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/agent_context/scaffold.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/api_model.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/builder.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/naming.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/paths.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/apidocs/renderer.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/attr_class_map.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/cli.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/config.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/format.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generator_registry.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/template_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/instance_artifacts.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/overwrite_policy.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/runner.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/runtime/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/type_map.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/core_types.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/datatype.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/documentation/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/documentation/doc_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/documentation/doc_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/documentation/doc_schema.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/errors.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/merge.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/meta_data_loader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/registered_validation.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/sources/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/sources/directory_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/sources/file_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/sources/uri_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/validate_discriminator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/validate_field_readonly.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/loader/validation_passes.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/attr/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/field/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/field/field_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/field/meta_field.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/identity/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/index/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/index/index_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/index/meta_index.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/meta_object.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/object_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/object_extract.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/object/value_object.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/validator/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/meta_data.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/meta_root.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/provider_extends.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/template/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/template/meta_template.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/template/prompt_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/meta/template/template_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/naming_refs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/parser.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/parser_yaml.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/py.typed +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/registry.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/registry_manifest.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/email_document.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/escapers.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/coerce.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/extract_map.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/locate.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/normalize.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/strip.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/filesystem_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/prompt_field.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/prompt/prompt_style.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/renderer.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/render/verify.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/runtime/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/runtime/llm_recorder.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/runtime/n2m_resolver.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/runtime/object_manager.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/runtime/tph.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/serializer_json.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/shared/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/shared/base_types.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/shared/separators.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/shared/structural.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/source/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/source/error_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/source/json_path.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/source/semantic_diff.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/source/yaml_positions.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/attr.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/db.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/documentation.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/field.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/identity.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/index.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/layout.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/object.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/origin.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/prompt.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/relationship.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/source.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/template.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/ui.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/validator.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/spec_metamodel/view.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/super_resolve.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/validation_types.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/yaml_desugar.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/extends/meta.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/nested-array/meta.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/scalars/expected/Report.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/scalars/meta.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/golden/vanilla/meta.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_abstract_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_api_docs_builder.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_api_docs_paths.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli_providers.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli_registry.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli_staleness_nudge.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli_verify_strict.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_cli_verify_subverbs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_constants_config.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_enum_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_extractor_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_filter_allowlist_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_format.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_fr010_nested_object_prompt.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_fr010_output_codegen.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_generator_extension_seams.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_golden.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_instance_artifacts.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_m2m_codegen.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_output_parser_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_overwrite_policy.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_payload_vo_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_projection_compile.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_render_helper_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_render_helper_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_reverse_finders.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_router_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_runner.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_data.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_output_pattern.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_scope_helpers.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_scope_walk.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_template_spec.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_tph_codegen.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_trace_helper_generator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_type_map.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/codegen/test_validation_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/capabilities.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/conformance-expected-failures.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/conformance_adapter.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/corpus.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/expected_failures.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/fixture_discovery.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/navigator.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_extract_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_extract_object_verdict.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_fr010_loader_attrs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_fr011_attrs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_generator_registry_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_object_model_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_provider_composition_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_registry_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_runner_hardfail.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_spec_metamodel_embed.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_strict_attr_load.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_template_codegen_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_template_generator_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/test_yaml_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/api_contract_assertions.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/api_contract_jsonb_server.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/api_contract_m2m_server.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/meta_ai_trace.yaml +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/normalization.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/postgres_container.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/query_runner.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/scenarios.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract_generated.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract_jsonb.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract_m2m.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract_m2m_generated.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_api_contract_tph_generated.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_llm_call_trace.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_normalization.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_query_scenarios.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/integration/test_runtime_return_types.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/open_closed_proof_test.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_coerce.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_extract.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_extract_map.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_json_forgiving_reader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_locate.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_model.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_normalize.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_strip.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/prompt/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/prompt/test_output_format_renderer.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_email_document.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_filesystem_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_output_format_renderer_nested.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_output_prompt_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_render_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_render_max_chars.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_verify.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/render/test_verify_conformance.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_fr5c_merge_attribution.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_fr5d_reference_resolution.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_fr5e_database_source_shape.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_json_path.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_semantic_diff.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_source_on_node.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/source/test_yaml_positions.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/test_api_docs_accuracy.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/__init__.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_agent_context_staleness.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_capabilities.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_common_attrs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_core_types.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_effective_package.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_errors.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_field_enum.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_field_map_validation.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_index_lookup.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_llm_recorder.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_loader.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_loader_bom.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_loader_class.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_merge.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_meta_attr.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_meta_data.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_meta_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_module_shortcuts.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_n2m_resolver.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_n2m_resolver_inherited.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_one_primary_source.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_parser.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_parser_yaml.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_provider.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_provider_extension.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_registry.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_registry_completeness.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_registry_extend.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_registry_sealed.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_relationship_referential_actions.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_resolution_key.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_serializer.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_shared_constants.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_smoke.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_sources.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_strict_child_placement.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_super_resolve.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_template_toolcall.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_validation_attr_schema.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_validation_filter_values.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_validation_origin_paths.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_validation_sort_field.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_validation_warnings.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_write_coercion_arrays.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/tests/unit/test_yaml_desugar.py +0 -0
- {metaobjects-0.15.13 → metaobjects-0.16.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metaobjects
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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.
|
|
3
|
+
version = "0.16.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
|
{metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/entity_model.py
RENAMED
|
@@ -5,7 +5,14 @@ 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
17
|
from metaobjects.shared.base_types import TYPE_VALIDATOR
|
|
11
18
|
from metaobjects.shared.separators import PACKAGE_SEP
|
|
@@ -98,7 +105,10 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
|
|
|
98
105
|
max_len = _first_attr(field, vc.VALIDATOR_SUBTYPE_LENGTH, vc.VALIDATOR_ATTR_MAX)
|
|
99
106
|
field_max = field.attrs().get(fc.FIELD_ATTR_MAX_LENGTH)
|
|
100
107
|
if _is_int(field_max):
|
|
101
|
-
|
|
108
|
+
# FR-036 A3 — strictest-wins: when BOTH a validator.length @max and a field
|
|
109
|
+
# @maxLength cap a string, the smaller bound governs (never the field attr
|
|
110
|
+
# unconditionally, which could loosen a tighter validator).
|
|
111
|
+
max_len = field_max if max_len is None else min(max_len, field_max)
|
|
102
112
|
if min_len is not None:
|
|
103
113
|
kwargs["min_length"] = min_len
|
|
104
114
|
if max_len is not None:
|
|
@@ -127,7 +137,11 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
|
|
|
127
137
|
# located via the resolving field.children() in _validators().
|
|
128
138
|
pattern = v.attr(vc.VALIDATOR_ATTR_PATTERN)
|
|
129
139
|
if isinstance(pattern, str):
|
|
130
|
-
|
|
140
|
+
# FR-036 Pin 2 — a validator.regex is a FULL match. Pydantic v2 `pattern=`
|
|
141
|
+
# is search/partial (un-anchored), so wrap the authored expression in an
|
|
142
|
+
# anchored non-capturing group so alternation still spans the whole value
|
|
143
|
+
# (a redundant anchor on an already-anchored pattern matches identically).
|
|
144
|
+
kwargs["pattern"] = f"^(?:{pattern})$"
|
|
131
145
|
break
|
|
132
146
|
|
|
133
147
|
# ADR-0036/0037 Wave 3 — @stringFormat: hostname -> a codegen-owned canonical
|
|
@@ -137,60 +151,87 @@ def _validator_constraints(field: MetaField) -> dict[str, object]:
|
|
|
137
151
|
if field.attrs().get(fc.FIELD_ATTR_STRING_FORMAT) == fc.STRING_FORMAT_HOSTNAME:
|
|
138
152
|
kwargs["pattern"] = _HOSTNAME_REGEX
|
|
139
153
|
|
|
154
|
+
# FR-036 Pin 1 — a @required non-array string is non-empty: reject null / "" but
|
|
155
|
+
# ACCEPT whitespace-only. Emit an implicit min_length of 1, keeping the stricter
|
|
156
|
+
# floor if a validator.length @min already set one.
|
|
157
|
+
if (
|
|
158
|
+
field.sub_type == fc.FIELD_SUBTYPE_STRING
|
|
159
|
+
and not field_is_array(field)
|
|
160
|
+
and field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
|
|
161
|
+
):
|
|
162
|
+
existing_min = kwargs.get("min_length")
|
|
163
|
+
kwargs["min_length"] = max(1, existing_min) if isinstance(existing_min, int) else 1
|
|
164
|
+
|
|
140
165
|
return kwargs
|
|
141
166
|
|
|
142
167
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
168
|
+
# Constraint kwargs are emitted in this fixed order so generated output is deterministic.
|
|
169
|
+
_CONSTRAINT_ORDER = ("pattern", "ge", "le", "min_length", "max_length")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _constraint_parts(constraints: dict[str, object]) -> list[str]:
|
|
173
|
+
"""The ``Field(...)`` constraint kwargs rendered in the stable, deterministic order."""
|
|
174
|
+
return [f"{k}={constraints[k]!r}" for k in _CONSTRAINT_ORDER if k in constraints]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _type_expr_for_field(
|
|
178
|
+
field: MetaField, imports: set[str], config: GenConfig
|
|
179
|
+
) -> tuple[str, str | None]:
|
|
180
|
+
"""The Python annotation text for *field* (arrays wrapped in ``list[...]``) plus the
|
|
181
|
+
shared-enum class name when the field resolves to one (used to render an enum
|
|
182
|
+
default). Collects any required imports into *imports*. Shared by the create-model
|
|
183
|
+
and patch-model emitters so both carry byte-identical typing.
|
|
184
|
+
|
|
185
|
+
FR-019: a field resolving to a package-level shared enum is typed by the materialized
|
|
186
|
+
standalone class (imported from the shared ``enums`` module) or — when @provided — an
|
|
187
|
+
external module; inline enums keep their ``Literal[...]`` (py_type_for)."""
|
|
148
188
|
shared = (
|
|
149
189
|
fr019.shared_enum_for_field(field)
|
|
150
190
|
if field.sub_type == fc.FIELD_SUBTYPE_ENUM
|
|
151
191
|
else None
|
|
152
192
|
)
|
|
193
|
+
enum_type_name: str | None = None
|
|
153
194
|
if shared is not None:
|
|
154
195
|
type_name, import_line = fr019.enum_type_and_import(shared, config)
|
|
155
196
|
imports.add(import_line)
|
|
197
|
+
enum_type_name = type_name
|
|
156
198
|
type_expr = f"list[{type_name}]" if field_is_array(field) else type_name
|
|
157
199
|
else:
|
|
158
200
|
pt = py_type_for(field)
|
|
159
201
|
imports.update(pt.imports)
|
|
160
202
|
type_expr = pt.expr
|
|
161
|
-
# ADR-0036/0037 Wave 3 — @stringFormat: email narrows a plain string to a
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
# pattern= (handled in _validator_constraints). The field stays a string.
|
|
203
|
+
# ADR-0036/0037 Wave 3 — @stringFormat: email narrows a plain string to a validated
|
|
204
|
+
# email (Pydantic EmailStr, the Python analogue of Zod .email()); hostname stays a
|
|
205
|
+
# plain str with a codegen-owned canonical pattern= (in _validator_constraints).
|
|
165
206
|
if (
|
|
166
207
|
field.sub_type == fc.FIELD_SUBTYPE_STRING
|
|
167
208
|
and field.attrs().get(fc.FIELD_ATTR_STRING_FORMAT) == fc.STRING_FORMAT_EMAIL
|
|
168
209
|
):
|
|
169
|
-
base_expr = "EmailStr"
|
|
170
210
|
imports.add("from pydantic import EmailStr")
|
|
171
|
-
type_expr =
|
|
211
|
+
type_expr = "list[EmailStr]" if field_is_array(field) else "EmailStr"
|
|
172
212
|
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
|
-
#
|
|
175
|
-
#
|
|
213
|
+
# A field.object (-> VO) and a field.map with @objectRef (-> dict[str, VO]) both
|
|
214
|
+
# reference a value-object by name; import it so the emitted annotation resolves.
|
|
215
|
+
# @objectRef is FQN-expanded at load; the generated VOs live flat in one package,
|
|
216
|
+
# so import by the bare class name.
|
|
176
217
|
ref = field.attrs().get(fc.FIELD_ATTR_OBJECT_REF)
|
|
177
218
|
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
219
|
ref_name = str(ref).split("::")[-1]
|
|
181
220
|
imports.add(f"from .{ref_name} import {ref_name}")
|
|
221
|
+
return type_expr, enum_type_name
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple[str, bool]:
|
|
225
|
+
"""Return (source line, uses_field). Collects required imports into *imports*."""
|
|
226
|
+
type_expr, enum_type_name = _type_expr_for_field(field, imports, config)
|
|
182
227
|
required = field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
|
|
183
228
|
default_raw = field.attrs().get(fc.FIELD_ATTR_DEFAULT)
|
|
184
229
|
# A server-side expression default (now(), gen_random_uuid(), CURRENT_TIMESTAMP)
|
|
185
230
|
# is filled by the DB — the Python model carries no default for it (the field keeps
|
|
186
231
|
# its required/optional shape). Only literal defaults become Pydantic defaults.
|
|
187
232
|
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
233
|
|
|
190
|
-
|
|
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]
|
|
234
|
+
parts = _constraint_parts(_validator_constraints(field))
|
|
194
235
|
uses_field = bool(parts)
|
|
195
236
|
|
|
196
237
|
# @default — render an enum member (Type.MEMBER, UPPER_CASE) or a Python literal.
|
|
@@ -226,6 +267,106 @@ def _field_line(field: MetaField, imports: set[str], config: GenConfig) -> tuple
|
|
|
226
267
|
return f" {py_name}: {annotation}{assignment}", uses_field
|
|
227
268
|
|
|
228
269
|
|
|
270
|
+
def _primary_identity_field_names(entity: MetaObject) -> list[str]:
|
|
271
|
+
"""The field names in the entity's primary identity (``identity.primary @fields``),
|
|
272
|
+
normalized to a list. Empty when there is no primary identity. Mirrors the TS
|
|
273
|
+
``primaryIdentityFieldNames``."""
|
|
274
|
+
primary = entity.primary_identity()
|
|
275
|
+
if primary is None:
|
|
276
|
+
return []
|
|
277
|
+
# ADR-0039: resolving (identity attr) — an identity.primary may be inherited via extends.
|
|
278
|
+
fields = primary.get_meta_attr(IDENTITY_ATTR_FIELDS)
|
|
279
|
+
if isinstance(fields, (list, tuple)):
|
|
280
|
+
return [str(f) for f in fields]
|
|
281
|
+
if isinstance(fields, str) and fields:
|
|
282
|
+
return [s.strip() for s in fields.split(",") if s.strip()]
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _auto_gen_pk_field_names(entity: MetaObject) -> set[str]:
|
|
287
|
+
"""Primary-key field names that the SERVER generates (``identity.primary
|
|
288
|
+
@generation: increment|uuid``) — excluded from the create-validation shape because
|
|
289
|
+
the caller never supplies them. An ``assigned`` (or ungenerated) PK is NOT excluded
|
|
290
|
+
(the caller provides it). Mirrors the TS ``autoGenPkFieldNames``."""
|
|
291
|
+
primary = entity.primary_identity()
|
|
292
|
+
if primary is None:
|
|
293
|
+
return set()
|
|
294
|
+
# ADR-0039: resolving (identity attr).
|
|
295
|
+
generation = primary.get_meta_attr(IDENTITY_ATTR_GENERATION)
|
|
296
|
+
if generation not in (GENERATION_INCREMENT, GENERATION_UUID):
|
|
297
|
+
return set()
|
|
298
|
+
return set(_primary_identity_field_names(entity))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _create_field_line(
|
|
302
|
+
field: MetaField, imports: set[str], config: GenConfig
|
|
303
|
+
) -> tuple[str, bool]:
|
|
304
|
+
"""A CREATE-model field line (FR-036): keyed by the WIRE name (``field.name``) and
|
|
305
|
+
carrying the same per-field constraints as the read model. A ``@required`` field
|
|
306
|
+
that is SERVER-FILLED — it has ANY ``@default`` (a literal OR a SQL expression such
|
|
307
|
+
as ``now()`` / ``gen_random_uuid()`` / ``CURRENT_TIMESTAMP``) or an ``@autoSet`` — is
|
|
308
|
+
emitted OPTIONAL, because the DB / trigger provides it and a POST that omits it must
|
|
309
|
+
not 400 (mirrors the TS InsertSchema ``fieldWillBeOptional``). A literal ``@default``
|
|
310
|
+
is preserved as the Python default; a SQL-expression default / ``@autoSet`` carries
|
|
311
|
+
no Python default (the server fills it). Returns (source line, uses_field)."""
|
|
312
|
+
type_expr, enum_type_name = _type_expr_for_field(field, imports, config)
|
|
313
|
+
required = field.attrs().get(fc.FIELD_ATTR_REQUIRED) is True
|
|
314
|
+
default_raw = field.attrs().get(fc.FIELD_ATTR_DEFAULT)
|
|
315
|
+
literal_default = default_raw is not None and not _is_sql_expr_default(default_raw)
|
|
316
|
+
auto_set = field.attrs().get(fc.FIELD_ATTR_AUTO_SET)
|
|
317
|
+
is_auto_set = auto_set in (fc.AUTO_SET_ON_CREATE, fc.AUTO_SET_ON_UPDATE)
|
|
318
|
+
# A field is required in the CREATE shape only when @required AND not server-filled:
|
|
319
|
+
# ANY @default (literal or SQL-expr) or @autoSet means the DB fills it, so the wire
|
|
320
|
+
# body may omit it (the FR-036 #1 regression fix).
|
|
321
|
+
server_filled = default_raw is not None or is_auto_set
|
|
322
|
+
create_required = required and not server_filled
|
|
323
|
+
|
|
324
|
+
parts = _constraint_parts(_validator_constraints(field))
|
|
325
|
+
uses_field = bool(parts)
|
|
326
|
+
|
|
327
|
+
py_name = field.name # FR-036 #3 — validation models key by the WIRE name (field.name)
|
|
328
|
+
|
|
329
|
+
if literal_default:
|
|
330
|
+
# Preserve the literal @default (enum member or Python literal) exactly as the
|
|
331
|
+
# read model does — the caller may omit the field and the model fills the default.
|
|
332
|
+
if enum_type_name is not None and not field_is_array(field):
|
|
333
|
+
default_expr = f"{enum_type_name}.{str(default_raw).upper()}"
|
|
334
|
+
else:
|
|
335
|
+
default_expr = repr(default_raw)
|
|
336
|
+
annotation = type_expr
|
|
337
|
+
assignment = (
|
|
338
|
+
f" = Field(default={default_expr}, {', '.join(parts)})"
|
|
339
|
+
if uses_field
|
|
340
|
+
else f" = {default_expr}"
|
|
341
|
+
)
|
|
342
|
+
elif create_required:
|
|
343
|
+
annotation = type_expr
|
|
344
|
+
assignment = f" = Field({', '.join(parts)})" if uses_field else ""
|
|
345
|
+
else:
|
|
346
|
+
annotation = f"{type_expr} | None"
|
|
347
|
+
assignment = (
|
|
348
|
+
f" = Field(default=None, {', '.join(parts)})" if uses_field else " = None"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return f" {py_name}: {annotation}{assignment}", uses_field
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _patch_field_line(
|
|
355
|
+
field: MetaField, imports: set[str], config: GenConfig
|
|
356
|
+
) -> tuple[str, bool]:
|
|
357
|
+
"""A PATCH-model field line: the field is made Optional (default ``None``) yet
|
|
358
|
+
carries the SAME per-field constraints as the create model, so a present, non-null
|
|
359
|
+
value is validated with the create rules (FR-036) while an absent field is left
|
|
360
|
+
untouched. A ``@default`` is never emitted (a patch does not default a column).
|
|
361
|
+
Returns (source line, uses_field)."""
|
|
362
|
+
type_expr, _enum = _type_expr_for_field(field, imports, config)
|
|
363
|
+
parts = _constraint_parts(_validator_constraints(field))
|
|
364
|
+
py_name = field.name # FR-036 #3 — validation models key by the WIRE name (field.name)
|
|
365
|
+
if parts:
|
|
366
|
+
return f" {py_name}: {type_expr} | None = Field(default=None, {', '.join(parts)})", True
|
|
367
|
+
return f" {py_name}: {type_expr} | None = None", False
|
|
368
|
+
|
|
369
|
+
|
|
229
370
|
def _effective_fqn(entity: MetaObject) -> str:
|
|
230
371
|
"""`package::name` via the canonical :meth:`MetaData.resolution_key` — own
|
|
231
372
|
package, else the file-default package (captured at parse), else the nearest
|
|
@@ -295,6 +436,76 @@ class EntityModelGenerator:
|
|
|
295
436
|
lines.append(f" {d.relation_name}: list[{element}] = []")
|
|
296
437
|
return lines, uses_field
|
|
297
438
|
|
|
439
|
+
def _emit_create_lines(
|
|
440
|
+
self,
|
|
441
|
+
entity: MetaObject,
|
|
442
|
+
imports: set[str],
|
|
443
|
+
config: GenConfig | None = None,
|
|
444
|
+
) -> tuple[list[str], bool]:
|
|
445
|
+
"""The ``<Name>Create`` body — the CREATE-validation shape the generated router
|
|
446
|
+
validates a POST body against (FR-036). Restates every EFFECTIVE field (own +
|
|
447
|
+
inherited via ``extends``) EXCEPT the ones the server owns: an auto-generated PK
|
|
448
|
+
(``identity.primary @generation``; FR-036 #2) and any ``@readOnly`` field
|
|
449
|
+
(FR-013) are omitted, and a ``@required`` server-filled field (``@default`` /
|
|
450
|
+
``@autoSet``; FR-036 #1) is made optional. Keyed by the wire name; a TPH
|
|
451
|
+
subtype's discriminator is pinned to its ``Literal`` value (the route injects it
|
|
452
|
+
from the URL, so it stays optional with a default). M:N navigations are omitted
|
|
453
|
+
(a create body targets columns, not relations). Returns ``(lines, uses_field)``.
|
|
454
|
+
Override to add/transform create-model lines."""
|
|
455
|
+
cfg = config if config is not None else GenConfig(out_dir="")
|
|
456
|
+
auto_gen_pk = _auto_gen_pk_field_names(entity)
|
|
457
|
+
binding = tph_subtype_binding(entity)
|
|
458
|
+
disc_field = binding[0] if binding is not None else None
|
|
459
|
+
uses_field = False
|
|
460
|
+
lines: list[str] = []
|
|
461
|
+
# RESOLVING (fields()): the flat Create model subclasses BaseModel (not the entity
|
|
462
|
+
# base), so it restates inherited fields too — unlike the read model's own_fields
|
|
463
|
+
# subclass-emit.
|
|
464
|
+
for f in entity.fields():
|
|
465
|
+
if disc_field is not None and f.name == disc_field:
|
|
466
|
+
continue # pinned below (prepended), never as a normal field line
|
|
467
|
+
if f.name in auto_gen_pk:
|
|
468
|
+
continue # FR-036 #2 — a server-generated PK is never in the create body
|
|
469
|
+
if f.attrs().get(fc.FIELD_ATTR_READ_ONLY) is True:
|
|
470
|
+
continue # FR-013 — a read-only column is DB/owner-written, not create input
|
|
471
|
+
line, used = _create_field_line(f, imports, cfg)
|
|
472
|
+
uses_field = uses_field or used
|
|
473
|
+
lines.append(line)
|
|
474
|
+
# FR-017 TPH: pin the inherited discriminator to this subtype's value (with a
|
|
475
|
+
# default so the URL-injected create body may omit it) — mirrors the read model.
|
|
476
|
+
if binding is not None:
|
|
477
|
+
disc_name, disc_value = binding
|
|
478
|
+
lines = [f' {disc_name}: Literal["{disc_value}"] = "{disc_value}"', *lines]
|
|
479
|
+
imports.add("from typing import Literal")
|
|
480
|
+
return lines, uses_field
|
|
481
|
+
|
|
482
|
+
def _emit_patch_lines(
|
|
483
|
+
self,
|
|
484
|
+
entity: MetaObject,
|
|
485
|
+
imports: set[str],
|
|
486
|
+
config: GenConfig | None = None,
|
|
487
|
+
) -> tuple[list[str], bool]:
|
|
488
|
+
"""The ``<Name>Patch`` body: every EFFECTIVE field (own + inherited via
|
|
489
|
+
``extends``) made Optional while retaining its create-model constraints, so the
|
|
490
|
+
generated PATCH handler can validate present, non-null values without
|
|
491
|
+
bean-validating absent ones (FR-036). The primary-key field(s) are excluded
|
|
492
|
+
(route-authoritative; FR-036 #4) and M:N navigations are omitted (a patch
|
|
493
|
+
targets columns, not relations). Returns ``(lines, uses_field)``. Override to
|
|
494
|
+
add/transform patch-model lines."""
|
|
495
|
+
cfg = config if config is not None else GenConfig(out_dir="")
|
|
496
|
+
pk_fields = set(_primary_identity_field_names(entity))
|
|
497
|
+
uses_field = False
|
|
498
|
+
lines: list[str] = []
|
|
499
|
+
# RESOLVING (fields()): the flat Patch model is self-contained (subclasses
|
|
500
|
+
# BaseModel, not the entity base), so it restates inherited fields too.
|
|
501
|
+
for f in entity.fields():
|
|
502
|
+
if f.name in pk_fields:
|
|
503
|
+
continue # FR-036 #4 — the PK is route-authoritative, never patched
|
|
504
|
+
line, used = _patch_field_line(f, imports, cfg)
|
|
505
|
+
uses_field = uses_field or used
|
|
506
|
+
lines.append(line)
|
|
507
|
+
return lines, uses_field
|
|
508
|
+
|
|
298
509
|
def render_entity_model(
|
|
299
510
|
self,
|
|
300
511
|
entity: MetaObject,
|
|
@@ -333,11 +544,29 @@ class EntityModelGenerator:
|
|
|
333
544
|
imports.add("from typing import Literal")
|
|
334
545
|
body = lines if lines else [" pass"]
|
|
335
546
|
|
|
336
|
-
#
|
|
547
|
+
# FR-036 — an object.entity also emits two flat validation models the generated
|
|
548
|
+
# router binds: a ``<Name>Create`` (POST-body shape: auto-gen PK / @readOnly
|
|
549
|
+
# omitted, @default/@autoSet optional, other @required required) and an
|
|
550
|
+
# all-optional ``<Name>Patch`` (PATCH-body shape: PK excluded). Both key by the
|
|
551
|
+
# WIRE name and carry the field constraints, so present values are validated with
|
|
552
|
+
# the create rules. Value objects / projections are never routed, so they emit
|
|
553
|
+
# neither (only the read model). See findings #1–#4.
|
|
554
|
+
emit_validation_models = entity.sub_type == OBJECT_SUBTYPE_ENTITY
|
|
555
|
+
create_lines: list[str] = []
|
|
556
|
+
create_uses_field = False
|
|
557
|
+
patch_lines: list[str] = []
|
|
558
|
+
patch_uses_field = False
|
|
559
|
+
if emit_validation_models:
|
|
560
|
+
create_lines, create_uses_field = self._emit_create_lines(entity, imports, config)
|
|
561
|
+
patch_lines, patch_uses_field = self._emit_patch_lines(entity, imports, config)
|
|
562
|
+
|
|
563
|
+
# Import only the pydantic names actually referenced. The main model imports
|
|
564
|
+
# BaseModel only when it has no super, but the flat Create/Patch models always
|
|
565
|
+
# subclass BaseModel, so their presence forces the import regardless.
|
|
337
566
|
pyd_names: list[str] = []
|
|
338
|
-
if entity.super_data is None:
|
|
567
|
+
if entity.super_data is None or emit_validation_models:
|
|
339
568
|
pyd_names.append("BaseModel")
|
|
340
|
-
if uses_field:
|
|
569
|
+
if uses_field or create_uses_field or patch_uses_field:
|
|
341
570
|
pyd_names.append("Field")
|
|
342
571
|
|
|
343
572
|
parts: list[str] = [
|
|
@@ -350,7 +579,22 @@ class EntityModelGenerator:
|
|
|
350
579
|
parts += [*extra_imports, ""]
|
|
351
580
|
if pyd_names:
|
|
352
581
|
parts += [f"from pydantic import {', '.join(pyd_names)}", ""]
|
|
353
|
-
parts += ["", self._emit_class_header(entity, base_class), *body
|
|
582
|
+
parts += ["", self._emit_class_header(entity, base_class), *body]
|
|
583
|
+
if emit_validation_models:
|
|
584
|
+
parts += [
|
|
585
|
+
"",
|
|
586
|
+
"",
|
|
587
|
+
f"class {entity.name}Create(BaseModel):",
|
|
588
|
+
' """GENERATED — CREATE input: auto-gen PK / @readOnly omitted; '
|
|
589
|
+
'@default/@autoSet optional; present values validated (FR-036)."""',
|
|
590
|
+
*create_lines,
|
|
591
|
+
"",
|
|
592
|
+
"",
|
|
593
|
+
f"class {entity.name}Patch(BaseModel):",
|
|
594
|
+
' """GENERATED — PATCH input: all fields optional (PK excluded); present values validated (FR-036)."""',
|
|
595
|
+
*patch_lines,
|
|
596
|
+
]
|
|
597
|
+
parts += [""]
|
|
354
598
|
return "\n".join(parts)
|
|
355
599
|
|
|
356
600
|
def _render_shared_enums_module(self, entities: list[MetaObject]) -> EmittedFile | None:
|
{metaobjects-0.15.13 → metaobjects-0.16.0}/src/metaobjects/codegen/generators/router_generator.py
RENAMED
|
@@ -39,6 +39,7 @@ from metaobjects.apidocs.naming import reverse_finder_fn, reverse_finder_in_fn
|
|
|
39
39
|
from metaobjects.apidocs.naming import snake_case as _snake_case
|
|
40
40
|
from metaobjects.codegen.constants import generated_header
|
|
41
41
|
from metaobjects.codegen.format import ruff_format
|
|
42
|
+
from metaobjects.codegen.fr010_field_mapping import is_required
|
|
42
43
|
from metaobjects.codegen.generator import EmittedFile, GenContext, Generator, per_entity
|
|
43
44
|
from metaobjects.codegen.generators.m2m_codegen import (
|
|
44
45
|
M2mDescriptor,
|
|
@@ -89,6 +90,24 @@ def _scalar_fields(entity: MetaObject) -> list[MetaField]:
|
|
|
89
90
|
return [f for f in entity.fields() if f.sub_type != fc.FIELD_SUBTYPE_OBJECT]
|
|
90
91
|
|
|
91
92
|
|
|
93
|
+
def _required_field_names(entity: MetaObject) -> list[str]:
|
|
94
|
+
"""Every @required field name — scalar AND object/jsonb (unlike
|
|
95
|
+
_scalar_fields, which drops object fields for the sort allowlist). FR-035
|
|
96
|
+
PATCH-2 guards present-null on any of these, and a @required jsonb column can
|
|
97
|
+
be nulled just like a scalar one."""
|
|
98
|
+
return [f.name for f in entity.fields() if is_required(f)]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _py_set_literal(names: list[str], *, frozen: bool = False) -> str:
|
|
102
|
+
"""A Python set/frozenset literal from field names, matching the generated
|
|
103
|
+
allowlist idiom (one quoted name per line). Empty → ``frozenset()`` / ``set()``."""
|
|
104
|
+
empty = "frozenset()" if frozen else "set()"
|
|
105
|
+
if not names:
|
|
106
|
+
return empty
|
|
107
|
+
body = "{\n" + "".join(f' "{name}",\n' for name in names) + "}"
|
|
108
|
+
return f"frozenset({body})" if frozen else body
|
|
109
|
+
|
|
110
|
+
|
|
92
111
|
def _pk_py_type(entity: MetaObject) -> PyType:
|
|
93
112
|
"""The Python type of the entity's primary-key path/id parameter, derived
|
|
94
113
|
from the PK field's declared subtype via ``type_map.py_type_for`` — the same
|
|
@@ -238,10 +257,14 @@ class RouterGenerator:
|
|
|
238
257
|
repo_class: str,
|
|
239
258
|
fields_const: str,
|
|
240
259
|
ops_const: str,
|
|
260
|
+
model_name: str,
|
|
261
|
+
patch_model: str,
|
|
241
262
|
) -> list[str]:
|
|
242
263
|
"""One CRUD route handler block, dispatched by *name*
|
|
243
264
|
(``list`` / ``get`` / ``create`` / ``update`` / ``delete``). Override to
|
|
244
|
-
change a handler's body / decorators / response shape.
|
|
265
|
+
change a handler's body / decorators / response shape. *model_name* /
|
|
266
|
+
*patch_model* are the generated create + all-optional PATCH Pydantic models
|
|
267
|
+
used to run field constraints on POST / PATCH (FR-036)."""
|
|
245
268
|
if name == "list":
|
|
246
269
|
return [
|
|
247
270
|
'@router.get("")',
|
|
@@ -289,6 +312,12 @@ class RouterGenerator:
|
|
|
289
312
|
" dto: dict[str, Any],",
|
|
290
313
|
f" repo: Annotated[{repo_class}, Depends(get_repository)],",
|
|
291
314
|
") -> Any:",
|
|
315
|
+
" # FR-036: run the generated field constraints (length / pattern /",
|
|
316
|
+
" # numeric bounds) before persisting; a violation is the cross-port 400.",
|
|
317
|
+
" try:",
|
|
318
|
+
f" {model_name}(**dto)",
|
|
319
|
+
" except ValidationError:",
|
|
320
|
+
' return JSONResponse(status_code=400, content={"error": "validation"})',
|
|
292
321
|
" return repo.create(dto)",
|
|
293
322
|
]
|
|
294
323
|
if name == "update":
|
|
@@ -300,6 +329,18 @@ class RouterGenerator:
|
|
|
300
329
|
" dto: dict[str, Any],",
|
|
301
330
|
f" repo: Annotated[{repo_class}, Depends(get_repository)],",
|
|
302
331
|
") -> Any:",
|
|
332
|
+
" # FR-035 PATCH-2: an explicit null on a @required field is a 400 —",
|
|
333
|
+
" # a present null on a NON-required field falls through and clears it,",
|
|
334
|
+
" # and an OMITTED required field is untouched (never a 400).",
|
|
335
|
+
" for _k in _REQUIRED_FIELDS:",
|
|
336
|
+
" if _k in dto and dto[_k] is None:",
|
|
337
|
+
' return JSONResponse(status_code=400, content={"error": "validation"})',
|
|
338
|
+
" # FR-036: validate PRESENT, non-null values with the create rules (the",
|
|
339
|
+
" # all-optional patch model never fires required-checks on ABSENT fields).",
|
|
340
|
+
" try:",
|
|
341
|
+
f" {patch_model}(**dto)",
|
|
342
|
+
" except ValidationError:",
|
|
343
|
+
' return JSONResponse(status_code=400, content={"error": "validation"})',
|
|
303
344
|
f" saved = repo.update({pk_param}, dto)",
|
|
304
345
|
" if saved is None:",
|
|
305
346
|
' return JSONResponse(status_code=404, content={"error": "not_found"})',
|
|
@@ -385,9 +426,18 @@ class RouterGenerator:
|
|
|
385
426
|
if f.name not in seen:
|
|
386
427
|
seen.add(f.name)
|
|
387
428
|
sort_fields.append(f.name)
|
|
388
|
-
sort_set_body =
|
|
389
|
-
|
|
390
|
-
|
|
429
|
+
sort_set_body = _py_set_literal(sort_fields)
|
|
430
|
+
# FR-035 PATCH-2: @required fields across the base AND every subtype — an
|
|
431
|
+
# explicit null on any of these is a 400 (the per-subtype update handlers
|
|
432
|
+
# guard against it before the repo call). Union, stable order.
|
|
433
|
+
required_names: list[str] = _required_field_names(entity)
|
|
434
|
+
req_seen = set(required_names)
|
|
435
|
+
for st in plan.subtypes:
|
|
436
|
+
for name in _required_field_names(st.entity):
|
|
437
|
+
if name not in req_seen:
|
|
438
|
+
req_seen.add(name)
|
|
439
|
+
required_names.append(name)
|
|
440
|
+
required_set_body = _py_set_literal(required_names, frozen=True)
|
|
391
441
|
|
|
392
442
|
h = generated_header(short_name, _effective_fqn(entity)).rstrip()
|
|
393
443
|
parts: list[str] = []
|
|
@@ -406,7 +456,7 @@ class RouterGenerator:
|
|
|
406
456
|
parts.append("")
|
|
407
457
|
parts.append("from fastapi import APIRouter, Depends, Query, Request, status")
|
|
408
458
|
parts.append("from fastapi.responses import JSONResponse")
|
|
409
|
-
parts.append("from pydantic import BaseModel")
|
|
459
|
+
parts.append("from pydantic import BaseModel, ValidationError")
|
|
410
460
|
parts.append("")
|
|
411
461
|
parts.append("from metaobjects.codegen.runtime.filter_parser import (")
|
|
412
462
|
parts.append(" FilterPredicate,")
|
|
@@ -414,6 +464,12 @@ class RouterGenerator:
|
|
|
414
464
|
parts.append(")")
|
|
415
465
|
parts.append("")
|
|
416
466
|
parts.append(f"from .{allowlist_module} import {fields_const}, {ops_const}")
|
|
467
|
+
# FR-036: each concrete subtype's CREATE + all-optional PATCH validation models
|
|
468
|
+
# drive the field constraints run on the per-subtype POST / PATCH (sibling
|
|
469
|
+
# modules). These are the wire-shaped validation models, NOT the read model.
|
|
470
|
+
for st in plan.subtypes:
|
|
471
|
+
sub = st.entity.name
|
|
472
|
+
parts.append(f"from .{sub} import {sub}Create, {sub}Patch")
|
|
417
473
|
parts.append("")
|
|
418
474
|
parts.append(f'router = APIRouter(prefix="/api/{plural}", tags=["{plural}"])')
|
|
419
475
|
parts.append("")
|
|
@@ -427,6 +483,9 @@ class RouterGenerator:
|
|
|
427
483
|
parts.append(f"_SORT_ALLOWLIST: set[str] = {sort_set_body}")
|
|
428
484
|
parts.append("")
|
|
429
485
|
parts.append("")
|
|
486
|
+
parts.append(f"_REQUIRED_FIELDS: frozenset[str] = {required_set_body}")
|
|
487
|
+
parts.append("")
|
|
488
|
+
parts.append("")
|
|
430
489
|
parts.append("def _parse_sort(raw: str) -> _SortClause | None:")
|
|
431
490
|
parts.append(' """Parse `field:asc|desc`; return None for malformed / disallowed input."""')
|
|
432
491
|
parts.append(' parts = raw.split(":", 1)')
|
|
@@ -480,6 +539,7 @@ class RouterGenerator:
|
|
|
480
539
|
for st in plan.subtypes:
|
|
481
540
|
seg = st.route_segment
|
|
482
541
|
val = st.value
|
|
542
|
+
sub = st.entity.name # the subtype's generated create + PATCH model classes
|
|
483
543
|
sfx = st.route_segment # handler-name suffix = URL segment (e.g. "bridge"), matches the route
|
|
484
544
|
parts.extend(list_sig(f"list_{plural}_{sfx}", f"/{seg}"))
|
|
485
545
|
parts.extend(self._emit_tph_list_body(f'"{val}"', fields_const, ops_const))
|
|
@@ -490,6 +550,11 @@ class RouterGenerator:
|
|
|
490
550
|
parts.append(" dto: dict[str, Any],")
|
|
491
551
|
parts.append(f" repo: Annotated[{repo_class}, Depends(get_repository)],")
|
|
492
552
|
parts.append(") -> Any:")
|
|
553
|
+
parts.append(" # FR-036: run the subtype's field constraints before persisting.")
|
|
554
|
+
parts.append(" try:")
|
|
555
|
+
parts.append(f" {sub}Create(**dto)")
|
|
556
|
+
parts.append(" except ValidationError:")
|
|
557
|
+
parts.append(' return JSONResponse(status_code=400, content={"error": "validation"})')
|
|
493
558
|
parts.append(f' return repo.create("{val}", dto)')
|
|
494
559
|
parts.append("")
|
|
495
560
|
parts.append("")
|
|
@@ -511,6 +576,15 @@ class RouterGenerator:
|
|
|
511
576
|
parts.append(" dto: dict[str, Any],")
|
|
512
577
|
parts.append(f" repo: Annotated[{repo_class}, Depends(get_repository)],")
|
|
513
578
|
parts.append(") -> Any:")
|
|
579
|
+
parts.append(" # FR-035 PATCH-2: an explicit null on a @required field is a 400.")
|
|
580
|
+
parts.append(" for _k in _REQUIRED_FIELDS:")
|
|
581
|
+
parts.append(" if _k in dto and dto[_k] is None:")
|
|
582
|
+
parts.append(' return JSONResponse(status_code=400, content={"error": "validation"})')
|
|
583
|
+
parts.append(" # FR-036: validate PRESENT, non-null values with the subtype's create rules.")
|
|
584
|
+
parts.append(" try:")
|
|
585
|
+
parts.append(f" {sub}Patch(**dto)")
|
|
586
|
+
parts.append(" except ValidationError:")
|
|
587
|
+
parts.append(' return JSONResponse(status_code=400, content={"error": "validation"})')
|
|
514
588
|
parts.append(f' saved = repo.update("{val}", {pk_param}, dto)')
|
|
515
589
|
parts.append(" if saved is None:")
|
|
516
590
|
parts.append(' return JSONResponse(status_code=404, content={"error": "not_found"})')
|
|
@@ -600,9 +674,10 @@ class RouterGenerator:
|
|
|
600
674
|
ops_const = f"{upper}_FILTER_OPS_BY_FIELD"
|
|
601
675
|
allowlist_module = f"{snake}_filter_allowlist"
|
|
602
676
|
|
|
603
|
-
sort_set_body =
|
|
604
|
-
|
|
605
|
-
|
|
677
|
+
sort_set_body = _py_set_literal(sort_fields)
|
|
678
|
+
# FR-035 PATCH-2: an explicit null on a @required field (scalar or jsonb)
|
|
679
|
+
# is a 400 — the update handler guards these before the repo call.
|
|
680
|
+
required_set_body = _py_set_literal(_required_field_names(entity), frozen=True)
|
|
606
681
|
|
|
607
682
|
parts: list[str] = []
|
|
608
683
|
parts.append(
|
|
@@ -619,7 +694,7 @@ class RouterGenerator:
|
|
|
619
694
|
parts.append("")
|
|
620
695
|
parts.append("from fastapi import APIRouter, Depends, Query, Request, status")
|
|
621
696
|
parts.append("from fastapi.responses import JSONResponse")
|
|
622
|
-
parts.append("from pydantic import BaseModel")
|
|
697
|
+
parts.append("from pydantic import BaseModel, ValidationError")
|
|
623
698
|
parts.append("")
|
|
624
699
|
parts.append("from metaobjects.codegen.runtime.filter_parser import (")
|
|
625
700
|
parts.append(" FilterPredicate,")
|
|
@@ -627,6 +702,11 @@ class RouterGenerator:
|
|
|
627
702
|
parts.append(")")
|
|
628
703
|
parts.append("")
|
|
629
704
|
parts.append(f"from .{allowlist_module} import {fields_const}, {ops_const}")
|
|
705
|
+
# FR-036: the generated CREATE + all-optional PATCH validation models drive the
|
|
706
|
+
# field constraints run on POST / PATCH. These are the WIRE-shaped validation
|
|
707
|
+
# models (auto-gen PK / @readOnly omitted from create; PK omitted from patch;
|
|
708
|
+
# both keyed by field.name), NOT the read/entity-shape model.
|
|
709
|
+
parts.append(f"from .{short_name} import {short_name}Create, {short_name}Patch")
|
|
630
710
|
parts.append("")
|
|
631
711
|
parts.append(f'router = APIRouter(prefix="/api/{plural}", tags=["{plural}"])')
|
|
632
712
|
parts.append("")
|
|
@@ -642,6 +722,9 @@ class RouterGenerator:
|
|
|
642
722
|
parts.append(f"_SORT_ALLOWLIST: set[str] = {sort_set_body}")
|
|
643
723
|
parts.append("")
|
|
644
724
|
parts.append("")
|
|
725
|
+
parts.append(f"_REQUIRED_FIELDS: frozenset[str] = {required_set_body}")
|
|
726
|
+
parts.append("")
|
|
727
|
+
parts.append("")
|
|
645
728
|
parts.append('def _parse_sort(raw: str) -> _SortClause | None:')
|
|
646
729
|
parts.append(' """Parse `field:asc|desc`; return None for malformed / disallowed input."""')
|
|
647
730
|
parts.append(' parts = raw.split(":", 1)')
|
|
@@ -672,6 +755,8 @@ class RouterGenerator:
|
|
|
672
755
|
repo_class=repo_class,
|
|
673
756
|
fields_const=fields_const,
|
|
674
757
|
ops_const=ops_const,
|
|
758
|
+
model_name=f"{short_name}Create",
|
|
759
|
+
patch_model=f"{short_name}Patch",
|
|
675
760
|
)
|
|
676
761
|
for i, hname in enumerate(("list", "get", "create", "update", "delete")):
|
|
677
762
|
if i > 0:
|
|
@@ -92,6 +92,12 @@ def _extract(
|
|
|
92
92
|
data[f.name] = coerced
|
|
93
93
|
report.add_coercion(Coercion(path, "", f.default_value, "default"))
|
|
94
94
|
report.set(path, FieldExtraction.DEFAULTED)
|
|
95
|
+
# A default SATISFIES @required, so this field never appears in
|
|
96
|
+
# lost_required() — which is what the generated guards key on. Record
|
|
97
|
+
# it separately so "the document did not answer a required field"
|
|
98
|
+
# stays askable.
|
|
99
|
+
if f.required:
|
|
100
|
+
report.mark_defaulted_required(path)
|
|
95
101
|
continue
|
|
96
102
|
report.set(
|
|
97
103
|
path,
|