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.
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
modelable/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from modelable import _pydantic_py314_compat as _ # noqa: F401
modelable/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from modelable.cli import cli
2
+
3
+ cli()
@@ -0,0 +1,31 @@
1
+ """
2
+ Compatibility shim for pydantic 2.x on Python 3.14.
3
+
4
+ pydantic 2.x calls typing._eval_type(..., prefer_fwd_module=True) when it
5
+ detects Python 3.14, but Python 3.14 RC2 renamed that parameter to
6
+ parent_fwdref. This shim patches typing._eval_type to accept both names so
7
+ pydantic model definitions succeed without modification.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import typing
13
+
14
+ _real_eval_type = typing._eval_type # type: ignore[attr-defined]
15
+
16
+
17
+ def _compat_eval_type(
18
+ t: object,
19
+ globalns: object = None,
20
+ localns: object = None,
21
+ type_params: object = None,
22
+ *,
23
+ prefer_fwd_module: object = None,
24
+ **kwargs: object,
25
+ ) -> object:
26
+ if prefer_fwd_module is not None:
27
+ kwargs.setdefault("parent_fwdref", prefer_fwd_module)
28
+ return _real_eval_type(t, globalns, localns, type_params=type_params, **kwargs)
29
+
30
+
31
+ typing._eval_type = _compat_eval_type # type: ignore[attr-defined]
modelable/cli.py ADDED
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+
5
+ from modelable.commands.apicurio import register_apicurio_commands
6
+ from modelable.commands.codegen import register_codegen_commands
7
+ from modelable.commands.compile import register_compile_commands
8
+ from modelable.commands.create import register_create_commands
9
+ from modelable.commands.diff import register_diff_commands
10
+ from modelable.commands.graph import register_graph_commands
11
+ from modelable.commands.llm import register_llm_commands
12
+ from modelable.commands.lsp import register_lsp_commands
13
+ from modelable.commands.runtime import register_runtime_commands
14
+ from modelable.commands.scenario import register_scenario_commands
15
+ from modelable.commands.spec import register_spec_commands
16
+ from modelable.commands.workspace import register_workspace_commands
17
+
18
+
19
+ @click.group()
20
+ @click.version_option(package_name="modelable", prog_name="modelable")
21
+ def cli() -> None:
22
+ """Modelable domain-owned data model compiler.
23
+
24
+ MVP workflows cover validate, resolve, lineage, diff, compile, docs,
25
+ inspect, codegen, lsp, scenario, create helpers, and Apicurio JSON Schema
26
+ artifact publish/pull.
27
+ """
28
+
29
+
30
+ register_workspace_commands(cli)
31
+ register_compile_commands(cli)
32
+ register_create_commands(cli)
33
+ register_diff_commands(cli)
34
+ register_graph_commands(cli)
35
+ register_lsp_commands(cli)
36
+ register_llm_commands(cli)
37
+ register_codegen_commands(cli)
38
+ register_scenario_commands(cli)
39
+ register_runtime_commands(cli)
40
+ register_apicurio_commands(cli)
41
+ register_spec_commands(cli)
@@ -0,0 +1 @@
1
+ from __future__ import annotations
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from modelable.commands.common import console, load_workspace_or_exit
10
+ from modelable.emitters.json_schema import emit_json_schema
11
+ from modelable.registry.apicurio import ApicurioArtifact, ApicurioRegistryClient, ApicurioRegistryError
12
+
13
+
14
+ def register_apicurio_commands(cli_group: click.Group) -> None:
15
+ cli_group.add_command(publish)
16
+ cli_group.add_command(pull)
17
+
18
+
19
+ @click.group()
20
+ def publish() -> None:
21
+ """Publish generated artifacts to external registries."""
22
+
23
+
24
+ @publish.command(name="apicurio")
25
+ @click.argument("source", type=click.Path(exists=True, path_type=Path))
26
+ @click.option("--url", required=True, help="Apicurio Registry base URL or /apis/registry/v3 URL.")
27
+ @click.option("--group", default="default", show_default=True, help="Apicurio artifact group.")
28
+ @click.option("--token", default=None, help="Bearer token. Defaults to MODELABLE_APICURIO_TOKEN.")
29
+ @click.option("--dry-run", is_flag=True, help="List artifacts without publishing.")
30
+ def publish_apicurio(source: Path, url: str, group: str, token: str | None, dry_run: bool) -> None:
31
+ """Publish JSON Schema artifacts generated from SOURCE to Apicurio Registry."""
32
+ workspace = load_workspace_or_exit(source)
33
+ emitted = emit_json_schema(workspace, Path(".modelable/apicurio"))
34
+ artifacts = [
35
+ ApicurioArtifact(
36
+ artifact_id=artifact.artifact_id,
37
+ version=artifact.artifact_id.rsplit(".v", 1)[1],
38
+ content=artifact.content,
39
+ )
40
+ for artifact in emitted
41
+ if isinstance(artifact.content, dict)
42
+ ]
43
+
44
+ if dry_run:
45
+ console.print(f"[yellow]DRY RUN[/yellow] would publish {len(artifacts)} JSON Schema artifact(s) to {url}")
46
+ for artifact in artifacts:
47
+ console.print(f"- {artifact.artifact_id} (group={group}, version={artifact.version})")
48
+ sys.exit(0)
49
+
50
+ client = ApicurioRegistryClient(url, token=token or os.getenv("MODELABLE_APICURIO_TOKEN"))
51
+ try:
52
+ for artifact in artifacts:
53
+ client.publish_json_schema(artifact, group=group)
54
+ console.print(f"[green]OK[/green] published {group}/{artifact.artifact_id}@{artifact.version}")
55
+ except ApicurioRegistryError as exc:
56
+ console.print(f"[red]ERROR[/red] {exc}")
57
+ sys.exit(1)
58
+
59
+ if not artifacts:
60
+ console.print("[yellow]No JSON Schema artifacts generated.[/yellow]")
61
+ sys.exit(0)
62
+
63
+
64
+ @click.group()
65
+ def pull() -> None:
66
+ """Pull generated artifacts from external registries."""
67
+
68
+
69
+ @pull.command(name="apicurio")
70
+ @click.argument("ref")
71
+ @click.option("--url", required=True, help="Apicurio Registry base URL or /apis/registry/v3 URL.")
72
+ @click.option("--group", default="default", show_default=True, help="Apicurio artifact group.")
73
+ @click.option("--out", "out_dir", type=click.Path(path_type=Path), default=Path("./dist/jsonschema"))
74
+ @click.option("--token", default=None, help="Bearer token. Defaults to MODELABLE_APICURIO_TOKEN.")
75
+ def pull_apicurio(ref: str, url: str, group: str, out_dir: Path, token: str | None) -> None:
76
+ """Pull a JSON Schema artifact by Modelable REF from Apicurio Registry."""
77
+ client = ApicurioRegistryClient(url, token=token or os.getenv("MODELABLE_APICURIO_TOKEN"))
78
+ try:
79
+ path = client.pull_json_schema(ref, group=group, out_dir=out_dir)
80
+ except ApicurioRegistryError as exc:
81
+ console.print(f"[red]ERROR[/red] {exc}")
82
+ sys.exit(1)
83
+ console.print(f"[green]OK[/green] wrote {path}")
84
+ sys.exit(0)
@@ -0,0 +1,241 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+ from rich.console import Console
5
+
6
+ from modelable.emitters.shapes import type_shape_catalog
7
+ from modelable.emitters.targets import (
8
+ get_codegen_target,
9
+ list_implemented_codegen_targets,
10
+ )
11
+ from modelable.emitters.targets import (
12
+ list_codegen_targets as _list_codegen_targets,
13
+ )
14
+
15
+ console = Console()
16
+
17
+
18
+ def register_codegen_commands(cli_group: click.Group) -> None:
19
+ cli_group.add_command(codegen)
20
+
21
+
22
+ @click.group()
23
+ def codegen() -> None:
24
+ """Explore supported code generation formats and type mappings."""
25
+
26
+
27
+ @codegen.command(name="formats")
28
+ def formats() -> None:
29
+ """List supported compilation targets."""
30
+ console.print("Supported code generation formats:", markup=False)
31
+ for entry in _list_codegen_targets():
32
+ console.print(f"- {entry.name}: {entry.description} [{entry.status}, {entry.kind}]", markup=False)
33
+
34
+
35
+ @codegen.command(name="types")
36
+ @click.option(
37
+ "--format",
38
+ "format_name",
39
+ type=click.Choice([entry.name for entry in list_implemented_codegen_targets()]),
40
+ default="typescript",
41
+ show_default=True,
42
+ help="Target format to describe.",
43
+ )
44
+ def types(format_name: str) -> None:
45
+ """Show the field-type mapping for a target format."""
46
+ entry = get_codegen_target(format_name)
47
+ console.print("Target inventory:", markup=False)
48
+ for target in _list_codegen_targets():
49
+ console.print(f"- {target.name}: {target.status} [{target.kind}]", markup=False)
50
+ console.print("", markup=False)
51
+ console.print(f"{format_name} type mappings", markup=False)
52
+ console.print(f"{entry.description}", markup=False)
53
+ console.print("", markup=False)
54
+ console.print("Type shape catalog:", markup=False)
55
+ for label, shape, note in type_shape_catalog():
56
+ line = f"- {label}: {shape.describe()}"
57
+ if note:
58
+ line += f" ({note})"
59
+ console.print(line, markup=False)
60
+ console.print("", markup=False)
61
+
62
+ for source_type, target_type, note in _type_mappings_for(format_name):
63
+ line = f"- {source_type} -> {target_type}"
64
+ if note:
65
+ line += f" ({note})"
66
+ console.print(line, markup=False)
67
+
68
+
69
+ def list_codegen_targets() -> list[dict[str, object]]:
70
+ return [
71
+ {
72
+ "name": target.name,
73
+ "description": target.description,
74
+ "status": target.status,
75
+ "kind": target.kind,
76
+ "default_out_dir": str(target.default_out_dir) if target.default_out_dir is not None else None,
77
+ }
78
+ for target in _list_codegen_targets()
79
+ ]
80
+
81
+
82
+ def _type_mappings_for(format_name: str) -> list[tuple[str, str, str | None]]:
83
+ if format_name == "json-schema":
84
+ return [
85
+ ("string", '{"type":"string"}', None),
86
+ ("bool", '{"type":"boolean"}', None),
87
+ ("int", '{"type":"integer","format":"int64"}', None),
88
+ ("float", '{"type":"number"}', None),
89
+ ("uuid", '{"type":"string","format":"uuid"}', None),
90
+ ("timestamp", '{"type":"string","format":"date-time"}', None),
91
+ ("date", '{"type":"string","format":"date"}', None),
92
+ ("time", '{"type":"string","format":"time"}', None),
93
+ ("duration", '{"type":"string","format":"duration"}', None),
94
+ ("binary", '{"type":"string","contentEncoding":"base64"}', None),
95
+ ("decimal(p, s)", '{"type":"string","pattern":"^-?\\d+(\\.\\d+)?$"}', None),
96
+ ("array<T>", '{"type":"array","items":<T>}', None),
97
+ ("map<K, V>", '{"type":"object","additionalProperties":<V>}', None),
98
+ ("ref<T>", '{"type":"string","x-modelable-ref":"T"}', None),
99
+ ("enum(...)", '{"type":"string","enum":[...]}', None),
100
+ ("object { ... }", '{"type":"object","properties":{...}}', None),
101
+ ("named", '{"type":"object","x-modelable-field":{"namedType":"Name"}}', None),
102
+ ]
103
+ if format_name == "csharp":
104
+ return [
105
+ ("string", "string", "optional fields use string?"),
106
+ ("bool", "bool", "optional fields use bool?"),
107
+ ("int", "int", "optional fields use int?"),
108
+ ("float", "double", "optional fields use double?"),
109
+ ("uuid", "Guid", "optional fields use Guid?"),
110
+ ("timestamp", "DateTime", "optional fields use DateTime?"),
111
+ ("date", "DateOnly", "optional fields use DateOnly?"),
112
+ ("time", "TimeOnly", "optional fields use TimeOnly?"),
113
+ ("duration", "TimeSpan", "optional fields use TimeSpan?"),
114
+ ("binary", "byte[]", "optional fields use byte[]?"),
115
+ ("decimal(p, s)", "decimal", "optional fields use decimal?"),
116
+ ("array<T>", "List<T>", None),
117
+ ("map<K, V>", "Dictionary<string, V>", None),
118
+ ("ref<T>", "string", "references compile to reference strings"),
119
+ ("enum(...)", "string", None),
120
+ ("object { ... }", "{ ... }", "inline objects become nested records"),
121
+ ("named", "Name", None),
122
+ ]
123
+ if format_name == "java":
124
+ return [
125
+ ("string", "String", "optional fields use Optional<String>"),
126
+ ("bool", "Boolean", "optional fields use Optional<Boolean>"),
127
+ ("int", "Long", "optional fields use Optional<Long>"),
128
+ ("float", "Double", "optional fields use Optional<Double>"),
129
+ ("uuid", "UUID", "optional fields use Optional<UUID>"),
130
+ ("timestamp", "Instant", "optional fields use Optional<Instant>"),
131
+ ("date", "LocalDate", "optional fields use Optional<LocalDate>"),
132
+ ("time", "LocalTime", "optional fields use Optional<LocalTime>"),
133
+ ("duration", "Duration", "optional fields use Optional<Duration>"),
134
+ ("binary", "byte[]", "optional fields use Optional<byte[]>"),
135
+ ("decimal(p, s)", "BigDecimal", "optional fields use Optional<BigDecimal>"),
136
+ ("array<T>", "List<T>", None),
137
+ ("map<K, V>", "Map<String, V>", None),
138
+ ("ref<T>", "String", "references compile to reference strings"),
139
+ ("enum(...)", "String", None),
140
+ ("object { ... }", "{ ... }", "inline objects become nested records"),
141
+ ("named", "NameV1", None),
142
+ ]
143
+ if format_name == "python":
144
+ return [
145
+ ("string", "str", "optional fields use Optional[str]"),
146
+ ("bool", "bool", "optional fields use Optional[bool]"),
147
+ ("int", "int", "optional fields use Optional[int]"),
148
+ ("float", "float", "optional fields use Optional[float]"),
149
+ ("uuid", "UUID", "optional fields use Optional[UUID]"),
150
+ ("timestamp", "datetime", "optional fields use Optional[datetime]"),
151
+ ("date", "date", "optional fields use Optional[date]"),
152
+ ("time", "time", "optional fields use Optional[time]"),
153
+ ("duration", "timedelta", "optional fields use Optional[timedelta]"),
154
+ ("binary", "bytes", "optional fields use Optional[bytes]"),
155
+ ("decimal(p, s)", "Decimal", "optional fields use Optional[Decimal]"),
156
+ ("array<T>", "list[T]", None),
157
+ ("map<K, V>", "dict[str, V]", None),
158
+ ("ref<T>", "str", "references compile to reference strings"),
159
+ ("enum(...)", "str", None),
160
+ ("object { ... }", "{ ... }", "inline objects become nested dataclasses"),
161
+ ("named", "Name", None),
162
+ ]
163
+ if format_name == "rust":
164
+ return [
165
+ ("string", "String", "optional fields use Option<String>"),
166
+ ("bool", "bool", "optional fields use Option<bool>"),
167
+ ("int", "i64", "optional fields use Option<i64>"),
168
+ ("float", "f64", "optional fields use Option<f64>"),
169
+ ("uuid", "String", "optional fields use Option<String>"),
170
+ ("timestamp", "String", "optional fields use Option<String>"),
171
+ ("date", "String", "optional fields use Option<String>"),
172
+ ("time", "String", "optional fields use Option<String>"),
173
+ ("duration", "String", "optional fields use Option<String>"),
174
+ ("binary", "Vec<u8>", "optional fields use Option<Vec<u8>>"),
175
+ ("decimal(p, s)", "String", "optional fields use Option<String>"),
176
+ ("array<T>", "Vec<T>", None),
177
+ ("map<K, V>", "HashMap<String, V>", None),
178
+ ("ref<T>", "String", "references compile to reference strings"),
179
+ ("enum(...)", "String", None),
180
+ ("object { ... }", "{ ... }", "inline objects become nested structs"),
181
+ ("named", "Name", None),
182
+ ]
183
+ if format_name == "go":
184
+ return [
185
+ ("string", "string", "optional fields use *string"),
186
+ ("bool", "bool", "optional fields use *bool"),
187
+ ("int", "int64", "optional fields use *int64"),
188
+ ("float", "float64", "optional fields use *float64"),
189
+ ("uuid", "string", "optional fields use *string"),
190
+ ("timestamp", "time.Time", "optional fields use *time.Time"),
191
+ ("date", "time.Time", "optional fields use *time.Time"),
192
+ ("time", "time.Time", "optional fields use *time.Time"),
193
+ ("duration", "time.Duration", "optional fields use *time.Duration"),
194
+ ("binary", "[]byte", "optional fields use *[]byte"),
195
+ ("decimal(p, s)", "string", "optional fields use *string"),
196
+ ("array<T>", "[]T", None),
197
+ ("map<K, V>", "map[string]V", None),
198
+ ("ref<T>", "string", "references compile to reference strings"),
199
+ ("enum(...)", "string", None),
200
+ ("object { ... }", "{ ... }", "inline objects become nested structs"),
201
+ ("named", "Name", None),
202
+ ]
203
+ if format_name == "markdown":
204
+ return [
205
+ ("string", "string", "rendered as canonical .mdl text"),
206
+ ("bool", "bool", "rendered as canonical .mdl text"),
207
+ ("int", "int", "rendered as canonical .mdl text"),
208
+ ("float", "float", "rendered as canonical .mdl text"),
209
+ ("uuid", "uuid", "rendered as canonical .mdl text"),
210
+ ("timestamp", "timestamp", "rendered as canonical .mdl text"),
211
+ ("date", "date", "rendered as canonical .mdl text"),
212
+ ("time", "time", "rendered as canonical .mdl text"),
213
+ ("duration", "duration", "rendered as canonical .mdl text"),
214
+ ("binary", "binary", "rendered as canonical .mdl text"),
215
+ ("decimal(p, s)", "decimal(p, s)", "rendered as canonical .mdl text"),
216
+ ("array<T>", "array<T>", "rendered as canonical .mdl text"),
217
+ ("map<K, V>", "map<K, V>", "rendered as canonical .mdl text"),
218
+ ("ref<T>", "ref<T>", "rendered as canonical .mdl text"),
219
+ ("enum(...)", "enum(...)", "rendered as canonical .mdl text"),
220
+ ("object { ... }", "object { ... }", "rendered as canonical .mdl text"),
221
+ ("named", "Name", "rendered as canonical .mdl text"),
222
+ ]
223
+ return [
224
+ ("string", "string", None),
225
+ ("bool", "boolean", None),
226
+ ("int", "number", None),
227
+ ("float", "number", None),
228
+ ("uuid", "string", "preserves uuid format as a string"),
229
+ ("timestamp", "string", "preserves date-time semantics"),
230
+ ("date", "string", "preserves date semantics"),
231
+ ("time", "string", "preserves time semantics"),
232
+ ("duration", "string", "preserves duration semantics"),
233
+ ("binary", "string", "base64-encoded"),
234
+ ("decimal(p, s)", "string", "decimal precision and scale encoded as text"),
235
+ ("array<T>", "T[]", None),
236
+ ("map<K, V>", "Record<string, V>", None),
237
+ ("ref<T>", "string", "reference string"),
238
+ ("enum(...)", "'a' | 'b' | ...", None),
239
+ ("object { ... }", "{ ... }", None),
240
+ ("named", "Name", None),
241
+ ]
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from rich.console import Console
7
+
8
+ from modelable.compiler.workspace import load_workspace
9
+ from modelable.diagnostics.model import render_diagnostic
10
+ from modelable.parser.ir import ParseError, VersionPinned
11
+
12
+ console = Console()
13
+
14
+
15
+ def load_workspace_or_exit(path: Path):
16
+ try:
17
+ workspace = load_workspace(path)
18
+ except FileNotFoundError:
19
+ console.print("[yellow]No .mdl files found.[/yellow]")
20
+ sys.exit(0)
21
+ except ParseError as exc:
22
+ console.print(f"[red]ERROR[/red] {render_diagnostic(exc.diagnostic(path=path))}")
23
+ sys.exit(1)
24
+
25
+ if workspace.errors:
26
+ for diagnostic in workspace.errors:
27
+ console.print(f"[red]ERROR[/red] {render_diagnostic(diagnostic)}", soft_wrap=True)
28
+ sys.exit(1)
29
+
30
+ return workspace
31
+
32
+
33
+ def render_version_spec(version_spec) -> str:
34
+ kind = getattr(version_spec, "kind", None)
35
+ if kind == "exact":
36
+ return str(version_spec.version)
37
+ if kind == "range":
38
+ return f">={version_spec.min_inclusive}<{version_spec.max_exclusive}"
39
+ if kind == "min":
40
+ return f">={version_spec.min_inclusive}"
41
+ if isinstance(version_spec, VersionPinned):
42
+ return f"{version_spec.version}#{version_spec.content_hash}"
43
+ return "?"