metaobjects 0.13.0__tar.gz → 0.14.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.13.0 → metaobjects-0.14.0}/PKG-INFO +1 -1
- {metaobjects-0.13.0 → metaobjects-0.14.0}/pyproject.toml +1 -1
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/cli.py +48 -7
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/type_map.py +12 -0
- metaobjects-0.14.0/tests/codegen/test_cli_verify_strict.py +139 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_entity_model.py +14 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_type_map.py +14 -0
- metaobjects-0.14.0/tests/integration/api_contract_jsonb_server.py +160 -0
- metaobjects-0.14.0/tests/integration/generated_jsonb_app.py +135 -0
- metaobjects-0.14.0/tests/integration/test_api_contract_jsonb.py +153 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/.gitignore +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/LICENSE +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/README.md +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/hatch_build.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/agent_context/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/agent_context/scaffold.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/api_model.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/builder.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/naming.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/paths.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/apidocs/renderer.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/attr_class_map.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/config.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/format.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generator_registry.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/entity_model.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/router_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/template_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/instance_artifacts.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/overwrite_policy.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runner.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runtime/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/core_types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/datatype.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/documentation/doc_schema.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/errors.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/merge.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/meta_data_loader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/registered_validation.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/directory_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/file_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/sources/uri_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_discriminator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_field_readonly.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/loader/validation_passes.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/field_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/field/meta_field.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/meta_object.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/object_extract.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/object/value_object.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/validator/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/meta_data.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/meta_root.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/provider_extends.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/meta_template.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/prompt_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/meta/template/template_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/naming_refs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/parser.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/parser_yaml.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/py.typed +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/registry.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/registry_manifest.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/email_document.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/escapers.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/coerce.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/extract.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/extract_map.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/locate.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/normalize.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/strip.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/filesystem_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_field.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/prompt/prompt_style.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/renderer.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/render/verify.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/llm_recorder.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/n2m_resolver.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/object_manager.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/runtime/tph.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/serializer_json.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/base_types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/separators.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/shared/structural.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/error_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/json_path.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/semantic_diff.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/source/yaml_positions.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/attr.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/db.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/documentation.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/field.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/identity.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/layout.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/object.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/origin.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/prompt.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/relationship.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/source.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/template.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/ui.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/validator.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/spec_metamodel/view.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/super_resolve.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/validation_types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/src/metaobjects/yaml_desugar.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/expected/Program.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/extends/meta.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/nested-array/meta.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/expected/Report.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/scalars/meta.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/golden/vanilla/meta.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_abstract_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_api_docs_builder.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_api_docs_paths.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_registry.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_staleness_nudge.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_cli_verify_subverbs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_constants_config.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_enum_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_extractor_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_filter_allowlist_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_format.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_fr010_output_codegen.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_generator_extension_seams.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_golden.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_inheritance_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_instance_artifacts.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_m2m_codegen.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_output_parser_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_overwrite_policy.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_payload_vo_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_projection_compile.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_render_helper_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_render_helper_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_router_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_runner.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_data.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_output_pattern.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_scope_helpers.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_scope_walk.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_template_spec.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_tph_codegen.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_trace_helper_generator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/codegen/test_validation_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/capabilities.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/conformance-expected-failures.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/conformance_adapter.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/corpus.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/expected_failures.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/fixture_discovery.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/navigator.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_extract_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_extract_object_verdict.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_fr010_loader_attrs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_fr011_attrs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_generator_registry_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_object_model_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_registry_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_runner_hardfail.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_spec_metamodel_embed.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_strict_attr_load.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_template_codegen_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_template_generator_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/test_yaml_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_assertions.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_m2m_server.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/api_contract_server.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_m2m_app.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_router_app.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/generated_tph_app.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/meta_ai_trace.yaml +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/normalization.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/postgres_container.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/query_runner.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/scenarios.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_generated.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_m2m.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_m2m_generated.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_api_contract_tph_generated.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_llm_call_trace.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_normalization.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_query_scenarios.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/integration/test_runtime_return_types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/open_closed_proof_test.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_coerce.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_extract.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_extract_map.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_json_forgiving_reader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_locate.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_model.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_normalize.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_strip.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/prompt/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/prompt/test_output_format_renderer.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_email_document.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_filesystem_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_output_format_renderer_nested.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_output_prompt_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_render_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_render_max_chars.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_verify.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/render/test_verify_conformance.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5c_merge_attribution.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5d_reference_resolution.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_fr5e_database_source_shape.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_json_path.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_semantic_diff.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_source_on_node.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/source/test_yaml_positions.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/test_api_docs_accuracy.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/__init__.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_agent_context_staleness.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_capabilities.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_common_attrs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_core_types.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_effective_package.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_errors.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_enum.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_map_validation.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_fr016_source_name_and_kind_aliases.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_llm_recorder.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader_bom.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_loader_class.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_merge.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_attr.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_data.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_meta_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_module_shortcuts.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_n2m_resolver.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_one_primary_source.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_parser.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_parser_yaml.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_provider.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_provider_extension.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_completeness.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_extend.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_registry_sealed.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_relationship_referential_actions.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_resolution_key.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_serializer.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_shared_constants.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_smoke.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_sources.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_strict_child_placement.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_super_resolve.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_template_toolcall.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_attr_schema.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_filter_values.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_origin_paths.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_sort_field.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_validation_warnings.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.0}/tests/unit/test_yaml_desugar.py +0 -0
- {metaobjects-0.13.0 → metaobjects-0.14.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.14.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.14.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"
|
|
@@ -103,15 +103,33 @@ def _default_generators() -> list[Generator]:
|
|
|
103
103
|
]
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
def _load_root(
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
def _load_root(
|
|
107
|
+
metadata_dir: str, strict: bool = False
|
|
108
|
+
) -> tuple[MetaData | None, list[str]]:
|
|
109
|
+
"""Load metadata; return ``(root, error_messages)``. ``root`` is None on error.
|
|
110
|
+
|
|
111
|
+
``strict`` (ADR-0023, #96) — when True, an undeclared own ``@attr`` →
|
|
112
|
+
``ERR_UNKNOWN_ATTR``. Defaults False so ``gen`` / ``docs`` keep the legacy
|
|
113
|
+
open-attr load; only ``verify`` opts in (strict-by-default with a ``--lax``
|
|
114
|
+
escape).
|
|
115
|
+
"""
|
|
116
|
+
result = MetaDataLoader.from_directory(metadata_dir, strict=strict)
|
|
109
117
|
if result.errors:
|
|
110
118
|
msgs = [f"{e.code}: {e.message}" for e in result.errors]
|
|
111
119
|
return None, msgs
|
|
112
120
|
return result.root, []
|
|
113
121
|
|
|
114
122
|
|
|
123
|
+
def _strict_load_hint() -> str:
|
|
124
|
+
"""Actionable next-steps when strict verify rejects an undeclared @attr."""
|
|
125
|
+
return (
|
|
126
|
+
"verify is strict (ADR-0023): every authored @attr must be declared. Fix: "
|
|
127
|
+
"register the attr on a metadata provider, OR move arbitrary author-supplied "
|
|
128
|
+
"properties into an `attr.properties` bag, OR re-run with `--lax` to keep the "
|
|
129
|
+
"legacy open-attr load."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
115
133
|
def _resolve_generators(names: str) -> tuple[list[Generator], list[str]]:
|
|
116
134
|
"""Resolve a comma-separated list of STABLE generator names via the registry.
|
|
117
135
|
|
|
@@ -174,6 +192,7 @@ def _generate(
|
|
|
174
192
|
generators: list[Generator] | None = None,
|
|
175
193
|
entity_filter: list[str] | None = None,
|
|
176
194
|
emit_package_init: bool = True,
|
|
195
|
+
strict: bool = False,
|
|
177
196
|
) -> tuple[list[str], list[str]]:
|
|
178
197
|
"""Run the generator suite into ``out_dir``.
|
|
179
198
|
|
|
@@ -186,9 +205,10 @@ def _generate(
|
|
|
186
205
|
output is text/markdown/csv/json/xml/html, never a Python package) so no
|
|
187
206
|
spurious ``__init__.py`` is scattered through their output tree. Returns
|
|
188
207
|
``(written_paths, errors)``. On a load error, ``errors`` is non-empty and no
|
|
189
|
-
files are written.
|
|
208
|
+
files are written. ``strict`` (ADR-0023, #96) is threaded to the load — the
|
|
209
|
+
``verify --codegen`` caller passes True; ``gen`` keeps the default False.
|
|
190
210
|
"""
|
|
191
|
-
root, errors = _load_root(metadata_dir)
|
|
211
|
+
root, errors = _load_root(metadata_dir, strict=strict)
|
|
192
212
|
if root is None:
|
|
193
213
|
return [], errors
|
|
194
214
|
return _run_suite(root, out_dir, generators, entity_filter, emit_package_init), []
|
|
@@ -408,13 +428,20 @@ def _verify_codegen(args: argparse.Namespace) -> int:
|
|
|
408
428
|
# Reuse the exact gen code path — regenerate into a throwaway temp dir. The
|
|
409
429
|
# --entities filter must match the `gen` that produced --out, or the diff
|
|
410
430
|
# reports the un-emitted entities as spurious drift.
|
|
431
|
+
# ADR-0023 strict-by-default (#96): verify loads strict unless --lax is given,
|
|
432
|
+
# so an undeclared/typo'd own @attr fails verify (matching Java's Maven goal).
|
|
433
|
+
strict = not getattr(args, "lax", False)
|
|
411
434
|
with tempfile.TemporaryDirectory() as tmp:
|
|
412
435
|
entities = _parse_entities(getattr(args, "entities", None))
|
|
413
|
-
written, errors = _generate(
|
|
436
|
+
written, errors = _generate(
|
|
437
|
+
args.metadata_dir, tmp, None, entities, strict=strict
|
|
438
|
+
)
|
|
414
439
|
if errors:
|
|
415
440
|
print("error: failed to load metadata:", file=sys.stderr)
|
|
416
441
|
for msg in errors:
|
|
417
442
|
print(f" {msg}", file=sys.stderr)
|
|
443
|
+
if strict and any("ERR_UNKNOWN_ATTR" in m for m in errors):
|
|
444
|
+
print(_strict_load_hint(), file=sys.stderr)
|
|
418
445
|
return 1
|
|
419
446
|
|
|
420
447
|
expected = _relative_set(Path(tmp))
|
|
@@ -478,11 +505,16 @@ def _verify_templates(args: argparse.Namespace) -> int:
|
|
|
478
505
|
)
|
|
479
506
|
return 2
|
|
480
507
|
|
|
481
|
-
|
|
508
|
+
# ADR-0023 strict-by-default (#96) — same as --codegen: an undeclared @attr
|
|
509
|
+
# fails verify unless --lax is passed.
|
|
510
|
+
strict = not getattr(args, "lax", False)
|
|
511
|
+
root, errors = _load_root(args.metadata_dir, strict=strict)
|
|
482
512
|
if root is None:
|
|
483
513
|
print("error: failed to load metadata:", file=sys.stderr)
|
|
484
514
|
for msg in errors:
|
|
485
515
|
print(f" {msg}", file=sys.stderr)
|
|
516
|
+
if strict and any("ERR_UNKNOWN_ATTR" in m for m in errors):
|
|
517
|
+
print(_strict_load_hint(), file=sys.stderr)
|
|
486
518
|
return 1
|
|
487
519
|
|
|
488
520
|
provider = FilesystemProvider(template_root)
|
|
@@ -760,6 +792,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
760
792
|
default=None,
|
|
761
793
|
help="comma-separated entity allowlist for --codegen drift (match `gen --entities`)",
|
|
762
794
|
)
|
|
795
|
+
verify.add_argument(
|
|
796
|
+
"--lax",
|
|
797
|
+
action="store_true",
|
|
798
|
+
help=(
|
|
799
|
+
"opt out of strict-attr load (ADR-0023, #96): verify is strict by "
|
|
800
|
+
"default — an undeclared/typo'd @attr fails. --lax restores the legacy "
|
|
801
|
+
"open-attr load."
|
|
802
|
+
),
|
|
803
|
+
)
|
|
763
804
|
verify.set_defaults(func=_cmd_verify)
|
|
764
805
|
|
|
765
806
|
agent_docs = sub.add_parser(
|
|
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
|
|
6
6
|
from metaobjects.meta.core.field.meta_field import MetaField
|
|
7
7
|
from metaobjects.meta.core.field import field_constants as fc
|
|
8
|
+
from metaobjects.meta.persistence.db import db_constants as dbc
|
|
8
9
|
from metaobjects.shared.structural import KEY_IS_ARRAY
|
|
9
10
|
|
|
10
11
|
|
|
@@ -94,6 +95,17 @@ def py_type_for(field: MetaField) -> PyType:
|
|
|
94
95
|
base = PyType(f"Literal[{members}]", ("from typing import Literal",))
|
|
95
96
|
else:
|
|
96
97
|
base = PyType("str")
|
|
98
|
+
elif (
|
|
99
|
+
field.sub_type == fc.FIELD_SUBTYPE_STRING
|
|
100
|
+
and field.attrs().get(dbc.FIELD_ATTR_DB_COLUMN_TYPE) == dbc.DB_COLUMN_TYPE_JSONB
|
|
101
|
+
):
|
|
102
|
+
# Issue #98 — a field.string carrying @dbColumnType:jsonb is the escape hatch
|
|
103
|
+
# for a genuinely-open JSON column (ADR-0013). pg8000 auto-decodes a jsonb
|
|
104
|
+
# column to a native Python object (dict/list/scalar) at read time, so a `str`
|
|
105
|
+
# annotation is a type lie. Bind ``Any`` — the Python analogue of TS's
|
|
106
|
+
# z.unknown() (#97) — since jsonb can hold any JSON value. (Other ports whose
|
|
107
|
+
# drivers return jsonb as raw text correctly keep their string type.)
|
|
108
|
+
base = PyType("Any", ("from typing import Any",))
|
|
97
109
|
else:
|
|
98
110
|
base = _SCALAR.get(field.sub_type, PyType("str"))
|
|
99
111
|
if field_is_array(field):
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Issue #96 — `metaobjects verify` is strict-by-default (ADR-0023 cross-port).
|
|
2
|
+
|
|
3
|
+
Under strict load an authored own ``@attr`` that no provider declares is an
|
|
4
|
+
``ERR_UNKNOWN_ATTR``. Java's Maven verify goal already forces strict; this brings
|
|
5
|
+
the Python (and TS) CLI verify in line: verify FAILS on an undeclared attr unless
|
|
6
|
+
``--lax`` is passed. ``gen`` keeps loading lax (only verify defaults strict).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from metaobjects.cli import main
|
|
14
|
+
|
|
15
|
+
# Shared cross-port fixture (also asserted by the TS CLI verify-strict test):
|
|
16
|
+
# a registered field.string carrying one undeclared own @attr.
|
|
17
|
+
_FIXTURE = (
|
|
18
|
+
Path(__file__).parents[4]
|
|
19
|
+
/ "fixtures"
|
|
20
|
+
/ "verify-strict-conformance"
|
|
21
|
+
/ "unregistered-attr"
|
|
22
|
+
/ "input"
|
|
23
|
+
/ "meta.users.json"
|
|
24
|
+
)
|
|
25
|
+
_MADE_UP = _FIXTURE.read_text()
|
|
26
|
+
|
|
27
|
+
_CLEAN = """\
|
|
28
|
+
{
|
|
29
|
+
"metadata.root": {
|
|
30
|
+
"package": "acme::users",
|
|
31
|
+
"children": [
|
|
32
|
+
{
|
|
33
|
+
"object.entity": {
|
|
34
|
+
"name": "Account",
|
|
35
|
+
"children": [
|
|
36
|
+
{ "field.long": { "name": "id" } },
|
|
37
|
+
{ "field.string": { "name": "email", "@description": "the email" } },
|
|
38
|
+
{ "identity.primary": { "name": "pk", "@fields": ["id"] } }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _meta_dir(tmp_path: Path, body: str) -> str:
|
|
49
|
+
d = tmp_path / "meta"
|
|
50
|
+
d.mkdir()
|
|
51
|
+
(d / "meta.json").write_text(body)
|
|
52
|
+
return str(d)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# --- verify --codegen ------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_verify_codegen_fails_on_undeclared_attr_by_default(
|
|
59
|
+
tmp_path: Path, capsys
|
|
60
|
+
) -> None:
|
|
61
|
+
meta_dir = _meta_dir(tmp_path, _MADE_UP)
|
|
62
|
+
out = tmp_path / "out"
|
|
63
|
+
rc = main(["verify", "--codegen", meta_dir, "--out", str(out)])
|
|
64
|
+
assert rc != 0
|
|
65
|
+
err = capsys.readouterr().err
|
|
66
|
+
assert "ERR_UNKNOWN_ATTR" in err
|
|
67
|
+
# Actionable hint: register a provider / attr.properties bag / --lax.
|
|
68
|
+
assert "--lax" in err
|
|
69
|
+
assert "attr.properties" in err
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_verify_codegen_passes_with_lax(tmp_path: Path) -> None:
|
|
73
|
+
meta_dir = _meta_dir(tmp_path, _MADE_UP)
|
|
74
|
+
out = tmp_path / "out"
|
|
75
|
+
# Lax load tolerates the undeclared attr; gen-to-temp + diff (vs --out) is
|
|
76
|
+
# the codegen drift result, NOT a load failure. First gen (already lax),
|
|
77
|
+
# then verify --lax against the committed output.
|
|
78
|
+
assert main(["gen", meta_dir, "--out", str(out)]) == 0
|
|
79
|
+
assert main(["verify", "--lax", "--codegen", meta_dir, "--out", str(out)]) == 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --- verify --templates ----------------------------------------------------
|
|
83
|
+
|
|
84
|
+
_TEMPLATE_META = """\
|
|
85
|
+
{
|
|
86
|
+
"metadata.root": {
|
|
87
|
+
"package": "acme::ai",
|
|
88
|
+
"children": [
|
|
89
|
+
{
|
|
90
|
+
"object.value": {
|
|
91
|
+
"name": "Welcome",
|
|
92
|
+
"children": [
|
|
93
|
+
{ "field.string": { "name": "name", "@madeUpAttr": "nope" } }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"template.output": {
|
|
99
|
+
"name": "WelcomePage",
|
|
100
|
+
"@kind": "document",
|
|
101
|
+
"@payloadRef": "Welcome",
|
|
102
|
+
"@textRef": "pages/welcome",
|
|
103
|
+
"@format": "html"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_verify_templates_fails_on_undeclared_attr_by_default(
|
|
113
|
+
tmp_path: Path, capsys
|
|
114
|
+
) -> None:
|
|
115
|
+
meta_dir = _meta_dir(tmp_path, _TEMPLATE_META)
|
|
116
|
+
troot = tmp_path / "templates"
|
|
117
|
+
(troot / "pages").mkdir(parents=True)
|
|
118
|
+
(troot / "pages" / "welcome.mustache").write_text("Hello {{name}}")
|
|
119
|
+
rc = main(["verify", "--templates", meta_dir, "--templates-root", str(troot)])
|
|
120
|
+
assert rc != 0
|
|
121
|
+
err = capsys.readouterr().err
|
|
122
|
+
assert "ERR_UNKNOWN_ATTR" in err
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --- gen stays lax (only verify defaults strict) ---------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_gen_stays_lax_by_default(tmp_path: Path) -> None:
|
|
129
|
+
meta_dir = _meta_dir(tmp_path, _MADE_UP)
|
|
130
|
+
out = tmp_path / "out"
|
|
131
|
+
# gen tolerates the undeclared attr (no strict default for gen).
|
|
132
|
+
assert main(["gen", meta_dir, "--out", str(out)]) == 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_verify_clean_metadata_passes_under_strict(tmp_path: Path) -> None:
|
|
136
|
+
meta_dir = _meta_dir(tmp_path, _CLEAN)
|
|
137
|
+
out = tmp_path / "out"
|
|
138
|
+
assert main(["gen", meta_dir, "--out", str(out)]) == 0
|
|
139
|
+
assert main(["verify", "--codegen", meta_dir, "--out", str(out)]) == 0
|
|
@@ -175,3 +175,17 @@ def test_field_default_is_emitted_as_literal() -> None:
|
|
|
175
175
|
assert "n: int = 1" in out
|
|
176
176
|
assert "c: float = 0.5" in out
|
|
177
177
|
assert "s: str = 'approx'" in out
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_dbcolumntype_jsonb_field_is_any_with_import() -> None:
|
|
181
|
+
"""Issue #98 — a field.string @dbColumnType:jsonb stores genuinely-open JSON,
|
|
182
|
+
which pg8000 decodes to a native Python object at read time. The generated
|
|
183
|
+
Pydantic field is typed ``Any`` (not the lying ``str``) and the module threads
|
|
184
|
+
``from typing import Any`` into its import block."""
|
|
185
|
+
from metaobjects.meta.persistence.db import db_constants as dbc
|
|
186
|
+
|
|
187
|
+
payload = MetaField(TYPE_FIELD, fc.FIELD_SUBTYPE_STRING, "payload")
|
|
188
|
+
payload.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_JSONB)
|
|
189
|
+
out = render_entity_model(_entity("Event", [payload], package="myapp::events"))
|
|
190
|
+
assert "from typing import Any" in out
|
|
191
|
+
assert "payload: Any | None = None" in out
|
|
@@ -102,3 +102,17 @@ def test_dbcolumntype_uuid_string_stays_str() -> None:
|
|
|
102
102
|
f = _field(fc.FIELD_SUBTYPE_STRING)
|
|
103
103
|
f.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_UUID)
|
|
104
104
|
assert py_type_for(f).expr == "str"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_dbcolumntype_jsonb_string_becomes_any() -> None:
|
|
108
|
+
# Issue #98 — a field.string carrying @dbColumnType:jsonb stores genuinely-open
|
|
109
|
+
# JSON; pg8000 auto-decodes a jsonb column to a native Python object (dict/list/
|
|
110
|
+
# scalar), so `str` is a type lie. Emit `Any` (the Python analogue of TS's
|
|
111
|
+
# z.unknown(), #97), threading `from typing import Any` into the module imports.
|
|
112
|
+
from metaobjects.meta.persistence.db import db_constants as dbc
|
|
113
|
+
|
|
114
|
+
f = _field(fc.FIELD_SUBTYPE_STRING)
|
|
115
|
+
f.set_attr(dbc.FIELD_ATTR_DB_COLUMN_TYPE, dbc.DB_COLUMN_TYPE_JSONB)
|
|
116
|
+
t = py_type_for(f)
|
|
117
|
+
assert t.expr == "Any"
|
|
118
|
+
assert "from typing import Any" in t.imports
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Hand-rolled reference FastAPI app for the ``Document`` entity from the
|
|
2
|
+
``api-contract-conformance/jsonb/`` corpus.
|
|
3
|
+
|
|
4
|
+
The contract under test is the ``field.string @dbColumnType:jsonb`` open bag:
|
|
5
|
+
a posted JSON object must round-trip as an object (never a JSON-encoded
|
|
6
|
+
string). Pared down from ``api_contract_server.py`` (the Author reference) —
|
|
7
|
+
the jsonb scenario only POSTs + GETs by id, so this server implements just
|
|
8
|
+
those verbs.
|
|
9
|
+
|
|
10
|
+
Backend: pg8000 (pure-python DB-API; same driver as ``api_contract_server.py``).
|
|
11
|
+
``payload`` is a bare ``JSONB`` column — the open bag holds any JSON value.
|
|
12
|
+
pg8000 auto-decodes jsonb to a native Python object on read; on write the
|
|
13
|
+
value is ``json.dumps``-serialized and bound with a ``::jsonb`` cast.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from contextlib import closing
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
import pg8000.dbapi as pg8000
|
|
22
|
+
from fastapi import FastAPI, Request, status
|
|
23
|
+
from fastapi.responses import JSONResponse
|
|
24
|
+
|
|
25
|
+
from .postgres_container import PostgresInfo
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DocumentRepository:
|
|
29
|
+
"""Direct-JDBC-equivalent repo against the test Postgres instance."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, info: PostgresInfo) -> None:
|
|
32
|
+
self._info = info
|
|
33
|
+
|
|
34
|
+
def create_schema(self) -> None:
|
|
35
|
+
self._exec(
|
|
36
|
+
'CREATE TABLE IF NOT EXISTS "documents" ('
|
|
37
|
+
" id BIGSERIAL PRIMARY KEY,"
|
|
38
|
+
" title VARCHAR(200) NOT NULL,"
|
|
39
|
+
" payload JSONB"
|
|
40
|
+
")"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def truncate(self) -> None:
|
|
44
|
+
self._exec('TRUNCATE TABLE "documents" RESTART IDENTITY')
|
|
45
|
+
|
|
46
|
+
def apply_seed(self, rows: list[dict[str, Any]]) -> None:
|
|
47
|
+
"""Insert seed rows with explicit ids, then bump the sequence so the
|
|
48
|
+
next implicit-id POST lands at max(id) + 1 (the GET-after-POST
|
|
49
|
+
contract relies on a deterministic id)."""
|
|
50
|
+
self.truncate()
|
|
51
|
+
with closing(self._connect()) as conn:
|
|
52
|
+
cur = conn.cursor()
|
|
53
|
+
try:
|
|
54
|
+
for r in rows:
|
|
55
|
+
cur.execute(
|
|
56
|
+
'INSERT INTO "documents" (id, title, payload) '
|
|
57
|
+
"VALUES (%s, %s, %s::jsonb)",
|
|
58
|
+
(int(r["id"]), r["title"], _dump_payload(r.get("payload"))),
|
|
59
|
+
)
|
|
60
|
+
cur.execute(
|
|
61
|
+
"SELECT setval(pg_get_serial_sequence('documents', 'id'), "
|
|
62
|
+
"COALESCE((SELECT MAX(id) FROM documents), 1))"
|
|
63
|
+
)
|
|
64
|
+
conn.commit()
|
|
65
|
+
finally:
|
|
66
|
+
cur.close()
|
|
67
|
+
|
|
68
|
+
def find_by_id(self, ident: int) -> dict[str, Any] | None:
|
|
69
|
+
with closing(self._connect()) as conn:
|
|
70
|
+
cur = conn.cursor()
|
|
71
|
+
try:
|
|
72
|
+
cur.execute(
|
|
73
|
+
'SELECT id, title, payload FROM "documents" WHERE id = %s',
|
|
74
|
+
(int(ident),),
|
|
75
|
+
)
|
|
76
|
+
row = cur.fetchone()
|
|
77
|
+
return _row_to_dict(row) if row is not None else None
|
|
78
|
+
finally:
|
|
79
|
+
cur.close()
|
|
80
|
+
|
|
81
|
+
def create(self, dto: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
with closing(self._connect()) as conn:
|
|
83
|
+
cur = conn.cursor()
|
|
84
|
+
try:
|
|
85
|
+
cur.execute(
|
|
86
|
+
'INSERT INTO "documents" (title, payload) '
|
|
87
|
+
"VALUES (%s, %s::jsonb) RETURNING id",
|
|
88
|
+
(dto.get("title"), _dump_payload(dto.get("payload"))),
|
|
89
|
+
)
|
|
90
|
+
new_id = int(cur.fetchone()[0])
|
|
91
|
+
conn.commit()
|
|
92
|
+
finally:
|
|
93
|
+
cur.close()
|
|
94
|
+
created = self.find_by_id(new_id)
|
|
95
|
+
if created is None:
|
|
96
|
+
raise RuntimeError("create: row vanished between INSERT and SELECT")
|
|
97
|
+
return created
|
|
98
|
+
|
|
99
|
+
def _connect(self) -> Any:
|
|
100
|
+
return pg8000.connect(
|
|
101
|
+
host=self._info.host,
|
|
102
|
+
port=self._info.port,
|
|
103
|
+
user=self._info.user,
|
|
104
|
+
password=self._info.password,
|
|
105
|
+
database=self._info.database,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def _exec(self, sql: str) -> None:
|
|
109
|
+
with closing(self._connect()) as conn:
|
|
110
|
+
cur = conn.cursor()
|
|
111
|
+
try:
|
|
112
|
+
cur.execute(sql)
|
|
113
|
+
conn.commit()
|
|
114
|
+
finally:
|
|
115
|
+
cur.close()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def make_jsonb_app(repo: DocumentRepository) -> FastAPI:
|
|
119
|
+
app = FastAPI()
|
|
120
|
+
|
|
121
|
+
@app.exception_handler(404)
|
|
122
|
+
async def _default_404(_req: Request, _exc: Any) -> JSONResponse:
|
|
123
|
+
return JSONResponse(status_code=404, content={"error": "not_found"})
|
|
124
|
+
|
|
125
|
+
@app.get("/api/documents/{document_id}")
|
|
126
|
+
def get_document(document_id: int) -> Any:
|
|
127
|
+
row = repo.find_by_id(document_id)
|
|
128
|
+
if row is None:
|
|
129
|
+
return JSONResponse(status_code=404, content={"error": "not_found"})
|
|
130
|
+
return row
|
|
131
|
+
|
|
132
|
+
@app.post("/api/documents", status_code=status.HTTP_201_CREATED)
|
|
133
|
+
def create_document(dto: dict[str, Any]) -> Any:
|
|
134
|
+
return repo.create(dto)
|
|
135
|
+
|
|
136
|
+
return app
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _dump_payload(value: Any) -> Any:
|
|
140
|
+
"""jsonb bind: a dict/list is ``json.dumps``-serialized for the ``::jsonb``
|
|
141
|
+
cast; ``None`` stays ``None`` (SQL NULL); a pre-serialized string passes
|
|
142
|
+
through."""
|
|
143
|
+
if value is None:
|
|
144
|
+
return None
|
|
145
|
+
if isinstance(value, str):
|
|
146
|
+
return value
|
|
147
|
+
return json.dumps(value)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _row_to_dict(row: Any) -> dict[str, Any]:
|
|
151
|
+
ident, title, payload = row
|
|
152
|
+
# pg8000 auto-decodes jsonb → native Python object; if a driver returned raw
|
|
153
|
+
# text we'd parse it, but the open-bag contract is "parsed value", so a str
|
|
154
|
+
# that is JSON is decoded here defensively.
|
|
155
|
+
if isinstance(payload, str):
|
|
156
|
+
try:
|
|
157
|
+
payload = json.loads(payload)
|
|
158
|
+
except json.JSONDecodeError:
|
|
159
|
+
pass
|
|
160
|
+
return {"id": int(ident), "title": title, "payload": payload}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Boot the GENERATED FastAPI router for the ``Document`` entity over HTTP.
|
|
2
|
+
|
|
3
|
+
Peer to ``generated_router_app.py`` (the Author generated lane), for the
|
|
4
|
+
``api-contract-conformance/jsonb/`` corpus. Runs the REAL ``render_router``
|
|
5
|
+
output for ``Document`` (whose ``payload`` field is ``field.string
|
|
6
|
+
@dbColumnType:jsonb`` → a Pydantic ``Any`` per #98), imports the emitted module
|
|
7
|
+
UNMODIFIED, and mounts it on a FastAPI app with a minimal in-memory repo behind
|
|
8
|
+
the generated consumer seam. The generated router is the artifact under test:
|
|
9
|
+
its DTO must accept a posted object and surface it back as an object.
|
|
10
|
+
|
|
11
|
+
No Testcontainers — the controller is the artifact, its persistence seam is
|
|
12
|
+
in-memory. Real DB behavior stays owned by persistence-conformance.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib.util
|
|
17
|
+
import shutil
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
import uuid
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from fastapi import FastAPI
|
|
25
|
+
|
|
26
|
+
from metaobjects import MetaDataLoader
|
|
27
|
+
from metaobjects.codegen.generators.filter_allowlist_generator import render_filter_allowlist
|
|
28
|
+
from metaobjects.codegen.generators.router_generator import render_router
|
|
29
|
+
from metaobjects.meta.core.object.meta_object import MetaObject
|
|
30
|
+
from metaobjects.shared.base_types import TYPE_OBJECT
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _find_document_entity(meta_json: Path) -> MetaObject:
|
|
34
|
+
tmp = Path(tempfile.mkdtemp(prefix="apic-jsonb-meta-"))
|
|
35
|
+
shutil.copy(meta_json, tmp / "meta.json")
|
|
36
|
+
result = MetaDataLoader.from_directory(str(tmp))
|
|
37
|
+
if result.errors:
|
|
38
|
+
msgs = "; ".join(f"{e.code}: {e.message}" for e in result.errors)
|
|
39
|
+
raise RuntimeError(f"corpus meta.json failed to load: {msgs}")
|
|
40
|
+
objects = [
|
|
41
|
+
c for c in result.root.children()
|
|
42
|
+
if c.type == TYPE_OBJECT and isinstance(c, MetaObject)
|
|
43
|
+
]
|
|
44
|
+
for obj in objects:
|
|
45
|
+
if obj.name == "Document" or obj.name.endswith("::Document"):
|
|
46
|
+
return obj
|
|
47
|
+
raise RuntimeError(f"Document entity not found among {[o.name for o in objects]}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_generated_jsonb_app(corpus_root: Path) -> tuple[FastAPI, "InMemoryDocumentRepository"]:
|
|
51
|
+
"""Generate the Document router, import it, mount it, and wire the seam."""
|
|
52
|
+
document = _find_document_entity(corpus_root / "meta.json")
|
|
53
|
+
|
|
54
|
+
router_src = render_router(document)
|
|
55
|
+
allowlist_src = render_filter_allowlist(document)
|
|
56
|
+
if router_src is None or allowlist_src is None:
|
|
57
|
+
raise RuntimeError("router_generator / filter_allowlist_generator returned None for Document")
|
|
58
|
+
|
|
59
|
+
pkg_name = f"genjsonb_{uuid.uuid4().hex[:8]}"
|
|
60
|
+
tmp = Path(tempfile.mkdtemp(prefix="apic-jsonb-gen-"))
|
|
61
|
+
pkg_dir = tmp / pkg_name
|
|
62
|
+
pkg_dir.mkdir()
|
|
63
|
+
(pkg_dir / "__init__.py").write_text("")
|
|
64
|
+
(pkg_dir / "document_filter_allowlist.py").write_text(allowlist_src)
|
|
65
|
+
(pkg_dir / "document_router.py").write_text(router_src)
|
|
66
|
+
|
|
67
|
+
sys.path.insert(0, str(tmp))
|
|
68
|
+
pkg_spec = importlib.util.spec_from_file_location(
|
|
69
|
+
pkg_name, pkg_dir / "__init__.py", submodule_search_locations=[str(pkg_dir)]
|
|
70
|
+
)
|
|
71
|
+
pkg_mod = importlib.util.module_from_spec(pkg_spec)
|
|
72
|
+
sys.modules[pkg_name] = pkg_mod
|
|
73
|
+
pkg_spec.loader.exec_module(pkg_mod)
|
|
74
|
+
spec = importlib.util.spec_from_file_location(
|
|
75
|
+
f"{pkg_name}.document_router", pkg_dir / "document_router.py"
|
|
76
|
+
)
|
|
77
|
+
router_mod = importlib.util.module_from_spec(spec)
|
|
78
|
+
sys.modules[f"{pkg_name}.document_router"] = router_mod
|
|
79
|
+
spec.loader.exec_module(router_mod)
|
|
80
|
+
|
|
81
|
+
repo = InMemoryDocumentRepository()
|
|
82
|
+
app = FastAPI()
|
|
83
|
+
app.include_router(router_mod.router)
|
|
84
|
+
app.dependency_overrides[router_mod.get_repository] = lambda: repo
|
|
85
|
+
return app, repo
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InMemoryDocumentRepository:
|
|
89
|
+
"""In-memory impl of the GENERATED ``DocumentRepository`` Protocol (test seam).
|
|
90
|
+
|
|
91
|
+
The jsonb scenario only POSTs + GETs by id, but the generated router's
|
|
92
|
+
Protocol declares the full CRUD surface, so all verbs are implemented (the
|
|
93
|
+
payload open bag is stored/echoed verbatim — never serialized to a string).
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self) -> None:
|
|
97
|
+
self._rows: list[dict[str, Any]] = []
|
|
98
|
+
|
|
99
|
+
def reset(self) -> None:
|
|
100
|
+
self._rows = []
|
|
101
|
+
|
|
102
|
+
def seed(self, rows: list[dict[str, Any]]) -> None:
|
|
103
|
+
self._rows = [dict(r) for r in rows]
|
|
104
|
+
|
|
105
|
+
def list(self, limit: int, offset: int, sort: Any, filters: list[Any]) -> list[Any]:
|
|
106
|
+
rows = sorted(self._rows, key=lambda r: r["id"])
|
|
107
|
+
return [dict(r) for r in rows[offset : offset + limit]]
|
|
108
|
+
|
|
109
|
+
def count(self, filters: list[Any]) -> int:
|
|
110
|
+
return len(self._rows)
|
|
111
|
+
|
|
112
|
+
def find_by_id(self, id: int) -> Any | None:
|
|
113
|
+
for r in self._rows:
|
|
114
|
+
if r["id"] == id:
|
|
115
|
+
return dict(r)
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
def create(self, dto: Any) -> Any:
|
|
119
|
+
row = dict(dto)
|
|
120
|
+
if row.get("id") is None:
|
|
121
|
+
row["id"] = (max((r["id"] for r in self._rows), default=0)) + 1
|
|
122
|
+
self._rows.append(dict(row))
|
|
123
|
+
return row
|
|
124
|
+
|
|
125
|
+
def update(self, id: int, dto: Any) -> Any | None:
|
|
126
|
+
for r in self._rows:
|
|
127
|
+
if r["id"] == id:
|
|
128
|
+
r.update({k: v for k, v in dict(dto).items() if k != "id"})
|
|
129
|
+
return dict(r)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def delete(self, id: int) -> bool:
|
|
133
|
+
before = len(self._rows)
|
|
134
|
+
self._rows = [r for r in self._rows if r["id"] != id]
|
|
135
|
+
return len(self._rows) != before
|