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.
- metaobjects/__init__.py +75 -0
- metaobjects/agent_context/__init__.py +55 -0
- metaobjects/agent_context/_content/README.md +14 -0
- metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
- metaobjects/agent_context/_content/servers/java.meta.json +5 -0
- metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
- metaobjects/agent_context/_content/servers/python.meta.json +5 -0
- metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
- metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
- metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
- metaobjects/agent_context/assemble.py +133 -0
- metaobjects/agent_context/content_root.py +54 -0
- metaobjects/agent_context/scaffold.py +191 -0
- metaobjects/agent_context/types.py +44 -0
- metaobjects/attr_class_map.py +23 -0
- metaobjects/cli.py +696 -0
- metaobjects/codegen/__init__.py +0 -0
- metaobjects/codegen/config.py +11 -0
- metaobjects/codegen/constants.py +13 -0
- metaobjects/codegen/extract_delegate_emitter.py +384 -0
- metaobjects/codegen/extract_schema_emitter.py +139 -0
- metaobjects/codegen/format.py +31 -0
- metaobjects/codegen/fr010_field_mapping.py +220 -0
- metaobjects/codegen/generator.py +62 -0
- metaobjects/codegen/generator_registry.py +163 -0
- metaobjects/codegen/generators/__init__.py +0 -0
- metaobjects/codegen/generators/entity_model.py +263 -0
- metaobjects/codegen/generators/extractor_generator.py +317 -0
- metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
- metaobjects/codegen/generators/m2m_codegen.py +192 -0
- metaobjects/codegen/generators/output_parser_generator.py +272 -0
- metaobjects/codegen/generators/output_prompt_generator.py +192 -0
- metaobjects/codegen/generators/payload_vo_generator.py +672 -0
- metaobjects/codegen/generators/render_helper_generator.py +451 -0
- metaobjects/codegen/generators/router_generator.py +635 -0
- metaobjects/codegen/generators/template_generator.py +70 -0
- metaobjects/codegen/generators/tph_plan.py +120 -0
- metaobjects/codegen/generators/trace_helper_generator.py +336 -0
- metaobjects/codegen/instance_artifacts.py +15 -0
- metaobjects/codegen/output_format_spec_emitter.py +79 -0
- metaobjects/codegen/overwrite_policy.py +27 -0
- metaobjects/codegen/runner.py +110 -0
- metaobjects/codegen/runtime/__init__.py +6 -0
- metaobjects/codegen/runtime/filter_parser.py +193 -0
- metaobjects/codegen/type_map.py +84 -0
- metaobjects/core_types.py +809 -0
- metaobjects/datatype.py +19 -0
- metaobjects/documentation/__init__.py +28 -0
- metaobjects/documentation/doc_constants.py +20 -0
- metaobjects/documentation/doc_provider.py +20 -0
- metaobjects/documentation/doc_schema.py +24 -0
- metaobjects/errors.py +124 -0
- metaobjects/loader/__init__.py +0 -0
- metaobjects/loader/merge.py +287 -0
- metaobjects/loader/meta_data_loader.py +245 -0
- metaobjects/loader/sources/__init__.py +24 -0
- metaobjects/loader/sources/directory_source.py +50 -0
- metaobjects/loader/sources/file_source.py +41 -0
- metaobjects/loader/sources/meta_data_source.py +67 -0
- metaobjects/loader/sources/uri_source.py +56 -0
- metaobjects/loader/validate_discriminator.py +181 -0
- metaobjects/loader/validate_field_readonly.py +146 -0
- metaobjects/loader/validate_source_parameter_ref.py +159 -0
- metaobjects/loader/validate_source_physical_names.py +140 -0
- metaobjects/loader/validation_passes.py +1513 -0
- metaobjects/meta/__init__.py +1 -0
- metaobjects/meta/core/__init__.py +0 -0
- metaobjects/meta/core/attr/__init__.py +0 -0
- metaobjects/meta/core/attr/attr_constants.py +31 -0
- metaobjects/meta/core/attr/meta_attr.py +136 -0
- metaobjects/meta/core/field/__init__.py +0 -0
- metaobjects/meta/core/field/field_constants.py +105 -0
- metaobjects/meta/core/field/meta_field.py +76 -0
- metaobjects/meta/core/identity/__init__.py +0 -0
- metaobjects/meta/core/identity/identity_constants.py +19 -0
- metaobjects/meta/core/identity/meta_identity.py +8 -0
- metaobjects/meta/core/object/__init__.py +0 -0
- metaobjects/meta/core/object/meta_object.py +65 -0
- metaobjects/meta/core/object/meta_object_aware.py +43 -0
- metaobjects/meta/core/object/object_class_registry.py +56 -0
- metaobjects/meta/core/object/object_constants.py +13 -0
- metaobjects/meta/core/object/object_extract.py +400 -0
- metaobjects/meta/core/object/value_object.py +70 -0
- metaobjects/meta/core/relationship/__init__.py +0 -0
- metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
- metaobjects/meta/core/relationship/meta_relationship.py +54 -0
- metaobjects/meta/core/relationship/relationship_constants.py +51 -0
- metaobjects/meta/core/validator/__init__.py +0 -0
- metaobjects/meta/core/validator/validator_constants.py +18 -0
- metaobjects/meta/meta_data.py +206 -0
- metaobjects/meta/meta_root.py +8 -0
- metaobjects/meta/persistence/__init__.py +0 -0
- metaobjects/meta/persistence/db/__init__.py +1 -0
- metaobjects/meta/persistence/db/db_constants.py +41 -0
- metaobjects/meta/persistence/db/db_provider.py +60 -0
- metaobjects/meta/persistence/origin/__init__.py +0 -0
- metaobjects/meta/persistence/origin/meta_origin.py +8 -0
- metaobjects/meta/persistence/origin/origin_constants.py +20 -0
- metaobjects/meta/persistence/source/__init__.py +0 -0
- metaobjects/meta/persistence/source/meta_source.py +137 -0
- metaobjects/meta/persistence/source/source_constants.py +115 -0
- metaobjects/meta/presentation/__init__.py +0 -0
- metaobjects/meta/presentation/layout/__init__.py +0 -0
- metaobjects/meta/presentation/layout/layout_constants.py +13 -0
- metaobjects/meta/presentation/layout/meta_layout.py +8 -0
- metaobjects/meta/presentation/view/__init__.py +0 -0
- metaobjects/meta/presentation/view/meta_view.py +8 -0
- metaobjects/meta/presentation/view/view_constants.py +22 -0
- metaobjects/meta/template/__init__.py +0 -0
- metaobjects/meta/template/meta_template.py +46 -0
- metaobjects/meta/template/template_constants.py +112 -0
- metaobjects/meta/template/template_provider.py +43 -0
- metaobjects/parser.py +380 -0
- metaobjects/parser_yaml.py +82 -0
- metaobjects/provider.py +111 -0
- metaobjects/py.typed +0 -0
- metaobjects/registry.py +210 -0
- metaobjects/registry_manifest.py +223 -0
- metaobjects/render/__init__.py +74 -0
- metaobjects/render/email_document.py +14 -0
- metaobjects/render/escapers.py +109 -0
- metaobjects/render/extract/__init__.py +59 -0
- metaobjects/render/extract/coerce.py +279 -0
- metaobjects/render/extract/extract.py +211 -0
- metaobjects/render/extract/extract_map.py +61 -0
- metaobjects/render/extract/json_forgiving_reader.py +203 -0
- metaobjects/render/extract/locate.py +65 -0
- metaobjects/render/extract/normalize.py +96 -0
- metaobjects/render/extract/strip.py +20 -0
- metaobjects/render/extract/types.py +332 -0
- metaobjects/render/extract/xml_forgiving_reader.py +162 -0
- metaobjects/render/filesystem_provider.py +51 -0
- metaobjects/render/prompt/__init__.py +32 -0
- metaobjects/render/prompt/output_format_renderer.py +340 -0
- metaobjects/render/prompt/output_format_spec.py +28 -0
- metaobjects/render/prompt/prompt_field.py +29 -0
- metaobjects/render/prompt/prompt_overrides.py +29 -0
- metaobjects/render/prompt/prompt_style.py +38 -0
- metaobjects/render/renderer.py +358 -0
- metaobjects/render/verify.py +266 -0
- metaobjects/runtime/__init__.py +39 -0
- metaobjects/runtime/llm_recorder.py +210 -0
- metaobjects/runtime/n2m_resolver.py +155 -0
- metaobjects/runtime/object_manager.py +715 -0
- metaobjects/runtime/tph.py +50 -0
- metaobjects/serializer_json.py +172 -0
- metaobjects/shared/__init__.py +0 -0
- metaobjects/shared/base_types.py +16 -0
- metaobjects/shared/separators.py +4 -0
- metaobjects/shared/structural.py +9 -0
- metaobjects/source/__init__.py +79 -0
- metaobjects/source/error_source.py +266 -0
- metaobjects/source/json_path.py +106 -0
- metaobjects/source/semantic_diff.py +98 -0
- metaobjects/source/yaml_positions.py +174 -0
- metaobjects/super_resolve.py +128 -0
- metaobjects/yaml_desugar.py +481 -0
- metaobjects-0.9.0.dist-info/METADATA +97 -0
- metaobjects-0.9.0.dist-info/RECORD +181 -0
- metaobjects-0.9.0.dist-info/WHEEL +4 -0
- metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
- 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
|