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,798 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from lark import Transformer
6
+
7
+ from modelable.parser.ir import (
8
+ AccessBlock,
9
+ AccessGrant,
10
+ AiConfig,
11
+ AnnClassification,
12
+ AnnCustom,
13
+ AnnDeprecated,
14
+ AnnKey,
15
+ AnnLatestBefore,
16
+ AnnLatestOnly,
17
+ AnnOwner,
18
+ AnnPii,
19
+ AnnPitCutoff,
20
+ AnnServer,
21
+ AnnWire,
22
+ ArrayType,
23
+ AutoProjectionDecl,
24
+ AutoProjectionTarget,
25
+ BindingDef,
26
+ ChangeKind,
27
+ ComputedMapping,
28
+ DecimalType,
29
+ DirectMapping,
30
+ DomainDef,
31
+ EnumType,
32
+ FieldDef,
33
+ FieldMapping,
34
+ FieldType,
35
+ GenerateTarget,
36
+ JoinRef,
37
+ MapType,
38
+ MdlFile,
39
+ ModelKind,
40
+ ModelVersion,
41
+ NamedType,
42
+ ObjectType,
43
+ PrimitiveType,
44
+ ProjectionField,
45
+ ProjectionVersion,
46
+ RefType,
47
+ SourceRef,
48
+ VersionExact,
49
+ VersionMin,
50
+ VersionPinned,
51
+ VersionRange,
52
+ WireTargetHint,
53
+ WorkspaceDef,
54
+ )
55
+
56
+ ANNOTATION_TYPES = (
57
+ AnnKey,
58
+ AnnPii,
59
+ AnnClassification,
60
+ AnnDeprecated,
61
+ AnnOwner,
62
+ AnnServer,
63
+ AnnWire,
64
+ AnnPitCutoff,
65
+ AnnLatestBefore,
66
+ AnnLatestOnly,
67
+ AnnCustom,
68
+ )
69
+
70
+
71
+ def _str(value: object) -> str:
72
+ text = str(value)
73
+ if len(text) >= 2 and text[0] == '"' and text[-1] == '"':
74
+ return text[1:-1]
75
+ return text
76
+
77
+
78
+ class MdlTransformer(Transformer[list[object], Any]):
79
+ def start(self, items: list[object]) -> MdlFile:
80
+ domains: list[DomainDef] = []
81
+ bindings: list[BindingDef] = []
82
+ workspace: WorkspaceDef | None = None
83
+ for item in items:
84
+ if isinstance(item, DomainDef):
85
+ domains.append(item)
86
+ elif isinstance(item, BindingDef):
87
+ bindings.append(item)
88
+ elif isinstance(item, WorkspaceDef):
89
+ workspace = item
90
+ return MdlFile(domains=domains, bindings=bindings, workspace=workspace)
91
+
92
+ def statement(self, items: list[object]) -> object:
93
+ return items[0]
94
+
95
+ def domain_decl(self, items: list[object]) -> DomainDef:
96
+ name = _str(items[0])
97
+ owner = None
98
+ contact = None
99
+ description = None
100
+ models: dict[str, list[ModelVersion]] = {}
101
+ projections: dict[str, list[ProjectionVersion]] = {}
102
+ auto_projections: list[AutoProjectionDecl] = []
103
+ generate_targets: list[GenerateTarget] = []
104
+
105
+ for tag, value in [item for item in items[1:] if isinstance(item, tuple)]:
106
+ if tag == "owner":
107
+ owner = value
108
+ elif tag == "contact":
109
+ contact = value
110
+ elif tag == "description":
111
+ description = value
112
+ elif tag == "model":
113
+ model_name, model_version = value
114
+ models.setdefault(model_name, []).append(model_version)
115
+ elif tag == "projection":
116
+ projection_name, projection_version = value
117
+ projections.setdefault(projection_name, []).append(projection_version)
118
+ elif tag == "auto_projection":
119
+ auto_projections.append(value)
120
+ elif tag == "generate":
121
+ generate_targets = value
122
+
123
+ return DomainDef(
124
+ name=name,
125
+ owner=owner,
126
+ contact=contact,
127
+ description=description,
128
+ models=models,
129
+ projections=projections,
130
+ auto_projections=auto_projections,
131
+ generate_targets=generate_targets,
132
+ )
133
+
134
+ def domain_name(self, items: list[object]) -> str:
135
+ return _str(items[0])
136
+
137
+ def domain_item(self, items: list[object]) -> object:
138
+ return items[0]
139
+
140
+ def owner_attr(self, items: list[object]) -> tuple[str, str]:
141
+ return ("owner", _str(items[0]))
142
+
143
+ def contact_attr(self, items: list[object]) -> tuple[str, str]:
144
+ return ("contact", _str(items[0]))
145
+
146
+ def desc_attr(self, items: list[object]) -> tuple[str, str]:
147
+ return ("description", _str(items[0]))
148
+
149
+ def model_decl(self, items: list[object]) -> tuple[str, tuple[str, ModelVersion]]:
150
+ annotations = [item for item in items if isinstance(item, ANNOTATION_TYPES)]
151
+ items = [item for item in items if not isinstance(item, ANNOTATION_TYPES)]
152
+ name = str(items[1])
153
+ header = items[2] if len(items) > 2 and isinstance(items[2], tuple) and items[2][0] == "model_header" else None
154
+ body_start = 3 if header is not None else 2
155
+ version = header[1] if header is not None else 0
156
+ change_kind = header[2] if header is not None else ChangeKind.additive
157
+ has_change_kind = header[3] if header is not None else False
158
+ access = next((item for item in items[body_start:] if isinstance(item, AccessBlock)), None)
159
+ model_kind = items[0] if isinstance(items[0], ModelKind) else ModelKind.entity
160
+ model_version = ModelVersion(
161
+ model_kind=model_kind,
162
+ version=int(version) if isinstance(version, (int, str)) else 0,
163
+ change_kind=change_kind if isinstance(change_kind, ChangeKind) else ChangeKind.additive,
164
+ fields=[item for item in items[body_start:] if isinstance(item, FieldDef)],
165
+ access=access,
166
+ has_version_header=header is not None,
167
+ has_change_kind=has_change_kind,
168
+ annotations=annotations,
169
+ )
170
+ return ("model", (name, model_version))
171
+
172
+ def model_header(self, items: list[object]) -> tuple[str, int, ChangeKind, bool]:
173
+ if len(items) == 1 and isinstance(items[0], tuple):
174
+ h = items[0]
175
+ return (
176
+ "model_header",
177
+ int(h[1]) if len(h) > 1 else 0,
178
+ h[2] if len(h) > 2 and isinstance(h[2], ChangeKind) else ChangeKind.additive,
179
+ True,
180
+ )
181
+ if len(items) == 2:
182
+ v = int(items[0]) if isinstance(items[0], (int, str)) else 0
183
+ ck = items[1] if isinstance(items[1], ChangeKind) else ChangeKind.additive
184
+ return ("model_header", v, ck, True)
185
+ return ("model_header", 0, ChangeKind.additive, False)
186
+
187
+ def model_change(self, items: list[object]) -> object:
188
+ return items[0]
189
+
190
+ def model_body_item(self, items: list[object]) -> object:
191
+ return items[0]
192
+
193
+ def mk_entity(self, _items: list[object]) -> ModelKind:
194
+ return ModelKind.entity
195
+
196
+ def mk_aggregate(self, _items: list[object]) -> ModelKind:
197
+ return ModelKind.aggregate
198
+
199
+ def mk_event(self, _items: list[object]) -> ModelKind:
200
+ return ModelKind.event
201
+
202
+ def mk_value(self, _items: list[object]) -> ModelKind:
203
+ return ModelKind.value
204
+
205
+ def ck_additive(self, _items: list[object]) -> ChangeKind:
206
+ return ChangeKind.additive
207
+
208
+ def ck_breaking(self, _items: list[object]) -> ChangeKind:
209
+ return ChangeKind.breaking
210
+
211
+ def field_decl(self, items: list[object]) -> FieldDef:
212
+ annotations = [item for item in items if isinstance(item, ANNOTATION_TYPES)]
213
+ rest = [item for item in items if not isinstance(item, ANNOTATION_TYPES)]
214
+ default = next((item[1] for item in rest if isinstance(item, tuple) and item[0] == "default"), None)
215
+ type_item = next(
216
+ (
217
+ item
218
+ for item in rest
219
+ if not isinstance(item, str) and not (isinstance(item, tuple) and item[0] == "default")
220
+ ),
221
+ None,
222
+ )
223
+ return FieldDef(
224
+ name=str(rest[0]),
225
+ optional=any(item == "?" for item in rest),
226
+ type=type_item
227
+ if isinstance(
228
+ type_item, (PrimitiveType, DecimalType, ArrayType, MapType, RefType, EnumType, ObjectType, NamedType)
229
+ )
230
+ else PrimitiveType(kind="string"),
231
+ default=default,
232
+ annotations=annotations,
233
+ )
234
+
235
+ def optional_marker(self, _items: list[object]) -> str:
236
+ return "?"
237
+
238
+ def field_default(self, items: list[object]) -> tuple[str, str]:
239
+ return ("default", str(items[0]).strip())
240
+
241
+ def ann_key(self, _items: list[object]) -> AnnKey:
242
+ return AnnKey()
243
+
244
+ def ann_pii(self, _items: list[object]) -> AnnPii:
245
+ return AnnPii()
246
+
247
+ def ann_classification(self, items: list[object]) -> AnnClassification:
248
+ return AnnClassification(level=_str(items[0]))
249
+
250
+ def ann_deprecated(self, items: list[object]) -> AnnDeprecated:
251
+ return AnnDeprecated(replaced_by=_str(items[0]))
252
+
253
+ def ann_owner(self, items: list[object]) -> AnnOwner:
254
+ return AnnOwner(team=_str(items[0]))
255
+
256
+ def ann_server(self, _items: list[object]) -> AnnServer:
257
+ return AnnServer()
258
+
259
+ def ann_wire(self, items: list[object]) -> AnnWire:
260
+ targets: dict[str, WireTargetHint] = {}
261
+ for target, modifier, value in items:
262
+ hint = targets.get(target, WireTargetHint())
263
+ if modifier is None:
264
+ if hint.encoding is not None and hint.encoding != value:
265
+ raise ValueError(
266
+ f"conflicting wire encodings for target '{target}': {hint.encoding!r} vs {value!r}"
267
+ )
268
+ hint.encoding = value
269
+ elif modifier == "type":
270
+ if hint.type is not None and hint.type != value:
271
+ raise ValueError(f"conflicting wire types for target '{target}': {hint.type!r} vs {value!r}")
272
+ hint.type = value
273
+ elif modifier == "case":
274
+ if hint.case is not None and hint.case != value:
275
+ raise ValueError(f"conflicting wire cases for target '{target}': {hint.case!r} vs {value!r}")
276
+ hint.case = value
277
+ elif modifier == "overrides":
278
+ overlap = sorted(set(hint.overrides) & set(value))
279
+ for key in overlap:
280
+ if hint.overrides[key] != value[key]:
281
+ raise ValueError(
282
+ f"conflicting wire override for target '{target}' member '{key}': "
283
+ f"{hint.overrides[key]!r} vs {value[key]!r}"
284
+ )
285
+ hint.overrides.update(value)
286
+ elif modifier == "fieldCase":
287
+ if hint.field_case is not None and hint.field_case != value:
288
+ raise ValueError(
289
+ f"conflicting wire field cases for target '{target}': {hint.field_case!r} vs {value!r}"
290
+ )
291
+ hint.field_case = value
292
+ else:
293
+ raise ValueError(f"unsupported wire modifier: {modifier}")
294
+ targets[target] = hint
295
+ return AnnWire(targets=targets)
296
+
297
+ def ann_pit_cutoff(self, items: list[object]) -> AnnPitCutoff:
298
+ return AnnPitCutoff(expression=str(items[0]).strip())
299
+
300
+ def ann_latest_before(self, items: list[object]) -> AnnLatestBefore:
301
+ return AnnLatestBefore(expression=str(items[0]).strip())
302
+
303
+ def ann_latest_only(self, _items: list[object]) -> AnnLatestOnly:
304
+ return AnnLatestOnly()
305
+
306
+ def ann_custom(self, items: list[object]) -> AnnCustom:
307
+ name = str(items[0])
308
+ expression = str(items[1]).strip() if len(items) > 1 else None
309
+ return AnnCustom(name=name, expression=expression)
310
+
311
+ def annotation(self, items: list[object]) -> object:
312
+ return items[0]
313
+
314
+ def wire_option(self, items: list[object]) -> tuple[object, object, object]:
315
+ target, modifier = items[0]
316
+ return target, modifier, items[1]
317
+
318
+ def wire_key(self, items: list[object]) -> tuple[str, str | None]:
319
+ if len(items) == 1:
320
+ return str(items[0]), None
321
+ return str(items[0]), str(items[1])
322
+
323
+ def wire_string(self, items: list[object]) -> str:
324
+ return _str(items[0])
325
+
326
+ def wire_value(self, items: list[object]) -> object:
327
+ return items[0]
328
+
329
+ def wire_map(self, items: list[object]) -> dict[str, object]:
330
+ return dict(items)
331
+
332
+ def wire_map_item(self, items: list[object]) -> tuple[str, str]:
333
+ return str(items[0]), _str(items[1])
334
+
335
+ def type_expr(self, items: list[object]) -> FieldType:
336
+ item = items[0]
337
+ if isinstance(item, str):
338
+ return NamedType(name=item)
339
+ return item # type: ignore[return-value]
340
+
341
+ def pt_string(self, _items: list[object]) -> PrimitiveType:
342
+ return PrimitiveType(kind="string")
343
+
344
+ def pt_int(self, _items: list[object]) -> PrimitiveType:
345
+ return PrimitiveType(kind="int")
346
+
347
+ def pt_float(self, _items: list[object]) -> PrimitiveType:
348
+ return PrimitiveType(kind="float")
349
+
350
+ def pt_bool(self, _items: list[object]) -> PrimitiveType:
351
+ return PrimitiveType(kind="bool")
352
+
353
+ def pt_date(self, _items: list[object]) -> PrimitiveType:
354
+ return PrimitiveType(kind="date")
355
+
356
+ def pt_time(self, _items: list[object]) -> PrimitiveType:
357
+ return PrimitiveType(kind="time")
358
+
359
+ def pt_timestamp(self, _items: list[object]) -> PrimitiveType:
360
+ return PrimitiveType(kind="timestamp")
361
+
362
+ def pt_uuid(self, _items: list[object]) -> PrimitiveType:
363
+ return PrimitiveType(kind="uuid")
364
+
365
+ def pt_duration(self, _items: list[object]) -> PrimitiveType:
366
+ return PrimitiveType(kind="duration")
367
+
368
+ def pt_binary(self, _items: list[object]) -> PrimitiveType:
369
+ return PrimitiveType(kind="binary")
370
+
371
+ def pt_json(self, _items: list[object]) -> PrimitiveType:
372
+ return PrimitiveType(kind="json")
373
+
374
+ def primitive_type(self, items: list[object]) -> object:
375
+ return items[0]
376
+
377
+ def decimal_type(self, items: list[object]) -> DecimalType:
378
+ return DecimalType(precision=int(items[0]), scale=int(items[1]))
379
+
380
+ def enum_type(self, items: list[object]) -> EnumType:
381
+ return EnumType(values=[str(item) for item in items])
382
+
383
+ def array_type(self, items: list[object]) -> ArrayType:
384
+ return ArrayType(item=items[0])
385
+
386
+ def map_type(self, items: list[object]) -> MapType:
387
+ return MapType(key=items[0], value=items[1])
388
+
389
+ def ref_type(self, items: list[object]) -> RefType:
390
+ return RefType(target=str(items[0]))
391
+
392
+ def object_type(self, items: list[object]) -> ObjectType:
393
+ return ObjectType(fields=[item for item in items if isinstance(item, FieldDef)])
394
+
395
+ def dotted_ref(self, items: list[object]) -> str:
396
+ return ".".join(str(item) for item in items)
397
+
398
+ def IDENT(self, token: object) -> str: # noqa: N802
399
+ return str(token)
400
+
401
+ def projection_decl(self, items: list[object]) -> tuple[str, tuple[str, ProjectionVersion]]:
402
+ annotations = [item for item in items if isinstance(item, ANNOTATION_TYPES)]
403
+ items = [item for item in items if not isinstance(item, ANNOTATION_TYPES)]
404
+ source_index = next(
405
+ (
406
+ i
407
+ for i, item in enumerate(items[2:], start=2)
408
+ if isinstance(item, tuple) and len(item) == 4 and isinstance(item[0], SourceRef)
409
+ ),
410
+ None,
411
+ )
412
+ if source_index is None:
413
+ source = SourceRef(model="", version=VersionExact(version=0), alias="", where=None)
414
+ joins: list[JoinRef] = []
415
+ where = None
416
+ group_by: list[str] = []
417
+ body_start = 2
418
+ else:
419
+ source, joins, where, group_by = items[source_index]
420
+ body_start = source_index + 1
421
+ access = next((item for item in items[body_start:] if isinstance(item, AccessBlock)), None)
422
+ projection_version = ProjectionVersion(
423
+ version=int(items[1]),
424
+ source=source,
425
+ joins=joins,
426
+ where=where,
427
+ group_by=group_by,
428
+ fields=[item for item in items[body_start:] if isinstance(item, ProjectionField)],
429
+ access=access,
430
+ annotations=annotations,
431
+ )
432
+ return ("projection", (str(items[0]), projection_version))
433
+
434
+ def join_prefix(self, items):
435
+ if len(items) == 5:
436
+ return ("join", "left", str(items[1]), items[2], str(items[3]), str(items[4]).strip())
437
+ return ("join", "inner", str(items[0]), items[1], str(items[2]), str(items[3]).strip())
438
+
439
+ def projection_body_item(self, items):
440
+ return items[0]
441
+
442
+ def projection_source_block(self, items):
443
+ return items[0]
444
+
445
+ def source_clause(self, items):
446
+ joins = [item for item in items[3:] if isinstance(item, JoinRef)]
447
+ where = next((item for item in items[3:] if isinstance(item, str)), None)
448
+ group_by = next((item for item in items[3:] if isinstance(item, list)), [])
449
+ return (
450
+ SourceRef(model=str(items[0]), version=items[1], alias=str(items[2]), where=where),
451
+ joins,
452
+ where,
453
+ group_by,
454
+ )
455
+
456
+ def join_clause(self, items):
457
+ prefix = items[0]
458
+ annotations = [item for item in items[1:] if isinstance(item, ANNOTATION_TYPES)]
459
+ cardinality = next(
460
+ (item[1] for item in items[1:] if isinstance(item, tuple) and item[0] == "cardinality"), None
461
+ )
462
+ return JoinRef(
463
+ model=str(prefix[2]),
464
+ version=prefix[3],
465
+ alias=str(prefix[4]),
466
+ on=prefix[5],
467
+ join_kind=prefix[1],
468
+ cardinality=cardinality,
469
+ annotations=annotations,
470
+ )
471
+
472
+ def where_clause(self, items):
473
+ return str(items[0]).strip()
474
+
475
+ def join_modifier(self, _items):
476
+ return "left"
477
+
478
+ def cardinality_attr(self, items):
479
+ return ("cardinality", str(items[0]))
480
+
481
+ def group_clause(self, items):
482
+ return [str(item) for item in items]
483
+
484
+ def group_item(self, items):
485
+ return str(items[0]).strip()
486
+
487
+ def version_spec(self, items):
488
+ return items[0]
489
+
490
+ def version_exact(self, items):
491
+ return VersionExact(version=int(items[0]))
492
+
493
+ def version_pinned(self, items):
494
+ return VersionPinned(version=int(items[0]), content_hash=str(items[1]))
495
+
496
+ def version_range(self, items):
497
+ return VersionRange(min_inclusive=int(items[0]), max_exclusive=int(items[1]))
498
+
499
+ def version_min(self, items):
500
+ return VersionMin(min_inclusive=int(items[0]))
501
+
502
+ def qualified_field(self, items):
503
+ return f"{items[0]}.{items[1]}"
504
+
505
+ def direct_field(self, items):
506
+ annotations = [item for item in items if isinstance(item, ANNOTATION_TYPES)]
507
+ rest = [item for item in items if not isinstance(item, ANNOTATION_TYPES)]
508
+ source_alias, source_field = str(rest[1]).split(".", 1)
509
+ return ProjectionField(
510
+ name=str(rest[0]),
511
+ mapping=DirectMapping(source_alias=source_alias, source_field=source_field),
512
+ annotations=annotations,
513
+ )
514
+
515
+ def computed_field(self, items):
516
+ annotations = [item for item in items if isinstance(item, ANNOTATION_TYPES)]
517
+ rest = [item for item in items if not isinstance(item, ANNOTATION_TYPES)]
518
+ return ProjectionField(
519
+ name=str(rest[0]),
520
+ mapping=ComputedMapping(expression=str(rest[1]).strip()),
521
+ annotations=annotations,
522
+ )
523
+
524
+ def proj_field(self, items):
525
+ return items[0]
526
+
527
+ def auto_projections_decl(self, items):
528
+ return (
529
+ "auto_projection",
530
+ AutoProjectionDecl(
531
+ model=str(items[0]),
532
+ version=int(items[1]),
533
+ targets=[item for item in items[2:] if isinstance(item, AutoProjectionTarget)],
534
+ ),
535
+ )
536
+
537
+ def access_block(self, items):
538
+ entity = []
539
+ properties: dict[str, list[AccessGrant]] = {}
540
+ for item in items:
541
+ if not isinstance(item, tuple):
542
+ continue
543
+ if item[0] == "entity":
544
+ entity.append(item[1])
545
+ elif item[0] == "property":
546
+ field_name, grant = item[1]
547
+ properties.setdefault(field_name, []).append(grant)
548
+ return AccessBlock(entity=entity, properties=properties)
549
+
550
+ def entity_grant(self, items):
551
+ return ("entity", AccessGrant(principal=str(items[0]), permissions=list(items[1])))
552
+
553
+ def property_grant(self, items):
554
+ field_name = str(items[0])
555
+ return (
556
+ "property",
557
+ (
558
+ field_name,
559
+ AccessGrant(principal=str(items[1]), permissions=list(items[2])),
560
+ ),
561
+ )
562
+
563
+ def access_item(self, items):
564
+ return items[0]
565
+
566
+ def principal(self, items):
567
+ return str(items[0])
568
+
569
+ def permission_list(self, items):
570
+ return [str(item) for item in items]
571
+
572
+ def p_read(self, _items):
573
+ return "read"
574
+
575
+ def p_project(self, _items):
576
+ return "project"
577
+
578
+ def p_subscribe(self, _items):
579
+ return "subscribe"
580
+
581
+ def p_write(self, _items):
582
+ return "write"
583
+
584
+ def p_transfer(self, _items):
585
+ return "transfer"
586
+
587
+ def p_manage_access(self, _items):
588
+ return "manage_access"
589
+
590
+ def p_derive(self, _items):
591
+ return "derive"
592
+
593
+ def p_redact(self, _items):
594
+ return "redact"
595
+
596
+ def auto_projection_item(self, items):
597
+ kind = items[0]
598
+ excluded_fields = []
599
+ excluded_annotations = []
600
+ operations = []
601
+ for option in items[1:]:
602
+ if option is None:
603
+ continue
604
+ opt_kind, opt_values = option
605
+ if opt_kind == "exclude":
606
+ for val in opt_values:
607
+ if isinstance(val, str):
608
+ excluded_fields.append(val)
609
+ else:
610
+ excluded_annotations.append(val)
611
+ elif opt_kind == "on":
612
+ operations.extend(opt_values)
613
+ return AutoProjectionTarget(
614
+ kind=kind,
615
+ excluded_fields=excluded_fields,
616
+ excluded_annotations=excluded_annotations,
617
+ operations=operations,
618
+ )
619
+
620
+ def auto_projection_kind(self, items):
621
+ return items[0]
622
+
623
+ def apk_db(self, _items):
624
+ return "db"
625
+
626
+ def apk_request(self, _items):
627
+ return "request"
628
+
629
+ def apk_reply(self, _items):
630
+ return "reply"
631
+
632
+ def apk_event(self, _items):
633
+ return "event"
634
+
635
+ def auto_projection_option(self, items):
636
+ return items[0]
637
+
638
+ def exclude_option(self, items):
639
+ return ("exclude", [item for item in items if item is not None])
640
+
641
+ def on_option(self, items):
642
+ return ("on", [str(item) for item in items if item is not None])
643
+
644
+ def auto_projection_exclusion(self, items):
645
+ return items[0]
646
+
647
+ def generate_block(self, items):
648
+ return ("generate", [item for item in items if isinstance(item, GenerateTarget)])
649
+
650
+ def generate_target(self, items):
651
+ target = items[0]
652
+ output_path = _str(items[1]) if len(items) > 1 else None
653
+ if isinstance(target, tuple):
654
+ name, dialect = target
655
+ else:
656
+ name, dialect = target, None
657
+ return GenerateTarget(name=name, dialect=dialect, output_path=output_path)
658
+
659
+ def target_name(self, items):
660
+ return items[0]
661
+
662
+ def tn_openapi(self, _items):
663
+ return "openapi"
664
+
665
+ def tn_typescript(self, _items):
666
+ return "typescript"
667
+
668
+ def tn_avro(self, _items):
669
+ return "avro"
670
+
671
+ def tn_protobuf(self, _items):
672
+ return "protobuf"
673
+
674
+ def tn_sql(self, items):
675
+ return ("sql", str(items[0]))
676
+
677
+ def tn_jsonschema(self, _items):
678
+ return "jsonschema"
679
+
680
+ def tn_asyncapi(self, _items):
681
+ return "asyncapi"
682
+
683
+ def tn_docs(self, _items):
684
+ return "docs"
685
+
686
+ def db_dialect(self, items):
687
+ return items[0]
688
+
689
+ def dd_postgres(self, _items):
690
+ return "postgres"
691
+
692
+ def dd_mysql(self, _items):
693
+ return "mysql"
694
+
695
+ def dd_clickhouse(self, _items):
696
+ return "clickhouse"
697
+
698
+ def dd_sqlite(self, _items):
699
+ return "sqlite"
700
+
701
+ def binding_decl(self, items):
702
+ name = str(items[0])
703
+ model = ""
704
+ model_version = 0
705
+ adapter = ""
706
+ table = None
707
+ for item in items[1:]:
708
+ if not isinstance(item, tuple):
709
+ continue
710
+ tag, *vals = item
711
+ if tag == "adapter":
712
+ adapter = vals[0]
713
+ elif tag == "model":
714
+ model, model_version = vals[0], vals[1]
715
+ elif tag == "table":
716
+ table = vals[0]
717
+ return BindingDef(name=name, model=model, model_version=model_version, adapter=adapter, table=table)
718
+
719
+ def binding_item(self, items):
720
+ return items[0]
721
+
722
+ def binding_adapter_attr(self, items):
723
+ return ("adapter", str(items[0]))
724
+
725
+ def binding_model_attr(self, items):
726
+ model_fqn = str(items[0])
727
+ version = int(items[1])
728
+ return ("model", model_fqn, version)
729
+
730
+ def binding_table_attr(self, items):
731
+ return ("table", _str(items[0]))
732
+
733
+ def workspace_decl(self, _items):
734
+ label = None
735
+ name = None
736
+ description = None
737
+ generate_targets: list[GenerateTarget] = []
738
+ ai = None
739
+
740
+ for item in _items:
741
+ if isinstance(item, str):
742
+ label = _str(item)
743
+ elif isinstance(item, tuple):
744
+ tag, value = item
745
+ if tag == "name":
746
+ name = value
747
+ elif tag == "description":
748
+ description = value
749
+ elif tag == "generate":
750
+ generate_targets = value
751
+ elif tag == "ai":
752
+ ai = value
753
+
754
+ return WorkspaceDef(
755
+ label=label,
756
+ name=name,
757
+ description=description,
758
+ generate_targets=generate_targets,
759
+ ai=ai,
760
+ )
761
+
762
+ def workspace_item(self, items):
763
+ return items[0]
764
+
765
+ def workspace_label(self, items):
766
+ return _str(items[0])
767
+
768
+ def workspace_name_attr(self, items):
769
+ return ("name", _str(items[0]))
770
+
771
+ def workspace_description_attr(self, items):
772
+ return ("description", _str(items[0]))
773
+
774
+ def ai_block(self, items):
775
+ attrs = dict(items)
776
+ return (
777
+ "ai",
778
+ AiConfig(
779
+ provider=attrs.get("provider"),
780
+ model=attrs.get("model"),
781
+ repair_attempts=attrs.get("repair_attempts"),
782
+ ),
783
+ )
784
+
785
+ def ai_provider(self, items):
786
+ return ("provider", _str(items[0]))
787
+
788
+ def ai_model(self, items):
789
+ return ("model", _str(items[0]))
790
+
791
+ def ai_repair_attempts(self, items):
792
+ return ("repair_attempts", int(items[0]))
793
+
794
+ def field_mapping(self, items):
795
+ return FieldMapping(source=str(items[0]), target=str(items[1]))
796
+
797
+ def ai_item(self, items):
798
+ return items[0]