metaobjects 0.12.0__tar.gz → 0.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. {metaobjects-0.12.0 → metaobjects-0.13.0}/PKG-INFO +1 -1
  2. {metaobjects-0.12.0 → metaobjects-0.13.0}/pyproject.toml +1 -1
  3. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/cli.py +98 -10
  4. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generator.py +32 -0
  5. metaobjects-0.13.0/src/metaobjects/codegen/generators/template_generator.py +138 -0
  6. metaobjects-0.13.0/src/metaobjects/codegen/template_codegen/output_pattern.py +54 -0
  7. metaobjects-0.13.0/src/metaobjects/codegen/template_codegen/template_data.py +119 -0
  8. metaobjects-0.13.0/src/metaobjects/codegen/template_codegen/template_spec.py +90 -0
  9. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/validation_passes.py +637 -59
  10. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/escapers.py +4 -0
  11. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_cli.py +57 -0
  12. metaobjects-0.13.0/tests/codegen/test_projection_compile.py +55 -0
  13. metaobjects-0.13.0/tests/codegen/test_template_data.py +50 -0
  14. metaobjects-0.13.0/tests/codegen/test_template_output_pattern.py +32 -0
  15. metaobjects-0.13.0/tests/codegen/test_template_scope_helpers.py +52 -0
  16. metaobjects-0.13.0/tests/codegen/test_template_scope_walk.py +52 -0
  17. metaobjects-0.13.0/tests/codegen/test_template_spec.py +60 -0
  18. metaobjects-0.13.0/tests/conformance/conformance-expected-failures.json +6 -0
  19. metaobjects-0.13.0/tests/conformance/test_template_codegen_conformance.py +51 -0
  20. metaobjects-0.13.0/tests/unit/__init__.py +0 -0
  21. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_fr016_source_name_and_kind_aliases.py +22 -14
  22. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_validation_origin_paths.py +15 -6
  23. {metaobjects-0.12.0 → metaobjects-0.13.0}/uv.lock +1 -1
  24. metaobjects-0.12.0/src/metaobjects/codegen/generators/template_generator.py +0 -70
  25. metaobjects-0.12.0/tests/conformance/conformance-expected-failures.json +0 -25
  26. {metaobjects-0.12.0 → metaobjects-0.13.0}/.gitignore +0 -0
  27. {metaobjects-0.12.0 → metaobjects-0.13.0}/LICENSE +0 -0
  28. {metaobjects-0.12.0 → metaobjects-0.13.0}/README.md +0 -0
  29. {metaobjects-0.12.0 → metaobjects-0.13.0}/hatch_build.py +0 -0
  30. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/__init__.py +0 -0
  31. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/agent_context/__init__.py +0 -0
  32. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/agent_context/scaffold.py +0 -0
  33. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/__init__.py +0 -0
  34. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/api_model.py +0 -0
  35. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/builder.py +0 -0
  36. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/naming.py +0 -0
  37. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/paths.py +0 -0
  38. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/apidocs/renderer.py +0 -0
  39. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/attr_class_map.py +0 -0
  40. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/KNOWN_GAPS.md +0 -0
  41. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/__init__.py +0 -0
  42. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/config.py +0 -0
  43. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/constants.py +0 -0
  44. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/extract_delegate_emitter.py +0 -0
  45. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/extract_schema_emitter.py +0 -0
  46. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/format.py +0 -0
  47. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/fr010_field_mapping.py +0 -0
  48. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generator_registry.py +0 -0
  49. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/__init__.py +0 -0
  50. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/entity_model.py +0 -0
  51. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/extractor_generator.py +0 -0
  52. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/filter_allowlist_generator.py +0 -0
  53. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/fr019_shared_enum.py +0 -0
  54. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/m2m_codegen.py +0 -0
  55. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/output_parser_generator.py +0 -0
  56. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/output_prompt_generator.py +0 -0
  57. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/payload_vo_generator.py +0 -0
  58. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/render_helper_generator.py +0 -0
  59. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/router_generator.py +0 -0
  60. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/tph_plan.py +0 -0
  61. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/generators/trace_helper_generator.py +0 -0
  62. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/instance_artifacts.py +0 -0
  63. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/output_format_spec_emitter.py +0 -0
  64. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/overwrite_policy.py +0 -0
  65. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/runner.py +0 -0
  66. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/runtime/__init__.py +0 -0
  67. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/runtime/filter_parser.py +0 -0
  68. {metaobjects-0.12.0/src/metaobjects/loader → metaobjects-0.13.0/src/metaobjects/codegen/template_codegen}/__init__.py +0 -0
  69. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/codegen/type_map.py +0 -0
  70. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/core_types.py +0 -0
  71. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/datatype.py +0 -0
  72. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/documentation/__init__.py +0 -0
  73. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/documentation/doc_constants.py +0 -0
  74. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/documentation/doc_provider.py +0 -0
  75. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/documentation/doc_schema.py +0 -0
  76. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/errors.py +0 -0
  77. {metaobjects-0.12.0/src/metaobjects/meta/core → metaobjects-0.13.0/src/metaobjects/loader}/__init__.py +0 -0
  78. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/merge.py +0 -0
  79. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/meta_data_loader.py +0 -0
  80. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/registered_validation.py +0 -0
  81. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/sources/__init__.py +0 -0
  82. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/sources/directory_source.py +0 -0
  83. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/sources/file_source.py +0 -0
  84. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/sources/meta_data_source.py +0 -0
  85. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/sources/uri_source.py +0 -0
  86. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/validate_discriminator.py +0 -0
  87. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/validate_field_readonly.py +0 -0
  88. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/validate_source_parameter_ref.py +0 -0
  89. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/loader/validate_source_physical_names.py +0 -0
  90. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/__init__.py +0 -0
  91. {metaobjects-0.12.0/src/metaobjects/meta/core/attr → metaobjects-0.13.0/src/metaobjects/meta/core}/__init__.py +0 -0
  92. {metaobjects-0.12.0/src/metaobjects/meta/core/field → metaobjects-0.13.0/src/metaobjects/meta/core/attr}/__init__.py +0 -0
  93. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/attr/attr_constants.py +0 -0
  94. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/attr/meta_attr.py +0 -0
  95. {metaobjects-0.12.0/src/metaobjects/meta/core/identity → metaobjects-0.13.0/src/metaobjects/meta/core/field}/__init__.py +0 -0
  96. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/field/field_constants.py +0 -0
  97. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/field/meta_field.py +0 -0
  98. {metaobjects-0.12.0/src/metaobjects/meta/core/object → metaobjects-0.13.0/src/metaobjects/meta/core/identity}/__init__.py +0 -0
  99. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/identity/identity_constants.py +0 -0
  100. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/identity/meta_identity.py +0 -0
  101. {metaobjects-0.12.0/src/metaobjects/meta/core/relationship → metaobjects-0.13.0/src/metaobjects/meta/core/object}/__init__.py +0 -0
  102. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/meta_object.py +0 -0
  103. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/meta_object_aware.py +0 -0
  104. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/object_class_registry.py +0 -0
  105. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/object_constants.py +0 -0
  106. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/object_extract.py +0 -0
  107. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/object/value_object.py +0 -0
  108. {metaobjects-0.12.0/src/metaobjects/meta/core/validator → metaobjects-0.13.0/src/metaobjects/meta/core/relationship}/__init__.py +0 -0
  109. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/relationship/derive_m2m_fields.py +0 -0
  110. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/relationship/meta_relationship.py +0 -0
  111. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/relationship/relationship_constants.py +0 -0
  112. {metaobjects-0.12.0/src/metaobjects/meta/persistence → metaobjects-0.13.0/src/metaobjects/meta/core/validator}/__init__.py +0 -0
  113. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/core/validator/validator_constants.py +0 -0
  114. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/meta_data.py +0 -0
  115. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/meta_root.py +0 -0
  116. {metaobjects-0.12.0/src/metaobjects/meta/persistence/origin → metaobjects-0.13.0/src/metaobjects/meta/persistence}/__init__.py +0 -0
  117. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/db/__init__.py +0 -0
  118. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/db/db_constants.py +0 -0
  119. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/db/db_provider.py +0 -0
  120. {metaobjects-0.12.0/src/metaobjects/meta/persistence/source → metaobjects-0.13.0/src/metaobjects/meta/persistence/origin}/__init__.py +0 -0
  121. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/origin/meta_origin.py +0 -0
  122. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/origin/origin_constants.py +0 -0
  123. {metaobjects-0.12.0/src/metaobjects/meta/presentation → metaobjects-0.13.0/src/metaobjects/meta/persistence/source}/__init__.py +0 -0
  124. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/source/meta_source.py +0 -0
  125. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/persistence/source/source_constants.py +0 -0
  126. {metaobjects-0.12.0/src/metaobjects/meta/presentation/layout → metaobjects-0.13.0/src/metaobjects/meta/presentation}/__init__.py +0 -0
  127. {metaobjects-0.12.0/src/metaobjects/meta/presentation/ui → metaobjects-0.13.0/src/metaobjects/meta/presentation/layout}/__init__.py +0 -0
  128. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/presentation/layout/layout_constants.py +0 -0
  129. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/presentation/layout/meta_layout.py +0 -0
  130. {metaobjects-0.12.0/src/metaobjects/meta/presentation/view → metaobjects-0.13.0/src/metaobjects/meta/presentation/ui}/__init__.py +0 -0
  131. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/presentation/ui/ui_provider.py +0 -0
  132. {metaobjects-0.12.0/src/metaobjects/meta/template → metaobjects-0.13.0/src/metaobjects/meta/presentation/view}/__init__.py +0 -0
  133. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/presentation/view/meta_view.py +0 -0
  134. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/presentation/view/view_constants.py +0 -0
  135. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/provider_extends.py +0 -0
  136. {metaobjects-0.12.0/src/metaobjects/shared → metaobjects-0.13.0/src/metaobjects/meta/template}/__init__.py +0 -0
  137. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/template/meta_template.py +0 -0
  138. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/template/prompt_provider.py +0 -0
  139. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/meta/template/template_constants.py +0 -0
  140. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/naming_refs.py +0 -0
  141. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/parser.py +0 -0
  142. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/parser_yaml.py +0 -0
  143. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/provider.py +0 -0
  144. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/py.typed +0 -0
  145. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/registry.py +0 -0
  146. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/registry_manifest.py +0 -0
  147. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/__init__.py +0 -0
  148. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/email_document.py +0 -0
  149. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/KNOWN_GAPS.md +0 -0
  150. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/__init__.py +0 -0
  151. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/coerce.py +0 -0
  152. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/extract.py +0 -0
  153. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/extract_map.py +0 -0
  154. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/json_forgiving_reader.py +0 -0
  155. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/locate.py +0 -0
  156. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/normalize.py +0 -0
  157. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/strip.py +0 -0
  158. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/types.py +0 -0
  159. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/extract/xml_forgiving_reader.py +0 -0
  160. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/filesystem_provider.py +0 -0
  161. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/__init__.py +0 -0
  162. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/output_format_renderer.py +0 -0
  163. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/output_format_spec.py +0 -0
  164. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/prompt_field.py +0 -0
  165. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/prompt_overrides.py +0 -0
  166. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/prompt/prompt_style.py +0 -0
  167. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/renderer.py +0 -0
  168. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/render/verify.py +0 -0
  169. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/runtime/__init__.py +0 -0
  170. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/runtime/llm_recorder.py +0 -0
  171. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/runtime/n2m_resolver.py +0 -0
  172. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/runtime/object_manager.py +0 -0
  173. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/runtime/tph.py +0 -0
  174. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/serializer_json.py +0 -0
  175. {metaobjects-0.12.0/tests → metaobjects-0.13.0/src/metaobjects/shared}/__init__.py +0 -0
  176. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/shared/base_types.py +0 -0
  177. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/shared/separators.py +0 -0
  178. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/shared/structural.py +0 -0
  179. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/source/__init__.py +0 -0
  180. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/source/error_source.py +0 -0
  181. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/source/json_path.py +0 -0
  182. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/source/semantic_diff.py +0 -0
  183. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/source/yaml_positions.py +0 -0
  184. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/__init__.py +0 -0
  185. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/attr.json +0 -0
  186. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/db.json +0 -0
  187. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/documentation.json +0 -0
  188. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/field.json +0 -0
  189. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/identity.json +0 -0
  190. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/layout.json +0 -0
  191. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/object.json +0 -0
  192. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/origin.json +0 -0
  193. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/prompt.json +0 -0
  194. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/relationship.json +0 -0
  195. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/source.json +0 -0
  196. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/template.json +0 -0
  197. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/ui.json +0 -0
  198. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/validator.json +0 -0
  199. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/spec_metamodel/view.json +0 -0
  200. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/super_resolve.py +0 -0
  201. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/validation_types.py +0 -0
  202. {metaobjects-0.12.0 → metaobjects-0.13.0}/src/metaobjects/yaml_desugar.py +0 -0
  203. {metaobjects-0.12.0/tests/codegen → metaobjects-0.13.0/tests}/__init__.py +0 -0
  204. {metaobjects-0.12.0/tests/conformance → metaobjects-0.13.0/tests/codegen}/__init__.py +0 -0
  205. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/extends/expected/BaseEntity.py +0 -0
  206. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/extends/expected/Program.py +0 -0
  207. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/extends/meta.json +0 -0
  208. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/nested-array/expected/AuthorBrief.py +0 -0
  209. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/nested-array/expected/PostBrief.py +0 -0
  210. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/nested-array/meta.json +0 -0
  211. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/scalars/expected/Metric.py +0 -0
  212. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/scalars/expected/Report.py +0 -0
  213. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/scalars/meta.json +0 -0
  214. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/vanilla/expected/Subscriber.py +0 -0
  215. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/golden/vanilla/meta.json +0 -0
  216. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_abstract_conformance.py +0 -0
  217. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_api_docs_builder.py +0 -0
  218. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_api_docs_paths.py +0 -0
  219. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_cli_registry.py +0 -0
  220. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_cli_staleness_nudge.py +0 -0
  221. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_cli_verify_subverbs.py +0 -0
  222. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_constants_config.py +0 -0
  223. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_entity_model.py +0 -0
  224. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_enum_conformance.py +0 -0
  225. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_extractor_generator.py +0 -0
  226. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_filter_allowlist_generator.py +0 -0
  227. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_format.py +0 -0
  228. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_fr010_output_codegen.py +0 -0
  229. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_fr019_shared_provided_conformance.py +0 -0
  230. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_generator.py +0 -0
  231. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_generator_extension_seams.py +0 -0
  232. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_golden.py +0 -0
  233. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_inherit_without_restate_gate.py +0 -0
  234. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_inheritance_conformance.py +0 -0
  235. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_instance_artifacts.py +0 -0
  236. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_m2m_codegen.py +0 -0
  237. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_output_parser_generator.py +0 -0
  238. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_overwrite_policy.py +0 -0
  239. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_payload_vo_generator.py +0 -0
  240. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_render_helper_conformance.py +0 -0
  241. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_render_helper_generator.py +0 -0
  242. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_router_generator.py +0 -0
  243. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_runner.py +0 -0
  244. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_template_generator.py +0 -0
  245. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_tph_codegen.py +0 -0
  246. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_trace_helper_generator.py +0 -0
  247. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_type_map.py +0 -0
  248. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/codegen/test_validation_conformance.py +0 -0
  249. {metaobjects-0.12.0/tests/integration → metaobjects-0.13.0/tests/conformance}/__init__.py +0 -0
  250. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/capabilities.py +0 -0
  251. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/conformance_adapter.py +0 -0
  252. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/corpus.py +0 -0
  253. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/expected_failures.py +0 -0
  254. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/fixture_discovery.py +0 -0
  255. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/navigator.py +0 -0
  256. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_api_docs_cross_port_conformance.py +0 -0
  257. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_conformance.py +0 -0
  258. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_extract_conformance.py +0 -0
  259. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_extract_object_verdict.py +0 -0
  260. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_fr010_loader_attrs.py +0 -0
  261. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_fr011_attrs.py +0 -0
  262. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_generator_registry_conformance.py +0 -0
  263. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_object_model_conformance.py +0 -0
  264. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_registry_conformance.py +0 -0
  265. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_runner_hardfail.py +0 -0
  266. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_spec_metamodel_embed.py +0 -0
  267. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_strict_attr_load.py +0 -0
  268. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_template_generator_conformance.py +0 -0
  269. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/test_yaml_conformance.py +0 -0
  270. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/conformance/yaml-conformance-expected-failures.json +0 -0
  271. {metaobjects-0.12.0/tests/render → metaobjects-0.13.0/tests/integration}/__init__.py +0 -0
  272. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/api_contract_assertions.py +0 -0
  273. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/api_contract_m2m_server.py +0 -0
  274. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/api_contract_server.py +0 -0
  275. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/generated_m2m_app.py +0 -0
  276. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/generated_router_app.py +0 -0
  277. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/generated_tph_app.py +0 -0
  278. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/meta_ai_trace.yaml +0 -0
  279. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/normalization.py +0 -0
  280. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/postgres_container.py +0 -0
  281. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/query_runner.py +0 -0
  282. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/scenarios.py +0 -0
  283. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_api_contract.py +0 -0
  284. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_api_contract_generated.py +0 -0
  285. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_api_contract_m2m.py +0 -0
  286. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_api_contract_m2m_generated.py +0 -0
  287. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_api_contract_tph_generated.py +0 -0
  288. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_llm_call_trace.py +0 -0
  289. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_normalization.py +0 -0
  290. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_query_scenarios.py +0 -0
  291. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/integration/test_runtime_return_types.py +0 -0
  292. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/open_closed_proof_test.py +0 -0
  293. {metaobjects-0.12.0/tests/render/extract → metaobjects-0.13.0/tests/render}/__init__.py +0 -0
  294. {metaobjects-0.12.0/tests/render/prompt → metaobjects-0.13.0/tests/render/extract}/__init__.py +0 -0
  295. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_coerce.py +0 -0
  296. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_extract.py +0 -0
  297. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_extract_map.py +0 -0
  298. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_json_forgiving_reader.py +0 -0
  299. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_locate.py +0 -0
  300. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_model.py +0 -0
  301. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_normalize.py +0 -0
  302. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_strip.py +0 -0
  303. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/extract/test_xml_forgiving_reader.py +0 -0
  304. {metaobjects-0.12.0/tests/source → metaobjects-0.13.0/tests/render/prompt}/__init__.py +0 -0
  305. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/prompt/test_output_format_renderer.py +0 -0
  306. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_email_document.py +0 -0
  307. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_filesystem_provider.py +0 -0
  308. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_output_format_renderer_nested.py +0 -0
  309. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_output_prompt_conformance.py +0 -0
  310. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_render_conformance.py +0 -0
  311. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_render_max_chars.py +0 -0
  312. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_verify.py +0 -0
  313. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/render/test_verify_conformance.py +0 -0
  314. {metaobjects-0.12.0/tests/unit → metaobjects-0.13.0/tests/source}/__init__.py +0 -0
  315. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_fr5c_merge_attribution.py +0 -0
  316. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_fr5d_reference_resolution.py +0 -0
  317. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_fr5e_database_source_shape.py +0 -0
  318. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_json_path.py +0 -0
  319. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_semantic_diff.py +0 -0
  320. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_source_on_node.py +0 -0
  321. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/source/test_yaml_positions.py +0 -0
  322. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/test_api_docs_accuracy.py +0 -0
  323. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_agent_context_staleness.py +0 -0
  324. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_capabilities.py +0 -0
  325. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_common_attrs.py +0 -0
  326. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_core_types.py +0 -0
  327. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_effective_package.py +0 -0
  328. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_errors.py +0 -0
  329. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_field_enum.py +0 -0
  330. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_field_map_validation.py +0 -0
  331. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_field_uuid_dbcolumntype.py +0 -0
  332. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_llm_recorder.py +0 -0
  333. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_loader.py +0 -0
  334. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_loader_bom.py +0 -0
  335. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_loader_class.py +0 -0
  336. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_merge.py +0 -0
  337. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_meta_attr.py +0 -0
  338. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_meta_data.py +0 -0
  339. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_meta_source.py +0 -0
  340. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_module_shortcuts.py +0 -0
  341. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_n2m_resolver.py +0 -0
  342. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_object_manager_uuid_coercion.py +0 -0
  343. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_one_primary_source.py +0 -0
  344. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_parser.py +0 -0
  345. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_parser_yaml.py +0 -0
  346. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_provider.py +0 -0
  347. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_provider_extension.py +0 -0
  348. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_registry.py +0 -0
  349. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_registry_completeness.py +0 -0
  350. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_registry_extend.py +0 -0
  351. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_registry_sealed.py +0 -0
  352. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_relationship_referential_actions.py +0 -0
  353. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_resolution_key.py +0 -0
  354. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_runtime_resolution_key_binding.py +0 -0
  355. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_serializer.py +0 -0
  356. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_shared_constants.py +0 -0
  357. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_smoke.py +0 -0
  358. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_sources.py +0 -0
  359. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_strict_child_placement.py +0 -0
  360. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_super_resolve.py +0 -0
  361. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_template_toolcall.py +0 -0
  362. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_template_wrong_subtype_attrs.py +0 -0
  363. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_validation_attr_schema.py +0 -0
  364. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_validation_filter_values.py +0 -0
  365. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_validation_sort_field.py +0 -0
  366. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_validation_warnings.py +0 -0
  367. {metaobjects-0.12.0 → metaobjects-0.13.0}/tests/unit/test_yaml_desugar.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metaobjects
3
- Version: 0.12.0
3
+ Version: 0.13.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.12.0"
3
+ version = "0.13.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"
@@ -3,9 +3,13 @@
3
3
  Two subcommands:
4
4
 
5
5
  metaobjects gen <metadataDir> --out <dir> [--package <pkg>]
6
+ [--template-spec <json> [--templates <dir>]]
6
7
  Load metadata, run the Python codegen generator suite, and write files
7
8
  under ``--out`` (guarded by the @generated header). Prints each written
8
- file. Non-zero exit on a load error.
9
+ file. Non-zero exit on a load error. ``--template-spec`` appends the
10
+ declarative Mustache template generators (scope perEntity/perPackage/
11
+ perModel + outputPattern) described by a JSON spec, resolving template
12
+ refs under ``--templates`` (default ``templates``).
9
13
 
10
14
  metaobjects verify <metadataDir> [--codegen] [--templates] [--db URL] ...
11
15
  Drift gate with explicit subverbs (ADR-0021 D2 — one verify vocabulary
@@ -138,11 +142,38 @@ def _parse_entities(value: str | None) -> list[str] | None:
138
142
  return names or None
139
143
 
140
144
 
145
+ def _run_suite(
146
+ root: MetaData,
147
+ out_dir: str,
148
+ generators: list[Generator] | None = None,
149
+ entity_filter: list[str] | None = None,
150
+ emit_package_init: bool = True,
151
+ ) -> list[str]:
152
+ """Run a generator suite against an ALREADY-LOADED ``root`` into ``out_dir``.
153
+
154
+ Split out from :func:`_generate` so a single gen invocation can load the
155
+ metadata once and run multiple passes against it (the default Python suite +
156
+ the ``--template-spec`` pass). See :func:`_generate` for the parameter
157
+ semantics. Returns the written paths.
158
+
159
+ NOTE: ``run_gen``'s output-path collision guard is per-pass — a
160
+ ``--template-spec`` ``outputPattern`` that collides with a default-suite path
161
+ is not flagged across the two passes (it would overwrite, since default files
162
+ carry the ``@generated`` header). Accepted marginal limitation (requires user
163
+ misconfiguration).
164
+ """
165
+ config = GenConfig(out_dir=out_dir, emit_package_init=emit_package_init)
166
+ suite = generators if generators is not None else _default_generators()
167
+ result = run_gen(config, root, generators=suite, entity_filter=entity_filter)
168
+ return [path for path, status in result.files if status != "refused"]
169
+
170
+
141
171
  def _generate(
142
172
  metadata_dir: str,
143
173
  out_dir: str,
144
174
  generators: list[Generator] | None = None,
145
175
  entity_filter: list[str] | None = None,
176
+ emit_package_init: bool = True,
146
177
  ) -> tuple[list[str], list[str]]:
147
178
  """Run the generator suite into ``out_dir``.
148
179
 
@@ -150,18 +181,17 @@ def _generate(
150
181
  resolved subset for ``--generators``. ``entity_filter`` (the ``--entities``
151
182
  allowlist) restricts which entities are EMITTED while the WHOLE model is still
152
183
  loaded, so cross-entity references (``extends`` bases, ``@objectRef`` VOs)
153
- resolve — emit a subset without splitting the metadata. Returns
184
+ resolve — emit a subset without splitting the metadata. ``emit_package_init``
185
+ is ``False`` for the format-agnostic ``--template-spec`` generators (whose
186
+ output is text/markdown/csv/json/xml/html, never a Python package) so no
187
+ spurious ``__init__.py`` is scattered through their output tree. Returns
154
188
  ``(written_paths, errors)``. On a load error, ``errors`` is non-empty and no
155
189
  files are written.
156
190
  """
157
191
  root, errors = _load_root(metadata_dir)
158
192
  if root is None:
159
193
  return [], errors
160
- config = GenConfig(out_dir=out_dir)
161
- suite = generators if generators is not None else _default_generators()
162
- result = run_gen(config, root, generators=suite, entity_filter=entity_filter)
163
- written = [path for path, status in result.files if status != "refused"]
164
- return written, []
194
+ return _run_suite(root, out_dir, generators, entity_filter, emit_package_init), []
165
195
 
166
196
 
167
197
  #: The default api-surface subdir (the cross-port contract's ``api/python``).
@@ -293,13 +323,54 @@ def _cmd_gen(args: argparse.Namespace) -> int:
293
323
  print(f" {msg}", file=sys.stderr)
294
324
  return 1
295
325
 
326
+ # SP-1: declarative Mustache generators from a JSON template-spec. Their output
327
+ # is format-agnostic (text/markdown/csv/json/xml/html), so they run as a SECOND,
328
+ # SEPARATE pass with emit_package_init=False — no Python __init__.py is injected
329
+ # into their (possibly non-Python) output tree. Templates resolve under
330
+ # --templates via a FilesystemProvider.
331
+ spec_gens: list[Generator] = []
332
+ if getattr(args, "template_spec", None):
333
+ from metaobjects.codegen.template_codegen.template_spec import (
334
+ parse_template_spec,
335
+ template_spec_to_generators,
336
+ )
337
+
338
+ try:
339
+ spec = parse_template_spec(json.loads(Path(args.template_spec).read_text(encoding="utf-8")))
340
+ except (OSError, ValueError) as exc:
341
+ print(f"error: invalid --template-spec: {exc}", file=sys.stderr)
342
+ return 1
343
+ provider = FilesystemProvider(args.templates)
344
+ spec_gens = template_spec_to_generators(spec, provider)
345
+
296
346
  entities = _parse_entities(getattr(args, "entities", None))
297
- written, errors = _generate(args.metadata_dir, args.out, generators, entities)
298
- if errors:
347
+ # Load the metadata ONCE; both the default suite and the (optional)
348
+ # --template-spec pass run against this single loaded root.
349
+ root, load_errors = _load_root(args.metadata_dir)
350
+ if root is None:
299
351
  print("error: failed to load metadata:", file=sys.stderr)
300
- for msg in errors:
352
+ for msg in load_errors:
301
353
  print(f" {msg}", file=sys.stderr)
302
354
  return 1
355
+ written = _run_suite(root, args.out, generators, entities)
356
+ if spec_gens:
357
+ # The template-spec pass renders user-supplied templates: a bad ref or a
358
+ # wrong --templates dir raises RenderError (not OSError/ValueError, so it
359
+ # escapes the parse-time guard above). Report it as a clean error.
360
+ from metaobjects.render.renderer import RenderError
361
+
362
+ try:
363
+ spec_written = _run_suite(
364
+ root, args.out, spec_gens, entities, emit_package_init=False
365
+ )
366
+ except RenderError as exc:
367
+ print(
368
+ f"error: --template-spec render failed (check the template refs and "
369
+ f"--templates dir {args.templates!r}): {exc}",
370
+ file=sys.stderr,
371
+ )
372
+ return 1
373
+ written = list(written) + spec_written
303
374
  for path in written:
304
375
  print(path)
305
376
  print(f"metaobjects gen: wrote {len(written)} file(s) to {args.out}")
@@ -581,6 +652,23 @@ def _build_parser() -> argparse.ArgumentParser:
581
652
  action="store_true",
582
653
  help="list registered generators (stable name + description) and exit",
583
654
  )
655
+ gen.add_argument(
656
+ "--template-spec",
657
+ dest="template_spec",
658
+ default=None,
659
+ help=(
660
+ "path to a JSON template-spec ({\"generators\":[{name,template,scope,"
661
+ "outputPattern,format?}]}) — declarative Mustache generators appended "
662
+ "to the suite (SP-1). Templates resolve under --templates. For output "
663
+ "to be regenerable, the template must emit the @generated header itself "
664
+ "(the codegen write path refuses to overwrite files lacking it)."
665
+ ),
666
+ )
667
+ gen.add_argument(
668
+ "--templates",
669
+ default="templates",
670
+ help="templates root for --template-spec (default: templates)",
671
+ )
584
672
  gen.add_argument(
585
673
  "--package",
586
674
  default=None,
@@ -60,3 +60,35 @@ def once_per_run(
60
60
  return r if isinstance(r, list) else [r]
61
61
 
62
62
  return run
63
+
64
+
65
+ def per_package(
66
+ fn: Callable[[str, list[MetaObject], GenContext], "EmittedFile | list[EmittedFile]"],
67
+ ) -> Callable[[GenContext], list[EmittedFile]]:
68
+ """One-file-per-package convenience. Groups matched entities by effective
69
+ package, runs ``fn(pkg, entities, ctx)`` once per package (packages ascending,
70
+ entities keeping ``ctx.entities`` order). The package scope from
71
+ codegen-concepts §10 (object + model already exist via per_entity + per_model)."""
72
+
73
+ def run(ctx: GenContext) -> list[EmittedFile]:
74
+ # Local import avoids a module cycle (template_data imports nothing from here).
75
+ from metaobjects.codegen.template_codegen.template_data import package_of
76
+
77
+ by_pkg: dict[str, list[MetaObject]] = {}
78
+ for e in ctx.entities:
79
+ if not ctx.matches(e):
80
+ continue
81
+ by_pkg.setdefault(package_of(e), []).append(e)
82
+ out: list[EmittedFile] = []
83
+ for pkg in sorted(by_pkg):
84
+ r = fn(pkg, by_pkg[pkg], ctx)
85
+ out.extend(r if isinstance(r, list) else [r])
86
+ return out
87
+
88
+ return run
89
+
90
+
91
+ #: App-scope alias — run once over the whole model. The canonical name for the
92
+ #: one-shot scope (``once_per_run`` stays as a soft-deprecated alias; "run" is
93
+ #: ambiguous under multi-target output, ``per_model`` names the data scope).
94
+ per_model = once_per_run
@@ -0,0 +1,138 @@
1
+ """template_generator() — Python port of the TS rc.12 factory.
2
+
3
+ Walks the loaded MetaRoot -> renders shared Mustache templates via the
4
+ metaobjects.render engine -> returns EmittedFile[]. Same Generator Protocol
5
+ as the per-entity hand-coded generators; just adds the "Mustache template"
6
+ + "walk that yields a data dict per output" primitives.
7
+
8
+ Design: spec/design-docs/2026-05-28-cross-port-template-generator.md.
9
+ Cross-port byte-equivalence verified via fixtures/render-conformance/template-generator/.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, Callable, Iterable, Sequence
15
+
16
+ from metaobjects.codegen.generator import EmittedFile, GenContext, Generator
17
+ from metaobjects.codegen.template_codegen.output_pattern import expand_output_pattern
18
+ from metaobjects.codegen.template_codegen.template_data import (
19
+ build_entity_template_data,
20
+ build_model_template_data,
21
+ build_package_template_data,
22
+ bare_name,
23
+ is_concrete,
24
+ package_of,
25
+ )
26
+ from metaobjects.render import escapers
27
+ from metaobjects.render.renderer import RenderRequest, render
28
+ from metaobjects.render.verify import Provider
29
+
30
+ #: The three built-in walk scopes (SP-1 §3.1).
31
+ SCOPES = ("perEntity", "perPackage", "perModel")
32
+
33
+
34
+ def _scope_walk(scope: str, pattern: str) -> Callable[[Any], list[dict]]:
35
+ """Derive a walk from a built-in scope + output pattern. Each scope yields the
36
+ neutral data dict for its unit and names the file via the pattern."""
37
+
38
+ def walk(root: Any) -> list[dict]:
39
+ from metaobjects.meta.core.object.meta_object import MetaObject
40
+
41
+ objects = [c for c in root.children() if isinstance(c, MetaObject)]
42
+ concrete = [o for o in objects if is_concrete(o)]
43
+ if scope == "perEntity":
44
+ return [
45
+ {
46
+ "data": build_entity_template_data(o),
47
+ "output_path": expand_output_pattern(pattern, bare_name(o), package_of(o)),
48
+ }
49
+ for o in concrete
50
+ ]
51
+ if scope == "perPackage":
52
+ by_pkg: dict[str, list[Any]] = {}
53
+ for o in concrete:
54
+ by_pkg.setdefault(package_of(o), []).append(o)
55
+ return [
56
+ {
57
+ "data": build_package_template_data(pkg, by_pkg[pkg]),
58
+ "output_path": expand_output_pattern(pattern, None, pkg),
59
+ }
60
+ for pkg in sorted(by_pkg)
61
+ ]
62
+ # perModel — one file over the whole model.
63
+ return [
64
+ {
65
+ "data": build_model_template_data(objects),
66
+ "output_path": expand_output_pattern(pattern, None, None),
67
+ }
68
+ ]
69
+
70
+ return walk
71
+
72
+
73
+ @dataclass
74
+ class _TemplateGenerator:
75
+ name: str
76
+ template: str
77
+ walk: Callable[[Any], Sequence[dict]]
78
+ provider: Provider
79
+ format: str = escapers.FORMAT_TEXT
80
+
81
+ def generate(self, ctx: GenContext) -> list[EmittedFile]:
82
+ walk_results: Iterable[dict] = self.walk(ctx.loaded_root)
83
+ files: list[EmittedFile] = []
84
+ for entry in walk_results:
85
+ content = render(
86
+ RenderRequest(
87
+ payload=entry["data"],
88
+ provider=self.provider,
89
+ ref=self.template,
90
+ format=self.format,
91
+ )
92
+ )
93
+ files.append(EmittedFile(path=entry["output_path"], content=content))
94
+ return files
95
+
96
+
97
+ def template_generator(
98
+ *,
99
+ name: str,
100
+ template: str,
101
+ provider: Provider,
102
+ walk: Callable[[Any], Sequence[dict]] | None = None,
103
+ scope: str | None = None,
104
+ output_pattern: str | None = None,
105
+ format: str = escapers.FORMAT_TEXT,
106
+ ) -> Generator:
107
+ """Build a Generator that renders a Mustache template per walk entry.
108
+
109
+ Provide exactly one of ``walk`` (the power-user escape hatch) or
110
+ (``scope`` + ``output_pattern``) — the declarative built-in walk.
111
+
112
+ Args:
113
+ name: kebab-case identifier; surfaces in diagnostics.
114
+ template: ref resolved by the provider (e.g. "custom/hello").
115
+ provider: ref-resolver for the template.
116
+ walk: callback taking the loaded MetaRoot, returning dicts shaped
117
+ {"data": <payload>, "output_path": <relative path>}.
118
+ scope: built-in walk scope (perEntity/perPackage/perModel).
119
+ output_pattern: output path pattern for the built-in scope walk.
120
+ format: render format ("text", "html", "markdown", ...). Defaults to text.
121
+ """
122
+ has_walk = walk is not None
123
+ has_scope = scope is not None
124
+ if has_walk == has_scope:
125
+ raise ValueError(
126
+ f"template_generator({name}): provide exactly one of `walk` or "
127
+ "(`scope` + `output_pattern`)"
128
+ )
129
+ if has_scope and not output_pattern:
130
+ raise ValueError(f"template_generator({name}): `scope` requires `output_pattern`")
131
+ resolved_walk = walk if has_walk else _scope_walk(scope, output_pattern) # type: ignore[arg-type]
132
+ return _TemplateGenerator(
133
+ name=name,
134
+ template=template,
135
+ walk=resolved_walk,
136
+ provider=provider,
137
+ format=format,
138
+ )
@@ -0,0 +1,54 @@
1
+ """Expands the cross-port output-pattern grammar (SP-1 §3.3): ``{name}``,
2
+ ``{Name}`` (PascalCase), ``{package}`` (``::`` -> ``/``). An empty ``{package}``
3
+ collapses its trailing/leading slash so ``{package}/{name}`` with no package
4
+ yields just ``{name}``. Unknown placeholders raise.
5
+
6
+ Byte-equivalent to the TypeScript ``output-pattern.ts`` and the JVM
7
+ ``OutputPattern``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+
14
+ _TOKEN = re.compile(r"\{(\w+)\}")
15
+
16
+
17
+ def _pascal(s: str) -> str:
18
+ return "".join(w[:1].upper() + w[1:] for w in re.split(r"[^A-Za-z0-9]+", s) if w)
19
+
20
+
21
+ def expand_output_pattern(
22
+ pattern: str, name: str | None, package: str | None
23
+ ) -> str:
24
+ """Expand ``pattern``. ``name`` may be ``None`` (perPackage/perModel);
25
+ ``package`` may be ``None``/empty."""
26
+ pkg_empty = False
27
+
28
+ def repl(m: re.Match[str]) -> str:
29
+ nonlocal pkg_empty
30
+ token = m.group(1)
31
+ if token == "package":
32
+ p = (package or "").replace("::", "/")
33
+ if p == "":
34
+ pkg_empty = True
35
+ return p
36
+ if token == "name":
37
+ if name is None:
38
+ raise ValueError(
39
+ f"output pattern {pattern!r} uses {{name}} but no entity name is in scope"
40
+ )
41
+ return name
42
+ if token == "Name":
43
+ if name is None:
44
+ raise ValueError(
45
+ f"output pattern {pattern!r} uses {{Name}} but no entity name is in scope"
46
+ )
47
+ return _pascal(name)
48
+ raise ValueError(f"unknown placeholder {{{token}}} in output pattern {pattern!r}")
49
+
50
+ out = _TOKEN.sub(repl, pattern)
51
+ if pkg_empty:
52
+ out = re.sub(r"^/+", "", out)
53
+ out = re.sub(r"/{2,}", "/", out)
54
+ return out
@@ -0,0 +1,119 @@
1
+ """The NEUTRAL, structural codegen template data dict (SP-1 §3.2) for Python.
2
+
3
+ Plain ``dict``/``list`` mirroring the TS ``EntityTemplateData`` keys EXACTLY — a
4
+ byte-gated cross-port contract (verified against the TS-produced
5
+ ``fixtures/template-codegen-conformance/expected/``). Optional keys
6
+ (``maxLength``, ``enumValues``) are OMITTED when absent so a ``{{#maxLength}}``
7
+ section gates identically to TS.
8
+
9
+ Own-vs-effective discipline (matching the TS ``ownAttr`` semantics, per the JVM
10
+ review): ``is_abstract`` per-node; ``@required`` attr + ``maxLength`` read from
11
+ ``own_attrs()`` (own-only); the required-*validator* branch effective; enum
12
+ ``values`` from ``attrs()`` (effective); effective package derived from
13
+ ``resolution_key()`` (matching the TS ``effectivePackage``).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any
19
+
20
+ from metaobjects.shared.base_types import (
21
+ TYPE_IDENTITY,
22
+ TYPE_RELATIONSHIP,
23
+ TYPE_VALIDATOR,
24
+ )
25
+ from metaobjects.shared.separators import PACKAGE_SEP
26
+
27
+ _SUBTYPE_ENUM = "enum"
28
+ _VALIDATOR_REQUIRED = "required"
29
+
30
+
31
+ def bare_name(o: Any) -> str:
32
+ """The object name. Python objects already carry the bare leaf name."""
33
+ return o.name
34
+
35
+
36
+ def package_of(o: Any) -> str:
37
+ """Effective package, matching the TS ``effectivePackage(resolutionKey)``.
38
+
39
+ Derived from ``resolution_key()`` (own ``package`` → ``file_default_package`` →
40
+ nearest ancestor's ``package``) by stripping the trailing ``::<name>`` suffix —
41
+ so programmatically-built / plugin trees (no ``file_default_package``, package
42
+ only on an ancestor) resolve the ancestor package like TS does. "" when the
43
+ resolution key carries no package segment.
44
+ """
45
+ key = o.resolution_key()
46
+ idx = key.rfind(PACKAGE_SEP)
47
+ return key[:idx] if idx >= 0 else ""
48
+
49
+
50
+ def is_concrete(o: Any) -> bool:
51
+ return not o.is_abstract
52
+
53
+
54
+ def _is_required(f: Any) -> bool:
55
+ if f.own_attrs().get(_VALIDATOR_REQUIRED) is True:
56
+ return True
57
+ return any(
58
+ c.type == TYPE_VALIDATOR and c.sub_type == _VALIDATOR_REQUIRED
59
+ for c in f.children()
60
+ )
61
+
62
+
63
+ def _field_data(f: Any) -> dict[str, Any]:
64
+ d: dict[str, Any] = {
65
+ "name": f.name,
66
+ "type": f.sub_type,
67
+ "required": _is_required(f),
68
+ "isArray": bool(f.is_array),
69
+ }
70
+ own = f.own_attrs()
71
+ if "maxLength" in own:
72
+ d["maxLength"] = int(own["maxLength"])
73
+ if f.sub_type == _SUBTYPE_ENUM:
74
+ values = f.attrs().get("values")
75
+ if isinstance(values, list):
76
+ d["enumValues"] = [str(v) for v in values]
77
+ return d
78
+
79
+
80
+ def build_entity_template_data(o: Any) -> dict[str, Any]:
81
+ fields = [_field_data(f) for f in o.fields()]
82
+ identities: list[dict[str, Any]] = []
83
+ relationships: list[dict[str, Any]] = []
84
+ for c in o.children():
85
+ if c.type == TYPE_IDENTITY:
86
+ id_fields = c.own_attrs().get("fields")
87
+ identities.append(
88
+ {"kind": c.sub_type, "fields": list(id_fields) if isinstance(id_fields, list) else []}
89
+ )
90
+ elif c.type == TYPE_RELATIONSHIP:
91
+ own = c.own_attrs()
92
+ relationships.append(
93
+ {
94
+ "name": c.name,
95
+ "cardinality": own.get("cardinality", "") or "",
96
+ "targetRef": own.get("objectRef", "") or "",
97
+ }
98
+ )
99
+ return {
100
+ "name": bare_name(o),
101
+ "package": package_of(o),
102
+ "fields": fields,
103
+ "identities": identities,
104
+ "relationships": relationships,
105
+ }
106
+
107
+
108
+ def build_package_template_data(pkg: str, entities: list[Any]) -> dict[str, Any]:
109
+ return {"package": pkg, "entities": [build_entity_template_data(o) for o in entities]}
110
+
111
+
112
+ def build_model_template_data(objects: list[Any]) -> dict[str, Any]:
113
+ by_pkg: dict[str, list[Any]] = {}
114
+ for o in objects:
115
+ if not is_concrete(o):
116
+ continue
117
+ by_pkg.setdefault(package_of(o), []).append(o)
118
+ packages = [build_package_template_data(p, by_pkg[p]) for p in sorted(by_pkg)]
119
+ return {"packages": packages}
@@ -0,0 +1,90 @@
1
+ """The declarative JSON template-spec the CLI ports (Python/C#) consume.
2
+
3
+ The JSON shape is the cross-port contract (SP-1 §4) — a JSON Schema sits beside
4
+ the TS port at codegen-ts/src/template-codegen/template-spec.schema.json. This
5
+ module validates + maps it to runnable Generators.
6
+
7
+ Regenerability note: ``--template-spec`` output flows through the standard codegen
8
+ write path, which refuses to overwrite a file that lacks the ``@generated`` marker
9
+ (the hand-edit guard the rest of codegen and the TS port share). For a template's
10
+ output to be safely regenerable, the **template author** must emit the
11
+ ``@generated`` header in the template body itself — the same author responsibility
12
+ the rest of the codegen pipeline relies on.
13
+
14
+ The Python port has no output-*target* concept (the codegen pipeline writes every
15
+ ``EmittedFile`` relative to a single ``out_dir``). A per-generator ``target`` field
16
+ is therefore REJECTED rather than silently dropped, so a cross-port spec authored
17
+ with ``target`` fails loudly here instead of producing a different layout than TS.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any
23
+
24
+ from metaobjects.codegen.generator import Generator
25
+ from metaobjects.codegen.generators.template_generator import SCOPES, template_generator
26
+ from metaobjects.render import escapers
27
+ from metaobjects.render.verify import Provider
28
+
29
+ _FORMATS = escapers.FORMATS
30
+
31
+ _REQUIRED_STR = ("name", "template", "scope", "outputPattern")
32
+
33
+
34
+ def parse_template_spec(obj: object) -> dict[str, Any]:
35
+ """Validate + return a normalized ``{"generators": [...]}`` dict. Raises
36
+ ``ValueError`` on any shape violation."""
37
+ if not isinstance(obj, dict) or not isinstance(obj.get("generators"), list):
38
+ raise ValueError("template-spec: expected an object with a `generators` array")
39
+ generators: list[dict[str, Any]] = []
40
+ for i, raw in enumerate(obj["generators"]):
41
+ if not isinstance(raw, dict):
42
+ raise ValueError(f"template-spec generators[{i}]: expected an object")
43
+ for key in _REQUIRED_STR:
44
+ if not isinstance(raw.get(key), str) or raw[key] == "":
45
+ raise ValueError(
46
+ f"template-spec generators[{i}]: missing or empty required string {key!r}"
47
+ )
48
+ if raw["scope"] not in SCOPES:
49
+ raise ValueError(
50
+ f"template-spec generators[{i}]: scope must be one of "
51
+ f"{' | '.join(SCOPES)}, got {raw['scope']!r}"
52
+ )
53
+ entry: dict[str, Any] = {
54
+ "name": raw["name"],
55
+ "template": raw["template"],
56
+ "scope": raw["scope"],
57
+ "outputPattern": raw["outputPattern"],
58
+ }
59
+ if "format" in raw:
60
+ if raw["format"] not in _FORMATS:
61
+ raise ValueError(
62
+ f"template-spec generators[{i}]: format must be one of "
63
+ f"{' | '.join(_FORMATS)}, got {raw['format']!r}"
64
+ )
65
+ entry["format"] = raw["format"]
66
+ if "target" in raw:
67
+ raise ValueError(
68
+ f"template-spec generators[{i}]: target is not supported by the "
69
+ "Python port — it has no output-target concept"
70
+ )
71
+ generators.append(entry)
72
+ return {"generators": generators}
73
+
74
+
75
+ def template_spec_to_generators(spec: dict[str, Any], provider: Provider) -> list[Generator]:
76
+ """Map a parsed spec into runnable Generators (one per entry). The caller
77
+ supplies the ``provider`` (the CLI builds a ``FilesystemProvider``)."""
78
+ out: list[Generator] = []
79
+ for e in spec["generators"]:
80
+ out.append(
81
+ template_generator(
82
+ name=e["name"],
83
+ template=e["template"],
84
+ scope=e["scope"],
85
+ output_pattern=e["outputPattern"],
86
+ provider=provider,
87
+ format=e.get("format", escapers.FORMAT_TEXT),
88
+ )
89
+ )
90
+ return out