modelable 1.0.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.
Potentially problematic release.
This version of modelable might be problematic. Click here for more details.
- modelable/__init__.py +1 -0
- modelable/__main__.py +3 -0
- modelable/_pydantic_py314_compat.py +31 -0
- modelable/cli.py +41 -0
- modelable/commands/__init__.py +1 -0
- modelable/commands/apicurio.py +84 -0
- modelable/commands/codegen.py +241 -0
- modelable/commands/common.py +43 -0
- modelable/commands/compile.py +237 -0
- modelable/commands/create.py +164 -0
- modelable/commands/diff.py +82 -0
- modelable/commands/graph.py +53 -0
- modelable/commands/llm.py +564 -0
- modelable/commands/lsp.py +15 -0
- modelable/commands/runtime.py +37 -0
- modelable/commands/scenario.py +104 -0
- modelable/commands/spec.py +197 -0
- modelable/commands/workspace.py +240 -0
- modelable/compat/__init__.py +11 -0
- modelable/compat/checker.py +179 -0
- modelable/compat/diff.py +169 -0
- modelable/compiler/__init__.py +3 -0
- modelable/compiler/compiler.py +19 -0
- modelable/compiler/workspace.py +346 -0
- modelable/diagnostics/__init__.py +3 -0
- modelable/diagnostics/model.py +27 -0
- modelable/emitters/__init__.py +0 -0
- modelable/emitters/base.py +22 -0
- modelable/emitters/csharp.py +245 -0
- modelable/emitters/dbt_yaml.py +290 -0
- modelable/emitters/diagnostics.py +25 -0
- modelable/emitters/fhir.py +694 -0
- modelable/emitters/fhir_validator.py +36 -0
- modelable/emitters/go.py +334 -0
- modelable/emitters/java.py +264 -0
- modelable/emitters/json_schema.py +458 -0
- modelable/emitters/markdown.py +252 -0
- modelable/emitters/odcs.py +355 -0
- modelable/emitters/openlineage.py +315 -0
- modelable/emitters/openmetadata.py +258 -0
- modelable/emitters/python.py +282 -0
- modelable/emitters/rust.py +643 -0
- modelable/emitters/shapes.py +261 -0
- modelable/emitters/sql.py +266 -0
- modelable/emitters/targets.py +141 -0
- modelable/emitters/typescript.py +352 -0
- modelable/expressions/__init__.py +0 -0
- modelable/expressions/cel.py +547 -0
- modelable/governance/__init__.py +3 -0
- modelable/governance/checker.py +271 -0
- modelable/governance/por.py +46 -0
- modelable/grammar/__init__.py +1 -0
- modelable/grammar/modelable.lark +257 -0
- modelable/graph/__init__.py +5 -0
- modelable/graph/export.py +442 -0
- modelable/llm/__init__.py +43 -0
- modelable/llm/chat.py +255 -0
- modelable/llm/config.py +87 -0
- modelable/llm/context.py +194 -0
- modelable/llm/engine.py +976 -0
- modelable/llm/importers.py +1077 -0
- modelable/llm/provenance.py +84 -0
- modelable/llm/providers.py +182 -0
- modelable/llm/qa.py +126 -0
- modelable/llm/recommendations.py +33 -0
- modelable/llm/redaction.py +19 -0
- modelable/llm/render.py +279 -0
- modelable/llm/update_plan.py +101 -0
- modelable/llm/validation_help.py +10 -0
- modelable/lsp/__init__.py +3 -0
- modelable/lsp/__main__.py +4 -0
- modelable/lsp/code_actions.py +210 -0
- modelable/lsp/completion.py +480 -0
- modelable/lsp/definition.py +343 -0
- modelable/lsp/diagnostics.py +31 -0
- modelable/lsp/document_symbols.py +197 -0
- modelable/lsp/federation.py +261 -0
- modelable/lsp/folding.py +33 -0
- modelable/lsp/formatting.py +64 -0
- modelable/lsp/highlight.py +30 -0
- modelable/lsp/hover.py +370 -0
- modelable/lsp/inlay_hints.py +158 -0
- modelable/lsp/references.py +511 -0
- modelable/lsp/rename.py +564 -0
- modelable/lsp/semantic_tokens.py +412 -0
- modelable/lsp/server.py +370 -0
- modelable/lsp/workspace.py +83 -0
- modelable/lsp/workspace_symbols.py +104 -0
- modelable/parser/__init__.py +94 -0
- modelable/parser/ir.py +451 -0
- modelable/parser/parse.py +47 -0
- modelable/parser/transformer.py +798 -0
- modelable/parser/wire.py +68 -0
- modelable/planner/__init__.py +0 -0
- modelable/planner/lineage.py +91 -0
- modelable/planner/planner.py +134 -0
- modelable/planner/plans.py +122 -0
- modelable/py.typed +0 -0
- modelable/registry/__init__.py +9 -0
- modelable/registry/apicurio.py +166 -0
- modelable/registry/base.py +18 -0
- modelable/registry/factory.py +18 -0
- modelable/registry/index.py +419 -0
- modelable/registry/local.py +26 -0
- modelable/registry/oci.py +22 -0
- modelable/registry/resolver.py +213 -0
- modelable/registry/schema.sql +119 -0
- modelable/registry/signature.py +26 -0
- modelable/release.py +125 -0
- modelable/runtime/__init__.py +5 -0
- modelable/runtime/adapter/__init__.py +17 -0
- modelable/runtime/adapter/base.py +18 -0
- modelable/runtime/adapter/postgres.py +82 -0
- modelable/specs/__init__.py +23 -0
- modelable/specs/tracking.py +220 -0
- modelable/validation/__init__.py +3 -0
- modelable/validation/semantic.py +659 -0
- modelable-1.0.0.dist-info/METADATA +61 -0
- modelable-1.0.0.dist-info/RECORD +122 -0
- modelable-1.0.0.dist-info/WHEEL +4 -0
- modelable-1.0.0.dist-info/entry_points.txt +2 -0
- modelable-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from modelable.compiler.workspace import Workspace
|
|
7
|
+
from modelable.emitters.base import EmittedArtifact, compute_content_hash
|
|
8
|
+
from modelable.emitters.diagnostics import type_loss
|
|
9
|
+
from modelable.emitters.shapes import TypeShape
|
|
10
|
+
from modelable.parser.ir import DirectMapping, DomainDef, ModelVersion, ProjectionVersion
|
|
11
|
+
from modelable.registry.resolver import resolve_model_ref
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def emit_python(workspace: Workspace, out_dir: Path) -> list[EmittedArtifact]:
|
|
15
|
+
"""Emit Python dataclass modules for every published model and projection version."""
|
|
16
|
+
artifacts: list[EmittedArtifact] = []
|
|
17
|
+
for domain in workspace.mdl.domains:
|
|
18
|
+
for model_name, versions in domain.models.items():
|
|
19
|
+
for version in versions:
|
|
20
|
+
artifacts.append(_emit_model(domain, model_name, version, out_dir))
|
|
21
|
+
for projection_name, versions in domain.projections.items():
|
|
22
|
+
for version in versions:
|
|
23
|
+
artifacts.append(_emit_projection(domain, projection_name, version, out_dir, workspace.mdl))
|
|
24
|
+
return artifacts
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _artifact_id(domain: str, name: str, version: int) -> str:
|
|
28
|
+
return f"{domain}.{name}.v{version}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _pascalize(value: str) -> str:
|
|
32
|
+
parts = [part for part in re.split(r"[^A-Za-z0-9]+", value) if part]
|
|
33
|
+
return "".join(part[:1].upper() + part[1:] for part in parts) or "Generated"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _snake_case(value: str) -> str:
|
|
37
|
+
text = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value)
|
|
38
|
+
text = re.sub(r"[^A-Za-z0-9]+", "_", text)
|
|
39
|
+
text = text.strip("_").lower()
|
|
40
|
+
return text or "generated"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _stable_type_name(domain: str, name: str, version: int) -> str:
|
|
44
|
+
return f"{_pascalize(domain)}{_pascalize(name)}V{version}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _module_filename(type_name: str) -> str:
|
|
48
|
+
return f"{_snake_case(type_name)}.py"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _emit_model(domain: DomainDef, model_name: str, version: ModelVersion, out_dir: Path) -> EmittedArtifact:
|
|
52
|
+
artifact_id = _artifact_id(domain.name, model_name, version.version)
|
|
53
|
+
type_name = _stable_type_name(domain.name, model_name, version.version)
|
|
54
|
+
nested_definitions: dict[str, list[str]] = {}
|
|
55
|
+
field_specs = _field_specs_from_model_fields(
|
|
56
|
+
version.fields, owner_type=type_name, path=[], definitions=nested_definitions
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
lines = _header_lines()
|
|
60
|
+
lines.extend(_render_dataclass_definition(type_name, field_specs))
|
|
61
|
+
lines.extend(_render_nested_definitions(nested_definitions))
|
|
62
|
+
|
|
63
|
+
text = "\n".join(lines) + "\n"
|
|
64
|
+
return EmittedArtifact(
|
|
65
|
+
target="python",
|
|
66
|
+
ref=f"{domain.name}.{model_name}@{version.version}",
|
|
67
|
+
artifact_id=artifact_id,
|
|
68
|
+
path=out_dir / _module_path(domain.name, type_name),
|
|
69
|
+
content=text,
|
|
70
|
+
content_hash=compute_content_hash(text),
|
|
71
|
+
warnings=[],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _emit_projection(
|
|
76
|
+
domain: DomainDef,
|
|
77
|
+
projection_name: str,
|
|
78
|
+
version: ProjectionVersion,
|
|
79
|
+
out_dir: Path,
|
|
80
|
+
mdl,
|
|
81
|
+
) -> EmittedArtifact:
|
|
82
|
+
artifact_id = _artifact_id(domain.name, projection_name, version.version)
|
|
83
|
+
type_name = _stable_type_name(domain.name, projection_name, version.version)
|
|
84
|
+
nested_definitions: dict[str, list[str]] = {}
|
|
85
|
+
warnings: list[str] = []
|
|
86
|
+
|
|
87
|
+
field_specs: list[tuple[int, str, str, bool]] = []
|
|
88
|
+
for index, field in enumerate(version.fields):
|
|
89
|
+
field_shape = _resolve_projection_field_shape(field, version, mdl)
|
|
90
|
+
if field_shape is None:
|
|
91
|
+
warnings.append(type_loss(f"{domain.name}.{projection_name}.{field.name}"))
|
|
92
|
+
field_specs.append((index, field.name, "object", False))
|
|
93
|
+
continue
|
|
94
|
+
annotation = _shape_annotation(
|
|
95
|
+
field_shape, owner_type=type_name, path=[field.name], definitions=nested_definitions
|
|
96
|
+
)
|
|
97
|
+
optional = field_shape.optional or field_shape.nullable
|
|
98
|
+
field_specs.append((index, field.name, annotation, optional))
|
|
99
|
+
|
|
100
|
+
lines = _header_lines()
|
|
101
|
+
lines.extend(_render_dataclass_definition(type_name, field_specs))
|
|
102
|
+
lines.extend(_render_nested_definitions(nested_definitions))
|
|
103
|
+
|
|
104
|
+
text = "\n".join(lines) + "\n"
|
|
105
|
+
return EmittedArtifact(
|
|
106
|
+
target="python",
|
|
107
|
+
ref=f"{domain.name}.{projection_name}@{version.version}",
|
|
108
|
+
artifact_id=artifact_id,
|
|
109
|
+
path=out_dir / _module_path(domain.name, type_name),
|
|
110
|
+
content=text,
|
|
111
|
+
content_hash=compute_content_hash(text),
|
|
112
|
+
warnings=warnings,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _header_lines() -> list[str]:
|
|
117
|
+
return [
|
|
118
|
+
"from __future__ import annotations",
|
|
119
|
+
"",
|
|
120
|
+
"from dataclasses import dataclass",
|
|
121
|
+
"from datetime import date, datetime, time, timedelta",
|
|
122
|
+
"from decimal import Decimal",
|
|
123
|
+
"from typing import Optional",
|
|
124
|
+
"from uuid import UUID",
|
|
125
|
+
"",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _render_nested_definitions(definitions: dict[str, list[str]]) -> list[str]:
|
|
130
|
+
lines: list[str] = []
|
|
131
|
+
for definition in definitions.values():
|
|
132
|
+
lines.append("")
|
|
133
|
+
lines.extend(definition)
|
|
134
|
+
return lines
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _module_path(domain: str, type_name: str) -> Path:
|
|
138
|
+
return Path(*_package_name(domain).split(".")) / _module_filename(type_name)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _package_name(domain: str) -> str:
|
|
142
|
+
parts = [part.lower() for part in re.split(r"[^A-Za-z0-9]+", domain) if part]
|
|
143
|
+
return ".".join(parts) or "modelable"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _render_dataclass_definition(type_name: str, field_specs: list[tuple[int, str, str, bool]]) -> list[str]:
|
|
147
|
+
lines = ["@dataclass(frozen=True, slots=True)", f"class {type_name}:"]
|
|
148
|
+
if not field_specs:
|
|
149
|
+
lines.append(" pass")
|
|
150
|
+
return lines
|
|
151
|
+
for _, name, annotation, default_none in sorted(field_specs, key=lambda item: (item[3], item[0])):
|
|
152
|
+
line = f" {name}: {annotation}"
|
|
153
|
+
if default_none:
|
|
154
|
+
line += " = None"
|
|
155
|
+
lines.append(line)
|
|
156
|
+
return lines
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _field_specs_from_model_fields(
|
|
160
|
+
fields,
|
|
161
|
+
*,
|
|
162
|
+
owner_type: str,
|
|
163
|
+
path: list[str],
|
|
164
|
+
definitions: dict[str, list[str]],
|
|
165
|
+
) -> list[tuple[int, str, str, bool]]:
|
|
166
|
+
specs: list[tuple[int, str, str, bool]] = []
|
|
167
|
+
for index, field in enumerate(fields):
|
|
168
|
+
shape = TypeShape.from_field_type(field.type, optional=field.optional)
|
|
169
|
+
annotation = _shape_annotation(shape, owner_type=owner_type, path=[*path, field.name], definitions=definitions)
|
|
170
|
+
default_none = shape.optional or shape.nullable
|
|
171
|
+
specs.append((index, field.name, annotation, default_none))
|
|
172
|
+
return specs
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _field_specs_from_object_fields(
|
|
176
|
+
fields,
|
|
177
|
+
*,
|
|
178
|
+
owner_type: str,
|
|
179
|
+
path: list[str],
|
|
180
|
+
definitions: dict[str, list[str]],
|
|
181
|
+
) -> list[tuple[int, str, str, bool]]:
|
|
182
|
+
specs: list[tuple[int, str, str, bool]] = []
|
|
183
|
+
for index, field in enumerate(fields):
|
|
184
|
+
annotation = _shape_annotation(
|
|
185
|
+
field.shape, owner_type=owner_type, path=[*path, field.name], definitions=definitions
|
|
186
|
+
)
|
|
187
|
+
default_none = field.optional or field.shape.optional or field.shape.nullable
|
|
188
|
+
specs.append((index, field.name, annotation, default_none))
|
|
189
|
+
return specs
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _shape_annotation(
|
|
193
|
+
shape: TypeShape,
|
|
194
|
+
*,
|
|
195
|
+
owner_type: str,
|
|
196
|
+
path: list[str],
|
|
197
|
+
definitions: dict[str, list[str]],
|
|
198
|
+
) -> str:
|
|
199
|
+
base = _shape_base_annotation(shape, owner_type=owner_type, path=path, definitions=definitions)
|
|
200
|
+
if shape.optional or shape.nullable:
|
|
201
|
+
return f"Optional[{base}]"
|
|
202
|
+
return base
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _shape_base_annotation(
|
|
206
|
+
shape: TypeShape,
|
|
207
|
+
*,
|
|
208
|
+
owner_type: str,
|
|
209
|
+
path: list[str],
|
|
210
|
+
definitions: dict[str, list[str]],
|
|
211
|
+
) -> str:
|
|
212
|
+
if shape.kind == "primitive":
|
|
213
|
+
return _primitive_to_python(shape.ref or "string")
|
|
214
|
+
if shape.kind == "decimal":
|
|
215
|
+
return "Decimal"
|
|
216
|
+
if shape.kind == "array":
|
|
217
|
+
element = shape.element or TypeShape(kind="primitive", ref="object")
|
|
218
|
+
element_type = _shape_annotation(element, owner_type=owner_type, path=[*path, "Item"], definitions=definitions)
|
|
219
|
+
return f"list[{element_type}]"
|
|
220
|
+
if shape.kind == "map":
|
|
221
|
+
value = shape.value or TypeShape(kind="primitive", ref="object")
|
|
222
|
+
value_type = _shape_annotation(value, owner_type=owner_type, path=[*path, "Value"], definitions=definitions)
|
|
223
|
+
return f"dict[str, {value_type}]"
|
|
224
|
+
if shape.kind == "ref":
|
|
225
|
+
return "str"
|
|
226
|
+
if shape.kind == "enum":
|
|
227
|
+
return "str"
|
|
228
|
+
if shape.kind == "named":
|
|
229
|
+
return _pascalize(shape.ref or "Named")
|
|
230
|
+
if shape.kind == "object":
|
|
231
|
+
type_name = _nested_type_name(owner_type, path)
|
|
232
|
+
if type_name not in definitions:
|
|
233
|
+
definitions[type_name] = _render_dataclass_definition(
|
|
234
|
+
type_name,
|
|
235
|
+
_field_specs_from_object_fields(
|
|
236
|
+
shape.fields,
|
|
237
|
+
owner_type=owner_type,
|
|
238
|
+
path=path,
|
|
239
|
+
definitions=definitions,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
return type_name
|
|
243
|
+
return "object"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _primitive_to_python(kind: str) -> str:
|
|
247
|
+
mapping = {
|
|
248
|
+
"string": "str",
|
|
249
|
+
"bool": "bool",
|
|
250
|
+
"int": "int",
|
|
251
|
+
"float": "float",
|
|
252
|
+
"uuid": "UUID",
|
|
253
|
+
"timestamp": "datetime",
|
|
254
|
+
"date": "date",
|
|
255
|
+
"time": "time",
|
|
256
|
+
"duration": "timedelta",
|
|
257
|
+
"binary": "bytes",
|
|
258
|
+
}
|
|
259
|
+
return mapping.get(kind, "str")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _nested_type_name(owner_type: str, path: list[str]) -> str:
|
|
263
|
+
suffix = "".join(_pascalize(part) for part in path)
|
|
264
|
+
return f"{owner_type}{suffix}" if suffix else owner_type
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _resolve_projection_field_shape(field, projection: ProjectionVersion, mdl):
|
|
268
|
+
if not isinstance(field.mapping, DirectMapping):
|
|
269
|
+
return None
|
|
270
|
+
try:
|
|
271
|
+
source_domain, source_model = projection.source.model.rsplit(".", 1)
|
|
272
|
+
except ValueError:
|
|
273
|
+
return None
|
|
274
|
+
try:
|
|
275
|
+
resolved = resolve_model_ref(mdl, f"{source_domain}.{source_model}", projection.source.version)
|
|
276
|
+
except LookupError:
|
|
277
|
+
return None
|
|
278
|
+
source_mv = resolved.version
|
|
279
|
+
for src_field in source_mv.fields:
|
|
280
|
+
if src_field.name == field.mapping.source_field:
|
|
281
|
+
return TypeShape.from_field_type(src_field.type, optional=src_field.optional)
|
|
282
|
+
return None
|