metaobjects 1.0.12__tar.gz → 1.0.13__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-1.0.12 → metaobjects-1.0.13}/PKG-INFO +1 -1
- {metaobjects-1.0.12 → metaobjects-1.0.13}/pyproject.toml +1 -1
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/naming.py +56 -7
- metaobjects-1.0.13/src/metaobjects/codegen/collection_name_collision.py +58 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/runner.py +5 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/errors.py +6 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/source/meta_source.py +13 -2
- metaobjects-1.0.13/tests/codegen/test_collection_name_collision.py +109 -0
- metaobjects-1.0.13/tests/codegen/test_route_path_naming.py +149 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_runner.py +21 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_fr016_source_name_and_kind_aliases.py +33 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/uv.lock +1 -1
- metaobjects-1.0.12/tests/codegen/test_route_path_naming.py +0 -67
- {metaobjects-1.0.12 → metaobjects-1.0.13}/.gitignore +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/LICENSE +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/README.md +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/hatch_build.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/scripts/generate_embedded_library.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/agent_context/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/agent_context/scaffold.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/api_model.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/builder.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/paths.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/apidocs/renderer.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/attr_class_map.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/cli.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/collision_names.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/eject.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/format.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generator_registry.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/entity_model.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/find_inbound.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/names_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/router_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/template_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/instance_artifacts.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/metaobjects-config.schema.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/model_walk.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/overwrite_policy.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/project_config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/runtime/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/runtime/constraint_errors.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/template_codegen/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/template_codegen/output_pattern.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/template_codegen/template_data.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/template_codegen/template_spec.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/type_map.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/codegen/value_objects.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/config/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/config/dependencies.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/config/neutral_config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/config/source_resolver.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/core_types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/datatype.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/documentation/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/documentation/doc_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/documentation/doc_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/documentation/doc_schema.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/fmt.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/library/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/library/embedded_library.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/library/library_sources.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/merge.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/meta_data_loader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/registered_validation.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/sources/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/sources/directory_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/sources/file_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/sources/uri_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_discriminator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_enum_normalize_ambiguity.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_field_mutability.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_source_escapes.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/loader/validation_passes.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/attr/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/field/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/field/field_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/field/meta_field.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/identity/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/index/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/index/index_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/index/meta_index.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/meta_object.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/object_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/object_extract.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/object/value_object.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/relationship/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/relationship/relationship_references.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/requirement/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/requirement/meta_requirement.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/requirement/requirement_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/validator/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/meta_data.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/meta_root.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/origin/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/source/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/layout/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/ui/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/view/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/provider_extends.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/template/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/template/meta_template.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/template/prompt_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/template/template_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta_endpoint.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/naming.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/naming_refs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/parser.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/parser_yaml.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/py.typed +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/registry.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/registry_manifest.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/email_document.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/escapers.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/coerce.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/extract.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/extract_map.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/locate.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/normalize.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/strip.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/filesystem_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/payload_accessors.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/prompt_field.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/prompt/prompt_style.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/renderer.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/render/verify.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/runtime/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/runtime/llm_recorder.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/runtime/n2m_resolver.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/runtime/object_manager.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/runtime/tph.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/scope.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/serializer_json.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/shared/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/shared/base_types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/shared/separators.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/shared/structural.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source/error_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source/json_path.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source/semantic_diff.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source/yaml_positions.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/source_resolution.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/attr.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/db.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/documentation.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/field.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/identity.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/index.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/layout.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/object.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/origin.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/prompt.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/relationship.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/requirement.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/source.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/template.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/ui-web.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/ui.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/validator.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/spec_metamodel/view.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/super_resolve.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/validation_types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/yaml_desugar.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/gen_suite.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/extends/expected/Program.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/extends/meta.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/nested-array/meta.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/scalars/expected/Report.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/scalars/meta.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/golden/vanilla/meta.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_abstract_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_api_docs_builder.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_api_docs_paths.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_config_gen.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_config_verify.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_dependencies.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_fmt.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_gen_unknown_attr.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_providers.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_registry.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_render_helper_template_root.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_staleness_nudge.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_verify_strict.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_cli_verify_subverbs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_codegen_compile_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_constants_config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_eject.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_eject_runtime.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_entity_model.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_enum_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_extends_abstract_field_inheritance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_extract_tier_collision.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_extractor_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_filter_allowlist_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_fmt_format_file.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_format.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_fr010_nested_object_prompt.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_fr010_output_codegen.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_gen_state_keying.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_generator_extension_seams.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_golden.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_inheritance_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_instance_artifacts.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_m2m_codegen.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_names_extends_chain.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_names_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_no_magic_physical_names.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_origin_nullability.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_output_parser_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_overwrite_policy.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_overwrite_policy_hash.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_project_config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_projection_compile.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_recipe_generators.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_render_helper_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_render_helper_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_reverse_finders.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_router_autoset.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_router_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_router_generator_m2m_target_tph.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_router_generator_tph_m2m.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_router_generator_tph_m2m_shadowed.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_source_resolution.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_sourceless_entity_surface.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_sourceless_projection.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_data.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_output_pattern.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_scope_helpers.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_scope_walk.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_template_spec.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_tph_codegen.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_trace_helper_generator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_type_map.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_validation_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_value_objects.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/codegen/test_write_through.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/config/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/config/test_dependencies.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/config/test_library_package_guard.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/config/test_neutral_config.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/config/test_source_resolver.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/capabilities.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/conformance-expected-failures.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/conformance_adapter.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/corpus.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/expected_failures.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/fixture_discovery.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/navigator.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_dependency_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_extract_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_extract_object_array_bounds.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_extract_object_verdict.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_fmt_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_fr010_loader_attrs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_fr011_attrs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_generator_registry_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_object_model_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_provider_composition_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_registry_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_runner_hardfail.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_scope_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_source_resolution_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_spec_metamodel_embed.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_strict_attr_load.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_template_codegen_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_template_generator_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/test_yaml_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/api_contract_assertions.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/api_contract_jsonb_server.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/api_contract_m2m_server.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/api_contract_server.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/generated_jsonb_app.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/generated_m2m_app.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/generated_projection_app.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/generated_router_app.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/generated_tph_app.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/meta_ai_trace.yaml +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/normalization.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/postgres_container.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/query_runner.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/scenarios.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_jsonb.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_m2m.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_m2m_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_projection.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_api_contract_tph_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_generated_router_uuid_pk.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_llm_call_trace.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_m2m_target_tph_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_normalization.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_query_scenarios.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_runtime_return_types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_tph_m2m_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/integration/test_tph_m2m_shadowed_generated.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_abstract_subtype_authored.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_child_wrapper_body_shape.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_enum_extends_values_conflict.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_field_mutability_order.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_shipped_library_ai.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/loader/test_template_payload_target_210.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/open_closed_proof_test.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_coerce.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_extract.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_extract_map.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_json_forgiving_reader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_locate.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_model.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_normalize.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_strip.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/prompt/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/prompt/test_output_format_renderer.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_email_document.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_filesystem_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_output_format_renderer_nested.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_output_prompt_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_render_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_render_max_chars.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_render_model_payload.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_verify.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/render/test_verify_conformance.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/runtime/test_constraint_errors.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/runtime/test_object_manager_currency_read.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/runtime/test_object_manager_enum_intvaluemap.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/runtime/test_object_manager_table_name.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_fr5c_merge_attribution.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_fr5d_reference_resolution.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_fr5e_database_source_shape.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_json_path.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_semantic_diff.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_source_on_node.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/source/test_yaml_positions.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/test_api_docs_accuracy.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/test_meta_endpoint.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/__init__.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_agent_context_staleness.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_capabilities.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_column_naming_strategy.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_common_attrs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_core_types.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_derive_m2m_declaring_entity.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_effective_children_sibling_shadowing.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_effective_package.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_errors.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_field_enum.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_field_enum_intvaluemap.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_field_map_validation.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_file_source_id.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_index_lookup.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_llm_recorder.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_loader.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_loader_bom.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_loader_class.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_merge.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_meta_attr.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_meta_data.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_meta_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_module_shortcuts.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_n2m_resolver.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_n2m_resolver_inherited.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_no_dead_config_fields.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_object_manager_autoset_stamping.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_object_manager_write_through.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_one_primary_source.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_parser.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_parser_yaml.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_provider.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_provider_extension.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_registry.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_registry_completeness.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_registry_extend.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_registry_sealed.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_relationship_m2m_validation.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_relationship_references.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_relationship_referential_actions.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_resolution_key.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_serializer.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_serializer_shared_document.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_shared_constants.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_smoke.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_source_escapes.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_sources.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_strict_child_placement.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_super_resolve.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_template_toolcall.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_attr_schema.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_filter_values.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_origin_195.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_origin_paths.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_sort_field.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_validation_warnings.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_write_coercion_arrays.py +0 -0
- {metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_yaml_desugar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: metaobjects
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.13
|
|
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 = "1.0.
|
|
3
|
+
version = "1.0.13"
|
|
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"
|
|
@@ -74,6 +74,52 @@ def _route_snake_case(name: str) -> str:
|
|
|
74
74
|
return to_snake_case(name)
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
_ALREADY_PLURAL_EXCLUDED_PRECEDING_CHARS = "suia"
|
|
78
|
+
"""The four letters that precede a genuinely SINGULAR "...s" ending in the common
|
|
79
|
+
patterns this codebase's entity names hit — status, address, bonus, alias, gas,
|
|
80
|
+
analysis. Anything else before a final "s" reads as already-plural."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _is_already_plural(word: str) -> bool:
|
|
84
|
+
"""True when ``word`` already reads as a plural noun, so running it through
|
|
85
|
+
the ordinary suffix rule would double it — the real defect this exists to
|
|
86
|
+
fix: ``program_purchase_stats`` -> ``program_purchase_statses`` shipped as a
|
|
87
|
+
REST collection path in an adopter's app.
|
|
88
|
+
|
|
89
|
+
Heuristic, not a dictionary: a word ending in ``s`` is already-plural UNLESS
|
|
90
|
+
the character immediately before that final ``s`` is one of s/u/i/a
|
|
91
|
+
(case-insensitive). Covers stats, settings, details, news, analytics,
|
|
92
|
+
series, photos; leaves status, address, bonus, alias, gas, analysis on the
|
|
93
|
+
ordinary suffix path (unchanged).
|
|
94
|
+
|
|
95
|
+
Known miss, deliberately not fixed here: a genuinely singular word ending in
|
|
96
|
+
"...s" with none of those four letters before it reads as already-plural too
|
|
97
|
+
— ``lens`` -> ``lens`` (correct plural ``lenses``). Fixing that needs a real
|
|
98
|
+
dictionary, which this is not; the heuristic optimizes for the shape the
|
|
99
|
+
doubling defect actually hits (entity names that are already a plural
|
|
100
|
+
concept)."""
|
|
101
|
+
if len(word) < 2:
|
|
102
|
+
return False
|
|
103
|
+
lowered = word.lower()
|
|
104
|
+
if lowered[-1] != "s":
|
|
105
|
+
return False
|
|
106
|
+
return lowered[-2] not in _ALREADY_PLURAL_EXCLUDED_PRECEDING_CHARS
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _pluralize_legacy_suffix_only(name: str) -> str:
|
|
110
|
+
"""FROZEN — byte-for-byte the pre-fix suffix-only pluralization rule, with no
|
|
111
|
+
already-plural detection. Exposed only so :func:`pluralize` can fall back to
|
|
112
|
+
it. The DEFAULT PHYSICAL name fallback lives in a completely separate,
|
|
113
|
+
independently-frozen implementation (``meta_source._pluralize``) — this is
|
|
114
|
+
NOT that one, and the two must never be merged into a single call site."""
|
|
115
|
+
lowered = name.lower()
|
|
116
|
+
if lowered.endswith(("s", "x", "z", "ch", "sh")):
|
|
117
|
+
return name + "es"
|
|
118
|
+
if len(name) >= 2 and lowered[-1] == "y" and lowered[-2] not in "aeiou":
|
|
119
|
+
return name[:-1] + "ies"
|
|
120
|
+
return name + "s"
|
|
121
|
+
|
|
122
|
+
|
|
77
123
|
def pluralize(name: str) -> str:
|
|
78
124
|
"""``post_category`` → ``post_categories``; ``address`` → ``addresses``.
|
|
79
125
|
|
|
@@ -82,13 +128,16 @@ def pluralize(name: str) -> str:
|
|
|
82
128
|
``y`` becomes ``ies``; anything else takes ``s``. The cross-port
|
|
83
129
|
byte-identity covers the already-lowercased word :func:`route_path` feeds
|
|
84
130
|
in — suffix tests here run on a lowercased copy, unlike the JVM port's
|
|
85
|
-
case-sensitive ones, so mixed-case input may diverge.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
131
|
+
case-sensitive ones, so mixed-case input may diverge.
|
|
132
|
+
|
|
133
|
+
Adds already-plural detection (:func:`_is_already_plural`) on top of that
|
|
134
|
+
historical suffix rule (:func:`_pluralize_legacy_suffix_only`) — API/code-
|
|
135
|
+
surface pluralization only; the DEFAULT PHYSICAL table name fallback is
|
|
136
|
+
frozen separately (``meta_source._pluralize``), so an existing adopter
|
|
137
|
+
database never sees a proposed rename."""
|
|
138
|
+
if _is_already_plural(name):
|
|
139
|
+
return name
|
|
140
|
+
return _pluralize_legacy_suffix_only(name)
|
|
92
141
|
|
|
93
142
|
|
|
94
143
|
def model_class_name(obj: MetaData) -> str:
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Collection-name collision refusal — the single choke point every route/router/
|
|
2
|
+
finder generator path goes through (called once, in ``run_gen``, over the full
|
|
3
|
+
entity set BEFORE any generator runs).
|
|
4
|
+
|
|
5
|
+
Two DISTINCT entities/projections pluralizing to the SAME API-surface collection
|
|
6
|
+
name (REST path, generated router module/finder names) is reachable from stock
|
|
7
|
+
metadata now that :func:`metaobjects.apidocs.naming.pluralize` no longer doubles an
|
|
8
|
+
already-plural word: ``Address`` (not already-plural: legacy-pluralizes to
|
|
9
|
+
``Addresses``) and ``Addresses`` (already-plural: left unchanged, ALSO
|
|
10
|
+
``Addresses``) collide, where before the fix they did not (``Addresses`` legacy-
|
|
11
|
+
pluralized to ``Addresseses``). Silently letting two entities share one collection
|
|
12
|
+
name would mean whichever generator runs second silently wins, or a straight
|
|
13
|
+
duplicate-module/route registration two files downstream of the real cause. This
|
|
14
|
+
refuses at the one place that can see the WHOLE set, with a message naming both
|
|
15
|
+
entities and the colliding name.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from metaobjects.apidocs.naming import pluralize
|
|
20
|
+
from metaobjects.errors import ErrorCode
|
|
21
|
+
from metaobjects.meta.core.object.meta_object import MetaObject
|
|
22
|
+
from metaobjects.meta.core.object.object_constants import OBJECT_SUBTYPE_VALUE
|
|
23
|
+
|
|
24
|
+
ERR_COLLECTION_NAME_COLLISION = ErrorCode.ERR_COLLECTION_NAME_COLLISION.value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def assert_no_collection_name_collisions(entities: list[MetaObject]) -> None:
|
|
28
|
+
"""Refuse a generation run in which two DISTINCT entities/projections pluralize
|
|
29
|
+
to the same API-surface collection name.
|
|
30
|
+
|
|
31
|
+
Scope: every object EXCEPT ``object.value`` (a value object never gets a route,
|
|
32
|
+
router module, or finder name — it is pure shape, so it cannot collide on any of
|
|
33
|
+
those). A TPH subtype IS included: it gets its own generated names against the
|
|
34
|
+
shared base table, so two subtypes (or a subtype and an unrelated top-level
|
|
35
|
+
entity) sharing a pluralized name collide on those generated symbols exactly as
|
|
36
|
+
two top-level entities would.
|
|
37
|
+
|
|
38
|
+
A PURE function of the entity set — never of traversal order: the reported pair
|
|
39
|
+
is deterministic (first-seen-by-input-order "owner" of a plural; raised as soon
|
|
40
|
+
as a second, different entity claims the same plural).
|
|
41
|
+
"""
|
|
42
|
+
owner_of_plural: dict[str, MetaObject] = {}
|
|
43
|
+
for obj in entities:
|
|
44
|
+
if obj.sub_type == OBJECT_SUBTYPE_VALUE:
|
|
45
|
+
continue
|
|
46
|
+
plural = pluralize(obj.name)
|
|
47
|
+
existing = owner_of_plural.get(plural)
|
|
48
|
+
if existing is not None:
|
|
49
|
+
if existing.resolution_key() != obj.resolution_key():
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"{ERR_COLLECTION_NAME_COLLISION}: \"{existing.name}\" and \"{obj.name}\" both "
|
|
52
|
+
f'pluralize to the API-surface collection name "{plural}" (REST path, generated '
|
|
53
|
+
"router module/finder names) — rename one entity so its pluralized name is "
|
|
54
|
+
"distinct. Default PHYSICAL table names are unaffected by this rule and are not "
|
|
55
|
+
"involved in the collision."
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
owner_of_plural[plural] = obj
|
|
@@ -10,6 +10,7 @@ from metaobjects.meta.meta_root import MetaRoot
|
|
|
10
10
|
from metaobjects.meta.meta_data import MetaData
|
|
11
11
|
from metaobjects.meta.core.object.meta_object import MetaObject
|
|
12
12
|
from metaobjects.shared.base_types import TYPE_OBJECT
|
|
13
|
+
from .collection_name_collision import assert_no_collection_name_collisions
|
|
13
14
|
from .config import GenConfig
|
|
14
15
|
from .constants import generated_package_init
|
|
15
16
|
from .generator import GenContext, Generator
|
|
@@ -107,6 +108,10 @@ def run_gen(
|
|
|
107
108
|
if not safe:
|
|
108
109
|
return result
|
|
109
110
|
|
|
111
|
+
# Refuse before any generator runs — the one choke point that sees the WHOLE
|
|
112
|
+
# entity set every route/router-emitting generator below reads from.
|
|
113
|
+
assert_no_collection_name_collisions(safe)
|
|
114
|
+
|
|
110
115
|
emitted: dict[str, tuple[str, str]] = {} # full_path -> (content, generated_by)
|
|
111
116
|
for gen in generators:
|
|
112
117
|
ctx = GenContext(
|
|
@@ -200,6 +200,12 @@ class ErrorCode(str, Enum):
|
|
|
200
200
|
# Codegen (ADR-0044): two value-object FQNs in one payload artifact derive the same
|
|
201
201
|
# emitted record name even after package-qualification. Peer of ERR_VAR_NOT_ON_PAYLOAD.
|
|
202
202
|
ERR_PAYLOAD_NAME_COLLISION = "ERR_PAYLOAD_NAME_COLLISION"
|
|
203
|
+
# Codegen: two DISTINCT entities/projections in one generation run pluralize to the
|
|
204
|
+
# SAME API-surface collection name (route path, generated router/finder names) —
|
|
205
|
+
# e.g. "Address" and "Addresses" both resolving to "Addresses". A codegen-time (not
|
|
206
|
+
# loader) error, peer of ERR_PAYLOAD_NAME_COLLISION: refused rather than silently
|
|
207
|
+
# letting one entity's route win.
|
|
208
|
+
ERR_COLLECTION_NAME_COLLISION = "ERR_COLLECTION_NAME_COLLISION"
|
|
203
209
|
ERR_OUTPUT_TAG_MISSING = "ERR_OUTPUT_TAG_MISSING"
|
|
204
210
|
# SP-H Unit9 — @filterable: true on a field subtype with no filter-operator
|
|
205
211
|
# band (e.g. field.object). Would silently generate an empty-ops filter.
|
{metaobjects-1.0.12 → metaobjects-1.0.13}/src/metaobjects/meta/persistence/source/meta_source.py
RENAMED
|
@@ -47,7 +47,17 @@ def _to_snake_case(s: str) -> str:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def _pluralize(s: str) -> str:
|
|
50
|
-
"""
|
|
50
|
+
"""FROZEN — the DEFAULT PHYSICAL name derivation only (step 4 below, reached
|
|
51
|
+
only when metadata declares no explicit physical name). Matches the TS
|
|
52
|
+
reference ``pluralizeTableNameLegacy`` (metadata/src/naming.ts) — NOT the TS
|
|
53
|
+
``pluralize``, which now differs for an already-plural word, deliberately
|
|
54
|
+
with NO already-plural detection. An adopter's live database was created
|
|
55
|
+
with this rule; adding the already-plural check here would make a schema
|
|
56
|
+
migration propose a rename for every already-plural entity name on the next
|
|
57
|
+
run. The API-surface analog (``metaobjects.apidocs.naming.pluralize``) has
|
|
58
|
+
that fix — this one must not, and the two are intentionally separate,
|
|
59
|
+
independently-maintained implementations (never merge them into one call
|
|
60
|
+
site)."""
|
|
51
61
|
if not s:
|
|
52
62
|
return s
|
|
53
63
|
lower = s.lower()
|
|
@@ -128,7 +138,8 @@ class MetaSource(MetaData):
|
|
|
128
138
|
2. Legacy ``@table`` for non-table kind (pre-1.0 fallback).
|
|
129
139
|
3. Source's bare structural ``name`` via snake_case
|
|
130
140
|
(no pluralize — the source's name IS the logical name).
|
|
131
|
-
4. Owning entity's name via ``pluralize(snake_case())
|
|
141
|
+
4. Owning entity's name via the frozen legacy ``pluralize(snake_case())``
|
|
142
|
+
— see module-level ``_pluralize``'s docstring for why it is frozen.
|
|
132
143
|
|
|
133
144
|
Callers needing the legacy raw ``@table`` slot only should use
|
|
134
145
|
:meth:`table_name`; codegen / runtime should use ``physical_name``.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""assert_no_collection_name_collisions — the single choke point every route/router
|
|
2
|
+
generator path goes through (called once, in run_gen, over the full entity set
|
|
3
|
+
BEFORE any generator runs). See collection_name_collision.py's module docstring.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from metaobjects import MetaDataLoader
|
|
13
|
+
from metaobjects.meta.core.object.meta_object import MetaObject
|
|
14
|
+
from metaobjects.shared.base_types import TYPE_OBJECT
|
|
15
|
+
from metaobjects.codegen.collection_name_collision import (
|
|
16
|
+
ERR_COLLECTION_NAME_COLLISION,
|
|
17
|
+
assert_no_collection_name_collisions,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_NAMING_CONFORMANCE_FIXTURE = (
|
|
21
|
+
Path(__file__).parents[4] / "fixtures" / "naming-conformance" / "already-plural-pluralize.json"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _entities(tmp_path: Path, *names: str) -> list:
|
|
26
|
+
children = [
|
|
27
|
+
{
|
|
28
|
+
"object.entity": {
|
|
29
|
+
"name": name,
|
|
30
|
+
"children": [
|
|
31
|
+
{"source.rdb": {"@table": name.lower()}},
|
|
32
|
+
{"field.long": {"name": "id"}},
|
|
33
|
+
{"identity.primary": {"@fields": "id"}},
|
|
34
|
+
],
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for name in names
|
|
38
|
+
]
|
|
39
|
+
meta_dir = tmp_path / "meta"
|
|
40
|
+
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
(meta_dir / "meta.json").write_text(
|
|
42
|
+
json.dumps({"metadata.root": {"package": "acme", "children": children}})
|
|
43
|
+
)
|
|
44
|
+
root = MetaDataLoader.from_directory(meta_dir).root
|
|
45
|
+
return [c for c in root.own_children() if c.type == TYPE_OBJECT and isinstance(c, MetaObject)]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _entity_and_value(tmp_path: Path, entity_name: str, value_name: str) -> list:
|
|
49
|
+
doc = {
|
|
50
|
+
"metadata.root": {
|
|
51
|
+
"package": "acme",
|
|
52
|
+
"children": [
|
|
53
|
+
{
|
|
54
|
+
"object.entity": {
|
|
55
|
+
"name": entity_name,
|
|
56
|
+
"children": [
|
|
57
|
+
{"source.rdb": {"@table": entity_name.lower()}},
|
|
58
|
+
{"field.long": {"name": "id"}},
|
|
59
|
+
{"identity.primary": {"@fields": "id"}},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"object.value": {
|
|
65
|
+
"name": value_name,
|
|
66
|
+
"children": [{"field.string": {"name": "text"}}],
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
meta_dir = tmp_path / "meta"
|
|
73
|
+
meta_dir.mkdir(parents=True, exist_ok=True)
|
|
74
|
+
(meta_dir / "meta.json").write_text(json.dumps(doc))
|
|
75
|
+
root = MetaDataLoader.from_directory(meta_dir).root
|
|
76
|
+
return [c for c in root.own_children() if c.type == TYPE_OBJECT and isinstance(c, MetaObject)]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_does_not_raise_for_distinct_plurals(tmp_path: Path) -> None:
|
|
80
|
+
assert_no_collection_name_collisions(_entities(tmp_path, "Post", "Author", "Category"))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_raises_for_address_and_addresses(tmp_path: Path) -> None:
|
|
84
|
+
with pytest.raises(ValueError) as exc_info:
|
|
85
|
+
assert_no_collection_name_collisions(_entities(tmp_path, "Address", "Addresses"))
|
|
86
|
+
msg = str(exc_info.value)
|
|
87
|
+
assert ERR_COLLECTION_NAME_COLLISION in msg
|
|
88
|
+
assert "Address" in msg
|
|
89
|
+
assert "Addresses" in msg
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_raises_for_order_and_orders(tmp_path: Path) -> None:
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
assert_no_collection_name_collisions(_entities(tmp_path, "Order", "Orders"))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_excludes_value_objects(tmp_path: Path) -> None:
|
|
98
|
+
# "Address" (entity) pluralizes to "Addresses"; an unrelated object.value named
|
|
99
|
+
# "Addresses" never gets a route, so it must not trip this.
|
|
100
|
+
assert_no_collection_name_collisions(_entity_and_value(tmp_path, "Address", "Addresses"))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_naming_conformance_collision_cases_are_refused(tmp_path: Path) -> None:
|
|
104
|
+
cases = json.loads(_NAMING_CONFORMANCE_FIXTURE.read_text())["collisionCases"]
|
|
105
|
+
for i, case in enumerate(cases):
|
|
106
|
+
with pytest.raises(ValueError):
|
|
107
|
+
assert_no_collection_name_collisions(
|
|
108
|
+
_entities(tmp_path / str(i), case["entityA"], case["entityB"])
|
|
109
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""The cross-port collection-URL spelling: the ENTITY NAME snake_cased, then pluralized.
|
|
2
|
+
|
|
3
|
+
One rule in all five ports. The multi-word and consonant+y axes are also gated
|
|
4
|
+
end-to-end by ``fixtures/api-contract-conformance/m2m/`` (the ``PostCategory``
|
|
5
|
+
scenario). The ACRONYM case is not — no corpus entity carries one — so it is
|
|
6
|
+
pinned here, against the same rule, in every port that owns a naming seam.
|
|
7
|
+
|
|
8
|
+
``route_path`` deliberately does NOT reuse :func:`metaobjects.apidocs.naming.snake_case`:
|
|
9
|
+
that one separates before every capital, which is right for a module name and
|
|
10
|
+
would emit ``h_t_t_p_server`` in a URL.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from metaobjects.apidocs.naming import pluralize, reverse_finder_fn, route_path, snake_case
|
|
20
|
+
|
|
21
|
+
_NAMING_CONFORMANCE_FIXTURE = (
|
|
22
|
+
Path(__file__).parents[4] / "fixtures" / "naming-conformance" / "already-plural-pluralize.json"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
("name", "expected"),
|
|
28
|
+
[
|
|
29
|
+
# Single regular word — every port's old rule already agreed here, which
|
|
30
|
+
# is exactly why four different spellings shipped green.
|
|
31
|
+
("Author", "authors"),
|
|
32
|
+
("Post", "posts"),
|
|
33
|
+
("Tag", "tags"),
|
|
34
|
+
("Person", "persons"),
|
|
35
|
+
("Account", "accounts"),
|
|
36
|
+
("Auth", "auths"),
|
|
37
|
+
# Multi-word: the capitals carry the word boundary, so lowercasing
|
|
38
|
+
# without separating used to yield "postcategorys".
|
|
39
|
+
("PostCategory", "post_categories"),
|
|
40
|
+
("OrderSummary", "order_summaries"),
|
|
41
|
+
# consonant + y -> ies; a sibilant takes -es.
|
|
42
|
+
("Category", "categories"),
|
|
43
|
+
("Address", "addresses"),
|
|
44
|
+
# Acronym: a run of capitals stays together until the final one that
|
|
45
|
+
# begins a word.
|
|
46
|
+
("HTTPServer", "http_servers"),
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_route_path_is_entity_name_snake_cased_then_pluralized(name: str, expected: str) -> None:
|
|
50
|
+
assert route_path(name) == expected
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_route_path_does_not_reuse_the_module_name_snake_case() -> None:
|
|
54
|
+
"""The two snake variants genuinely differ, so this is not a tautology."""
|
|
55
|
+
assert snake_case("HTTPServer") == "h_t_t_p_server"
|
|
56
|
+
assert route_path("HTTPServer") == "http_servers"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
("word", "expected"),
|
|
61
|
+
[
|
|
62
|
+
("post_category", "post_categories"),
|
|
63
|
+
("address", "addresses"),
|
|
64
|
+
("box", "boxes"),
|
|
65
|
+
("buzz", "buzzes"),
|
|
66
|
+
("match", "matches"),
|
|
67
|
+
("dish", "dishes"),
|
|
68
|
+
# A VOWEL before the y takes a plain "s" — "days", never "daies".
|
|
69
|
+
("day", "days"),
|
|
70
|
+
("author", "authors"),
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
def test_pluralize_contract(word: str, expected: str) -> None:
|
|
74
|
+
assert pluralize(word) == expected
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# ---- Already-plural detection --------------------------------
|
|
78
|
+
#
|
|
79
|
+
# An already-plural entity name used to double (program_purchase_stats ->
|
|
80
|
+
# program_purchase_statses) in the REST collection segment and the reverse-finder
|
|
81
|
+
# names. Mirrors the TS fix in metadata/src/naming.ts exactly (same four-letter
|
|
82
|
+
# exclusion set before a final "s").
|
|
83
|
+
|
|
84
|
+
@pytest.mark.parametrize(
|
|
85
|
+
"word",
|
|
86
|
+
["stats", "settings", "details", "news", "analytics", "series", "photos"],
|
|
87
|
+
)
|
|
88
|
+
def test_pluralize_leaves_already_plural_words_unchanged(word: str) -> None:
|
|
89
|
+
assert pluralize(word) == word
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
("name", "expected"),
|
|
94
|
+
[
|
|
95
|
+
("ProgramPurchaseStats", "program_purchase_stats"),
|
|
96
|
+
("Settings", "settings"),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
def test_route_path_does_not_double_pluralize_an_already_plural_entity_name(
|
|
100
|
+
name: str, expected: str
|
|
101
|
+
) -> None:
|
|
102
|
+
assert route_path(name) == expected
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.parametrize(
|
|
106
|
+
("word", "expected"),
|
|
107
|
+
[
|
|
108
|
+
("status", "statuses"),
|
|
109
|
+
("address", "addresses"),
|
|
110
|
+
("bonus", "bonuses"),
|
|
111
|
+
("alias", "aliases"),
|
|
112
|
+
("gas", "gases"),
|
|
113
|
+
# Documented pre-existing imperfection, explicitly out of scope — not "analyses".
|
|
114
|
+
("analysis", "analysises"),
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
def test_pluralize_keeps_existing_behavior_for_s_u_i_a_plus_s_endings(
|
|
118
|
+
word: str, expected: str
|
|
119
|
+
) -> None:
|
|
120
|
+
assert pluralize(word) == expected
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_pluralize_documented_known_miss_lens_reads_as_already_plural() -> None:
|
|
124
|
+
# Correct plural is "lenses"; this heuristic is not a dictionary. See
|
|
125
|
+
# pluralize's docstring.
|
|
126
|
+
assert pluralize("lens") == "lens"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_reverse_finder_fn_does_not_double_pluralize_an_already_plural_source_entity() -> None:
|
|
130
|
+
assert reverse_finder_fn("ProgramPurchaseStats", "programId") == (
|
|
131
|
+
"find_program_purchase_stats_by_program"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# fixtures/naming-conformance/ — the shared cross-port data proving every
|
|
137
|
+
# port's API-surface pluralizer agrees on the same inputs (the FROZEN legacy
|
|
138
|
+
# half of the same fixture is checked in test_fr016_source_name_and_kind_aliases.py,
|
|
139
|
+
# against meta_source._pluralize through a loaded entity). See that corpus's
|
|
140
|
+
# README.
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
_naming_conformance_cases = json.loads(_NAMING_CONFORMANCE_FIXTURE.read_text())["cases"]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@pytest.mark.parametrize(
|
|
146
|
+
"case", _naming_conformance_cases, ids=[c["name"] for c in _naming_conformance_cases]
|
|
147
|
+
)
|
|
148
|
+
def test_naming_conformance_api_plurals_match(case: dict) -> None:
|
|
149
|
+
assert pluralize(case["name"]) == case["apiPlural"]
|
|
@@ -210,3 +210,24 @@ def test_the_no_manifest_refusal_leads_with_the_performable_remedy(tmp_path: Pat
|
|
|
210
210
|
assert "--baseline=adopt" in msgs[0]
|
|
211
211
|
# What it must no longer do: prescribe committing a file this project cannot produce.
|
|
212
212
|
assert "Commit '.metaobjects/.gen-state/.hashes.json' and re-run" not in msgs[0]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_run_gen_refuses_a_model_with_colliding_collection_names(tmp_path: Path) -> None:
|
|
216
|
+
"""Integration-level: proves the collision gate is wired into the real entry
|
|
217
|
+
point (run_gen), not just callable as a standalone function. Address +
|
|
218
|
+
Addresses both pluralize to "Addresses" — reachable now that an already-
|
|
219
|
+
plural entity name is no longer double-pluralized."""
|
|
220
|
+
root = _load(tmp_path / "meta", {"metadata.root": {"package": "acme", "children": [
|
|
221
|
+
{"object.entity": {"name": "Address", "children": [
|
|
222
|
+
{"source.rdb": {"@table": "address"}},
|
|
223
|
+
{"field.long": {"name": "id"}},
|
|
224
|
+
{"identity.primary": {"@fields": "id"}},
|
|
225
|
+
]}},
|
|
226
|
+
{"object.entity": {"name": "Addresses", "children": [
|
|
227
|
+
{"source.rdb": {"@table": "addresses"}},
|
|
228
|
+
{"field.long": {"name": "id"}},
|
|
229
|
+
{"identity.primary": {"@fields": "id"}},
|
|
230
|
+
]}},
|
|
231
|
+
]}})
|
|
232
|
+
with pytest.raises(ValueError):
|
|
233
|
+
run_gen(GenConfig(out_dir=str(tmp_path / "out")), root, generators=[entity_model()])
|
{metaobjects-1.0.12 → metaobjects-1.0.13}/tests/unit/test_fr016_source_name_and_kind_aliases.py
RENAMED
|
@@ -19,6 +19,8 @@ import os
|
|
|
19
19
|
import tempfile
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
22
24
|
from metaobjects import MetaDataLoader
|
|
23
25
|
from metaobjects.core_types import core_provider
|
|
24
26
|
from metaobjects.meta.persistence.source.meta_source import MetaSource
|
|
@@ -160,6 +162,37 @@ def test_physical_name_step4_owning_entity_pluralize_snake_case() -> None:
|
|
|
160
162
|
assert _first_source(root).physical_name() == "authors"
|
|
161
163
|
|
|
162
164
|
|
|
165
|
+
def test_physical_name_step4_frozen_legacy_rule_still_doubles_an_already_plural_owner_name() -> None:
|
|
166
|
+
"""The default PHYSICAL table name derivation is FROZEN at the
|
|
167
|
+
pre-fix suffix-only rule, so an adopter's existing database never sees a
|
|
168
|
+
proposed rename for an already-plural entity name. Only the API-surface
|
|
169
|
+
``metaobjects.apidocs.naming.pluralize`` gets the already-plural fix."""
|
|
170
|
+
_, _, root, _ = _load(_one_source("ProgramPurchaseStats", {}))
|
|
171
|
+
assert _first_source(root).physical_name() == "program_purchase_statses"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# fixtures/naming-conformance/ — the shared cross-port data proving every
|
|
176
|
+
# port's FROZEN legacy pluralizer agrees on the same inputs (the API-surface
|
|
177
|
+
# half of the same fixture is checked in test_route_path_naming.py, against
|
|
178
|
+
# metaobjects.apidocs.naming.pluralize directly). See that corpus's README.
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
_NAMING_CONFORMANCE_FIXTURE = (
|
|
181
|
+
Path(__file__).parents[4] / "fixtures" / "naming-conformance" / "already-plural-pluralize.json"
|
|
182
|
+
)
|
|
183
|
+
_naming_conformance_cases = json.loads(_NAMING_CONFORMANCE_FIXTURE.read_text())["cases"]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.parametrize(
|
|
187
|
+
"case", _naming_conformance_cases, ids=[c["name"] for c in _naming_conformance_cases]
|
|
188
|
+
)
|
|
189
|
+
def test_naming_conformance_legacy_plurals_match(case: dict) -> None:
|
|
190
|
+
_, _, root, _ = _load(_one_source(case["name"], {}))
|
|
191
|
+
# Every fixture case is a single PascalCase word, so lowercasing is
|
|
192
|
+
# byte-equivalent to the snake_case step physical_name applies first.
|
|
193
|
+
assert _first_source(root).physical_name() == case["legacyPlural"].lower()
|
|
194
|
+
|
|
195
|
+
|
|
163
196
|
# ---------------------------------------------------------------------------
|
|
164
197
|
# Validation pass — kind mismatch, multiple aliases, empty string, legacy warn
|
|
165
198
|
# ---------------------------------------------------------------------------
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"""The cross-port collection-URL spelling: the ENTITY NAME snake_cased, then pluralized.
|
|
2
|
-
|
|
3
|
-
One rule in all five ports. The multi-word and consonant+y axes are also gated
|
|
4
|
-
end-to-end by ``fixtures/api-contract-conformance/m2m/`` (the ``PostCategory``
|
|
5
|
-
scenario). The ACRONYM case is not — no corpus entity carries one — so it is
|
|
6
|
-
pinned here, against the same rule, in every port that owns a naming seam.
|
|
7
|
-
|
|
8
|
-
``route_path`` deliberately does NOT reuse :func:`metaobjects.apidocs.naming.snake_case`:
|
|
9
|
-
that one separates before every capital, which is right for a module name and
|
|
10
|
-
would emit ``h_t_t_p_server`` in a URL.
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import pytest
|
|
15
|
-
|
|
16
|
-
from metaobjects.apidocs.naming import pluralize, route_path, snake_case
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@pytest.mark.parametrize(
|
|
20
|
-
("name", "expected"),
|
|
21
|
-
[
|
|
22
|
-
# Single regular word — every port's old rule already agreed here, which
|
|
23
|
-
# is exactly why four different spellings shipped green.
|
|
24
|
-
("Author", "authors"),
|
|
25
|
-
("Post", "posts"),
|
|
26
|
-
("Tag", "tags"),
|
|
27
|
-
("Person", "persons"),
|
|
28
|
-
("Account", "accounts"),
|
|
29
|
-
("Auth", "auths"),
|
|
30
|
-
# Multi-word: the capitals carry the word boundary, so lowercasing
|
|
31
|
-
# without separating used to yield "postcategorys".
|
|
32
|
-
("PostCategory", "post_categories"),
|
|
33
|
-
("OrderSummary", "order_summaries"),
|
|
34
|
-
# consonant + y -> ies; a sibilant takes -es.
|
|
35
|
-
("Category", "categories"),
|
|
36
|
-
("Address", "addresses"),
|
|
37
|
-
# Acronym: a run of capitals stays together until the final one that
|
|
38
|
-
# begins a word.
|
|
39
|
-
("HTTPServer", "http_servers"),
|
|
40
|
-
],
|
|
41
|
-
)
|
|
42
|
-
def test_route_path_is_entity_name_snake_cased_then_pluralized(name: str, expected: str) -> None:
|
|
43
|
-
assert route_path(name) == expected
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def test_route_path_does_not_reuse_the_module_name_snake_case() -> None:
|
|
47
|
-
"""The two snake variants genuinely differ, so this is not a tautology."""
|
|
48
|
-
assert snake_case("HTTPServer") == "h_t_t_p_server"
|
|
49
|
-
assert route_path("HTTPServer") == "http_servers"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@pytest.mark.parametrize(
|
|
53
|
-
("word", "expected"),
|
|
54
|
-
[
|
|
55
|
-
("post_category", "post_categories"),
|
|
56
|
-
("address", "addresses"),
|
|
57
|
-
("box", "boxes"),
|
|
58
|
-
("buzz", "buzzes"),
|
|
59
|
-
("match", "matches"),
|
|
60
|
-
("dish", "dishes"),
|
|
61
|
-
# A VOWEL before the y takes a plain "s" — "days", never "daies".
|
|
62
|
-
("day", "days"),
|
|
63
|
-
("author", "authors"),
|
|
64
|
-
],
|
|
65
|
-
)
|
|
66
|
-
def test_pluralize_contract(word: str, expected: str) -> None:
|
|
67
|
-
assert pluralize(word) == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|