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
weaver/resolution.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Local workspace resolution — names to locations.
|
|
2
|
+
|
|
3
|
+
Turns a :class:`~weaver.workspaces.LocalWorkspace` plus the level-three identities into
|
|
4
|
+
concrete :class:`~weaver.locations.Location` values::
|
|
5
|
+
|
|
6
|
+
LocalResolver(LocalWorkspace(workspace=".local"))
|
|
7
|
+
|
|
8
|
+
DeltaTarget("Sales") + Budget.Expense
|
|
9
|
+
-> .local/Sales/Tables/Budget/Expense
|
|
10
|
+
|
|
11
|
+
FolderTarget("Sales/Files") + Budget.BudgetPaper
|
|
12
|
+
-> .local/Sales/Files/Budget/BudgetPaper
|
|
13
|
+
|
|
14
|
+
CLI bundle handover
|
|
15
|
+
-> .local/Weaver/Files/cli/<execution-id>/install.weaver.zip
|
|
16
|
+
|
|
17
|
+
This is arithmetic only. Nothing here touches the filesystem — every location
|
|
18
|
+
can be inspected before any mutation occurs. Mutation is a
|
|
19
|
+
:class:`~weaver.store.Store` concern.
|
|
20
|
+
|
|
21
|
+
Together with the Fabric resolver (checkpoint 7) this is the *only* place that
|
|
22
|
+
knows how a name becomes a location. Everything downstream receives resolved
|
|
23
|
+
locations and never derives them, which is what makes "every target root is
|
|
24
|
+
explicit" enforceable rather than aspirational.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from .errors import CommandError
|
|
30
|
+
from .workspaces import BUILD_BUNDLES_AREA, CLI_AREA, WEAVER_ITEMS_AREA, LocalWorkspace
|
|
31
|
+
from .locations import LakehouseSparkLocation, Location
|
|
32
|
+
from .spark import SparkDestination, local_destination
|
|
33
|
+
from .targets import (
|
|
34
|
+
FILES_AREA,
|
|
35
|
+
DeltaTarget,
|
|
36
|
+
FolderTarget,
|
|
37
|
+
ItemRef,
|
|
38
|
+
WarehouseTarget,
|
|
39
|
+
validate_name,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
#: The Lakehouse area holding Delta tables. Never written by a user — a Delta
|
|
43
|
+
#: target names a Lakehouse and the area follows from the object kind.
|
|
44
|
+
TABLES_AREA = "Tables"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class LocalResolver:
|
|
48
|
+
"""Resolves level-three identities against a local workspace root.
|
|
49
|
+
|
|
50
|
+
Checkpoint 7 adds a Fabric resolver with the same surface, returning URL
|
|
51
|
+
locations. No shared protocol is declared yet: one implementation is a
|
|
52
|
+
guess at the shape, two make it visible.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, workspace: LocalWorkspace) -> None:
|
|
56
|
+
if not isinstance(workspace, LocalWorkspace):
|
|
57
|
+
raise CommandError(f"LocalResolver needs a LocalWorkspace, got {type(workspace).__name__}")
|
|
58
|
+
self.workspace = workspace
|
|
59
|
+
|
|
60
|
+
# --- level four ------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def root(self) -> Location:
|
|
64
|
+
return Location(str(self.workspace.workspace))
|
|
65
|
+
|
|
66
|
+
# --- level three -----------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def lakehouse(self, item: ItemRef) -> Location:
|
|
69
|
+
"""A Lakehouse root — a directory holding Files/ and Tables/.
|
|
70
|
+
|
|
71
|
+
Named ``lakehouse`` to match the Fabric resolver, where resolution is
|
|
72
|
+
typed: a bare name is never asked "what are you?".
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
return self.root / item.name
|
|
76
|
+
|
|
77
|
+
def lakehouse_exists(self, item: ItemRef) -> bool:
|
|
78
|
+
return self.lakehouse(item).path.is_dir()
|
|
79
|
+
|
|
80
|
+
def files_root(self, item: ItemRef) -> Location:
|
|
81
|
+
return self.lakehouse(item) / FILES_AREA
|
|
82
|
+
|
|
83
|
+
def tables_root(self, item: ItemRef) -> Location:
|
|
84
|
+
return self.lakehouse(item) / TABLES_AREA
|
|
85
|
+
|
|
86
|
+
def spark_root(self, item: ItemRef) -> str:
|
|
87
|
+
"""The root Spark writes through, for a Lakehouse.
|
|
88
|
+
|
|
89
|
+
The local counterpart of the Fabric ``abfss://`` root: same contract,
|
|
90
|
+
filesystem transport, and the same reason for existing — a destination is
|
|
91
|
+
addressed explicitly rather than by attaching the session to it.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
return self.lakehouse(item).value
|
|
95
|
+
|
|
96
|
+
# --- folder targets --------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def folder_root(self, target: FolderTarget) -> Location:
|
|
99
|
+
"""The Files area a folder target names. There is nothing below it to configure."""
|
|
100
|
+
|
|
101
|
+
return self.files_root(target.lakehouse)
|
|
102
|
+
|
|
103
|
+
def folder_object(self, target: FolderTarget, schema: str, name: str) -> Location:
|
|
104
|
+
"""Where one Folder object materialises — ``Files/<Schema>/<Object>``."""
|
|
105
|
+
|
|
106
|
+
return self.folder_root(target).join(
|
|
107
|
+
validate_name(schema, what="schema"),
|
|
108
|
+
validate_name(name, what="object name"),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def folder_staging(self, target: FolderTarget, schema: str, name: str) -> Location:
|
|
112
|
+
"""The object-local staging sibling. There is no shared staging area."""
|
|
113
|
+
|
|
114
|
+
destination = self.folder_object(target, schema, name)
|
|
115
|
+
return Location(f"{destination.value}_Staging")
|
|
116
|
+
|
|
117
|
+
# --- delta targets ---------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def delta_table(self, target: DeltaTarget, schema: str, name: str) -> Location:
|
|
120
|
+
return self.tables_root(target.lakehouse).join(
|
|
121
|
+
validate_name(schema, what="schema"),
|
|
122
|
+
validate_name(name, what="object name"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def lakehouse_spark_location(self, item: ItemRef) -> LakehouseSparkLocation:
|
|
126
|
+
"""One destination Lakehouse's physical roots, for Spark to address.
|
|
127
|
+
|
|
128
|
+
The local counterpart of the Fabric ``abfss://`` roots: same contract,
|
|
129
|
+
filesystem transport. Resolving a target once here is what keeps the
|
|
130
|
+
session's attached Lakehouse (Weaver) separate from the destinations a
|
|
131
|
+
build writes to — see :class:`~weaver.locations.LakehouseSparkLocation`.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
return LakehouseSparkLocation(
|
|
135
|
+
item=item.name,
|
|
136
|
+
tables_root=self.tables_root(item).value,
|
|
137
|
+
files_root=self.files_root(item).value,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def spark_destination(self, item: ItemRef) -> SparkDestination:
|
|
141
|
+
"""One Lakehouse, as this session's Spark catalogue names it.
|
|
142
|
+
|
|
143
|
+
The local proxy for Fabric's four-part namespace. Local Spark has one
|
|
144
|
+
namespace level and cannot be given another, so the Lakehouse is folded
|
|
145
|
+
into the database name and the database carries an explicit ``LOCATION``
|
|
146
|
+
under the Lakehouse's ``Tables`` area — same isolation, same storage
|
|
147
|
+
layout, different syntax. See
|
|
148
|
+
:mod:`weaver.spark.destination` for why it is folded rather than nested.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
return local_destination(
|
|
152
|
+
item=item.name, tables_root=self.tables_root(item).value
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# --- warehouse targets -----------------------------------------------
|
|
156
|
+
|
|
157
|
+
def warehouse(self, target: WarehouseTarget) -> Location:
|
|
158
|
+
"""Always fails: a local workspace has no SQL implementation.
|
|
159
|
+
|
|
160
|
+
Explicit rather than silently skipped, so a build carrying SQL objects
|
|
161
|
+
against a local workspace reports the reason.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
raise CommandError(
|
|
165
|
+
f"local Workspace has no SQL implementation, so Warehouse target "
|
|
166
|
+
f"{target.warehouse.name!r} cannot be resolved — Warehouse work is Fabric-only"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# --- the weaver lakehouse --------------------------------------------
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def weaver_lakehouse(self) -> Location:
|
|
173
|
+
return self.lakehouse(ItemRef(self._weaver_lakehouse_name()))
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def weaver_items_root(self) -> Location:
|
|
177
|
+
"""The workspace's one declaration, with item types directly below it."""
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
self.files_root(ItemRef(self._weaver_lakehouse_name()))
|
|
181
|
+
/ WEAVER_ITEMS_AREA
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def build_bundles_root(self) -> Location:
|
|
186
|
+
"""``<weaver-lakehouse>/Files/build_bundles`` — where persisted bundles live.
|
|
187
|
+
|
|
188
|
+
A generated bundle normally lands in a throwaway directory that is passed
|
|
189
|
+
straight to the installer. When one is kept — for handover, audit, or
|
|
190
|
+
inspection — its single .weaver.zip archive belongs here, beside the
|
|
191
|
+
repositories it was built from rather than in a remote working tree.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
return self.files_root(ItemRef(self._weaver_lakehouse_name())) / BUILD_BUNDLES_AREA
|
|
195
|
+
|
|
196
|
+
def build_bundle(self, name: str) -> Location:
|
|
197
|
+
"""One named bundle directory beneath ``build_bundles_root``.
|
|
198
|
+
|
|
199
|
+
Initialisation uses this because its bootstrap bundle is idempotent and there is
|
|
200
|
+
no value in a new name each run. A build that keeps its bundle for
|
|
201
|
+
handover or audit uses a timestamped archive instead.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
return self.build_bundles_root / validate_name(name, what="bundle name")
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def cli_root(self) -> Location:
|
|
208
|
+
"""Ephemeral desktop-to-session handover beneath ``Files/cli``."""
|
|
209
|
+
|
|
210
|
+
return self.files_root(ItemRef(self._weaver_lakehouse_name())) / CLI_AREA
|
|
211
|
+
|
|
212
|
+
def cli_execution(self, execution_id: str) -> Location:
|
|
213
|
+
return self.cli_root / validate_name(execution_id, what="execution id")
|
|
214
|
+
|
|
215
|
+
def cli_bundle(self, execution_id: str) -> Location:
|
|
216
|
+
return self.cli_execution(execution_id) / "install.weaver.zip"
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def control_tables_root(self) -> Location:
|
|
220
|
+
"""``<weaver-lakehouse>/Tables`` — the control-plane tables.
|
|
221
|
+
|
|
222
|
+
The table names and whether they sit under a schema are a checkpoint 16
|
|
223
|
+
decision; this is only their root.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
return self.tables_root(ItemRef(self._weaver_lakehouse_name()))
|
|
227
|
+
|
|
228
|
+
def _weaver_lakehouse_name(self) -> str:
|
|
229
|
+
name = self.workspace.weaver_lakehouse
|
|
230
|
+
if name is None:
|
|
231
|
+
raise CommandError(
|
|
232
|
+
"no Weaver Lakehouse for this Workspace — set weaver_lakehouse on the Workspace "
|
|
233
|
+
"or supply it explicitly"
|
|
234
|
+
)
|
|
235
|
+
return name
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# --- choosing an implementation for a workspace -----------------------------------
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def resolver_for(workspace):
|
|
242
|
+
"""The resolver for a workspace in the current executor.
|
|
243
|
+
|
|
244
|
+
A Fabric session resolves within its current workspace through
|
|
245
|
+
NotebookUtils. A desktop process uses the REST-backed Fabric resolver; that
|
|
246
|
+
cross-boundary caller supplies its DFS store explicitly.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
from .workspaces import FabricWorkspace, LocalWorkspace
|
|
250
|
+
|
|
251
|
+
if isinstance(workspace, LocalWorkspace):
|
|
252
|
+
return LocalResolver(workspace)
|
|
253
|
+
|
|
254
|
+
if isinstance(workspace, FabricWorkspace):
|
|
255
|
+
try:
|
|
256
|
+
from notebookutils import lakehouse, runtime
|
|
257
|
+
except ImportError:
|
|
258
|
+
pass
|
|
259
|
+
else:
|
|
260
|
+
from .fabric.session import FabricSessionResolver
|
|
261
|
+
|
|
262
|
+
return FabricSessionResolver(
|
|
263
|
+
workspace, lakehouse=lakehouse, runtime=runtime
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
from .fabric.resolution import FabricResolver
|
|
267
|
+
|
|
268
|
+
return FabricResolver(workspace)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def store_for(workspace):
|
|
272
|
+
"""The **within-workspace** default store for a workspace.
|
|
273
|
+
|
|
274
|
+
Local execution uses the filesystem. Fabric execution uses NotebookUtils,
|
|
275
|
+
which is available only inside a Fabric session. A desktop caller crossing
|
|
276
|
+
into Fabric still constructs ``OneLakeDfsClient`` and injects it explicitly,
|
|
277
|
+
so DFS is never mistaken for the within-workspace default.
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
from .workspaces import FabricWorkspace, LocalWorkspace
|
|
281
|
+
from .store import LocalStore
|
|
282
|
+
|
|
283
|
+
if isinstance(workspace, LocalWorkspace):
|
|
284
|
+
return LocalStore()
|
|
285
|
+
if isinstance(workspace, FabricWorkspace):
|
|
286
|
+
from .fabric.store import FabricStore
|
|
287
|
+
|
|
288
|
+
return FabricStore()
|
|
289
|
+
|
|
290
|
+
raise CommandError(
|
|
291
|
+
f"{type(workspace).__name__} has no within-workspace store"
|
|
292
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Load mechanics — the half of Weaver that runs where the data is.
|
|
2
|
+
|
|
3
|
+
Everything else in the core decides *what should exist* and freezes it into a
|
|
4
|
+
bundle. This package is what happens afterwards, when an installed artefact is
|
|
5
|
+
executed against a real target: the contract a running module reads out of its
|
|
6
|
+
own docstring, the result every primitive reports, and the machinery a Python
|
|
7
|
+
table or folder loads through.
|
|
8
|
+
|
|
9
|
+
It is deliberately reachable without any of that upstream: a load primitive
|
|
10
|
+
takes a session and a target and nothing more. No repository, no catalogue, no
|
|
11
|
+
planner and no orchestrator — see :mod:`weaver.runtime.load_contract` for why
|
|
12
|
+
that boundary is the point rather than an omission.
|
|
13
|
+
|
|
14
|
+
Nothing here imports PySpark or Delta. A session arrives from the caller and is
|
|
15
|
+
used through its ordinary API, and Delta operations are issued as SQL text, so
|
|
16
|
+
the core stays importable on a machine with no JVM.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from .load_contract import FolderLoadContract, LoadContract, document_for_module
|
|
22
|
+
from .load_result import RESULT_COLUMNS, LoadResult
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"RESULT_COLUMNS",
|
|
26
|
+
"FolderLoadContract",
|
|
27
|
+
"LoadContract",
|
|
28
|
+
"LoadResult",
|
|
29
|
+
"document_for_module",
|
|
30
|
+
]
|