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.
- weaver/__init__.py +59 -0
- weaver/build_bundle/__init__.py +109 -0
- weaver/build_bundle/aliases.py +325 -0
- weaver/build_bundle/bundle.py +359 -0
- weaver/build_bundle/catalogue_actions.py +275 -0
- weaver/build_bundle/changes.py +186 -0
- weaver/build_bundle/endpoints.py +83 -0
- weaver/build_bundle/executors/__init__.py +69 -0
- weaver/build_bundle/executors/alias.py +202 -0
- weaver/build_bundle/executors/base.py +132 -0
- weaver/build_bundle/executors/folder.py +71 -0
- weaver/build_bundle/executors/load_file.py +205 -0
- weaver/build_bundle/executors/spark_case.py +26 -0
- weaver/build_bundle/executors/spark_schema.py +60 -0
- weaver/build_bundle/executors/spark_sql.py +59 -0
- weaver/build_bundle/executors/spark_sql_batch.py +57 -0
- weaver/build_bundle/executors/spark_table.py +213 -0
- weaver/build_bundle/executors/sql_endpoint_refresh.py +34 -0
- weaver/build_bundle/executors/tsql.py +81 -0
- weaver/build_bundle/incremental.py +288 -0
- weaver/build_bundle/installer.py +384 -0
- weaver/build_bundle/models.py +288 -0
- weaver/build_bundle/payloads.py +34 -0
- weaver/build_bundle/physical.py +625 -0
- weaver/build_bundle/planner.py +389 -0
- weaver/build_bundle/prune.py +620 -0
- weaver/build_bundle/report.py +108 -0
- weaver/build_bundle/stages.py +196 -0
- weaver/build_bundle/targets.py +272 -0
- weaver/build_bundle/workflow.py +585 -0
- weaver/catalogue/__init__.py +73 -0
- weaver/catalogue/builtin.py +238 -0
- weaver/catalogue/claims.py +121 -0
- weaver/catalogue/projection.py +437 -0
- weaver/catalogue/reader.py +152 -0
- weaver/catalogue/reconcile.py +231 -0
- weaver/catalogue/render.py +410 -0
- weaver/catalogue/state.py +660 -0
- weaver/catalogue/tables.py +648 -0
- weaver/config.py +178 -0
- weaver/declaration/__init__.py +171 -0
- weaver/declaration/columns.py +223 -0
- weaver/declaration/ddl.py +266 -0
- weaver/declaration/dependencies.py +544 -0
- weaver/declaration/graph.py +240 -0
- weaver/declaration/item_dependencies.py +292 -0
- weaver/declaration/load.py +191 -0
- weaver/declaration/metadata.py +1405 -0
- weaver/declaration/model.py +448 -0
- weaver/declaration/references.py +294 -0
- weaver/declaration/repository.py +959 -0
- weaver/declaration/schemas.py +135 -0
- weaver/declaration/source.py +674 -0
- weaver/declaration/spark_load.py +759 -0
- weaver/declaration/sql_shaping.py +591 -0
- weaver/declaration/templates/ddl/declared_create_table.sql +64 -0
- weaver/declaration/templates/ddl/infer_create_table.sql +97 -0
- weaver/declaration/templates/ddl/metadata_column_validation.sql +30 -0
- weaver/declaration/templates/load/column_metadata.sql +40 -0
- weaver/declaration/templates/load/full_replace_body.sql +21 -0
- weaver/declaration/templates/load/install_load_procedure.sql +27 -0
- weaver/declaration/templates/load/load_procedure.sql +48 -0
- weaver/declaration/templates/load/primary_key_body.sql +113 -0
- weaver/declaration/tsql_ddl.py +468 -0
- weaver/declaration/tsql_load.py +417 -0
- weaver/declaration/warehouse_type_mapping.yml +93 -0
- weaver/diagnostics.py +247 -0
- weaver/errors.py +61 -0
- weaver/etl.py +469 -0
- weaver/fabric/__init__.py +107 -0
- weaver/fabric/auth.py +137 -0
- weaver/fabric/capacity.py +143 -0
- weaver/fabric/client.py +147 -0
- weaver/fabric/environment.py +460 -0
- weaver/fabric/livy.py +478 -0
- weaver/fabric/notebooks.py +201 -0
- weaver/fabric/onelake.py +263 -0
- weaver/fabric/resolution.py +344 -0
- weaver/fabric/resources.py +245 -0
- weaver/fabric/session.py +148 -0
- weaver/fabric/shortcuts.py +120 -0
- weaver/fabric/sql.py +118 -0
- weaver/fabric/store.py +198 -0
- weaver/initialise.py +209 -0
- weaver/lakehouse.py +386 -0
- weaver/load.py +474 -0
- weaver/load_execution.py +483 -0
- weaver/load_plan.py +912 -0
- weaver/load_report.py +330 -0
- weaver/load_resolution.py +386 -0
- weaver/locations.py +164 -0
- weaver/objects.py +392 -0
- weaver/operations.py +757 -0
- weaver/physical_wipe.py +369 -0
- weaver/push.py +76 -0
- weaver/resolution.py +292 -0
- weaver/runtime/__init__.py +30 -0
- weaver/runtime/folder_load.py +402 -0
- weaver/runtime/load_contract.py +245 -0
- weaver/runtime/load_result.py +104 -0
- weaver/runtime/spark_load.py +152 -0
- weaver/runtime/table_load.py +497 -0
- weaver/spark/__init__.py +49 -0
- weaver/spark/catalogue.py +245 -0
- weaver/spark/destination.py +195 -0
- weaver/spark/session.py +84 -0
- weaver/spark/tokens.py +138 -0
- weaver/sql/__init__.py +40 -0
- weaver/sql/authentication.py +38 -0
- weaver/sql/connection.py +90 -0
- weaver/sql/errors.py +25 -0
- weaver/sql/execution.py +123 -0
- weaver/sql/pool.py +174 -0
- weaver/sql/wipe.py +156 -0
- weaver/store.py +209 -0
- weaver/targets.py +257 -0
- weaver/task_logging.py +215 -0
- weaver/unbind.py +74 -0
- weaver/workspaces.py +175 -0
- weaver_cli/__init__.py +12 -0
- weaver_cli/__main__.py +7 -0
- weaver_cli/main.py +626 -0
- weaverstack-0.1.1.dist-info/METADATA +113 -0
- weaverstack-0.1.1.dist-info/RECORD +127 -0
- weaverstack-0.1.1.dist-info/WHEEL +4 -0
- weaverstack-0.1.1.dist-info/entry_points.txt +2 -0
- 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("`", "``") + "`"
|