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,54 @@
1
+ """MetaRelationship — relationship.association / aggregation / composition node."""
2
+ from __future__ import annotations
3
+
4
+ from ...meta_data import MetaData
5
+ from .relationship_constants import (
6
+ RELATIONSHIP_ATTR_CARDINALITY,
7
+ RELATIONSHIP_ATTR_OBJECT_REF,
8
+ RELATIONSHIP_ATTR_ON_DELETE,
9
+ RELATIONSHIP_ATTR_ON_UPDATE,
10
+ RELATIONSHIP_ATTR_SOURCE_REF_FIELD,
11
+ RELATIONSHIP_ATTR_SYMMETRIC,
12
+ RELATIONSHIP_ATTR_THROUGH,
13
+ )
14
+
15
+
16
+ class MetaRelationship(MetaData):
17
+ """A relationship.* node. Accessors mirror the TS reference / Java port —
18
+ own-attr reads; defaults for @onDelete/@onUpdate are resolved at consumption
19
+ time (codegen / migration) not here."""
20
+
21
+ def cardinality(self) -> str | None:
22
+ v = self.attr(RELATIONSHIP_ATTR_CARDINALITY)
23
+ return v if isinstance(v, str) else None
24
+
25
+ def object_ref(self) -> str | None:
26
+ """FQN of the target object (e.g., ``"acme::vehicle::Car"``)."""
27
+ v = self.attr(RELATIONSHIP_ATTR_OBJECT_REF)
28
+ return v if isinstance(v, str) else None
29
+
30
+ def through(self) -> str | None:
31
+ """Junction (through) entity name for M:N relationships."""
32
+ v = self.attr(RELATIONSHIP_ATTR_THROUGH)
33
+ return v if isinstance(v, str) else None
34
+
35
+ def source_ref_field(self) -> str | None:
36
+ """Source-side FK field on the junction (directed self-join disambiguator)."""
37
+ v = self.attr(RELATIONSHIP_ATTR_SOURCE_REF_FIELD)
38
+ return v if isinstance(v, str) else None
39
+
40
+ def symmetric(self) -> bool:
41
+ """Whether this M:N relationship is an undirected (symmetric) self-join."""
42
+ return self.attr(RELATIONSHIP_ATTR_SYMMETRIC) is True
43
+
44
+ def on_delete(self) -> str | None:
45
+ """Referential action on parent delete. ``None`` when not explicitly set
46
+ (default derives from subtype at consumption time)."""
47
+ v = self.attr(RELATIONSHIP_ATTR_ON_DELETE)
48
+ return v if isinstance(v, str) and v else None
49
+
50
+ def on_update(self) -> str | None:
51
+ """Referential action on key update. ``None`` when not explicitly set
52
+ (default: cascade at consumption time)."""
53
+ v = self.attr(RELATIONSHIP_ATTR_ON_UPDATE)
54
+ return v if isinstance(v, str) and v else None
@@ -0,0 +1,51 @@
1
+ """Relationship subtype vocabulary (colocated)."""
2
+ from ....shared.base_types import SUBTYPE_BASE
3
+
4
+ RELATIONSHIP_SUBTYPE_ASSOCIATION = "association"
5
+ RELATIONSHIP_SUBTYPE_AGGREGATION = "aggregation"
6
+ RELATIONSHIP_SUBTYPE_COMPOSITION = "composition"
7
+
8
+ RELATIONSHIP_SUBTYPES = (
9
+ SUBTYPE_BASE,
10
+ RELATIONSHIP_SUBTYPE_ASSOCIATION,
11
+ RELATIONSHIP_SUBTYPE_AGGREGATION,
12
+ RELATIONSHIP_SUBTYPE_COMPOSITION,
13
+ )
14
+
15
+ RELATIONSHIP_ATTR_OBJECT_REF = "objectRef"
16
+ RELATIONSHIP_ATTR_CARDINALITY = "cardinality"
17
+ # M:N junction (through) entity — a third entity declaring two identity.reference
18
+ # children, one per FK side. The relationship's FK fields are DERIVED from those
19
+ # references (the identity.reference SSOT for FK direction), never restated.
20
+ RELATIONSHIP_ATTR_THROUGH = "through"
21
+ # M:N directed-self-join disambiguator — names the source-side FK field on the
22
+ # junction (the other reference is the target side). Mutually exclusive with
23
+ # @symmetric.
24
+ RELATIONSHIP_ATTR_SOURCE_REF_FIELD = "sourceRefField"
25
+ # M:N undirected-self-join flag — union-on-read; valid only when @objectRef ==
26
+ # the declaring entity. Mutually exclusive with @sourceRefField.
27
+ RELATIONSHIP_ATTR_SYMMETRIC = "symmetric"
28
+
29
+ # Cardinality values (for @cardinality). Open string at the metamodel level; the
30
+ # CARDINALITY_MANY constant gates M:N validation/derivation.
31
+ CARDINALITY_ONE = "one"
32
+ CARDINALITY_MANY = "many"
33
+
34
+ # --- Referential action attrs (@onDelete / @onUpdate) ----------------------
35
+
36
+ RELATIONSHIP_ATTR_ON_DELETE = "onDelete"
37
+ RELATIONSHIP_ATTR_ON_UPDATE = "onUpdate"
38
+
39
+ # Canonical cross-language referential-action set (kebab-case, no "setDefault").
40
+ # MUST equal TS migrate-ts FkAction; mirrors Java REFERENTIAL_ACTIONS.
41
+ REFERENTIAL_ACTIONS = ("cascade", "set-null", "restrict", "no-action")
42
+
43
+ # Default @onDelete per relationship subtype (rollout-decided defaults).
44
+ ON_DELETE_DEFAULT_BY_SUBTYPE: dict[str, str] = {
45
+ RELATIONSHIP_SUBTYPE_COMPOSITION: "cascade",
46
+ RELATIONSHIP_SUBTYPE_AGGREGATION: "set-null",
47
+ RELATIONSHIP_SUBTYPE_ASSOCIATION: "restrict",
48
+ }
49
+
50
+ # Default @onUpdate (subtype-independent).
51
+ ON_UPDATE_DEFAULT = "cascade"
File without changes
@@ -0,0 +1,18 @@
1
+ """Validator subtype + attr vocabulary (colocated).
2
+
3
+ Mirrors server/typescript/packages/metadata/src/core/validator/validator-constants.ts.
4
+ These are the cross-port-identical names for the ``validator.*`` type family and the
5
+ attrs codegen reads when emitting input-validation constraints.
6
+ """
7
+
8
+ # Validator subtypes (peer of TS VALIDATOR_SUBTYPES).
9
+ VALIDATOR_SUBTYPE_REQUIRED = "required"
10
+ VALIDATOR_SUBTYPE_LENGTH = "length"
11
+ VALIDATOR_SUBTYPE_REGEX = "regex"
12
+ VALIDATOR_SUBTYPE_NUMERIC = "numeric"
13
+ VALIDATOR_SUBTYPE_ARRAY = "array"
14
+
15
+ # Validator attr keys (read by codegen when lowering validator children).
16
+ VALIDATOR_ATTR_MIN = "min"
17
+ VALIDATOR_ATTR_MAX = "max"
18
+ VALIDATOR_ATTR_PATTERN = "pattern"
@@ -0,0 +1,206 @@
1
+ """Abstract node base — Python port of the typed-tree MetaData (ADR-0002)."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Callable, Optional, TypeVar, cast
5
+
6
+ from ..attr_class_map import attr_class_for
7
+ from ..shared.base_types import SUBTYPE_BASE, TYPE_ATTR
8
+ from ..shared.separators import PACKAGE_SEP
9
+ from ..source import CodeSource, ErrorSource
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ class MetaData:
15
+ def __init__(self, type_: str, sub_type: str, name: str) -> None:
16
+ self.type = type_
17
+ self.sub_type = sub_type
18
+ self.name = name
19
+ self.package: Optional[str] = None
20
+ # The file-default package captured at PARSE time (the package declared
21
+ # on the owning file's root). Distinct from ``package`` (which an object
22
+ # node leaves unset — the parser does not fold the file-default package
23
+ # onto an object's own package). Used by super-resolution to match a
24
+ # cross-package fully-qualified ``extends`` over the MERGED tree, where
25
+ # per-file root packages are no longer reachable via the parent chain.
26
+ # Mirrors TS ``MetaData.fileDefaultPackage`` / ``resolutionKey()``.
27
+ self.file_default_package: Optional[str] = None
28
+ self.super_ref: Optional[str] = None
29
+ self.super_data: Optional[MetaData] = None
30
+ self.is_abstract = False
31
+ self.is_overlay = False
32
+ self.is_array = False
33
+ self.parent: Optional[MetaData] = None
34
+ self._attr_nodes: dict[str, MetaData] = {} # name -> MetaAttr instance
35
+ self._children: list[MetaData] = []
36
+ self._cache: dict[str, object] = {}
37
+ self._frozen = False
38
+ # FR5a / ADR-0009 — provenance envelope. Always populated; defaults
39
+ # to CodeSource for programmatic / test construction. Loader phases
40
+ # (parser, merge) overwrite via set_source during the tree walk.
41
+ self._source: ErrorSource = CodeSource.DEFAULT
42
+
43
+ @property
44
+ def frozen(self) -> bool:
45
+ return self._frozen
46
+
47
+ @property
48
+ def source(self) -> ErrorSource:
49
+ """FR5a / ADR-0009 — provenance envelope for this node. Always populated."""
50
+ return self._source
51
+
52
+ def set_source(self, src: ErrorSource) -> None:
53
+ """Loader-internal: assign provenance. Honors the frozen-guard.
54
+
55
+ Called by parser / merge phases as they build the tree. Programmatic
56
+ callers (tests, plugins) may pass any envelope explicitly.
57
+ """
58
+ self._require_mutable()
59
+ self._source = src
60
+
61
+ def fqn(self) -> str:
62
+ """Own FQN: ``package::name`` if own package is set, else just ``name``."""
63
+ return self.name if not self.package else f"{self.package}{PACKAGE_SEP}{self.name}"
64
+
65
+ def resolution_key(self) -> str:
66
+ """Package-folded FQN: ``<pkg>::<name>`` where *pkg* is, in order:
67
+
68
+ 1. this node's own ``package`` (when declared), else
69
+ 2. its ``file_default_package`` (the file's root package captured at
70
+ PARSE time), else
71
+ 3. the nearest ancestor's ``package`` (a programmatic fallback for
72
+ hand-built trees / plugins).
73
+
74
+ Returns the bare name when none is available.
75
+
76
+ This is the Python equivalent of the C#/TS ``ResolutionKey`` and is
77
+ consistent with the #37 super-resolution fix (``node.package or
78
+ node.file_default_package or ctx_pkg``). It is the package-folded form a
79
+ nested field's ``@objectRef`` uses AND the key the runtime object model
80
+ binds on (``ObjectClassRegistry`` / ``MetaObject.new_instance``). Distinct
81
+ from ``fqn()``, which stays bare for objects (the parser does not fold the
82
+ file-default package onto an object's own ``package``).
83
+
84
+ The ``file_default_package`` step is load-bearing: after a multi-file
85
+ MERGE an object node carries no own ``package`` and its parent is the
86
+ single merged root (also package-less), so the ancestor walk alone would
87
+ fall through to the bare name and bind the WRONG (package-less) registry
88
+ key. Folding the file-default package keeps the binding correct
89
+ regardless of merge order.
90
+ """
91
+ pkg = self.package or self.file_default_package
92
+ if not pkg:
93
+ node = self.parent
94
+ while node is not None:
95
+ if node.package:
96
+ pkg = node.package
97
+ break
98
+ node = node.parent
99
+ if pkg:
100
+ return f"{pkg}{PACKAGE_SEP}{self.name}"
101
+ return self.name
102
+
103
+ def _require_mutable(self) -> None:
104
+ if self._frozen:
105
+ raise RuntimeError(f"Cannot mutate frozen MetaData {self.fqn()}")
106
+
107
+ def set_attr(self, name: str, value: object, sub_type: str | None = None) -> None:
108
+ self._require_mutable()
109
+ resolved_sub = sub_type if sub_type is not None else SUBTYPE_BASE
110
+ ctor = attr_class_for(resolved_sub)
111
+ # attr_class_for always returns a MetaData subclass at runtime; cast for type safety.
112
+ attr = cast("MetaData", ctor(TYPE_ATTR, resolved_sub, name))
113
+ attr.parent = self
114
+ attr.set_value(value) # type: ignore[attr-defined]
115
+ self._attr_nodes[name] = attr
116
+
117
+ def own_meta_attr(self, name: str) -> Optional[MetaData]:
118
+ return self._attr_nodes.get(name)
119
+
120
+ def own_meta_attrs(self) -> list[MetaData]:
121
+ return list(self._attr_nodes.values())
122
+
123
+ def attr(self, name: str) -> object:
124
+ """Own attr value for *name*, or ``None``."""
125
+ node = self._attr_nodes.get(name)
126
+ return getattr(node, "value", None) if node is not None else None
127
+
128
+ def own_attrs(self) -> dict[str, object]:
129
+ """Own attr value map — excludes inherited attrs."""
130
+ return {
131
+ name: getattr(node, "value", None)
132
+ for name, node in self._attr_nodes.items()
133
+ if getattr(node, "value", None) is not None
134
+ }
135
+
136
+ def attrs(self) -> dict[str, object]:
137
+ """Effective attr value map: own + inherited via super chain (own wins on key conflict).
138
+
139
+ Cycle-guarded and cached.
140
+ """
141
+ return self._cached("attrs", lambda: self._effective_attrs_inner({self}))
142
+
143
+ def _effective_attrs_inner(self, visited: "set[MetaData]") -> dict[str, object]:
144
+ """Compute effective attrs with cycle detection via *visited* set."""
145
+ if self.super_data is None or self.super_data in visited:
146
+ return self.own_attrs()
147
+ visited.add(self.super_data)
148
+ result = self.super_data._effective_attrs_inner(visited)
149
+ # own attrs override super
150
+ result.update(self.own_attrs())
151
+ return result
152
+
153
+ def add_child(self, child: "MetaData") -> None:
154
+ self._require_mutable()
155
+ child.parent = self
156
+ self._children.append(child)
157
+
158
+ def own_children(self) -> list["MetaData"]:
159
+ """Own (locally declared) children — excludes children inherited via extends."""
160
+ return list(self._children)
161
+
162
+ def children(self) -> list["MetaData"]:
163
+ """Effective children: own + inherited via the super chain, own shadowing super on (type, name).
164
+
165
+ Cycle-guarded and cached (after freeze).
166
+ """
167
+ return self._cached("children", lambda: self._effective_children_inner({self}))
168
+
169
+ def _effective_children_inner(
170
+ self, visited: "set[MetaData]"
171
+ ) -> list["MetaData"]:
172
+ """Compute effective children with cycle detection via *visited* set."""
173
+ if self.super_data is None or self.super_data in visited:
174
+ # No super, or super already on the current path — skip to avoid cycle.
175
+ result: list[MetaData] = list(self._children)
176
+ else:
177
+ visited.add(self.super_data)
178
+ result = self.super_data._effective_children_inner(visited)
179
+ for own in self._children:
180
+ idx = next(
181
+ (i for i, c in enumerate(result)
182
+ if c.type == own.type and c.name == own.name),
183
+ None,
184
+ )
185
+ if idx is None:
186
+ result.append(own)
187
+ else:
188
+ result[idx] = own
189
+ return result
190
+
191
+ def freeze(self) -> None:
192
+ if self._frozen:
193
+ return
194
+ self._frozen = True
195
+ for attr in self._attr_nodes.values():
196
+ attr.freeze()
197
+ for child in self._children:
198
+ child.freeze()
199
+
200
+ def _cached(self, key: str, compute: Callable[[], T]) -> T:
201
+ if self._frozen and key in self._cache:
202
+ return cast(T, self._cache[key])
203
+ value = compute()
204
+ if self._frozen:
205
+ self._cache[key] = value
206
+ return value
@@ -0,0 +1,8 @@
1
+ """MetaRoot — the metadata.root node."""
2
+ from __future__ import annotations
3
+
4
+ from .meta_data import MetaData
5
+
6
+
7
+ class MetaRoot(MetaData):
8
+ pass
File without changes
@@ -0,0 +1 @@
1
+ """DB-domain (persistence-layer) physical attributes (cross-port `metaobjects-db`)."""
@@ -0,0 +1,41 @@
1
+ """DB-domain physical-column attribute keys on fields (cross-port `metaobjects-db`).
2
+
3
+ Mirrors server/csharp/MetaObjects/Persistence/Db/DbConstants.cs and
4
+ server/typescript/packages/metadata/src/persistence/db/db-constants.ts so the
5
+ cross-language vocabulary stays byte-identical.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # R6 Plan 2b — @dbColumnType physical column-type attribute (ADR-0013).
11
+ #
12
+ # The canonical *physical* escape hatch: selects the DB column type while
13
+ # leaving the logical field subtype and its idiomatic native binding (ADR-0001)
14
+ # untouched. A closed, validated, introspection-round-trippable value set — no
15
+ # raw dialect passthrough (deferred). Own-only pairing validation lives in the
16
+ # loader's validation_passes (ERR_BAD_ATTR_VALUE), mirroring the field.enum
17
+ # @values precedent.
18
+ # ---------------------------------------------------------------------------
19
+
20
+ # Physical DB column-type override on a field (@dbColumnType). Closed value set:
21
+ # DB_COLUMN_TYPE_UUID / DB_COLUMN_TYPE_JSONB / DB_COLUMN_TYPE_TIMESTAMP_TZ.
22
+ FIELD_ATTR_DB_COLUMN_TYPE = "dbColumnType"
23
+
24
+ # @db.indexed — boolean DB-domain attr on every field subtype. Suppresses the
25
+ # @filterable-without-index warning by declaring an explicit index intent.
26
+ # Mirrors TS persistence/db/db-constants.ts FIELD_ATTR_DB_INDEXED.
27
+ FIELD_ATTR_DB_INDEXED = "db.indexed"
28
+
29
+ # @dbColumnType: uuid — native Postgres uuid column (legal on field.string).
30
+ DB_COLUMN_TYPE_UUID = "uuid"
31
+ # @dbColumnType: jsonb — genuinely-open JSON column (legal on field.string).
32
+ DB_COLUMN_TYPE_JSONB = "jsonb"
33
+ # @dbColumnType: timestamp_with_tz — timestamp with time zone (legal on field.timestamp).
34
+ DB_COLUMN_TYPE_TIMESTAMP_TZ = "timestamp_with_tz"
35
+
36
+ # The closed set of legal @dbColumnType values.
37
+ VALID_DB_COLUMN_TYPES = (
38
+ DB_COLUMN_TYPE_UUID,
39
+ DB_COLUMN_TYPE_JSONB,
40
+ DB_COLUMN_TYPE_TIMESTAMP_TZ,
41
+ )
@@ -0,0 +1,60 @@
1
+ """db_provider — the DB-domain MetaDataTypeProvider (cross-port ``metaobjects-db``).
2
+
3
+ Registers the DB-domain physical field attributes — ``@column`` and ``@dbColumnType``
4
+ — by EXTENDING the core-registered field types via :meth:`TypeRegistry.extend`.
5
+
6
+ These are DB-domain concerns, NOT core field properties, so they live in this domain
7
+ provider rather than on ``core_types._FIELD_COMMON_ATTRS`` — the same end-state as Java
8
+ (``CoreDBMetaDataProvider``), TypeScript (``dbProvider``), and C#
9
+ (``DbMetaDataProvider``), keeping all domain field-attrs in domain providers.
10
+
11
+ Note: the loader's ``_validate_db_column_type`` pass (the closed-set + (subtype × value)
12
+ pairing enforcement) runs UNCONDITIONALLY — it is not gated on this provider being
13
+ composed. This provider's job is to DECLARE the attrs (so the YAML coercion guard knows
14
+ their value types and the attr-schema validation accepts them as known) — the pairing
15
+ legality is still owned by the loader pass, mirroring the field.enum ``@values`` precedent.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from ...core.attr.attr_constants import ATTR_SUBTYPE_STRING
20
+ from ...core.field import field_constants as fc
21
+ from ....provider import Provider
22
+ from ....registry import AttrSchema, TypeRegistry
23
+ from ....shared.base_types import TYPE_FIELD
24
+ from .db_constants import FIELD_ATTR_DB_COLUMN_TYPE
25
+
26
+ # Every field subtype the DB-domain attrs apply to: the shared FIELD_SUBTYPES tuple
27
+ # (which deliberately excludes ``enum`` — registered separately in core_types) PLUS
28
+ # ``field.enum``. Mirrors the TS dbProvider's FIELD_SUBTYPES loop and the C#
29
+ # DbMetaDataProvider (whose FIELD_SUBTYPES includes enum) — Python reaches the same
30
+ # coverage by appending enum here.
31
+ _DB_FIELD_SUBTYPES: tuple[str, ...] = (*fc.FIELD_SUBTYPES, fc.FIELD_SUBTYPE_ENUM)
32
+
33
+ # @column — physical column-name override on a field. Bare string attr.
34
+ _COLUMN_SCHEMA = AttrSchema(name=fc.FIELD_ATTR_COLUMN, value_type=ATTR_SUBTYPE_STRING, required=False)
35
+
36
+ # @dbColumnType — physical column-type override on a field. Registered as a bare string
37
+ # attr (NO allowed_values) — matching Java/TS/C#, which register it unconstrained and let
38
+ # ONLY the loader's _validate_db_column_type pass enforce the closed set (so an unknown
39
+ # value fires exactly ONE ERR_BAD_ATTR_VALUE).
40
+ _DB_COLUMN_TYPE_SCHEMA = AttrSchema(
41
+ name=FIELD_ATTR_DB_COLUMN_TYPE, value_type=ATTR_SUBTYPE_STRING, required=False
42
+ )
43
+
44
+
45
+ def _register(registry: TypeRegistry) -> None:
46
+ for sub_type in _DB_FIELD_SUBTYPES:
47
+ registry.extend(
48
+ TYPE_FIELD,
49
+ sub_type,
50
+ attributes=[_COLUMN_SCHEMA, _DB_COLUMN_TYPE_SCHEMA],
51
+ )
52
+
53
+
54
+ def _make_db_provider() -> Provider:
55
+ p = Provider("metaobjects-db", dependencies=("metaobjects-core-types",))
56
+ p.on_register(_register)
57
+ return p
58
+
59
+
60
+ db_provider = _make_db_provider()
File without changes
@@ -0,0 +1,8 @@
1
+ """MetaOrigin — origin.passthrough / origin.aggregate node."""
2
+ from __future__ import annotations
3
+
4
+ from ...meta_data import MetaData
5
+
6
+
7
+ class MetaOrigin(MetaData):
8
+ pass
@@ -0,0 +1,20 @@
1
+ """Origin subtype vocabulary (colocated)."""
2
+ from ....shared.base_types import SUBTYPE_BASE
3
+
4
+ ORIGIN_SUBTYPE_PASSTHROUGH = "passthrough"
5
+ ORIGIN_SUBTYPE_AGGREGATE = "aggregate"
6
+ ORIGIN_SUBTYPE_COLLECTION = "collection"
7
+ ORIGIN_SUBTYPES = (
8
+ SUBTYPE_BASE,
9
+ ORIGIN_SUBTYPE_PASSTHROUGH,
10
+ ORIGIN_SUBTYPE_AGGREGATE,
11
+ ORIGIN_SUBTYPE_COLLECTION,
12
+ )
13
+
14
+ # passthrough attrs
15
+ ORIGIN_ATTR_FROM = "from"
16
+ ORIGIN_ATTR_VIA = "via"
17
+
18
+ # aggregate attrs
19
+ ORIGIN_ATTR_AGG = "agg"
20
+ ORIGIN_ATTR_OF = "of"
File without changes
@@ -0,0 +1,137 @@
1
+ """MetaSource — source.rdb node (ADR-0007 / FR-016 / ADR-0018).
2
+
3
+ Declares where an object's data lives. The rdb paradigm uses per-kind
4
+ physical-name aliases (@table / @view / @materializedView / @proc / @function)
5
+ + @kind / @role / @schema attrs; read-only-ness is derived from @kind.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from ...meta_data import MetaData
10
+ from .source_constants import (
11
+ ALL_PHYSICAL_NAME_ALIASES,
12
+ DEFAULT_SOURCE_KIND,
13
+ DEFAULT_SOURCE_ROLE,
14
+ PHYSICAL_NAME_ATTR_BY_KIND,
15
+ SOURCE_ATTR_KIND,
16
+ SOURCE_ATTR_ROLE,
17
+ SOURCE_ATTR_SCHEMA,
18
+ SOURCE_ATTR_TABLE,
19
+ SOURCE_READ_ONLY_KINDS,
20
+ )
21
+
22
+
23
+ def _to_snake_case(s: str) -> str:
24
+ """``MyName`` → ``my_name``; ``HTTPServer`` → ``http_server`` (mirrors TS
25
+ ``toSnakeCase`` regex pair, ASCII-only)."""
26
+ if not s:
27
+ return s
28
+ # Run 1: split between an uppercase-run and an upper+lower head.
29
+ out: list[str] = []
30
+ n = len(s)
31
+ for i, ch in enumerate(s):
32
+ is_upper = "A" <= ch <= "Z"
33
+ if is_upper and i > 0:
34
+ prev = s[i - 1]
35
+ prev_upper = "A" <= prev <= "Z"
36
+ prev_alnum_lower = ("a" <= prev <= "z") or ("0" <= prev <= "9")
37
+ nxt = s[i + 1] if i + 1 < n else ""
38
+ nxt_lower = "a" <= nxt <= "z"
39
+ if prev_alnum_lower:
40
+ out.append("_")
41
+ elif prev_upper and nxt_lower:
42
+ out.append("_")
43
+ out.append(ch.lower())
44
+ return "".join(out)
45
+
46
+
47
+ def _pluralize(s: str) -> str:
48
+ """Trivial cross-port pluralization (matches TS ``pluralize``)."""
49
+ if not s:
50
+ return s
51
+ lower = s.lower()
52
+ if lower.endswith(("s", "x", "z", "ch", "sh")):
53
+ return s + "es"
54
+ if len(s) >= 2 and lower[-1] == "y" and lower[-2] not in "aeiou":
55
+ return s[:-1] + "ies"
56
+ return s + "s"
57
+
58
+
59
+ class MetaSource(MetaData):
60
+ """A source.* node. Accessors mirror the TS reference (meta-source.ts) and
61
+ the Java port (MetaSource.java) — own-attr reads, with per-paradigm defaults.
62
+ """
63
+
64
+ def table_name(self) -> str | None:
65
+ """Physical SQL table name from the legacy ``@table`` slot. Returns
66
+ ``None`` if absent. Kept as a back-compat slot-only accessor: callers
67
+ should use :meth:`physical_name` for the FR-016 four-step rule.
68
+ """
69
+ v = self.attr(SOURCE_ATTR_TABLE)
70
+ return v if isinstance(v, str) and v else None
71
+
72
+ def effective_kind(self) -> str:
73
+ """The value of ``@kind``, defaulting to ``"table"`` when omitted
74
+ (ADR-0007 Rule 3 — per-paradigm default)."""
75
+ v = self.attr(SOURCE_ATTR_KIND)
76
+ return v if isinstance(v, str) and v else DEFAULT_SOURCE_KIND
77
+
78
+ def role(self) -> str:
79
+ """The value of ``@role``, defaulting to ``"primary"`` when omitted."""
80
+ v = self.attr(SOURCE_ATTR_ROLE)
81
+ return v if isinstance(v, str) and v else DEFAULT_SOURCE_ROLE
82
+
83
+ def is_read_only(self) -> bool:
84
+ """True when ``@kind`` names a read-only construct (view,
85
+ materializedView, storedProc, tableFunction)."""
86
+ return self.effective_kind() in SOURCE_READ_ONLY_KINDS
87
+
88
+ def is_writable(self) -> bool:
89
+ """True when this source is writable (i.e. not read-only)."""
90
+ return not self.is_read_only()
91
+
92
+ def schema(self) -> str | None:
93
+ """The value of ``@schema``, or ``None`` if absent."""
94
+ v = self.attr(SOURCE_ATTR_SCHEMA)
95
+ return v if isinstance(v, str) and v else None
96
+
97
+ def physical_name(self) -> str:
98
+ """Resolved physical SQL name for this source (FR-016 / ADR-0018).
99
+
100
+ Four-step rule:
101
+ 1. Kind-matching alias (e.g. ``@proc`` when ``@kind: "storedProc"``).
102
+ 2. Legacy ``@table`` for non-table kind (pre-1.0 fallback).
103
+ 3. Source's bare structural ``name`` via snake_case
104
+ (no pluralize — the source's name IS the logical name).
105
+ 4. Owning entity's name via ``pluralize(snake_case())``.
106
+
107
+ Callers needing the legacy raw ``@table`` slot only should use
108
+ :meth:`table_name`; codegen / runtime should use ``physical_name``.
109
+ """
110
+ kind = self.effective_kind()
111
+
112
+ # Step 1: kind-matching alias.
113
+ canonical_attr = PHYSICAL_NAME_ATTR_BY_KIND.get(kind)
114
+ if canonical_attr is not None:
115
+ v = self.attr(canonical_attr)
116
+ if isinstance(v, str) and v:
117
+ return v
118
+
119
+ # Step 2: legacy @table for non-table kind.
120
+ if canonical_attr != SOURCE_ATTR_TABLE:
121
+ legacy = self.attr(SOURCE_ATTR_TABLE)
122
+ if isinstance(legacy, str) and legacy:
123
+ return legacy
124
+
125
+ # Step 3: source's structural name → snake_case (no pluralize).
126
+ if self.name:
127
+ return _to_snake_case(self.name)
128
+
129
+ # Step 4: owning entity's name → pluralize(snake_case).
130
+ owner = self.parent
131
+ if owner is not None and getattr(owner, "name", ""):
132
+ return _pluralize(_to_snake_case(owner.name))
133
+
134
+ return ""
135
+
136
+
137
+ __all__ = ["MetaSource", "ALL_PHYSICAL_NAME_ALIASES"]