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,271 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass
4
+
5
+ from modelable.expressions.cel import extract_field_refs, parse_cel
6
+ from modelable.parser.ir import (
7
+ AccessBlock,
8
+ ClassificationLevel,
9
+ ComputedMapping,
10
+ DirectMapping,
11
+ FieldDef,
12
+ MdlFile,
13
+ ModelVersion,
14
+ ProjectionField,
15
+ ProjectionVersion,
16
+ )
17
+ from modelable.registry.resolver import resolve_model_ref
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class GovernanceFinding:
22
+ code: str
23
+ subject: str
24
+ message: str
25
+
26
+ def as_dict(self) -> dict[str, str]:
27
+ return asdict(self)
28
+
29
+
30
+ def build_projection_governance_findings(
31
+ domain_name: str,
32
+ projection_name: str,
33
+ pv: ProjectionVersion,
34
+ mdl: MdlFile,
35
+ ) -> list[GovernanceFinding]:
36
+ findings: list[GovernanceFinding] = []
37
+ subject = f"{domain_name}.{projection_name}@{pv.version}"
38
+
39
+ _check_projection_access(subject, domain_name, pv.access, findings)
40
+ _check_computed_field_access(subject, domain_name, pv, mdl, findings)
41
+ _check_projection_classification(subject, pv, mdl, findings)
42
+
43
+ return findings
44
+
45
+
46
+ def _check_projection_access(
47
+ subject: str,
48
+ domain_name: str,
49
+ access: AccessBlock | None,
50
+ findings: list[GovernanceFinding],
51
+ ) -> None:
52
+ if access is None:
53
+ findings.append(
54
+ GovernanceFinding(
55
+ code="missing_project_grant",
56
+ subject=subject,
57
+ message=f"{subject} has no documented project grant",
58
+ )
59
+ )
60
+ findings.append(
61
+ GovernanceFinding(
62
+ code="missing_read_grant",
63
+ subject=subject,
64
+ message=f"{subject} has no documented read grant",
65
+ )
66
+ )
67
+ return
68
+
69
+ if not _has_entity_permission(access, "project"):
70
+ findings.append(
71
+ GovernanceFinding(
72
+ code="missing_project_grant",
73
+ subject=subject,
74
+ message=f"{subject} has no documented project grant for domain '{domain_name}'",
75
+ )
76
+ )
77
+
78
+ if not _has_entity_permission(access, "read"):
79
+ findings.append(
80
+ GovernanceFinding(
81
+ code="missing_read_grant",
82
+ subject=subject,
83
+ message=f"{subject} has no documented read grant for domain '{domain_name}'",
84
+ )
85
+ )
86
+
87
+
88
+ def _check_computed_field_access(
89
+ subject: str,
90
+ projection_domain: str,
91
+ pv: ProjectionVersion,
92
+ mdl: MdlFile,
93
+ findings: list[GovernanceFinding],
94
+ ) -> None:
95
+ resolved_sources = _build_resolved_sources(pv, mdl)
96
+
97
+ for proj_field, source_refs in _iter_projection_field_sources(pv, resolved_sources):
98
+ if not isinstance(proj_field.mapping, ComputedMapping):
99
+ continue
100
+
101
+ for target_ref, target_version, field_name in source_refs:
102
+ if not _has_property_derivation(target_version.access, field_name, projection_domain):
103
+ findings.append(
104
+ GovernanceFinding(
105
+ code="missing_derivation_policy",
106
+ subject=subject,
107
+ message=(
108
+ f"{subject}.{proj_field.name} uses source field "
109
+ f"{target_ref}.{field_name} without documented derivation policy"
110
+ ),
111
+ )
112
+ )
113
+
114
+
115
+ def _check_projection_classification(
116
+ subject: str,
117
+ pv: ProjectionVersion,
118
+ mdl: MdlFile,
119
+ findings: list[GovernanceFinding],
120
+ ) -> None:
121
+ resolved_sources = _build_resolved_sources(pv, mdl)
122
+
123
+ for proj_field, source_refs in _iter_projection_field_sources(pv, resolved_sources):
124
+ for target_ref, target_version, field_name in source_refs:
125
+ source_field = _find_field(target_version, field_name)
126
+ if source_field is None:
127
+ continue
128
+
129
+ _check_pii_preservation(subject, proj_field, target_ref, source_field, findings)
130
+ _check_classification_preservation(subject, proj_field, target_ref, source_field, findings)
131
+
132
+
133
+ def _has_entity_permission(access: AccessBlock, permission: str) -> bool:
134
+ return any(permission in grant.permissions for grant in access.entity)
135
+
136
+
137
+ def _has_property_derivation(
138
+ access: AccessBlock | None,
139
+ field_name: str,
140
+ projection_domain: str,
141
+ ) -> bool:
142
+ if access is None:
143
+ return False
144
+
145
+ grants = access.properties.get(field_name, [])
146
+ for grant in grants:
147
+ if grant.principal != projection_domain:
148
+ continue
149
+ if "read" in grant.permissions and "derive" in grant.permissions:
150
+ return True
151
+ return False
152
+
153
+
154
+ def _iter_projection_field_sources(
155
+ pv: ProjectionVersion,
156
+ resolved_sources: dict[str, tuple[str, ModelVersion]],
157
+ ) -> list[tuple[ProjectionField, list[tuple[str, ModelVersion, str]]]]:
158
+ result: list[tuple[ProjectionField, list[tuple[str, ModelVersion, str]]]] = []
159
+ for proj_field in pv.fields:
160
+ source_refs: list[tuple[str, ModelVersion, str]] = []
161
+ mapping = proj_field.mapping
162
+
163
+ if isinstance(mapping, DirectMapping):
164
+ target = resolved_sources.get(mapping.source_alias)
165
+ if target is not None:
166
+ target_ref, target_version = target
167
+ source_refs.append((target_ref, target_version, mapping.source_field))
168
+ elif isinstance(mapping, ComputedMapping):
169
+ expr_ast, _ = parse_cel(mapping.expression)
170
+ if expr_ast is not None:
171
+ for alias, field_name in extract_field_refs(expr_ast):
172
+ target = resolved_sources.get(alias)
173
+ if target is None:
174
+ continue
175
+ target_ref, target_version = target
176
+ source_refs.append((target_ref, target_version, field_name))
177
+
178
+ result.append((proj_field, source_refs))
179
+
180
+ return result
181
+
182
+
183
+ def _check_pii_preservation(
184
+ subject: str,
185
+ proj_field: ProjectionField,
186
+ source_ref: str,
187
+ source_field: FieldDef,
188
+ findings: list[GovernanceFinding],
189
+ ) -> None:
190
+ if not source_field.is_pii or proj_field.is_pii:
191
+ return
192
+
193
+ findings.append(
194
+ GovernanceFinding(
195
+ code="missing_pii_metadata",
196
+ subject=subject,
197
+ message=(
198
+ f"{subject}.{proj_field.name} projects {source_ref}.{source_field.name} "
199
+ "without preserving @pii metadata"
200
+ ),
201
+ )
202
+ )
203
+
204
+
205
+ def _check_classification_preservation(
206
+ subject: str,
207
+ proj_field: ProjectionField,
208
+ source_ref: str,
209
+ source_field: FieldDef,
210
+ findings: list[GovernanceFinding],
211
+ ) -> None:
212
+ source_level = source_field.classification
213
+ if source_level is None:
214
+ return
215
+
216
+ projected_level = proj_field.classification
217
+ if projected_level is None:
218
+ findings.append(
219
+ GovernanceFinding(
220
+ code="missing_classification_metadata",
221
+ subject=subject,
222
+ message=(
223
+ f"{subject}.{proj_field.name} projects {source_ref}.{source_field.name} "
224
+ "without preserving classification metadata"
225
+ ),
226
+ )
227
+ )
228
+ return
229
+
230
+ if _classification_rank(projected_level) < _classification_rank(source_level):
231
+ findings.append(
232
+ GovernanceFinding(
233
+ code="lowered_classification",
234
+ subject=subject,
235
+ message=(
236
+ f"{subject}.{proj_field.name} lowers classification from "
237
+ f"{source_level.value} to {projected_level.value}"
238
+ ),
239
+ )
240
+ )
241
+
242
+
243
+ def _classification_rank(level: ClassificationLevel) -> int:
244
+ order = {
245
+ ClassificationLevel.open: 0,
246
+ ClassificationLevel.internal: 1,
247
+ ClassificationLevel.confidential: 2,
248
+ ClassificationLevel.restricted: 3,
249
+ ClassificationLevel.secret: 4,
250
+ }
251
+ return order[level]
252
+
253
+
254
+ def _find_field(version: ModelVersion, field_name: str) -> FieldDef | None:
255
+ return next((field for field in version.fields if field.name == field_name), None)
256
+
257
+
258
+ def _build_resolved_sources(pv: ProjectionVersion, mdl: MdlFile) -> dict[str, tuple[str, ModelVersion]]:
259
+ resolved_sources: dict[str, tuple[str, ModelVersion]] = {}
260
+ all_sources = [(pv.source.model, pv.source.version, pv.source.alias)]
261
+ for join in pv.joins:
262
+ all_sources.append((join.model, join.version, join.alias))
263
+
264
+ for model_ref, version_spec, alias in all_sources:
265
+ try:
266
+ resolved = resolve_model_ref(mdl, model_ref, version_spec)
267
+ resolved_sources[alias] = (f"{model_ref}@{resolved.version.version}", resolved.version)
268
+ except LookupError:
269
+ pass
270
+
271
+ return resolved_sources
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ DEFAULT_POR_ISSUER = "modelable-registry.local"
6
+ DEFAULT_POR_ISSUED_AT = "1970-01-01T00:00:00Z"
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class PortableOwnershipRecord:
11
+ model: str
12
+ issuer: str = DEFAULT_POR_ISSUER
13
+ issued_at: str = DEFAULT_POR_ISSUED_AT
14
+ signature: str | None = None
15
+
16
+ def as_dict(self) -> dict[str, str | None]:
17
+ return {
18
+ "model": self.model,
19
+ "issuer": self.issuer,
20
+ "issuedAt": self.issued_at,
21
+ **({"signature": self.signature} if self.signature else {}),
22
+ }
23
+
24
+
25
+ def build_por_reference(ref: str, *, issuer: str = DEFAULT_POR_ISSUER) -> dict[str, str]:
26
+ """Return the unsigned POR reference embedded in generated artifacts."""
27
+ return {
28
+ "model": ref,
29
+ "issuer": issuer,
30
+ "issuedAt": DEFAULT_POR_ISSUED_AT,
31
+ }
32
+
33
+
34
+ def build_por_record(
35
+ ref: str,
36
+ *,
37
+ issuer: str = DEFAULT_POR_ISSUER,
38
+ issued_at: str = DEFAULT_POR_ISSUED_AT,
39
+ signature: str | None = None,
40
+ ) -> PortableOwnershipRecord:
41
+ return PortableOwnershipRecord(
42
+ model=ref,
43
+ issuer=issuer,
44
+ issued_at=issued_at,
45
+ signature=signature,
46
+ )
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,257 @@
1
+ // Modelable IDL Grammar
2
+ // Parser: Earley
3
+
4
+ start: statement+
5
+
6
+ statement: domain_decl
7
+ | binding_decl
8
+ | workspace_decl
9
+ | import_domain_stmt
10
+ | projection_decl
11
+ | subscription_decl
12
+ | consumer_decl
13
+
14
+
15
+ // -- Domain ------------------------------------------------------------------
16
+
17
+ domain_decl: "domain" domain_name "{" domain_item* "}"
18
+ domain_name: IDENT | ESCAPED_STRING
19
+
20
+ domain_item: owner_attr
21
+ | contact_attr
22
+ | desc_attr
23
+ | model_decl
24
+ | projection_decl
25
+ | auto_projections_decl
26
+ | generate_block
27
+
28
+ owner_attr: "owner" ":" ESCAPED_STRING
29
+ contact_attr: "contact" ":" ESCAPED_STRING
30
+ desc_attr: "description" ":" ESCAPED_STRING
31
+
32
+
33
+ // -- Model -------------------------------------------------------------------
34
+
35
+ model_decl: wire_annotation* model_kind IDENT model_header? "{" model_body_item* "}"
36
+ model_header: "@" INT model_change?
37
+ | model_change
38
+ model_change: "(" change_kind ")"
39
+
40
+ model_body_item: field_decl
41
+ | access_block
42
+
43
+ model_kind: "entity" -> mk_entity
44
+ | "aggregate" -> mk_aggregate
45
+ | "event" -> mk_event
46
+ | "value" -> mk_value
47
+
48
+ change_kind: "additive" -> ck_additive
49
+ | "breaking" -> ck_breaking
50
+
51
+ field_decl: annotation* IDENT optional_marker? ":" type_expr field_default?
52
+ optional_marker: "?"
53
+ field_default: "=" EXPRESSION
54
+
55
+ annotation: "@key" -> ann_key
56
+ | "@pii" -> ann_pii
57
+ | "@classification" "(" ESCAPED_STRING ")" -> ann_classification
58
+ | "@deprecated" "(" "replacedBy" ":" ESCAPED_STRING ")" -> ann_deprecated
59
+ | "@owner" "(" ESCAPED_STRING ")" -> ann_owner
60
+ | "@server" -> ann_server
61
+ | wire_annotation
62
+ | "@pitCutoff" "(" ANNOTATION_EXPR ")" -> ann_pit_cutoff
63
+ | "@latestBefore" "(" ANNOTATION_EXPR ")" -> ann_latest_before
64
+ | "@latestOnly" -> ann_latest_only
65
+
66
+ wire_annotation: "@wire" "(" wire_option ("," wire_option)* ")" -> ann_wire
67
+ wire_option: wire_key ":" wire_value
68
+ wire_key: IDENT ("." IDENT)?
69
+ wire_value: ESCAPED_STRING -> wire_string
70
+ | wire_map
71
+ wire_map: "{" wire_map_item ("," wire_map_item)* "}"
72
+ wire_map_item: IDENT ":" ESCAPED_STRING
73
+
74
+
75
+ // -- Types -------------------------------------------------------------------
76
+
77
+ type_expr: primitive_type
78
+ | decimal_type
79
+ | enum_type
80
+ | array_type
81
+ | map_type
82
+ | ref_type
83
+ | object_type
84
+ | IDENT
85
+
86
+ primitive_type: "string" -> pt_string
87
+ | "int" -> pt_int
88
+ | "float" -> pt_float
89
+ | "bool" -> pt_bool
90
+ | "date" -> pt_date
91
+ | "time" -> pt_time
92
+ | "timestamp" -> pt_timestamp
93
+ | "uuid" -> pt_uuid
94
+ | "duration" -> pt_duration
95
+ | "binary" -> pt_binary
96
+ | "json" -> pt_json
97
+
98
+ decimal_type: "decimal" "(" INT "," INT ")"
99
+ enum_type: "enum" "(" IDENT ("," IDENT)* ")"
100
+ array_type: "array" "<" type_expr ">"
101
+ map_type: "map" "<" type_expr "," type_expr ">"
102
+ ref_type: "ref" "<" dotted_ref ">"
103
+ object_type: "object" "{" field_decl* "}"
104
+
105
+
106
+ // -- Projections --------------------------------------------------------------
107
+
108
+ projection_decl: wire_annotation* "projection" IDENT "@" INT projection_source_block? source_clause? "{" projection_body_item* "}"
109
+ projection_source_block: "{" source_clause "}"
110
+
111
+ projection_body_item: proj_field
112
+ | access_block
113
+ | subscription_block
114
+ | generate_block
115
+ | materialisation_block
116
+
117
+ source_clause: "from" dotted_ref "@" version_spec "as" IDENT join_clause* where_clause? group_clause?
118
+
119
+ where_clause: "where" FIELD_EXPRESSION
120
+
121
+ join_modifier: "left"
122
+
123
+ join_clause: join_prefix annotation*
124
+ | join_prefix join_option*
125
+ join_option: annotation
126
+ | cardinality_attr
127
+ cardinality_attr: "cardinality" ":" IDENT
128
+ join_prefix: "join" dotted_ref "@" version_spec "as" IDENT "on" EXPRESSION
129
+ | join_modifier "join" dotted_ref "@" version_spec "as" IDENT "on" EXPRESSION
130
+
131
+ group_clause: "group" "by" group_item ("," group_item)*
132
+
133
+ group_item: /[^,\n\r{}]+/
134
+
135
+ version_spec: INT -> version_exact
136
+ | INT "#" IDENT -> version_pinned
137
+ | ">=" INT "<" INT -> version_range
138
+ | ">=" INT -> version_min
139
+
140
+ proj_field: annotation* IDENT "<-" qualified_field -> direct_field
141
+ | annotation* IDENT "=" FIELD_EXPRESSION -> computed_field
142
+
143
+ subscription_block: "subscription" "{" subscription_item* "}"
144
+ subscription_item: IDENT ":" EXPRESSION
145
+ materialisation_block: "materialisation" "{" materialisation_item* "}"
146
+ materialisation_item: IDENT ":" EXPRESSION
147
+
148
+ qualified_field: IDENT "." IDENT
149
+
150
+
151
+ // -- Generate ----------------------------------------------------------------
152
+
153
+ generate_block: "generate" "{" generate_target* "}"
154
+
155
+ generate_target: target_name ("->" ESCAPED_STRING)?
156
+
157
+ target_name: "openapi" -> tn_openapi
158
+ | "typescript" -> tn_typescript
159
+ | "avro" -> tn_avro
160
+ | "protobuf" -> tn_protobuf
161
+ | "sql" "(" db_dialect ")" -> tn_sql
162
+ | "jsonschema" -> tn_jsonschema
163
+ | "asyncapi" -> tn_asyncapi
164
+ | "docs" -> tn_docs
165
+
166
+ db_dialect: "postgres" -> dd_postgres
167
+ | "mysql" -> dd_mysql
168
+ | "clickhouse" -> dd_clickhouse
169
+ | "sqlite" -> dd_sqlite
170
+
171
+
172
+ // -- Deferred top-level forms ------------------------------------------------
173
+ // These are parsed more fully in later parser-plan tasks.
174
+
175
+ binding_decl: "binding" IDENT "{" binding_item* "}"
176
+ binding_item: binding_adapter_attr
177
+ | binding_model_attr
178
+ | binding_table_attr
179
+ | ignored_block_item
180
+ binding_adapter_attr: "adapter" ":" IDENT
181
+ binding_model_attr: "model" ":" dotted_ref "@" INT
182
+ binding_table_attr: "table" ":" ESCAPED_STRING
183
+ subscription_decl: "subscription" IDENT "{" subscription_decl_item* "}"
184
+ subscription_decl_item: IDENT ":" EXPRESSION
185
+ workspace_decl: "workspace" workspace_label? "{" workspace_item* "}"
186
+ workspace_label: IDENT | ESCAPED_STRING
187
+ workspace_item: workspace_name_attr
188
+ | workspace_description_attr
189
+ | registry_block
190
+ | peers_block
191
+ | generate_block
192
+ | ai_block
193
+ workspace_name_attr: "name" ":" ESCAPED_STRING
194
+ workspace_description_attr: "description" ":" ESCAPED_STRING
195
+ registry_block: "registry" "{" ignored_block_item* "}"
196
+ peers_block: "peers" ":" "[" PEERS_CONTENT "]"
197
+ ai_block: "ai" "{" ai_item* "}"
198
+ ai_item: ai_provider | ai_model | ai_repair_attempts
199
+ ai_provider: "provider" ":" ESCAPED_STRING
200
+ ai_model: "model" ":" ESCAPED_STRING
201
+ ai_repair_attempts: "repair_attempts" ":" INT
202
+ import_domain_stmt: "import" "domain" dotted_ref import_source? pinned_import? version_selector?
203
+ import_source: "from" "registry" ESCAPED_STRING
204
+ pinned_import: "at" dotted_ref "@" INT "#" IDENT
205
+ consumer_decl: "consumer" IDENT? "{" ignored_block_item* "}"
206
+ auto_projections_decl: "auto" "projections" IDENT "@" INT "{" auto_projection_item* "}"
207
+
208
+ auto_projection_item: auto_projection_kind auto_projection_option*
209
+ auto_projection_kind: "db" -> apk_db
210
+ | "request" -> apk_request
211
+ | "reply" -> apk_reply
212
+ | "event" -> apk_event
213
+
214
+ auto_projection_option: exclude_option | on_option
215
+ exclude_option: "exclude" "[" auto_projection_exclusion ("," auto_projection_exclusion)* "]"
216
+ on_option: "on" "[" IDENT ("," IDENT)* "]"
217
+ auto_projection_exclusion: annotation | IDENT
218
+
219
+ access_block: "access" "{" access_item* "}"
220
+ access_item: entity_grant
221
+ | property_grant
222
+ entity_grant: "entity" principal permission_list
223
+ property_grant: "property" IDENT principal permission_list
224
+ principal: PRINCIPAL
225
+ permission_list: "[" permission ("," permission)* "]"
226
+ permission: "read" -> p_read
227
+ | "project" -> p_project
228
+ | "subscribe" -> p_subscribe
229
+ | "write" -> p_write
230
+ | "transfer" -> p_transfer
231
+ | "manage_access" -> p_manage_access
232
+ | "derive" -> p_derive
233
+ | "redact" -> p_redact
234
+
235
+ version_selector: "@" version_expr
236
+ version_expr: INT | VERSION_RANGE
237
+ dotted_ref: IDENT ("." IDENT)*
238
+
239
+ ignored_block_item: /[^{}]+/ | "{" ignored_block_item* "}"
240
+
241
+
242
+ // -- Lexing ------------------------------------------------------------------
243
+
244
+ VERSION_RANGE: /[<>~=^0-9.,\s]+/
245
+ EXPRESSION: /[^\n\r{}]+/
246
+ FIELD_EXPRESSION: /[^\n\r]+(?:\n(?!\s*(?:@|[A-Za-z_][A-Za-z0-9_-]*\s*(?:<-|=)|generate\s*\{|materialisation\s*\{|subscription\s*\{|access\s*\{|from\s|group\s+by|\{|\}))[^\n\r]+)*/
247
+ ANNOTATION_EXPR: /[^)\n\r{}]+/
248
+ IDENT: /[A-Za-z_][A-Za-z0-9_-]*/
249
+ PEERS_CONTENT: /[^\]]+/
250
+ PRINCIPAL: /\*|[A-Za-z_][A-Za-z0-9_.-]*/
251
+
252
+ COMMENT: /\/\/[^\n]*/
253
+ %import common.ESCAPED_STRING
254
+ %import common.INT
255
+ %import common.WS
256
+ %ignore WS
257
+ %ignore COMMENT
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from .export import build_graph_export, write_graph_export
4
+
5
+ __all__ = ["build_graph_export", "write_graph_export"]