weaverstack 0.1.1__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 (127) hide show
  1. weaver/__init__.py +59 -0
  2. weaver/build_bundle/__init__.py +109 -0
  3. weaver/build_bundle/aliases.py +325 -0
  4. weaver/build_bundle/bundle.py +359 -0
  5. weaver/build_bundle/catalogue_actions.py +275 -0
  6. weaver/build_bundle/changes.py +186 -0
  7. weaver/build_bundle/endpoints.py +83 -0
  8. weaver/build_bundle/executors/__init__.py +69 -0
  9. weaver/build_bundle/executors/alias.py +202 -0
  10. weaver/build_bundle/executors/base.py +132 -0
  11. weaver/build_bundle/executors/folder.py +71 -0
  12. weaver/build_bundle/executors/load_file.py +205 -0
  13. weaver/build_bundle/executors/spark_case.py +26 -0
  14. weaver/build_bundle/executors/spark_schema.py +60 -0
  15. weaver/build_bundle/executors/spark_sql.py +59 -0
  16. weaver/build_bundle/executors/spark_sql_batch.py +57 -0
  17. weaver/build_bundle/executors/spark_table.py +213 -0
  18. weaver/build_bundle/executors/sql_endpoint_refresh.py +34 -0
  19. weaver/build_bundle/executors/tsql.py +81 -0
  20. weaver/build_bundle/incremental.py +288 -0
  21. weaver/build_bundle/installer.py +384 -0
  22. weaver/build_bundle/models.py +288 -0
  23. weaver/build_bundle/payloads.py +34 -0
  24. weaver/build_bundle/physical.py +625 -0
  25. weaver/build_bundle/planner.py +389 -0
  26. weaver/build_bundle/prune.py +620 -0
  27. weaver/build_bundle/report.py +108 -0
  28. weaver/build_bundle/stages.py +196 -0
  29. weaver/build_bundle/targets.py +272 -0
  30. weaver/build_bundle/workflow.py +585 -0
  31. weaver/catalogue/__init__.py +73 -0
  32. weaver/catalogue/builtin.py +238 -0
  33. weaver/catalogue/claims.py +121 -0
  34. weaver/catalogue/projection.py +437 -0
  35. weaver/catalogue/reader.py +152 -0
  36. weaver/catalogue/reconcile.py +231 -0
  37. weaver/catalogue/render.py +410 -0
  38. weaver/catalogue/state.py +660 -0
  39. weaver/catalogue/tables.py +648 -0
  40. weaver/config.py +178 -0
  41. weaver/declaration/__init__.py +171 -0
  42. weaver/declaration/columns.py +223 -0
  43. weaver/declaration/ddl.py +266 -0
  44. weaver/declaration/dependencies.py +544 -0
  45. weaver/declaration/graph.py +240 -0
  46. weaver/declaration/item_dependencies.py +292 -0
  47. weaver/declaration/load.py +191 -0
  48. weaver/declaration/metadata.py +1405 -0
  49. weaver/declaration/model.py +448 -0
  50. weaver/declaration/references.py +294 -0
  51. weaver/declaration/repository.py +959 -0
  52. weaver/declaration/schemas.py +135 -0
  53. weaver/declaration/source.py +674 -0
  54. weaver/declaration/spark_load.py +759 -0
  55. weaver/declaration/sql_shaping.py +591 -0
  56. weaver/declaration/templates/ddl/declared_create_table.sql +64 -0
  57. weaver/declaration/templates/ddl/infer_create_table.sql +97 -0
  58. weaver/declaration/templates/ddl/metadata_column_validation.sql +30 -0
  59. weaver/declaration/templates/load/column_metadata.sql +40 -0
  60. weaver/declaration/templates/load/full_replace_body.sql +21 -0
  61. weaver/declaration/templates/load/install_load_procedure.sql +27 -0
  62. weaver/declaration/templates/load/load_procedure.sql +48 -0
  63. weaver/declaration/templates/load/primary_key_body.sql +113 -0
  64. weaver/declaration/tsql_ddl.py +468 -0
  65. weaver/declaration/tsql_load.py +417 -0
  66. weaver/declaration/warehouse_type_mapping.yml +93 -0
  67. weaver/diagnostics.py +247 -0
  68. weaver/errors.py +61 -0
  69. weaver/etl.py +469 -0
  70. weaver/fabric/__init__.py +107 -0
  71. weaver/fabric/auth.py +137 -0
  72. weaver/fabric/capacity.py +143 -0
  73. weaver/fabric/client.py +147 -0
  74. weaver/fabric/environment.py +460 -0
  75. weaver/fabric/livy.py +478 -0
  76. weaver/fabric/notebooks.py +201 -0
  77. weaver/fabric/onelake.py +263 -0
  78. weaver/fabric/resolution.py +344 -0
  79. weaver/fabric/resources.py +245 -0
  80. weaver/fabric/session.py +148 -0
  81. weaver/fabric/shortcuts.py +120 -0
  82. weaver/fabric/sql.py +118 -0
  83. weaver/fabric/store.py +198 -0
  84. weaver/initialise.py +209 -0
  85. weaver/lakehouse.py +386 -0
  86. weaver/load.py +474 -0
  87. weaver/load_execution.py +483 -0
  88. weaver/load_plan.py +912 -0
  89. weaver/load_report.py +330 -0
  90. weaver/load_resolution.py +386 -0
  91. weaver/locations.py +164 -0
  92. weaver/objects.py +392 -0
  93. weaver/operations.py +757 -0
  94. weaver/physical_wipe.py +369 -0
  95. weaver/push.py +76 -0
  96. weaver/resolution.py +292 -0
  97. weaver/runtime/__init__.py +30 -0
  98. weaver/runtime/folder_load.py +402 -0
  99. weaver/runtime/load_contract.py +245 -0
  100. weaver/runtime/load_result.py +104 -0
  101. weaver/runtime/spark_load.py +152 -0
  102. weaver/runtime/table_load.py +497 -0
  103. weaver/spark/__init__.py +49 -0
  104. weaver/spark/catalogue.py +245 -0
  105. weaver/spark/destination.py +195 -0
  106. weaver/spark/session.py +84 -0
  107. weaver/spark/tokens.py +138 -0
  108. weaver/sql/__init__.py +40 -0
  109. weaver/sql/authentication.py +38 -0
  110. weaver/sql/connection.py +90 -0
  111. weaver/sql/errors.py +25 -0
  112. weaver/sql/execution.py +123 -0
  113. weaver/sql/pool.py +174 -0
  114. weaver/sql/wipe.py +156 -0
  115. weaver/store.py +209 -0
  116. weaver/targets.py +257 -0
  117. weaver/task_logging.py +215 -0
  118. weaver/unbind.py +74 -0
  119. weaver/workspaces.py +175 -0
  120. weaver_cli/__init__.py +12 -0
  121. weaver_cli/__main__.py +7 -0
  122. weaver_cli/main.py +626 -0
  123. weaverstack-0.1.1.dist-info/METADATA +113 -0
  124. weaverstack-0.1.1.dist-info/RECORD +127 -0
  125. weaverstack-0.1.1.dist-info/WHEEL +4 -0
  126. weaverstack-0.1.1.dist-info/entry_points.txt +2 -0
  127. weaverstack-0.1.1.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,266 @@
1
+ """Generated create DDL — the *build* form of an Weaver document source.
2
+
3
+ Build creates structure; it does not load data. So the create definition for a
4
+ source is pure structure: a table becomes an empty table of the right shape, a
5
+ view becomes ``CREATE VIEW`` over its query body. Nothing here runs an
6
+ object's ``read()`` or reads a row — populating a table is *load*, a separate
7
+ phase, and the repository is read once to freeze a bundle, never again.
8
+
9
+ The source is the right place for this because it alone knows its language,
10
+ object kind, ID and validated body/schema. A build planner calls
11
+ :meth:`SourceDocument.create_ddl` and never re-derives create syntax.
12
+
13
+ Two invariants hold:
14
+
15
+ - **deterministic** — the same validated source and format version always produce
16
+ the same :class:`GeneratedDdl`.
17
+ - **destination-free** — a table or view is named ``{{object:Schema.Object}}`` and
18
+ the executor resolves that against whichever Lakehouse the batch is bound to.
19
+ No Lakehouse, workspace or filesystem path is baked into a payload, so the same
20
+ repository generates the same bytes in every environment (how-does-build-work §15)
21
+ and two bundles can be diffed for what actually differs.
22
+
23
+ The second used to read "path-free", and a bare ``Schema.Object`` was taken to
24
+ satisfy it. It does not. A two-part name is not free of a destination — it
25
+ silently *takes* one, from whatever catalogue the session is currently attached
26
+ to, which is the Weaver Lakehouse. Locally that was masked by pinning each schema
27
+ to the one destination's storage; on Fabric it put the object in the control
28
+ plane. The name has to say which Lakehouse it means, and only the installer knows
29
+ how that Lakehouse is spelled, so the payload names the object and defers the
30
+ spelling (see :mod:`weaver.spark.tokens`).
31
+
32
+ **Bodies are rewritten, not reformatted.** A view's query is the author's text
33
+ with each managed two-part reference replaced in place — same whitespace, same
34
+ comments, same casing, same delimiters. Three- and four-part references are left
35
+ exactly as written: the author named a physical thing deliberately, and Weaver
36
+ does not second-guess it.
37
+
38
+ Schema is **declared or inferred** (how-does-build-work §2). A Python-backed Delta
39
+ table has no query and must declare its schema; the generated DDL is a concrete
40
+ strict ``CREATE TABLE`` over the declared columns. A Spark SQL table has a
41
+ query, so it may declare its schema or leave it to be inferred at build — either
42
+ way the shape is only settled by running the query in the target session, so its
43
+ payload is not finished SQL but a deterministic instruction the ``spark_table``
44
+ executor completes in one self-contained install action (how-does-build-work §2).
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import json
50
+ from dataclasses import dataclass
51
+ from typing import TYPE_CHECKING
52
+
53
+ from ..spark.tokens import object_token
54
+ from .columns import metadata_column_references
55
+ from .dependencies import rewrite_sql_references
56
+ from .metadata import SPARK_SQL, SQL, TABLE, VIEW
57
+
58
+ if TYPE_CHECKING:
59
+ from .source import SourceDocument
60
+
61
+ #: The bundle format version this generator targets. A change to the generated
62
+ #: shape is a change to this number. Version 2 dropped the ``spark_table``
63
+ #: payload's identity column: a Delta table no longer has one to carry.
64
+ BUILD_FORMAT_VERSION = 2
65
+
66
+ #: The executor a concrete Spark statement runs through. It names a runtime
67
+ #: dispatch key, not an engine — a Fabric Spark session and a local one both use
68
+ #: ``spark_sql``.
69
+ SPARK_SQL_EXECUTOR = "spark_sql"
70
+ SPARK_SQL_EXTENSION = ".spark.sql"
71
+
72
+ #: The executor that completes a Spark SQL table's build: it runs the query,
73
+ #: reads the resulting ``DataFrame`` schema, validates, and creates the table.
74
+ #: Its payload is JSON, not SQL, because the DDL cannot be finished until the
75
+ #: query's shape is known in the session.
76
+ SPARK_TABLE_EXECUTOR = "spark_table"
77
+ SPARK_TABLE_EXTENSION = ".spark-table.json"
78
+
79
+ #: The executor that runs a T-SQL script against the Warehouse. Its payload is a
80
+ #: finished, self-contained script — a table build materialises and inspects its
81
+ #: own query shape server-side, so no round-trip is needed.
82
+ TSQL_EXECUTOR = "tsql"
83
+ TSQL_EXTENSION = ".sql"
84
+
85
+ #: Delta column mapping keeps declared column names with spaces (``Order id``)
86
+ #: legal without quoting them everywhere they later appear.
87
+ _COLUMN_MAPPING = "TBLPROPERTIES ('delta.columnMapping.mode' = 'name')"
88
+
89
+
90
+ @dataclass(frozen=True)
91
+ class GeneratedDdl:
92
+ """One source's generated, installable create definition."""
93
+
94
+ executor: str
95
+ content: str
96
+ extension: str
97
+
98
+
99
+ def generate_ddl(document: "SourceDocument") -> GeneratedDdl:
100
+ """The installable create definition for one validated source.
101
+
102
+ Folders have no create DDL — a Folder is a directory, created by the
103
+ installer rather than by a statement — so this is never called for one.
104
+ """
105
+
106
+ if document.language == SQL:
107
+ return _tsql_ddl(document)
108
+ if document.kind == TABLE:
109
+ if document.language == SPARK_SQL:
110
+ return _spark_table_ddl(document)
111
+ return _python_table_ddl(document)
112
+ if document.kind == VIEW:
113
+ return _view_ddl(document)
114
+ raise NotImplementedError(
115
+ f"{document.relative_path}: a {document.kind} has no create DDL"
116
+ )
117
+
118
+
119
+ def _tsql_ddl(document: "SourceDocument") -> GeneratedDdl:
120
+ """A Warehouse object's build: a self-contained T-SQL script.
121
+
122
+ A table materialises and inspects its own query shape server-side and creates
123
+ only its main table; a view is a strict ``CREATE VIEW`` over its body.
124
+ """
125
+
126
+ from .tsql_ddl import generate_tsql_table_script, generate_tsql_view_script
127
+
128
+ body = document.sql_body or ""
129
+ if document.kind == TABLE:
130
+ content = generate_tsql_table_script(document.document, body)
131
+ elif document.kind == VIEW:
132
+ content = generate_tsql_view_script(document.document, body)
133
+ else: # pragma: no cover - a SQL Folder is impossible (reader refuses it)
134
+ raise NotImplementedError(
135
+ f"{document.relative_path}: a {document.kind} has no create DDL"
136
+ )
137
+ return GeneratedDdl(
138
+ executor=TSQL_EXECUTOR, content=content, extension=TSQL_EXTENSION
139
+ )
140
+
141
+
142
+ def _object_name(document: "SourceDocument") -> str:
143
+ """How a payload names the object it builds."""
144
+
145
+ return object_token(document.object_id.schema, document.object_id.object)
146
+
147
+
148
+ def _addressed(body: str) -> str:
149
+ """One SQL body with its managed references named for a destination.
150
+
151
+ Only ordinary two-part references are rewritten, and that is exactly the set
152
+ the reader guarantees resolves inside the repository: a valid repository
153
+ resolves every one of them, so what is left over is deliberately outside — a
154
+ physically-qualified three- or four-part name, or a table-valued function.
155
+ Both are the author naming something Weaver does not manage, and both are
156
+ left alone.
157
+ """
158
+
159
+ def rewrite(reference):
160
+ object_id = reference.object_id
161
+ if object_id is None: # a call, or a qualified physical name
162
+ return None
163
+ return object_token(object_id.schema, object_id.object)
164
+
165
+ return rewrite_sql_references(body, rewrite)
166
+
167
+
168
+ def _python_table_ddl(document: "SourceDocument") -> GeneratedDdl:
169
+ """A Delta table from its declared columns, plus the audit columns.
170
+
171
+ A Python-backed table has no query to infer from, so the reader requires a
172
+ declared schema. The build is an empty table of that shape with Weaver's
173
+ audit columns appended — the concrete statement is known now, so it is frozen
174
+ directly rather than deferred to an executor.
175
+ """
176
+
177
+ columns = document.document.effective_schema
178
+ if not document.document.schema: # pragma: no cover - the reader requires it
179
+ raise NotImplementedError(
180
+ f"{document.relative_path}: a Python-backed Delta table must declare "
181
+ "its schema; schema inference needs a query"
182
+ )
183
+ content = _create_table_sql(_object_name(document), columns)
184
+ return GeneratedDdl(
185
+ executor=SPARK_SQL_EXECUTOR, content=content, extension=SPARK_SQL_EXTENSION
186
+ )
187
+
188
+
189
+ def _spark_table_ddl(document: "SourceDocument") -> GeneratedDdl:
190
+ """A Spark SQL table's deferred, deterministic build instruction.
191
+
192
+ Declared or inferred, its shape is only settled by running the query in the
193
+ session, so the payload is not finished SQL. It is a JSON instruction the
194
+ ``spark_table`` executor completes in one self-contained action: run the
195
+ query, read the ``DataFrame`` schema, validate the columns (the same guards a
196
+ declared schema passes at parse), choose the physical business columns,
197
+ append the audit columns, and create the table. Everything the executor needs
198
+ is frozen here, so it never reopens the Weaver document source (how-does-build-work §2).
199
+ """
200
+
201
+ ses = document.document
202
+ declared = ses.has_declared_schema
203
+ payload = {
204
+ "object": _object_name(document),
205
+ "schema_mode": "declared" if declared else "inferred",
206
+ "declared_columns": (
207
+ [_column_entry(column) for column in ses.schema] if declared else None
208
+ ),
209
+ "source_query": _addressed((document.sql_body or "").strip()),
210
+ "references": [list(pair) for pair in metadata_column_references(ses)],
211
+ "audit_columns": [_column_entry(column) for column in ses.audit_columns],
212
+ "column_mapping": True,
213
+ }
214
+ content = json.dumps(payload, indent=2, sort_keys=True) + "\n"
215
+ return GeneratedDdl(
216
+ executor=SPARK_TABLE_EXECUTOR, content=content, extension=SPARK_TABLE_EXTENSION
217
+ )
218
+
219
+
220
+ def _view_ddl(document: "SourceDocument") -> GeneratedDdl:
221
+ """A persistent view over the validated body, its managed names addressed.
222
+
223
+ The body is otherwise untouched. What changes is that every reference to
224
+ another managed object now says which Lakehouse it means — without which a
225
+ view built in one destination would read its inputs from whichever Lakehouse
226
+ the session happened to be attached to.
227
+ """
228
+
229
+ body = _addressed((document.sql_body or "").rstrip())
230
+ content = f"CREATE VIEW {_object_name(document)} AS\n{body}\n"
231
+ return GeneratedDdl(
232
+ executor=SPARK_SQL_EXECUTOR, content=content, extension=SPARK_SQL_EXTENSION
233
+ )
234
+
235
+
236
+ def _column_entry(column) -> list:
237
+ """A payload column triple ``[name, type, not_null]``.
238
+
239
+ Nullability travels with the column so the executor emits the same
240
+ constraint — the audit columns are always not null, and a declared primary
241
+ key or ``Not null`` column carries its constraint through too.
242
+ """
243
+
244
+ return [column.name, column.type, column.not_null]
245
+
246
+
247
+ def _create_table_sql(qualified: str, columns) -> str:
248
+ """A strict ``CREATE TABLE`` over concrete columns."""
249
+
250
+ column_lines = ",\n".join(
251
+ f" {_ident(c.name)} {c.type}{' NOT NULL' if c.not_null else ''}"
252
+ for c in columns
253
+ )
254
+ return (
255
+ f"CREATE TABLE {qualified} (\n"
256
+ f"{column_lines}\n"
257
+ ")\n"
258
+ "USING delta\n"
259
+ f"{_COLUMN_MAPPING}\n"
260
+ )
261
+
262
+
263
+ def _ident(name: str) -> str:
264
+ """Back-tick quote a column identifier so spaces and keywords are safe."""
265
+
266
+ return "`" + name.replace("`", "``") + "`"