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,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
|
|
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,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"]
|