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
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
+ ]