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.

Files changed (122) hide show
  1. modelable/__init__.py +1 -0
  2. modelable/__main__.py +3 -0
  3. modelable/_pydantic_py314_compat.py +31 -0
  4. modelable/cli.py +41 -0
  5. modelable/commands/__init__.py +1 -0
  6. modelable/commands/apicurio.py +84 -0
  7. modelable/commands/codegen.py +241 -0
  8. modelable/commands/common.py +43 -0
  9. modelable/commands/compile.py +237 -0
  10. modelable/commands/create.py +164 -0
  11. modelable/commands/diff.py +82 -0
  12. modelable/commands/graph.py +53 -0
  13. modelable/commands/llm.py +564 -0
  14. modelable/commands/lsp.py +15 -0
  15. modelable/commands/runtime.py +37 -0
  16. modelable/commands/scenario.py +104 -0
  17. modelable/commands/spec.py +197 -0
  18. modelable/commands/workspace.py +240 -0
  19. modelable/compat/__init__.py +11 -0
  20. modelable/compat/checker.py +179 -0
  21. modelable/compat/diff.py +169 -0
  22. modelable/compiler/__init__.py +3 -0
  23. modelable/compiler/compiler.py +19 -0
  24. modelable/compiler/workspace.py +346 -0
  25. modelable/diagnostics/__init__.py +3 -0
  26. modelable/diagnostics/model.py +27 -0
  27. modelable/emitters/__init__.py +0 -0
  28. modelable/emitters/base.py +22 -0
  29. modelable/emitters/csharp.py +245 -0
  30. modelable/emitters/dbt_yaml.py +290 -0
  31. modelable/emitters/diagnostics.py +25 -0
  32. modelable/emitters/fhir.py +694 -0
  33. modelable/emitters/fhir_validator.py +36 -0
  34. modelable/emitters/go.py +334 -0
  35. modelable/emitters/java.py +264 -0
  36. modelable/emitters/json_schema.py +458 -0
  37. modelable/emitters/markdown.py +252 -0
  38. modelable/emitters/odcs.py +355 -0
  39. modelable/emitters/openlineage.py +315 -0
  40. modelable/emitters/openmetadata.py +258 -0
  41. modelable/emitters/python.py +282 -0
  42. modelable/emitters/rust.py +643 -0
  43. modelable/emitters/shapes.py +261 -0
  44. modelable/emitters/sql.py +266 -0
  45. modelable/emitters/targets.py +141 -0
  46. modelable/emitters/typescript.py +352 -0
  47. modelable/expressions/__init__.py +0 -0
  48. modelable/expressions/cel.py +547 -0
  49. modelable/governance/__init__.py +3 -0
  50. modelable/governance/checker.py +271 -0
  51. modelable/governance/por.py +46 -0
  52. modelable/grammar/__init__.py +1 -0
  53. modelable/grammar/modelable.lark +257 -0
  54. modelable/graph/__init__.py +5 -0
  55. modelable/graph/export.py +442 -0
  56. modelable/llm/__init__.py +43 -0
  57. modelable/llm/chat.py +255 -0
  58. modelable/llm/config.py +87 -0
  59. modelable/llm/context.py +194 -0
  60. modelable/llm/engine.py +976 -0
  61. modelable/llm/importers.py +1077 -0
  62. modelable/llm/provenance.py +84 -0
  63. modelable/llm/providers.py +182 -0
  64. modelable/llm/qa.py +126 -0
  65. modelable/llm/recommendations.py +33 -0
  66. modelable/llm/redaction.py +19 -0
  67. modelable/llm/render.py +279 -0
  68. modelable/llm/update_plan.py +101 -0
  69. modelable/llm/validation_help.py +10 -0
  70. modelable/lsp/__init__.py +3 -0
  71. modelable/lsp/__main__.py +4 -0
  72. modelable/lsp/code_actions.py +210 -0
  73. modelable/lsp/completion.py +480 -0
  74. modelable/lsp/definition.py +343 -0
  75. modelable/lsp/diagnostics.py +31 -0
  76. modelable/lsp/document_symbols.py +197 -0
  77. modelable/lsp/federation.py +261 -0
  78. modelable/lsp/folding.py +33 -0
  79. modelable/lsp/formatting.py +64 -0
  80. modelable/lsp/highlight.py +30 -0
  81. modelable/lsp/hover.py +370 -0
  82. modelable/lsp/inlay_hints.py +158 -0
  83. modelable/lsp/references.py +511 -0
  84. modelable/lsp/rename.py +564 -0
  85. modelable/lsp/semantic_tokens.py +412 -0
  86. modelable/lsp/server.py +370 -0
  87. modelable/lsp/workspace.py +83 -0
  88. modelable/lsp/workspace_symbols.py +104 -0
  89. modelable/parser/__init__.py +94 -0
  90. modelable/parser/ir.py +451 -0
  91. modelable/parser/parse.py +47 -0
  92. modelable/parser/transformer.py +798 -0
  93. modelable/parser/wire.py +68 -0
  94. modelable/planner/__init__.py +0 -0
  95. modelable/planner/lineage.py +91 -0
  96. modelable/planner/planner.py +134 -0
  97. modelable/planner/plans.py +122 -0
  98. modelable/py.typed +0 -0
  99. modelable/registry/__init__.py +9 -0
  100. modelable/registry/apicurio.py +166 -0
  101. modelable/registry/base.py +18 -0
  102. modelable/registry/factory.py +18 -0
  103. modelable/registry/index.py +419 -0
  104. modelable/registry/local.py +26 -0
  105. modelable/registry/oci.py +22 -0
  106. modelable/registry/resolver.py +213 -0
  107. modelable/registry/schema.sql +119 -0
  108. modelable/registry/signature.py +26 -0
  109. modelable/release.py +125 -0
  110. modelable/runtime/__init__.py +5 -0
  111. modelable/runtime/adapter/__init__.py +17 -0
  112. modelable/runtime/adapter/base.py +18 -0
  113. modelable/runtime/adapter/postgres.py +82 -0
  114. modelable/specs/__init__.py +23 -0
  115. modelable/specs/tracking.py +220 -0
  116. modelable/validation/__init__.py +3 -0
  117. modelable/validation/semantic.py +659 -0
  118. modelable-1.0.0.dist-info/METADATA +61 -0
  119. modelable-1.0.0.dist-info/RECORD +122 -0
  120. modelable-1.0.0.dist-info/WHEEL +4 -0
  121. modelable-1.0.0.dist-info/entry_points.txt +2 -0
  122. 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