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,50 @@
|
|
|
1
|
+
"""FR-017 — table-per-hierarchy (TPH) discriminator resolution for the runtime.
|
|
2
|
+
|
|
3
|
+
A TPH SUBTYPE is an entity that declares ``@discriminatorValue`` and ``extends``
|
|
4
|
+
(transitively) a base that declares ``@discriminator``. The ObjectManager uses
|
|
5
|
+
this to:
|
|
6
|
+
- inject the discriminator value on create (the entity names the subtype; the
|
|
7
|
+
caller never sets it);
|
|
8
|
+
- scope every read/update/delete to the subtype (a row of a different subtype
|
|
9
|
+
is invisible), mirroring the generated per-subtype route's cross-subtype 404;
|
|
10
|
+
- treat the discriminator as immutable (stripped from update patches).
|
|
11
|
+
|
|
12
|
+
The discriminator FIELD name lives on the base (``@discriminator``); the VALUE
|
|
13
|
+
lives on the subtype (``@discriminatorValue``). For deep hierarchies the base may
|
|
14
|
+
be any ancestor, so we walk the resolved super chain to find it.
|
|
15
|
+
|
|
16
|
+
Mirrors the TS reference ``runtime-ts/src/tph.ts``.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import NamedTuple
|
|
21
|
+
|
|
22
|
+
from metaobjects.meta.core.object.object_constants import (
|
|
23
|
+
OBJECT_ATTR_DISCRIMINATOR,
|
|
24
|
+
OBJECT_ATTR_DISCRIMINATOR_VALUE,
|
|
25
|
+
)
|
|
26
|
+
from metaobjects.meta.meta_data import MetaData
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TphSubtype(NamedTuple):
|
|
30
|
+
#: The discriminator field NAME (declared on the base via ``@discriminator``).
|
|
31
|
+
field: str
|
|
32
|
+
#: This subtype's discriminator VALUE (declared via ``@discriminatorValue``).
|
|
33
|
+
value: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def tph_subtype_of(entity: MetaData) -> TphSubtype | None:
|
|
37
|
+
"""If *entity* is a TPH subtype, return its discriminator field + value; else
|
|
38
|
+
``None``. A subtype declares ``@discriminatorValue`` (own attr) and inherits
|
|
39
|
+
``@discriminator`` from an ancestor in its resolved super chain.
|
|
40
|
+
"""
|
|
41
|
+
value = entity.attr(OBJECT_ATTR_DISCRIMINATOR_VALUE) # own attr
|
|
42
|
+
if value is None:
|
|
43
|
+
return None
|
|
44
|
+
ancestor = entity.super_data
|
|
45
|
+
while ancestor is not None:
|
|
46
|
+
field = ancestor.attr(OBJECT_ATTR_DISCRIMINATOR) # own attr on the base
|
|
47
|
+
if field is not None:
|
|
48
|
+
return TphSubtype(field=str(field), value=str(value))
|
|
49
|
+
ancestor = ancestor.super_data
|
|
50
|
+
return None
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Canonical (fused-key) serializer. Deterministic per spec/conformance-tests.md."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from .meta.meta_data import MetaData
|
|
7
|
+
from .meta.persistence.source.source_constants import (
|
|
8
|
+
DEFAULT_SOURCE_KIND,
|
|
9
|
+
PHYSICAL_NAME_ATTR_BY_KIND,
|
|
10
|
+
SOURCE_ATTR_KIND,
|
|
11
|
+
SOURCE_ATTR_TABLE,
|
|
12
|
+
SOURCE_SUBTYPE_RDB,
|
|
13
|
+
)
|
|
14
|
+
from .shared.base_types import TYPE_SOURCE
|
|
15
|
+
from .shared.separators import ATTR_PREFIX, FUSED_KEY_SEP
|
|
16
|
+
from .shared.structural import (
|
|
17
|
+
KEY_ABSTRACT,
|
|
18
|
+
KEY_CHILDREN,
|
|
19
|
+
KEY_EXTENDS,
|
|
20
|
+
KEY_IS_ARRAY,
|
|
21
|
+
KEY_NAME,
|
|
22
|
+
KEY_PACKAGE,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_SOURCE_RDB_FUSED_KEY = f"{TYPE_SOURCE}{FUSED_KEY_SEP}{SOURCE_SUBTYPE_RDB}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def canonical_serialize(node: MetaData) -> str:
|
|
29
|
+
return _serialize(node, effective=False)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def canonical_serialize_effective(node: MetaData) -> str:
|
|
33
|
+
"""Like :func:`canonical_serialize`, but emits the EFFECTIVE tree —
|
|
34
|
+
``children()`` + ``attrs()`` at every node (own + inherited via the super
|
|
35
|
+
chain), so the super-chain merge is materialized in the output.
|
|
36
|
+
|
|
37
|
+
Used by the conformance harness's ``expected-effective.json`` fixtures.
|
|
38
|
+
Mirrors the TS reference ``canonicalSerializeEffective``: ``extends`` is
|
|
39
|
+
still emitted on every node (the ref stays in the body), but inherited
|
|
40
|
+
members are inlined rather than referenced.
|
|
41
|
+
"""
|
|
42
|
+
return _serialize(node, effective=True)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _serialize(node: MetaData, effective: bool) -> str:
|
|
46
|
+
parsed = _to_canonical(node, effective)
|
|
47
|
+
# FR-016 / ADR-0018 — rewrite legacy @table → kind-matching alias on
|
|
48
|
+
# source.rdb wrappers; run before serialization so the rewritten key sorts
|
|
49
|
+
# naturally with the rest of the body (alphabetical at our depth).
|
|
50
|
+
_rewrite_source_rdb_physical_names(parsed)
|
|
51
|
+
text = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
52
|
+
return text + "\n"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _to_canonical(node: MetaData, effective: bool = False) -> dict[str, object]:
|
|
56
|
+
return {f"{node.type}{FUSED_KEY_SEP}{node.sub_type}": _body(node, effective)}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _body(node: MetaData, effective: bool = False) -> dict[str, object]:
|
|
60
|
+
body: dict[str, object] = {}
|
|
61
|
+
if node.name:
|
|
62
|
+
body[KEY_NAME] = node.name
|
|
63
|
+
if node.package:
|
|
64
|
+
body[KEY_PACKAGE] = node.package
|
|
65
|
+
if node.super_ref:
|
|
66
|
+
body[KEY_EXTENDS] = node.super_ref
|
|
67
|
+
if node.is_abstract:
|
|
68
|
+
body[KEY_ABSTRACT] = True
|
|
69
|
+
if node.is_array:
|
|
70
|
+
body[KEY_IS_ARRAY] = True
|
|
71
|
+
|
|
72
|
+
# In effective mode use attrs()/children() (own + inherited via the super
|
|
73
|
+
# chain); in own mode use own_meta_attrs()/own_children() (declared here).
|
|
74
|
+
if effective:
|
|
75
|
+
for name in sorted(node.attrs()):
|
|
76
|
+
body[f"{ATTR_PREFIX}{name}"] = _normalize(node.attrs()[name])
|
|
77
|
+
else:
|
|
78
|
+
for attr in sorted(node.own_meta_attrs(), key=lambda a: a.name):
|
|
79
|
+
body[f"{ATTR_PREFIX}{attr.name}"] = _normalize(getattr(attr, "value", None))
|
|
80
|
+
|
|
81
|
+
children = node.children() if effective else node.own_children()
|
|
82
|
+
if children:
|
|
83
|
+
body[KEY_CHILDREN] = [_to_canonical(c, effective) for c in children]
|
|
84
|
+
return body
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _rewrite_source_rdb_physical_names(value: object) -> None:
|
|
88
|
+
"""FR-016 / ADR-0018 — rewrite legacy ``@table`` on source.rdb wrappers
|
|
89
|
+
whose ``@kind`` is non-table to the kind-matching alias
|
|
90
|
+
(``@view`` / ``@materializedView`` / ``@proc`` / ``@function``).
|
|
91
|
+
|
|
92
|
+
Mutates the parsed JSON in place; idempotent and a no-op for canonical
|
|
93
|
+
inputs (mirrors the TS reference ``rewriteSourceRdbPhysicalNames``).
|
|
94
|
+
"""
|
|
95
|
+
if isinstance(value, list):
|
|
96
|
+
for item in value:
|
|
97
|
+
_rewrite_source_rdb_physical_names(item)
|
|
98
|
+
return
|
|
99
|
+
if not isinstance(value, dict):
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
rdb_body = value.get(_SOURCE_RDB_FUSED_KEY)
|
|
103
|
+
if isinstance(rdb_body, dict):
|
|
104
|
+
kind_raw = rdb_body.get(f"{ATTR_PREFIX}{SOURCE_ATTR_KIND}")
|
|
105
|
+
kind = kind_raw if isinstance(kind_raw, str) and kind_raw else DEFAULT_SOURCE_KIND
|
|
106
|
+
canonical_alias = PHYSICAL_NAME_ATTR_BY_KIND.get(kind)
|
|
107
|
+
if canonical_alias is not None and canonical_alias != SOURCE_ATTR_TABLE:
|
|
108
|
+
legacy_key = f"{ATTR_PREFIX}{SOURCE_ATTR_TABLE}"
|
|
109
|
+
canonical_key = f"{ATTR_PREFIX}{canonical_alias}"
|
|
110
|
+
legacy_value = rdb_body.get(legacy_key)
|
|
111
|
+
if legacy_value is not None and canonical_key not in rdb_body:
|
|
112
|
+
# Re-insert in alphabetical position by rebuilding the dict.
|
|
113
|
+
new_body: dict[str, object] = {}
|
|
114
|
+
# The rewrite changes the key — preserve original insertion
|
|
115
|
+
# order semantics by walking, swapping in the canonical key in
|
|
116
|
+
# @table's slot, then re-sorting only the @-prefixed keys.
|
|
117
|
+
for k, v in rdb_body.items():
|
|
118
|
+
if k == legacy_key:
|
|
119
|
+
continue
|
|
120
|
+
new_body[k] = v
|
|
121
|
+
new_body[canonical_key] = legacy_value
|
|
122
|
+
# Restore structural-then-attr ordering with attrs alphabetically.
|
|
123
|
+
struct_keys = [
|
|
124
|
+
KEY_NAME, KEY_PACKAGE, KEY_EXTENDS,
|
|
125
|
+
KEY_ABSTRACT, KEY_IS_ARRAY,
|
|
126
|
+
]
|
|
127
|
+
ordered: dict[str, object] = {}
|
|
128
|
+
for k in struct_keys:
|
|
129
|
+
if k in new_body:
|
|
130
|
+
ordered[k] = new_body[k]
|
|
131
|
+
attr_keys = sorted(
|
|
132
|
+
k for k in new_body
|
|
133
|
+
if k.startswith(ATTR_PREFIX)
|
|
134
|
+
)
|
|
135
|
+
for k in attr_keys:
|
|
136
|
+
ordered[k] = new_body[k]
|
|
137
|
+
if KEY_CHILDREN in new_body:
|
|
138
|
+
ordered[KEY_CHILDREN] = new_body[KEY_CHILDREN]
|
|
139
|
+
rdb_body.clear()
|
|
140
|
+
rdb_body.update(ordered)
|
|
141
|
+
|
|
142
|
+
# Recurse through every value (in particular ``children``).
|
|
143
|
+
for v in value.values():
|
|
144
|
+
_rewrite_source_rdb_physical_names(v)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
_SCALAR_TYPES = (str, int, float, bool, type(None))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _normalize(value: object) -> object:
|
|
151
|
+
"""Mirror JSON.stringify: a whole-number float serializes as an int.
|
|
152
|
+
|
|
153
|
+
Object-valued attrs need different key order in canonical form, and the
|
|
154
|
+
serializer is schema-free — so distinguish by value shape, which exactly
|
|
155
|
+
tracks the object-attr subtypes:
|
|
156
|
+
* `properties` (e.g. @enumDoc / @enumAlias) is a flat scalar->scalar map ->
|
|
157
|
+
keys sort ordinally (cross-port canonical form; matches the Java
|
|
158
|
+
reference, whose Properties is unordered and serializes sorted).
|
|
159
|
+
* `filter` maps a field to an operator object ({eq:...}/{in:[...]}) — at
|
|
160
|
+
least one value is itself a dict/list -> declaration order is preserved
|
|
161
|
+
(significant). The FilterAttr desugar runs at set_value time, so filter
|
|
162
|
+
values are always op-object dicts before serialization reaches here.
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(value, float) and value.is_integer():
|
|
165
|
+
return int(value)
|
|
166
|
+
if isinstance(value, list):
|
|
167
|
+
return [_normalize(v) for v in value]
|
|
168
|
+
if isinstance(value, dict):
|
|
169
|
+
all_scalar = all(isinstance(v, _SCALAR_TYPES) for v in value.values())
|
|
170
|
+
items = sorted(value.items()) if all_scalar else value.items()
|
|
171
|
+
return {k: _normalize(v) for k, v in items}
|
|
172
|
+
return value
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""The base metamodel type names + the shared base/root subtype names."""
|
|
2
|
+
TYPE_METADATA = "metadata"
|
|
3
|
+
TYPE_OBJECT = "object"
|
|
4
|
+
TYPE_FIELD = "field"
|
|
5
|
+
TYPE_ATTR = "attr"
|
|
6
|
+
TYPE_VALIDATOR = "validator"
|
|
7
|
+
TYPE_IDENTITY = "identity"
|
|
8
|
+
TYPE_RELATIONSHIP = "relationship"
|
|
9
|
+
TYPE_VIEW = "view"
|
|
10
|
+
TYPE_LAYOUT = "layout"
|
|
11
|
+
TYPE_SOURCE = "source"
|
|
12
|
+
TYPE_ORIGIN = "origin"
|
|
13
|
+
TYPE_TEMPLATE = "template"
|
|
14
|
+
|
|
15
|
+
SUBTYPE_BASE = "base"
|
|
16
|
+
SUBTYPE_ROOT = "root"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Reserved structural body keys (NOT @-attrs). Documented order in conformance-tests.md."""
|
|
2
|
+
KEY_NAME = "name"
|
|
3
|
+
KEY_PACKAGE = "package"
|
|
4
|
+
KEY_EXTENDS = "extends"
|
|
5
|
+
KEY_ABSTRACT = "abstract"
|
|
6
|
+
KEY_OVERLAY = "overlay"
|
|
7
|
+
KEY_IS_ARRAY = "isArray"
|
|
8
|
+
KEY_CHILDREN = "children"
|
|
9
|
+
KEY_VALUE = "value"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""FR5a / ADR-0009 — Loader error envelope + source-on-node.
|
|
2
|
+
|
|
3
|
+
Cross-port-aligned types: every metaobjects port emits the same envelope shape
|
|
4
|
+
so a tool consuming errors from multiple language ports can compare them
|
|
5
|
+
byte-identically.
|
|
6
|
+
|
|
7
|
+
Public surface re-exported here:
|
|
8
|
+
* ErrorSource hierarchy (abstract base + 6 variants).
|
|
9
|
+
* JsonPathBuilder + JsonPath helpers (canonical JSONPath construction).
|
|
10
|
+
* LoaderError / LoaderWarning / NodeContext / Contributor envelope types.
|
|
11
|
+
* semantic_diff(a, b) for FR5c overlay-merge consumption.
|
|
12
|
+
|
|
13
|
+
See `docs/superpowers/specs/2026-05-25-fr5a-json-shape-loader-errors.md` and
|
|
14
|
+
`spec/decisions/ADR-0009-loader-error-envelope-and-source-on-node.md`.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from .error_source import (
|
|
19
|
+
CodeSource,
|
|
20
|
+
Contributor,
|
|
21
|
+
DatabaseSource,
|
|
22
|
+
DbLocation,
|
|
23
|
+
ErrorSource,
|
|
24
|
+
JsonSource,
|
|
25
|
+
LoaderError,
|
|
26
|
+
LoaderWarning,
|
|
27
|
+
MergedSource,
|
|
28
|
+
NodeContext,
|
|
29
|
+
ResolvedSource,
|
|
30
|
+
WARN_DUPLICATE_DECLARATION,
|
|
31
|
+
WARN_LEGACY,
|
|
32
|
+
WARN_LEGACY_PHYSICAL_NAME_ALIAS,
|
|
33
|
+
YamlPosition,
|
|
34
|
+
YamlSource,
|
|
35
|
+
resolved_source,
|
|
36
|
+
)
|
|
37
|
+
from .json_path import JsonPath, JsonPathBuilder
|
|
38
|
+
from .semantic_diff import semantic_diff
|
|
39
|
+
from .yaml_positions import (
|
|
40
|
+
YamlPositionMap,
|
|
41
|
+
get_position_map,
|
|
42
|
+
get_yaml_position,
|
|
43
|
+
parse_yaml_with_positions,
|
|
44
|
+
set_position_map,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
# ErrorSource hierarchy
|
|
49
|
+
"ErrorSource",
|
|
50
|
+
"JsonSource",
|
|
51
|
+
"YamlSource",
|
|
52
|
+
"MergedSource",
|
|
53
|
+
"ResolvedSource",
|
|
54
|
+
"resolved_source",
|
|
55
|
+
"DatabaseSource",
|
|
56
|
+
"CodeSource",
|
|
57
|
+
# Supporting types
|
|
58
|
+
"Contributor",
|
|
59
|
+
"DbLocation",
|
|
60
|
+
"NodeContext",
|
|
61
|
+
"YamlPosition",
|
|
62
|
+
"LoaderError",
|
|
63
|
+
"LoaderWarning",
|
|
64
|
+
# FR5c — warning code constants
|
|
65
|
+
"WARN_DUPLICATE_DECLARATION",
|
|
66
|
+
"WARN_LEGACY",
|
|
67
|
+
"WARN_LEGACY_PHYSICAL_NAME_ALIAS",
|
|
68
|
+
# JSONPath
|
|
69
|
+
"JsonPathBuilder",
|
|
70
|
+
"JsonPath",
|
|
71
|
+
# Semantic diff
|
|
72
|
+
"semantic_diff",
|
|
73
|
+
# FR5b — YAML positions
|
|
74
|
+
"YamlPositionMap",
|
|
75
|
+
"get_position_map",
|
|
76
|
+
"get_yaml_position",
|
|
77
|
+
"set_position_map",
|
|
78
|
+
"parse_yaml_with_positions",
|
|
79
|
+
]
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""FR5a / ADR-0009 — ErrorSource discriminated union + envelope types.
|
|
2
|
+
|
|
3
|
+
Closed hierarchy over the provenance variants a metadata node or error can
|
|
4
|
+
carry. The cross-port realization in Python uses ``@dataclass(frozen=True)`` so
|
|
5
|
+
each variant is value-equal, hashable, and immutable.
|
|
6
|
+
|
|
7
|
+
Pattern-match on the concrete subtype (``isinstance(src, JsonSource)``) to
|
|
8
|
+
access variant-specific fields, or read ``src.format`` for the discriminant
|
|
9
|
+
tag.
|
|
10
|
+
|
|
11
|
+
Mirrors:
|
|
12
|
+
* TS — `server/typescript/packages/metadata/src/source.ts`
|
|
13
|
+
* C# — `server/csharp/MetaObjects/Source/ErrorSource.cs`
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import ClassVar, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Discriminated union: ErrorSource hierarchy
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ErrorSource:
|
|
28
|
+
"""Provenance envelope for a metadata node or loader error.
|
|
29
|
+
|
|
30
|
+
Abstract base — never instantiated directly. Every concrete variant
|
|
31
|
+
declares a class-level ``format`` constant that names its discriminant tag
|
|
32
|
+
in the cross-port wire shape.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Subclasses override this class-level constant. Declared here as a
|
|
36
|
+
# placeholder so static type-checkers see a string attribute on the base.
|
|
37
|
+
format: ClassVar[str] = ""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class YamlPosition:
|
|
42
|
+
"""Optional YAML line/col source position (FR5b). 1-based."""
|
|
43
|
+
|
|
44
|
+
line: int
|
|
45
|
+
col: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class JsonSource(ErrorSource):
|
|
50
|
+
"""Authoring-time: single JSON file (FR5a).
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
files: Length-1 tuple of project-root-relative file paths. The FR5a
|
|
54
|
+
invariant guarantees exactly one file — multi-file provenance lives
|
|
55
|
+
on :class:`MergedSource` (FR5c). Enforced at construction.
|
|
56
|
+
json_path: Canonical JSONPath string for the node within ``files[0]``.
|
|
57
|
+
yaml_position: FR5b — optional YAML line/col position. Populated by
|
|
58
|
+
the YAML loader when the JSON document was authored as YAML; for
|
|
59
|
+
cross-port-safety, the envelope's ``format`` discriminator stays
|
|
60
|
+
``"json"`` (so the yaml-conformance fixtures stay byte-stable
|
|
61
|
+
until all four ports ship FR5b and the discriminator flips to
|
|
62
|
+
``"yaml"``). See TS reference (server/typescript/packages/metadata/
|
|
63
|
+
src/source.ts) for the rationale.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
files: tuple[str, ...]
|
|
67
|
+
json_path: str
|
|
68
|
+
yaml_position: Optional[YamlPosition] = None
|
|
69
|
+
format: ClassVar[str] = "json"
|
|
70
|
+
|
|
71
|
+
def __post_init__(self) -> None:
|
|
72
|
+
# FR5a invariant: exactly one file. Multi-file provenance is
|
|
73
|
+
# MergedSource (FR5c) territory; enforce here so the type can be
|
|
74
|
+
# trusted by cross-port comparison.
|
|
75
|
+
if len(self.files) != 1:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"JsonSource requires exactly one file path; got {len(self.files)}. "
|
|
78
|
+
"Use MergedSource for multi-file provenance."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class YamlSource(ErrorSource):
|
|
84
|
+
"""Authoring-time: single YAML file with optional source-map positions (FR5b)."""
|
|
85
|
+
|
|
86
|
+
files: tuple[str, ...]
|
|
87
|
+
json_path: str
|
|
88
|
+
yaml_position: Optional[YamlPosition] = None
|
|
89
|
+
format: ClassVar[str] = "yaml"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class Contributor:
|
|
94
|
+
"""One contributor in a :class:`MergedSource`.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
file: Path to the contributing file (project-root relative; forward slashes).
|
|
98
|
+
role: One of ``"overlay-base"``, ``"overlay-extension"``,
|
|
99
|
+
``"extends-base"``, ``"extends-extension"``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
file: str
|
|
103
|
+
role: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class MergedSource(ErrorSource):
|
|
108
|
+
"""Post-load: overlay merge that produced semantic change (FR5c)."""
|
|
109
|
+
|
|
110
|
+
files: tuple[str, ...]
|
|
111
|
+
json_path: str
|
|
112
|
+
contributors: tuple[Contributor, ...]
|
|
113
|
+
format: ClassVar[str] = "merged"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class ResolvedSource(ErrorSource):
|
|
118
|
+
"""Post-load: extends / @via / @objectRef / @payloadRef resolution failure (FR5d)."""
|
|
119
|
+
|
|
120
|
+
files: tuple[str, ...]
|
|
121
|
+
json_path: Optional[str] = None
|
|
122
|
+
referrer: Optional[str] = None
|
|
123
|
+
target: Optional[str] = None
|
|
124
|
+
format: ClassVar[str] = "resolved"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def resolved_source(
|
|
128
|
+
referrer_source: ErrorSource,
|
|
129
|
+
referrer: str,
|
|
130
|
+
target: str,
|
|
131
|
+
) -> ResolvedSource:
|
|
132
|
+
"""FR5d — build a ``ResolvedSource`` envelope from a referrer node's source
|
|
133
|
+
envelope (typically a parse-time JsonSource / YamlSource) plus the referrer
|
|
134
|
+
FQN and the unresolved target string.
|
|
135
|
+
|
|
136
|
+
Mirrors TS ``resolvedSource()`` (server/typescript/packages/metadata/src/
|
|
137
|
+
source.ts). The resolved envelope carries:
|
|
138
|
+
|
|
139
|
+
* ``files``: the referrer's source files (best-effort — for json/yaml/
|
|
140
|
+
merged variants this is the referrer's files tuple; for code/database
|
|
141
|
+
variants, an empty tuple).
|
|
142
|
+
* ``json_path``: the referrer's json_path when known (the location of the
|
|
143
|
+
broken reference on disk).
|
|
144
|
+
* ``referrer``: the FQN of the metadata node that declared the broken
|
|
145
|
+
reference (e.g. ``"myapp::content::Video"``).
|
|
146
|
+
* ``target``: the unresolved reference string itself (e.g. ``"BaseEntity"``,
|
|
147
|
+
``"Program.weeks.invalid"``).
|
|
148
|
+
"""
|
|
149
|
+
if isinstance(referrer_source, (JsonSource, YamlSource, MergedSource, ResolvedSource)):
|
|
150
|
+
files = tuple(referrer_source.files)
|
|
151
|
+
json_path = referrer_source.json_path
|
|
152
|
+
else:
|
|
153
|
+
# CodeSource / DatabaseSource / abstract base — no files/json_path to plumb.
|
|
154
|
+
files = ()
|
|
155
|
+
json_path = None
|
|
156
|
+
return ResolvedSource(
|
|
157
|
+
files=files,
|
|
158
|
+
json_path=json_path,
|
|
159
|
+
referrer=referrer,
|
|
160
|
+
target=target,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass(frozen=True)
|
|
165
|
+
class DbLocation:
|
|
166
|
+
"""Database location for a node sourced from a row."""
|
|
167
|
+
|
|
168
|
+
table: str
|
|
169
|
+
id: str
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(frozen=True)
|
|
173
|
+
class DatabaseSource(ErrorSource):
|
|
174
|
+
"""Future: database-sourced metadata (FR5e, gated on FR-003)."""
|
|
175
|
+
|
|
176
|
+
db_location: DbLocation
|
|
177
|
+
json_path: Optional[str] = None
|
|
178
|
+
format: ClassVar[str] = "database"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass(frozen=True)
|
|
182
|
+
class CodeSource(ErrorSource):
|
|
183
|
+
"""Programmatic / test construction.
|
|
184
|
+
|
|
185
|
+
The default for any node not built by a loader phase. Mirrors TS
|
|
186
|
+
``codeSource(caller?)``.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
caller: Optional human label (e.g. ``"QueriesTest.makePost"``).
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
caller: Optional[str] = None
|
|
193
|
+
format: ClassVar[str] = "code"
|
|
194
|
+
|
|
195
|
+
# Canonical singleton for the no-caller case (mirrors C# `CodeSource.Default`).
|
|
196
|
+
# The actual assignment lives below — Python doesn't allow self-reference in
|
|
197
|
+
# the dataclass body.
|
|
198
|
+
DEFAULT: ClassVar[CodeSource]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
CodeSource.DEFAULT = CodeSource(caller=None)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ---------------------------------------------------------------------------
|
|
205
|
+
# Envelope types: error / warning structures
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass(frozen=True)
|
|
210
|
+
class NodeContext:
|
|
211
|
+
"""Optional structural context attached to a loader error.
|
|
212
|
+
|
|
213
|
+
RECOMMENDED per ADR-0009; conformance does not enforce population.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
type: Optional[str] = None
|
|
217
|
+
sub_type: Optional[str] = None
|
|
218
|
+
name: Optional[str] = None
|
|
219
|
+
fqn: Optional[str] = None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class LoaderError:
|
|
224
|
+
"""Envelope shape every loader error conforms to. ADR-0009 §Decision.
|
|
225
|
+
|
|
226
|
+
``code``, ``message`` and ``source`` are REQUIRED (conformance-enforced).
|
|
227
|
+
The remaining fields are RECOMMENDED; FR5a does not populate them, FR5b–FR5e
|
|
228
|
+
may.
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
code: str
|
|
232
|
+
message: str
|
|
233
|
+
source: ErrorSource
|
|
234
|
+
suggestions: tuple[str, ...] = field(default_factory=tuple)
|
|
235
|
+
fixture: Optional[str] = None
|
|
236
|
+
node: Optional[NodeContext] = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(frozen=True)
|
|
240
|
+
class LoaderWarning:
|
|
241
|
+
"""Warning envelope — same shape as :class:`LoaderError` but a ``WARN_*`` code.
|
|
242
|
+
|
|
243
|
+
Today's initial warning codes:
|
|
244
|
+
* ``WARN_DUPLICATE_DECLARATION`` — emitted by overlay-merge when a
|
|
245
|
+
duplicate-with-no-change is detected (FR5c).
|
|
246
|
+
* ``WARN_LEGACY`` — legacy ``warnings: list[str]`` messages wrapped at
|
|
247
|
+
the loader boundary so the envelope channel is uniform.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
code: str
|
|
251
|
+
message: str
|
|
252
|
+
source: ErrorSource
|
|
253
|
+
suggestions: tuple[str, ...] = field(default_factory=tuple)
|
|
254
|
+
fixture: Optional[str] = None
|
|
255
|
+
node: Optional[NodeContext] = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# FR5c — string constants for warning codes. Mirrors the TS WARNING_CODES
|
|
259
|
+
# export. Keeping the values aligned across ports is part of the conformance
|
|
260
|
+
# contract.
|
|
261
|
+
WARN_DUPLICATE_DECLARATION: str = "WARN_DUPLICATE_DECLARATION"
|
|
262
|
+
WARN_LEGACY: str = "WARN_LEGACY"
|
|
263
|
+
#: FR-016 / ADR-0018 — emitted when a source.rdb uses the pre-1.0 ``@table``
|
|
264
|
+
#: spelling with a non-table ``@kind`` (e.g. ``@kind: "view"`` + ``@table``).
|
|
265
|
+
#: Loader accepts; the canonical serializer rewrites to the kind-matching alias.
|
|
266
|
+
WARN_LEGACY_PHYSICAL_NAME_ALIAS: str = "WARN_LEGACY_PHYSICAL_NAME_ALIAS"
|