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,389 @@
|
|
|
1
|
+
"""Orchestrate one item-oriented repository into a coordinated build bundle.
|
|
2
|
+
|
|
3
|
+
A build is planned as an ordered series of **item** builds. The repository owns
|
|
4
|
+
an acyclic item dependency graph and its topological layers; this walks those
|
|
5
|
+
layers and plans each item as one coherent group of stages:
|
|
6
|
+
|
|
7
|
+
.. code-block:: text
|
|
8
|
+
|
|
9
|
+
catalogue claim removal, when required
|
|
10
|
+
|
|
11
|
+
item layer 0
|
|
12
|
+
producer item A prune, drops, schemas, aliases, documents, refresh
|
|
13
|
+
independent producer B prune, drops, schemas, aliases, documents, refresh
|
|
14
|
+
item layer 1
|
|
15
|
+
consumer item C prune, drops, schemas, aliases, documents, refresh
|
|
16
|
+
|
|
17
|
+
final batched catalogue publication
|
|
18
|
+
Weaver Lakehouse SQL endpoint refresh
|
|
19
|
+
|
|
20
|
+
Items in the same layer share their barriers — one batch each — because nothing
|
|
21
|
+
orders them against each other. Items in different layers never do, which is the
|
|
22
|
+
one invariant multi-item build rests on: a consumer's aliases and documents
|
|
23
|
+
cannot begin until every item it reaches into has finished, endpoint included.
|
|
24
|
+
|
|
25
|
+
Inside an item the document dependency graph still decides everything. The item
|
|
26
|
+
graph is the outer boundary, not a replacement for it.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from dataclasses import dataclass, replace
|
|
32
|
+
from typing import Mapping
|
|
33
|
+
|
|
34
|
+
from ..catalogue.state import Catalogue
|
|
35
|
+
from ..declaration.model import WeaverItemId, WeaverRepository
|
|
36
|
+
from ..errors import BuildError
|
|
37
|
+
from ..locations import Location
|
|
38
|
+
from ..store import Store
|
|
39
|
+
from .aliases import plan_item_aliases
|
|
40
|
+
from .bundle import SUPPORTED_FORMAT_VERSION, BuildBundle, compute_bundle_id, write_bundle
|
|
41
|
+
from .catalogue_actions import (
|
|
42
|
+
render_catalogue_after_build,
|
|
43
|
+
render_catalogue_before_build,
|
|
44
|
+
)
|
|
45
|
+
from .endpoints import item_refresh_stage
|
|
46
|
+
from .incremental import select_build, stale_alias_destinations
|
|
47
|
+
from .models import OMIT_TARGET_UNBOUND, BuildPlan, OmittedNode
|
|
48
|
+
from ..etl import item_load_artefacts, load_artefacts, load_schemas
|
|
49
|
+
from .physical import (
|
|
50
|
+
item_build_stages,
|
|
51
|
+
item_load_removals,
|
|
52
|
+
item_load_stages,
|
|
53
|
+
item_drop_stages,
|
|
54
|
+
item_prune_stage,
|
|
55
|
+
item_schema_stage,
|
|
56
|
+
)
|
|
57
|
+
from .prune import TargetInventory
|
|
58
|
+
from .stages import PlannedStage, enumerate_stages, merge_layer_stages
|
|
59
|
+
from .targets import ItemBindings, LakehouseBinding
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def generate_item_build_bundle(
|
|
63
|
+
repository: WeaverRepository,
|
|
64
|
+
*,
|
|
65
|
+
bindings: ItemBindings,
|
|
66
|
+
output: Location,
|
|
67
|
+
store: Store,
|
|
68
|
+
target_inventories: Mapping[WeaverItemId, TargetInventory] | None = None,
|
|
69
|
+
catalogue: Catalogue,
|
|
70
|
+
stale_claims: tuple = (),
|
|
71
|
+
control_lakehouse: LakehouseBinding,
|
|
72
|
+
) -> BuildBundle:
|
|
73
|
+
"""Freeze the one incremental build model into an installable bundle."""
|
|
74
|
+
|
|
75
|
+
if control_lakehouse is None:
|
|
76
|
+
raise BuildError("every build needs an explicit control-plane Lakehouse")
|
|
77
|
+
by_item = bindings.by_item
|
|
78
|
+
if not by_item:
|
|
79
|
+
raise BuildError("at least one Weaver item must be bound")
|
|
80
|
+
known = {item.identity for item in repository.items}
|
|
81
|
+
unknown = set(by_item) - known
|
|
82
|
+
if unknown:
|
|
83
|
+
raise BuildError(
|
|
84
|
+
"binding names item(s) absent from the repository: "
|
|
85
|
+
+ ", ".join(sorted(map(str, unknown)))
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Three kinds of node are selectable, and most of what follows needs exactly
|
|
89
|
+
# one of them. Documents are what prune, schemas and the physical build
|
|
90
|
+
# pipelines are about; alias destinations are registered objects too — so
|
|
91
|
+
# they take part in selection and certification — but they are materialised
|
|
92
|
+
# by the alias executor rather than by any document stage. Load artefacts are
|
|
93
|
+
# the third, and are kept out of everything that assumes a selected identity
|
|
94
|
+
# maps to a parsed declaration: they are signed from their own content and
|
|
95
|
+
# installed by the item's final layer.
|
|
96
|
+
selected_documents = {
|
|
97
|
+
identity for identity in repository.source_documents if identity.item in by_item
|
|
98
|
+
}
|
|
99
|
+
selected_aliases = {
|
|
100
|
+
alias.destination
|
|
101
|
+
for alias in repository.aliases
|
|
102
|
+
if alias.destination.item in by_item
|
|
103
|
+
}
|
|
104
|
+
selected_loads = {
|
|
105
|
+
artefact.identity
|
|
106
|
+
for artefact in load_artefacts(repository)
|
|
107
|
+
if artefact.identity.item in by_item
|
|
108
|
+
}
|
|
109
|
+
selected_ids = selected_documents | selected_aliases | selected_loads
|
|
110
|
+
|
|
111
|
+
targets = tuple(
|
|
112
|
+
by_item[item].to_bound_target() for item in sorted(by_item, key=str)
|
|
113
|
+
)
|
|
114
|
+
target_by_item = {
|
|
115
|
+
item: by_item[item].to_bound_target() for item in sorted(by_item, key=str)
|
|
116
|
+
}
|
|
117
|
+
inventories = dict(target_inventories or {})
|
|
118
|
+
for item, target in target_by_item.items():
|
|
119
|
+
inventory = inventories.get(item)
|
|
120
|
+
if inventory is None:
|
|
121
|
+
raise BuildError(f"planning {item} requires a prepared target inventory")
|
|
122
|
+
if inventory.target_id != target.id:
|
|
123
|
+
raise BuildError(
|
|
124
|
+
f"inventory for {item} describes {inventory.target_id}, not {target.id}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Freshness is read before ``registered`` is narrowed, because the whole
|
|
128
|
+
# point is to compare against an item this build does *not* include.
|
|
129
|
+
stale_aliases = stale_alias_destinations(
|
|
130
|
+
repository, catalogue.registered, bound_items=by_item
|
|
131
|
+
)
|
|
132
|
+
registered = {
|
|
133
|
+
identity: document
|
|
134
|
+
for identity, document in catalogue.registered.items()
|
|
135
|
+
if identity.item in by_item
|
|
136
|
+
}
|
|
137
|
+
selection = select_build(
|
|
138
|
+
repository, registered, selected=selected_ids, stale_aliases=stale_aliases
|
|
139
|
+
)
|
|
140
|
+
selected_for_drop = set(selection.selected_for_drop)
|
|
141
|
+
selected_for_build = set(selection.selected_for_build)
|
|
142
|
+
removed = set(registered) - selected_ids
|
|
143
|
+
|
|
144
|
+
control_target = _control_target(control_lakehouse, targets)
|
|
145
|
+
if all(target.id != control_target.id for target in targets):
|
|
146
|
+
targets = targets + (control_target,)
|
|
147
|
+
|
|
148
|
+
stages: list[PlannedStage] = []
|
|
149
|
+
omitted: list[OmittedNode] = []
|
|
150
|
+
|
|
151
|
+
catalogue_before = render_catalogue_before_build(
|
|
152
|
+
catalogue,
|
|
153
|
+
removed | selected_for_drop,
|
|
154
|
+
control_target=control_target,
|
|
155
|
+
stale_claims=stale_claims,
|
|
156
|
+
)
|
|
157
|
+
if catalogue_before is not None:
|
|
158
|
+
stages.append(catalogue_before)
|
|
159
|
+
|
|
160
|
+
# Alias destinations this build wanted but could not materialise. They must
|
|
161
|
+
# not reach the Registry: a row there means the object's work succeeded, and
|
|
162
|
+
# for these no work was even planned.
|
|
163
|
+
uncertified: set = set()
|
|
164
|
+
|
|
165
|
+
for layer in _item_layers(repository, target_by_item):
|
|
166
|
+
layer_stages: list[PlannedStage] = []
|
|
167
|
+
for item in layer:
|
|
168
|
+
planned = plan_item_build(
|
|
169
|
+
repository,
|
|
170
|
+
item=item,
|
|
171
|
+
target=target_by_item[item],
|
|
172
|
+
inventory=inventories[item],
|
|
173
|
+
target_by_item=target_by_item,
|
|
174
|
+
selected_documents=selected_documents,
|
|
175
|
+
selected_aliases=selected_aliases,
|
|
176
|
+
selected_for_drop=selected_for_drop - selected_loads,
|
|
177
|
+
selected_for_build=selected_for_build - selected_loads,
|
|
178
|
+
selected_loads=selected_for_build & selected_loads,
|
|
179
|
+
removed=removed,
|
|
180
|
+
registered=registered,
|
|
181
|
+
)
|
|
182
|
+
layer_stages.extend(planned.stages)
|
|
183
|
+
omitted.extend(planned.omitted)
|
|
184
|
+
uncertified |= planned.uncertified
|
|
185
|
+
stages.extend(merge_layer_stages(layer_stages))
|
|
186
|
+
|
|
187
|
+
stages.extend(
|
|
188
|
+
render_catalogue_after_build(
|
|
189
|
+
repository,
|
|
190
|
+
selected_ids - uncertified,
|
|
191
|
+
target_by_item,
|
|
192
|
+
control_target=control_target,
|
|
193
|
+
# Passed for the *report* the diff can produce. The statements come
|
|
194
|
+
# from the desired side alone, so a bad read cannot change them.
|
|
195
|
+
current=catalogue,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
sequences, payloads, target_changes = enumerate_stages(stages)
|
|
200
|
+
|
|
201
|
+
omitted.extend(
|
|
202
|
+
OmittedNode(
|
|
203
|
+
node_id=str(identity),
|
|
204
|
+
reason=OMIT_TARGET_UNBOUND,
|
|
205
|
+
detail=f"item {identity.item} is not bound",
|
|
206
|
+
)
|
|
207
|
+
for identity in sorted(repository.source_documents, key=str)
|
|
208
|
+
if identity not in selected_ids
|
|
209
|
+
)
|
|
210
|
+
plan = BuildPlan(
|
|
211
|
+
format_version=SUPPORTED_FORMAT_VERSION,
|
|
212
|
+
bundle_id="",
|
|
213
|
+
repository_name=repository.name,
|
|
214
|
+
repository_signature=repository.signature,
|
|
215
|
+
targets=targets,
|
|
216
|
+
sequences=sequences,
|
|
217
|
+
selection=selection,
|
|
218
|
+
omitted_nodes=tuple(sorted(omitted, key=lambda node: (node.node_id, node.reason))),
|
|
219
|
+
target_changes=target_changes,
|
|
220
|
+
)
|
|
221
|
+
plan = replace(plan, bundle_id=compute_bundle_id(plan))
|
|
222
|
+
return write_bundle(
|
|
223
|
+
output,
|
|
224
|
+
plan=plan,
|
|
225
|
+
payloads=payloads,
|
|
226
|
+
store=store,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _item_layers(
|
|
231
|
+
repository: WeaverRepository,
|
|
232
|
+
target_by_item: Mapping[WeaverItemId, object],
|
|
233
|
+
) -> tuple[tuple[WeaverItemId, ...], ...]:
|
|
234
|
+
"""The bound items, grouped by their repository topological layer.
|
|
235
|
+
|
|
236
|
+
Selection is document-based and the bindings are sparse, so an unbound
|
|
237
|
+
producer simply drops out — but the items that remain keep the repository's
|
|
238
|
+
order rather than being re-derived here. That is the point of the repository
|
|
239
|
+
owning the graph: one authoritative ordering, consumed rather than rebuilt.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
layers = repository.item_layers
|
|
243
|
+
if not layers:
|
|
244
|
+
raise BuildError(
|
|
245
|
+
f"repository {repository.name!r} carries no item dependency layers, so "
|
|
246
|
+
"the order its items must be built in is unknown"
|
|
247
|
+
)
|
|
248
|
+
placed = {item for layer in layers for item in layer}
|
|
249
|
+
missing = set(target_by_item) - placed
|
|
250
|
+
if missing:
|
|
251
|
+
raise BuildError(
|
|
252
|
+
"bound item(s) absent from the repository item graph: "
|
|
253
|
+
+ ", ".join(sorted(map(str, missing)))
|
|
254
|
+
)
|
|
255
|
+
return tuple(
|
|
256
|
+
selected
|
|
257
|
+
for selected in (
|
|
258
|
+
tuple(item for item in layer if item in target_by_item) for layer in layers
|
|
259
|
+
)
|
|
260
|
+
if selected
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@dataclass(frozen=True)
|
|
265
|
+
class PlannedItem:
|
|
266
|
+
"""One item's physical plan: what to do, what was left out, what is uncertified."""
|
|
267
|
+
|
|
268
|
+
#: The item's contiguous stages, in the order they must run.
|
|
269
|
+
stages: tuple[PlannedStage, ...]
|
|
270
|
+
#: Nodes this item could not plan, each carrying why.
|
|
271
|
+
omitted: tuple[OmittedNode, ...]
|
|
272
|
+
#: Alias destinations this item could not materialise *and* was asked to
|
|
273
|
+
#: build. Withheld from certification: an alias whose source item is unbound
|
|
274
|
+
#: has no physical form under these bindings, and a Registry row for it would
|
|
275
|
+
#: claim an installation that never happened. One already installed from an
|
|
276
|
+
#: earlier build is left certified — it is still there — so only the
|
|
277
|
+
#: intersection with the build selection is withheld.
|
|
278
|
+
uncertified: frozenset
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def plan_item_build(
|
|
282
|
+
repository: WeaverRepository,
|
|
283
|
+
*,
|
|
284
|
+
item: WeaverItemId,
|
|
285
|
+
target,
|
|
286
|
+
inventory: TargetInventory,
|
|
287
|
+
target_by_item,
|
|
288
|
+
selected_documents,
|
|
289
|
+
selected_aliases,
|
|
290
|
+
selected_for_drop,
|
|
291
|
+
selected_for_build,
|
|
292
|
+
registered,
|
|
293
|
+
selected_loads=(),
|
|
294
|
+
removed=(),
|
|
295
|
+
) -> PlannedItem:
|
|
296
|
+
"""One item's physical plan, from prepared inputs.
|
|
297
|
+
|
|
298
|
+
The seam between deciding *what* to build and arranging a whole bundle. It
|
|
299
|
+
takes a selection that has already been made and an inventory that has
|
|
300
|
+
already been read, and answers only: for this one item against this one
|
|
301
|
+
target, which stages run and in what order.
|
|
302
|
+
|
|
303
|
+
Everything above it stays out — item layers, catalogue publication, the
|
|
304
|
+
control-plane target, bundle identity, writing. So a claim about one item's
|
|
305
|
+
prune-then-drop-then-schema-then-build ordering can be made without
|
|
306
|
+
generating a bundle to see it, which is what kept such claims in the
|
|
307
|
+
integration suite.
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
aliases = plan_item_aliases(
|
|
311
|
+
repository,
|
|
312
|
+
item=item,
|
|
313
|
+
target=target,
|
|
314
|
+
target_by_item=target_by_item,
|
|
315
|
+
selected=selected_for_build & selected_aliases,
|
|
316
|
+
)
|
|
317
|
+
artefacts = item_load_artefacts(repository, item=item)
|
|
318
|
+
stages: list[PlannedStage] = []
|
|
319
|
+
|
|
320
|
+
# Prune is given every *declared* alias destination, never only the selected
|
|
321
|
+
# ones: an alias this build decided not to touch is still desired state, and
|
|
322
|
+
# a prune that could not see it would delete the very thing incremental
|
|
323
|
+
# selection just chose to keep. Load artefacts are treated the same way, and
|
|
324
|
+
# the stage derives them itself.
|
|
325
|
+
prune = item_prune_stage(
|
|
326
|
+
repository, selected_documents, item=item, target=target, inventory=inventory
|
|
327
|
+
)
|
|
328
|
+
if prune is not None:
|
|
329
|
+
stages.append(prune)
|
|
330
|
+
stages.extend(
|
|
331
|
+
item_drop_stages(
|
|
332
|
+
repository,
|
|
333
|
+
selected_for_drop - selected_aliases,
|
|
334
|
+
item=item,
|
|
335
|
+
target=target,
|
|
336
|
+
registered=registered,
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
schemas = item_schema_stage(
|
|
340
|
+
selected_documents,
|
|
341
|
+
item=item,
|
|
342
|
+
target=target,
|
|
343
|
+
inventory=inventory,
|
|
344
|
+
# `_` is where a Warehouse's generated load procedures live, and no
|
|
345
|
+
# document declares an object in it — so like an alias's namespace it
|
|
346
|
+
# would never be created if only documents were consulted. It is derived
|
|
347
|
+
# from the artefacts, so an item with no procedures asks for no schema.
|
|
348
|
+
extra_schemas=tuple(aliases.schemas) + load_schemas(artefacts),
|
|
349
|
+
)
|
|
350
|
+
if schemas is not None:
|
|
351
|
+
stages.append(schemas)
|
|
352
|
+
if aliases.stage is not None:
|
|
353
|
+
stages.append(aliases.stage)
|
|
354
|
+
stages.extend(
|
|
355
|
+
item_build_stages(
|
|
356
|
+
repository,
|
|
357
|
+
selected_for_build - selected_aliases,
|
|
358
|
+
item=item,
|
|
359
|
+
target=target,
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
refresh = item_refresh_stage(stages, item=item, target=target)
|
|
364
|
+
if refresh is not None:
|
|
365
|
+
stages.append(refresh)
|
|
366
|
+
|
|
367
|
+
# The load layer closes the item, after its structure is built and its
|
|
368
|
+
# endpoint has caught up. Removals ride in it too: they come from the
|
|
369
|
+
# previous Registry rows rather than from any diff against the target, so
|
|
370
|
+
# they need no earlier barrier to be safe.
|
|
371
|
+
stages.extend(
|
|
372
|
+
item_load_stages(artefacts, selected_loads, item=item, target=target)
|
|
373
|
+
)
|
|
374
|
+
stages.extend(
|
|
375
|
+
item_load_removals(removed, item=item, target=target, registered=registered)
|
|
376
|
+
)
|
|
377
|
+
return PlannedItem(
|
|
378
|
+
stages=tuple(stages),
|
|
379
|
+
omitted=aliases.omitted,
|
|
380
|
+
uncertified=frozenset(aliases.omitted_destinations) & frozenset(selected_for_build),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _control_target(binding: LakehouseBinding, targets):
|
|
385
|
+
physical = binding.to_bound_target()
|
|
386
|
+
for target in targets:
|
|
387
|
+
if target.kind == physical.kind and target.item_id == physical.item_id:
|
|
388
|
+
return target
|
|
389
|
+
return replace(physical, id=f"control-{physical.id}")
|