metaobjects 0.9.0__py3-none-any.whl

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 (181) hide show
  1. metaobjects/__init__.py +75 -0
  2. metaobjects/agent_context/__init__.py +55 -0
  3. metaobjects/agent_context/_content/README.md +14 -0
  4. metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
  5. metaobjects/agent_context/_content/servers/java.meta.json +5 -0
  6. metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
  7. metaobjects/agent_context/_content/servers/python.meta.json +5 -0
  8. metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
  9. metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
  10. metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
  11. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
  12. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
  13. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
  14. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
  15. metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
  16. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
  17. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
  18. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
  19. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
  20. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
  21. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
  22. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
  23. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
  24. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
  25. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
  26. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
  27. metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
  28. metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
  29. metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
  30. metaobjects/agent_context/assemble.py +133 -0
  31. metaobjects/agent_context/content_root.py +54 -0
  32. metaobjects/agent_context/scaffold.py +191 -0
  33. metaobjects/agent_context/types.py +44 -0
  34. metaobjects/attr_class_map.py +23 -0
  35. metaobjects/cli.py +696 -0
  36. metaobjects/codegen/__init__.py +0 -0
  37. metaobjects/codegen/config.py +11 -0
  38. metaobjects/codegen/constants.py +13 -0
  39. metaobjects/codegen/extract_delegate_emitter.py +384 -0
  40. metaobjects/codegen/extract_schema_emitter.py +139 -0
  41. metaobjects/codegen/format.py +31 -0
  42. metaobjects/codegen/fr010_field_mapping.py +220 -0
  43. metaobjects/codegen/generator.py +62 -0
  44. metaobjects/codegen/generator_registry.py +163 -0
  45. metaobjects/codegen/generators/__init__.py +0 -0
  46. metaobjects/codegen/generators/entity_model.py +263 -0
  47. metaobjects/codegen/generators/extractor_generator.py +317 -0
  48. metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
  49. metaobjects/codegen/generators/m2m_codegen.py +192 -0
  50. metaobjects/codegen/generators/output_parser_generator.py +272 -0
  51. metaobjects/codegen/generators/output_prompt_generator.py +192 -0
  52. metaobjects/codegen/generators/payload_vo_generator.py +672 -0
  53. metaobjects/codegen/generators/render_helper_generator.py +451 -0
  54. metaobjects/codegen/generators/router_generator.py +635 -0
  55. metaobjects/codegen/generators/template_generator.py +70 -0
  56. metaobjects/codegen/generators/tph_plan.py +120 -0
  57. metaobjects/codegen/generators/trace_helper_generator.py +336 -0
  58. metaobjects/codegen/instance_artifacts.py +15 -0
  59. metaobjects/codegen/output_format_spec_emitter.py +79 -0
  60. metaobjects/codegen/overwrite_policy.py +27 -0
  61. metaobjects/codegen/runner.py +110 -0
  62. metaobjects/codegen/runtime/__init__.py +6 -0
  63. metaobjects/codegen/runtime/filter_parser.py +193 -0
  64. metaobjects/codegen/type_map.py +84 -0
  65. metaobjects/core_types.py +809 -0
  66. metaobjects/datatype.py +19 -0
  67. metaobjects/documentation/__init__.py +28 -0
  68. metaobjects/documentation/doc_constants.py +20 -0
  69. metaobjects/documentation/doc_provider.py +20 -0
  70. metaobjects/documentation/doc_schema.py +24 -0
  71. metaobjects/errors.py +124 -0
  72. metaobjects/loader/__init__.py +0 -0
  73. metaobjects/loader/merge.py +287 -0
  74. metaobjects/loader/meta_data_loader.py +245 -0
  75. metaobjects/loader/sources/__init__.py +24 -0
  76. metaobjects/loader/sources/directory_source.py +50 -0
  77. metaobjects/loader/sources/file_source.py +41 -0
  78. metaobjects/loader/sources/meta_data_source.py +67 -0
  79. metaobjects/loader/sources/uri_source.py +56 -0
  80. metaobjects/loader/validate_discriminator.py +181 -0
  81. metaobjects/loader/validate_field_readonly.py +146 -0
  82. metaobjects/loader/validate_source_parameter_ref.py +159 -0
  83. metaobjects/loader/validate_source_physical_names.py +140 -0
  84. metaobjects/loader/validation_passes.py +1513 -0
  85. metaobjects/meta/__init__.py +1 -0
  86. metaobjects/meta/core/__init__.py +0 -0
  87. metaobjects/meta/core/attr/__init__.py +0 -0
  88. metaobjects/meta/core/attr/attr_constants.py +31 -0
  89. metaobjects/meta/core/attr/meta_attr.py +136 -0
  90. metaobjects/meta/core/field/__init__.py +0 -0
  91. metaobjects/meta/core/field/field_constants.py +105 -0
  92. metaobjects/meta/core/field/meta_field.py +76 -0
  93. metaobjects/meta/core/identity/__init__.py +0 -0
  94. metaobjects/meta/core/identity/identity_constants.py +19 -0
  95. metaobjects/meta/core/identity/meta_identity.py +8 -0
  96. metaobjects/meta/core/object/__init__.py +0 -0
  97. metaobjects/meta/core/object/meta_object.py +65 -0
  98. metaobjects/meta/core/object/meta_object_aware.py +43 -0
  99. metaobjects/meta/core/object/object_class_registry.py +56 -0
  100. metaobjects/meta/core/object/object_constants.py +13 -0
  101. metaobjects/meta/core/object/object_extract.py +400 -0
  102. metaobjects/meta/core/object/value_object.py +70 -0
  103. metaobjects/meta/core/relationship/__init__.py +0 -0
  104. metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
  105. metaobjects/meta/core/relationship/meta_relationship.py +54 -0
  106. metaobjects/meta/core/relationship/relationship_constants.py +51 -0
  107. metaobjects/meta/core/validator/__init__.py +0 -0
  108. metaobjects/meta/core/validator/validator_constants.py +18 -0
  109. metaobjects/meta/meta_data.py +206 -0
  110. metaobjects/meta/meta_root.py +8 -0
  111. metaobjects/meta/persistence/__init__.py +0 -0
  112. metaobjects/meta/persistence/db/__init__.py +1 -0
  113. metaobjects/meta/persistence/db/db_constants.py +41 -0
  114. metaobjects/meta/persistence/db/db_provider.py +60 -0
  115. metaobjects/meta/persistence/origin/__init__.py +0 -0
  116. metaobjects/meta/persistence/origin/meta_origin.py +8 -0
  117. metaobjects/meta/persistence/origin/origin_constants.py +20 -0
  118. metaobjects/meta/persistence/source/__init__.py +0 -0
  119. metaobjects/meta/persistence/source/meta_source.py +137 -0
  120. metaobjects/meta/persistence/source/source_constants.py +115 -0
  121. metaobjects/meta/presentation/__init__.py +0 -0
  122. metaobjects/meta/presentation/layout/__init__.py +0 -0
  123. metaobjects/meta/presentation/layout/layout_constants.py +13 -0
  124. metaobjects/meta/presentation/layout/meta_layout.py +8 -0
  125. metaobjects/meta/presentation/view/__init__.py +0 -0
  126. metaobjects/meta/presentation/view/meta_view.py +8 -0
  127. metaobjects/meta/presentation/view/view_constants.py +22 -0
  128. metaobjects/meta/template/__init__.py +0 -0
  129. metaobjects/meta/template/meta_template.py +46 -0
  130. metaobjects/meta/template/template_constants.py +112 -0
  131. metaobjects/meta/template/template_provider.py +43 -0
  132. metaobjects/parser.py +380 -0
  133. metaobjects/parser_yaml.py +82 -0
  134. metaobjects/provider.py +111 -0
  135. metaobjects/py.typed +0 -0
  136. metaobjects/registry.py +210 -0
  137. metaobjects/registry_manifest.py +223 -0
  138. metaobjects/render/__init__.py +74 -0
  139. metaobjects/render/email_document.py +14 -0
  140. metaobjects/render/escapers.py +109 -0
  141. metaobjects/render/extract/__init__.py +59 -0
  142. metaobjects/render/extract/coerce.py +279 -0
  143. metaobjects/render/extract/extract.py +211 -0
  144. metaobjects/render/extract/extract_map.py +61 -0
  145. metaobjects/render/extract/json_forgiving_reader.py +203 -0
  146. metaobjects/render/extract/locate.py +65 -0
  147. metaobjects/render/extract/normalize.py +96 -0
  148. metaobjects/render/extract/strip.py +20 -0
  149. metaobjects/render/extract/types.py +332 -0
  150. metaobjects/render/extract/xml_forgiving_reader.py +162 -0
  151. metaobjects/render/filesystem_provider.py +51 -0
  152. metaobjects/render/prompt/__init__.py +32 -0
  153. metaobjects/render/prompt/output_format_renderer.py +340 -0
  154. metaobjects/render/prompt/output_format_spec.py +28 -0
  155. metaobjects/render/prompt/prompt_field.py +29 -0
  156. metaobjects/render/prompt/prompt_overrides.py +29 -0
  157. metaobjects/render/prompt/prompt_style.py +38 -0
  158. metaobjects/render/renderer.py +358 -0
  159. metaobjects/render/verify.py +266 -0
  160. metaobjects/runtime/__init__.py +39 -0
  161. metaobjects/runtime/llm_recorder.py +210 -0
  162. metaobjects/runtime/n2m_resolver.py +155 -0
  163. metaobjects/runtime/object_manager.py +715 -0
  164. metaobjects/runtime/tph.py +50 -0
  165. metaobjects/serializer_json.py +172 -0
  166. metaobjects/shared/__init__.py +0 -0
  167. metaobjects/shared/base_types.py +16 -0
  168. metaobjects/shared/separators.py +4 -0
  169. metaobjects/shared/structural.py +9 -0
  170. metaobjects/source/__init__.py +79 -0
  171. metaobjects/source/error_source.py +266 -0
  172. metaobjects/source/json_path.py +106 -0
  173. metaobjects/source/semantic_diff.py +98 -0
  174. metaobjects/source/yaml_positions.py +174 -0
  175. metaobjects/super_resolve.py +128 -0
  176. metaobjects/yaml_desugar.py +481 -0
  177. metaobjects-0.9.0.dist-info/METADATA +97 -0
  178. metaobjects-0.9.0.dist-info/RECORD +181 -0
  179. metaobjects-0.9.0.dist-info/WHEEL +4 -0
  180. metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
  181. metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
@@ -0,0 +1,220 @@
1
+ """Shared field-kind mapping for the FR-010 codegen emitters (``extract_schema_emitter``
2
+ + ``output_format_spec_emitter``).
3
+
4
+ Maps a metadata field subtype onto the render engine's ``FieldKind`` member, the
5
+ idiomatic ``X | None`` Python type used by the extract mirror dataclass, and the
6
+ ``extract_map.as_*`` accessor that reads it from the forgiving outcome map.
7
+
8
+ Mirrors the C# ``Fr010FieldMapping`` and the Java ``ExtractSchemaEmitter`` ordering.
9
+ Bounded scope (parity with Java / Kotlin / C#): scalar / enum / scalar-array. Nested
10
+ object + array-of-enum are deferred (see ``render/extract/KNOWN_GAPS.md``).
11
+
12
+ Python has a single ``int`` type, so the ``LONG`` and ``CURRENCY`` subtypes map to the
13
+ ``LONG`` ``FieldKind`` (and ``extract_map.as_long`` — identical to ``as_int``), keeping
14
+ the cross-port ``FieldKind`` vocabulary intact even though the emitted accessor is the
15
+ same. The mirror annotation collapses ``INT``/``LONG`` to ``int`` (one numeric int type).
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from collections.abc import Iterable
20
+
21
+ from metaobjects.meta.core.field import field_constants as fc
22
+ from metaobjects.meta.meta_data import MetaData
23
+ from metaobjects.meta.template.template_constants import TEMPLATE_ATTR_XML_TEXT
24
+ from metaobjects.shared.base_types import TYPE_FIELD
25
+ from metaobjects.shared.structural import KEY_IS_ARRAY
26
+
27
+
28
+ def fields(vo: MetaData) -> list[MetaData]:
29
+ """The field children of a payload value-object, in declaration order
30
+ (effective children — own + inherited via ``extends``)."""
31
+ return [c for c in vo.children() if c.type == TYPE_FIELD]
32
+
33
+
34
+ def is_array(field: MetaData) -> bool:
35
+ """Array-ness from either form: the node property (programmatic build) or the
36
+ ``@isArray`` attr (how metadata loads from JSON)."""
37
+ return bool(field.is_array) or field.attr(KEY_IS_ARRAY) is True
38
+
39
+
40
+ def is_required(field: MetaData) -> bool:
41
+ """``@required`` — accepts a bool ``True`` or the string ``"true"``."""
42
+ v = field.attr(fc.FIELD_ATTR_REQUIRED)
43
+ if v is True:
44
+ return True
45
+ return isinstance(v, str) and v.lower() == "true"
46
+
47
+
48
+ def xml_text(field: MetaData) -> bool:
49
+ """``@xmlText`` — the XML text-content extract marker (accepts a bool ``True`` or the
50
+ string ``"true"``). When set, codegen bakes a ``FieldSpec.text_content_field(...)``.
51
+ Mirrors the TS ``xmlText(field)`` helper."""
52
+ v = field.attr(TEMPLATE_ATTR_XML_TEXT)
53
+ if v is True:
54
+ return True
55
+ return isinstance(v, str) and v.lower() == "true"
56
+
57
+
58
+ def enum_values(field: MetaData) -> list[str]:
59
+ """The string members of an enum field's ``@values`` attr (empty when absent)."""
60
+ v = field.attr(fc.FIELD_ATTR_VALUES)
61
+ if isinstance(v, (list, tuple)):
62
+ return [str(x) for x in v]
63
+ return []
64
+
65
+
66
+ def _own_attr_string(node: MetaData, name: str) -> str | None:
67
+ """The own (locally declared) string value of attr *name*, or ``None``."""
68
+ v = node.attr(name)
69
+ return v if isinstance(v, str) else None
70
+
71
+
72
+ def coerce_default(field: MetaData) -> str | None:
73
+ """FR-011: the enum field's own ``@coerceDefault`` member, or ``None``."""
74
+ return _own_attr_string(field, fc.FIELD_ATTR_COERCE_DEFAULT)
75
+
76
+
77
+ def default_value(field: MetaData) -> str | None:
78
+ """FR-011: the enum field's own ``@default`` absent-fill member, or ``None``."""
79
+ return _own_attr_string(field, fc.FIELD_ATTR_DEFAULT)
80
+
81
+
82
+ def resolve_normalize(field: MetaData, owner: MetaData | None) -> str:
83
+ """FR-011: resolve the enum normalization mode — field-level ``@normalize``, else the
84
+ owning ``object.value``'s ``@normalize`` (the per-object default), else the global
85
+ default (``"strip"``). Mirrors the Java/Kotlin/C#/TS ``resolveNormalize``."""
86
+ field_mode = _own_attr_string(field, fc.FIELD_ATTR_NORMALIZE)
87
+ if field_mode is not None:
88
+ return field_mode
89
+ if owner is not None:
90
+ owner_mode = _own_attr_string(owner, fc.FIELD_ATTR_NORMALIZE)
91
+ if owner_mode is not None:
92
+ return owner_mode
93
+ return fc.NORMALIZE_DEFAULT
94
+
95
+
96
+ def scalar_kind(sub_type: str) -> str | None:
97
+ """The render-engine ``FieldKind`` member name for a scalar field subtype, or
98
+ ``None`` if the subtype is non-scalar (enum / object)."""
99
+ if sub_type in (
100
+ fc.FIELD_SUBTYPE_STRING,
101
+ fc.FIELD_SUBTYPE_DATE,
102
+ fc.FIELD_SUBTYPE_TIME,
103
+ fc.FIELD_SUBTYPE_TIMESTAMP,
104
+ ):
105
+ return "STRING"
106
+ if sub_type == fc.FIELD_SUBTYPE_INT:
107
+ return "INT"
108
+ if sub_type in (fc.FIELD_SUBTYPE_LONG, fc.FIELD_SUBTYPE_CURRENCY):
109
+ return "LONG"
110
+ if sub_type in (
111
+ fc.FIELD_SUBTYPE_DOUBLE,
112
+ fc.FIELD_SUBTYPE_FLOAT,
113
+ fc.FIELD_SUBTYPE_DECIMAL,
114
+ ):
115
+ return "DOUBLE"
116
+ if sub_type == fc.FIELD_SUBTYPE_BOOLEAN:
117
+ return "BOOLEAN"
118
+ return None
119
+
120
+
121
+ def mirror_type(field: MetaData) -> str:
122
+ """The ``X | None`` Python annotation for a field in the extract mirror dataclass.
123
+
124
+ A extracted array can contain null elements where individual items were lost, so
125
+ the array type is ``list[str | None] | None`` (matches ``extract_map.as_string_list``).
126
+ """
127
+ # Nested object (single OR array): the self-contained path defers to a typed null;
128
+ # the runtime-delegating path overrides this with the nested mirror type. Checked
129
+ # BEFORE the generic is_array branch so an array-of-objects is NOT mistyped as a
130
+ # string array. Mirrors the Java/TS fix.
131
+ if field.sub_type == fc.FIELD_SUBTYPE_OBJECT:
132
+ return "object | None" # nested deferred (self-contained path)
133
+ if is_array(field):
134
+ return "list[str | None] | None"
135
+ if field.sub_type == fc.FIELD_SUBTYPE_ENUM:
136
+ return "str | None" # enum is string-backed
137
+ kind = scalar_kind(field.sub_type)
138
+ if kind in ("INT", "LONG"):
139
+ return "int | None"
140
+ if kind == "DOUBLE":
141
+ return "float | None"
142
+ if kind == "BOOLEAN":
143
+ return "bool | None"
144
+ return "str | None"
145
+
146
+
147
+ def extract_map_helper(field: MetaData) -> str | None:
148
+ """The ``extract_map`` accessor a field reads through, or ``None``
149
+ (object/deferred fields read no helper).
150
+
151
+ Single source of the field → accessor decision; ``extract_map_call`` builds
152
+ the ``as_*(d, "name")`` call on top of this."""
153
+ # Nested object (single OR array) → no helper in the self-contained path. Checked
154
+ # BEFORE is_array so an array-of-objects is NOT read via as_string_list. Mirrors the
155
+ # Java/TS fix.
156
+ if field.sub_type == fc.FIELD_SUBTYPE_OBJECT:
157
+ return None
158
+ if is_array(field):
159
+ return "as_string_list"
160
+ if field.sub_type == fc.FIELD_SUBTYPE_ENUM:
161
+ return "as_string"
162
+ return {
163
+ "INT": "as_int",
164
+ "LONG": "as_long",
165
+ "DOUBLE": "as_double",
166
+ "BOOLEAN": "as_bool",
167
+ }.get(scalar_kind(field.sub_type) or "", "as_string")
168
+
169
+
170
+ def extract_map_call(field: MetaData) -> str:
171
+ """The ``as_*(d, "name")`` call that reads this field from the forgiving map ``d``.
172
+
173
+ The helpers are imported by name into the generated module (see
174
+ ``extract_schema_emitter.extract_map_imports``)."""
175
+ helper = extract_map_helper(field)
176
+ if helper is None:
177
+ # Nested object: the self-contained initializer leaves it None (the
178
+ # runtime-delegating path populates it). Bare ``None`` — no trailing inline
179
+ # comment: this expr is joined into a ``Name(field=..., …)`` call-args list.
180
+ return "None"
181
+ return f'{helper}(d, "{field.name}")'
182
+
183
+
184
+ def py_string_literal(value: str) -> str:
185
+ """A safe Python double-quoted string literal for embedding free text
186
+ (``@example`` / ``@instruction`` / ``@enumDoc`` values) into emitted source.
187
+
188
+ Uses ``json.dumps`` — a JSON string is a valid Python string literal and escapes
189
+ backslashes / quotes / control chars correctly (NOT naive quoting)."""
190
+ import json
191
+
192
+ return json.dumps(value, ensure_ascii=False)
193
+
194
+
195
+ def string_list_literal(values: Iterable[str]) -> str:
196
+ """A Python ``["a", "b"]`` list literal for enum members."""
197
+ return "[" + ", ".join(py_string_literal(v) for v in values) + "]"
198
+
199
+
200
+ def properties_map_literal(attr: object) -> str:
201
+ """A Python ``{"k": "v", …}`` dict literal for a properties-shaped attr
202
+ (``@enumAlias`` / ``@enumDoc``), or ``"None"`` when absent/empty.
203
+
204
+ Null values are dropped; keys are sorted to match the canonical-serializer
205
+ properties sort (deterministic output)."""
206
+ if not isinstance(attr, dict):
207
+ return "None"
208
+ entries = [
209
+ (k, v)
210
+ for k, v in attr.items()
211
+ if v is not None
212
+ ]
213
+ if not entries:
214
+ return "None"
215
+ entries.sort(key=lambda kv: str(kv[0]))
216
+ parts = [
217
+ f"{py_string_literal(str(k))}: {py_string_literal(str(v))}"
218
+ for k, v in entries
219
+ ]
220
+ return "{" + ", ".join(parts) + "}"
@@ -0,0 +1,62 @@
1
+ """Codegen plugin engine — Generator protocol + per-entity/once-per-run helpers.
2
+ Mirrors server/typescript/packages/codegen-ts/src/generator.ts."""
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Callable, Protocol
7
+
8
+ from metaobjects.meta.core.object.meta_object import MetaObject
9
+ from metaobjects.meta.meta_data import MetaData
10
+ from .config import GenConfig
11
+
12
+
13
+ @dataclass
14
+ class EmittedFile:
15
+ path: str # relative to GenConfig.out_dir
16
+ content: str # final, formatted Python source
17
+ generated_by: str = "" # set by the runner from Generator.name
18
+
19
+
20
+ @dataclass
21
+ class GenContext:
22
+ entities: list[MetaObject]
23
+ loaded_root: MetaData | None
24
+ matches: Callable[[MetaObject], bool]
25
+ config: GenConfig
26
+ warn: Callable[[str], None]
27
+
28
+
29
+ class Generator(Protocol):
30
+ name: str
31
+
32
+ def generate(self, ctx: GenContext) -> list[EmittedFile]: ...
33
+
34
+
35
+ def per_entity(
36
+ fn: Callable[[MetaObject, GenContext], "EmittedFile | list[EmittedFile]"],
37
+ ) -> Callable[[GenContext], list[EmittedFile]]:
38
+ """One-file-per-entity convenience; selects via ctx.matches."""
39
+
40
+ def run(ctx: GenContext) -> list[EmittedFile]:
41
+ out: list[EmittedFile] = []
42
+ for e in ctx.entities:
43
+ if not ctx.matches(e):
44
+ continue
45
+ r = fn(e, ctx)
46
+ out.extend(r if isinstance(r, list) else [r])
47
+ return out
48
+
49
+ return run
50
+
51
+
52
+ def once_per_run(
53
+ fn: Callable[[list[MetaObject], GenContext], "EmittedFile | list[EmittedFile]"],
54
+ ) -> Callable[[GenContext], list[EmittedFile]]:
55
+ """Called once with all matching entities (barrels / cross-entity files)."""
56
+
57
+ def run(ctx: GenContext) -> list[EmittedFile]:
58
+ matched = [e for e in ctx.entities if ctx.matches(e)]
59
+ r = fn(matched, ctx)
60
+ return r if isinstance(r, list) else [r]
61
+
62
+ return run
@@ -0,0 +1,163 @@
1
+ """ADR-0021 D3 — stable-name generator registry (Python port).
2
+
3
+ Generators are identified by a STABLE string id (e.g. ``entity``, ``routes``,
4
+ ``render-helper``) rather than by a language-specific factory import. The id is
5
+ the cross-port contract: the same logical generator carries the same stable name
6
+ in every port. This module is the discoverability + identity surface behind
7
+ ``metaobjects gen --list`` and the ``--generators a,b`` selection path.
8
+
9
+ It is ADDITIVE. The default suite in ``cli.py`` (``_default_generators``) and the
10
+ ``run_gen(..., generators=[...])`` factory-array path keep working unchanged — the
11
+ registry powers ``--list`` and stable identity; it does not replace those paths.
12
+
13
+ The registry's name set is conformance-tested for SET EQUALITY against the Python
14
+ slice of the canonical manifest
15
+ (``fixtures/generator-registry-conformance/registry.json``): exactly the manifest
16
+ entries whose ``ports`` array includes ``python``. All Python entries are
17
+ ``tier: native``.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass
22
+ from typing import Callable
23
+
24
+ from metaobjects.codegen.generator import Generator
25
+ from metaobjects.codegen.generators.entity_model import entity_model
26
+ from metaobjects.codegen.generators.extractor_generator import extractor_generator
27
+ from metaobjects.codegen.generators.filter_allowlist_generator import (
28
+ filter_allowlist_generator,
29
+ )
30
+ from metaobjects.codegen.generators.output_parser_generator import (
31
+ output_parser_generator,
32
+ )
33
+ from metaobjects.codegen.generators.output_prompt_generator import (
34
+ output_prompt_generator,
35
+ )
36
+ from metaobjects.codegen.generators.payload_vo_generator import payload_vo_generator
37
+ from metaobjects.codegen.generators.render_helper_generator import (
38
+ render_helper_generator,
39
+ )
40
+ from metaobjects.codegen.generators.router_generator import router_generator
41
+ from metaobjects.codegen.generators.template_generator import template_generator
42
+ from metaobjects.codegen.generators.trace_helper_generator import trace_helper_generator
43
+ from metaobjects.render.verify import InMemoryProvider
44
+
45
+ GeneratorTier = str # "native" | "neutral"
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class GeneratorEntry:
50
+ """A registry entry: stable name + one-line description + tier + factory."""
51
+
52
+ #: Stable, cross-port-consistent id. Equals the registry map key.
53
+ name: str
54
+ #: One-line (no newline) human description for ``--list``.
55
+ description: str
56
+ #: "native" = recommended ``metaobjects gen`` suite; "neutral" = ``meta docs``-owned.
57
+ tier: GeneratorTier
58
+ #: Constructs the generator with sensible defaults. Calling it must not throw.
59
+ factory: Callable[[], Generator]
60
+
61
+
62
+ def _template_primitive() -> Generator:
63
+ """A no-op default for the ``template`` PRIMITIVE generator.
64
+
65
+ ``template_generator`` requires caller-supplied ``template`` / ``walk`` /
66
+ ``provider`` (it is not a zero-config per-entity emitter). For registry
67
+ identity + ``--list`` we expose a default that constructs a valid Generator
68
+ without throwing and walks to zero outputs; real use passes opts via the
69
+ factory-array config path. Mirrors the TS ``templatePrimitive()``.
70
+ """
71
+ return template_generator(
72
+ name="template",
73
+ template="",
74
+ walk=lambda _root: [],
75
+ provider=InMemoryProvider(),
76
+ )
77
+
78
+
79
+ def _render_helper_default() -> Generator:
80
+ """Construct ``render-helper`` with a default ``template_root``.
81
+
82
+ The factory ctor only requires ``template_root`` to be non-empty (it builds a
83
+ ``FilesystemProvider`` lazily; no disk access at construction). Real use passes
84
+ the caller's on-disk template root via the factory-array config path; this
85
+ default exists only so registry identity + ``--list`` construct without throwing.
86
+ """
87
+ return render_helper_generator(template_root="templates")
88
+
89
+
90
+ #: Stable name -> GeneratorEntry. The 10 native generators whose manifest `ports`
91
+ #: include `python` (ADR-0021 D3). Set-equality conformance-tested vs the manifest.
92
+ GENERATOR_REGISTRY: dict[str, GeneratorEntry] = {
93
+ "entity": GeneratorEntry(
94
+ name="entity",
95
+ description="Per-entity model/class — the entity module (table-backed or value object).",
96
+ tier="native",
97
+ factory=entity_model,
98
+ ),
99
+ "routes": GeneratorEntry(
100
+ name="routes",
101
+ description="Per-entity REST endpoint surface (controllers / routes / router).",
102
+ tier="native",
103
+ factory=router_generator,
104
+ ),
105
+ "output-parser": GeneratorEntry(
106
+ name="output-parser",
107
+ description="Per-template tolerant output parser (recover-on-receipt).",
108
+ tier="native",
109
+ factory=output_parser_generator,
110
+ ),
111
+ "output-prompt": GeneratorEntry(
112
+ name="output-prompt",
113
+ description="Per-template output-format prompt fragment generator.",
114
+ tier="native",
115
+ factory=output_prompt_generator,
116
+ ),
117
+ "render-helper": GeneratorEntry(
118
+ name="render-helper",
119
+ description="Per-template.output render helper (document/email typed wrappers).",
120
+ tier="native",
121
+ factory=_render_helper_default,
122
+ ),
123
+ "extractor": GeneratorEntry(
124
+ name="extractor",
125
+ description="Per-template strict typed extract<Name> helper (strict payload extraction).",
126
+ tier="native",
127
+ factory=extractor_generator,
128
+ ),
129
+ "template": GeneratorEntry(
130
+ name="template",
131
+ description="Generic Mustache template primitive (walk + template -> files).",
132
+ tier="native",
133
+ factory=_template_primitive,
134
+ ),
135
+ "filter-allowlist": GeneratorEntry(
136
+ name="filter-allowlist",
137
+ description="Per-entity REST filter allowlist (queryable-field guard).",
138
+ tier="native",
139
+ factory=filter_allowlist_generator,
140
+ ),
141
+ "payload": GeneratorEntry(
142
+ name="payload",
143
+ description="Per-template payload value object (the strict payload type).",
144
+ tier="native",
145
+ factory=payload_vo_generator,
146
+ ),
147
+ "trace-helper": GeneratorEntry(
148
+ name="trace-helper",
149
+ description="Per-entity typed record<Entity> LLM-trace helper (extract + buildLlmCallRow + persist).",
150
+ tier="native",
151
+ factory=trace_helper_generator,
152
+ ),
153
+ }
154
+
155
+
156
+ def list_generators() -> list[GeneratorEntry]:
157
+ """All registry entries, sorted by stable name."""
158
+ return sorted(GENERATOR_REGISTRY.values(), key=lambda e: e.name)
159
+
160
+
161
+ def get_generator(name: str) -> GeneratorEntry | None:
162
+ """Resolve a generator entry by its stable id, or ``None`` if unknown."""
163
+ return GENERATOR_REGISTRY.get(name)
File without changes