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,648 @@
|
|
|
1
|
+
"""The fixed shape of every catalogue table, declared once.
|
|
2
|
+
|
|
3
|
+
These definitions are the single authority on what the catalogue *is*: the
|
|
4
|
+
tables, their ordered columns, their types, and the key that identifies a row.
|
|
5
|
+
Everything else — the built-in Weaver document that materialises them, the tolerant reader,
|
|
6
|
+
the projection, the rendered DML — reads them from here. A column added in one
|
|
7
|
+
place and forgotten in another is the failure mode this module exists to make
|
|
8
|
+
impossible, so nothing downstream is allowed its own list.
|
|
9
|
+
|
|
10
|
+
**Installation scope is in the key, not beside it.** Every table is keyed on
|
|
11
|
+
``repository`` and ``target_type`` before anything else, because the same
|
|
12
|
+
repository is installed independently into its Lakehouse and its Warehouse and
|
|
13
|
+
the same ``Schema.Object`` legitimately exists in both:
|
|
14
|
+
|
|
15
|
+
.. code-block:: text
|
|
16
|
+
|
|
17
|
+
SalesRepo | lakehouse | Sales | Customer
|
|
18
|
+
SalesRepo | warehouse | Sales | Customer
|
|
19
|
+
|
|
20
|
+
Those are two rows. A Lakehouse build must not touch the second. Making the scope
|
|
21
|
+
part of the identity is what stops a comparison or a delete from accidentally
|
|
22
|
+
spanning both — there is no way to name a row without naming its installation.
|
|
23
|
+
|
|
24
|
+
**The physical target's name is never identity.** A repository has at most one
|
|
25
|
+
current installation per target type, so rebinding it to a different Lakehouse
|
|
26
|
+
updates :data:`INSTALLATION`'s ``target_name``; it does not insert a second
|
|
27
|
+
installation.
|
|
28
|
+
|
|
29
|
+
**Signatures and audit columns.** Every table carries ``signature`` — the source
|
|
30
|
+
hash of whatever the row projects — plus Weaver's own audit columns, which the
|
|
31
|
+
ordinary build appends to any Delta table and which are therefore not declared
|
|
32
|
+
here as business columns. ``signature`` is what incremental planning compares to
|
|
33
|
+
decide what changed and must be dropped and rebuilt; it is the reason the
|
|
34
|
+
catalogue is the foundation for idempotent building rather than a report on it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from dataclasses import dataclass
|
|
40
|
+
|
|
41
|
+
from ..declaration.metadata import AUDIT_COLUMNS, SPARK_SQL, audit_column_name
|
|
42
|
+
|
|
43
|
+
#: The schema Weaver's own control plane lives in, inside the Weaver Lakehouse.
|
|
44
|
+
#: One character, reserved, and never touched by an application build's prune.
|
|
45
|
+
CATALOGUE_SCHEMA = "_"
|
|
46
|
+
|
|
47
|
+
#: What an installed object is, in the catalogue's vocabulary. Deliberately
|
|
48
|
+
#: coarse: enough for a later operation to know how to address the thing, without
|
|
49
|
+
#: inventing an identity scheme that competes with Weaver document's kind and language.
|
|
50
|
+
#: ``file`` and ``stored_procedure`` are what a load layer installs — a deployed
|
|
51
|
+
#: module or generated statement, and a generated load procedure — and they are
|
|
52
|
+
#: ordinary managed objects rather than infrastructure exempt from the lifecycle.
|
|
53
|
+
OBJECT_TYPES = ("folder", "table", "view", "file", "stored_procedure")
|
|
54
|
+
|
|
55
|
+
#: What an object is *for*. A ``data`` object holds or shapes rows; a ``load``
|
|
56
|
+
#: object does the work that fills one, and is installed by an item's load layer.
|
|
57
|
+
ROLE_DATA = "data"
|
|
58
|
+
ROLE_LOAD = "load"
|
|
59
|
+
OBJECT_ROLES = (ROLE_DATA, ROLE_LOAD)
|
|
60
|
+
|
|
61
|
+
#: How a logical key is classified. Both are declared, neither is built.
|
|
62
|
+
KEY_PRIMARY = "primary_key"
|
|
63
|
+
KEY_UNIQUE = "unique"
|
|
64
|
+
INDEX_TYPES = (KEY_PRIMARY, KEY_UNIQUE)
|
|
65
|
+
|
|
66
|
+
STRING = "string"
|
|
67
|
+
BOOLEAN = "boolean"
|
|
68
|
+
TIMESTAMP = "timestamp"
|
|
69
|
+
|
|
70
|
+
#: The signature column, on every table.
|
|
71
|
+
SIGNATURE = "signature"
|
|
72
|
+
|
|
73
|
+
#: When the Registry row was last published — one value shared by every row a
|
|
74
|
+
#: completed build writes, so two rows can be ordered against each other.
|
|
75
|
+
#:
|
|
76
|
+
#: Registry publication is Weaver's completion boundary: a row is written last,
|
|
77
|
+
#: after everything the object needed succeeded. So "when was this published"
|
|
78
|
+
#: *is* "when was this last built", and comparing two rows answers the one
|
|
79
|
+
#: question a cross-item build cannot answer from signatures alone — has this
|
|
80
|
+
#: alias's source been rebuilt since the alias was made? A signature cannot say
|
|
81
|
+
#: that, because reloading a source changes no declaration.
|
|
82
|
+
#:
|
|
83
|
+
#: It advances only when the row is inserted. Every rebuild goes through an
|
|
84
|
+
#: insert — a rebuilt object has its Registry claim removed before any physical
|
|
85
|
+
#: work — so an object that was not rebuilt keeps the epoch it had, even if some
|
|
86
|
+
#: other column of its row changed.
|
|
87
|
+
BUILD_EPOCH = "build_epoch"
|
|
88
|
+
|
|
89
|
+
#: Weaver's audit columns as this Delta table actually spells them. They are not
|
|
90
|
+
#: business columns — the build appends them to every table it creates — but the
|
|
91
|
+
#: catalogue writes them, so it has to know them.
|
|
92
|
+
AUDIT_COLUMN_NAMES = tuple(
|
|
93
|
+
audit_column_name(logical, SPARK_SQL) for logical in AUDIT_COLUMNS
|
|
94
|
+
)
|
|
95
|
+
AUDIT_INSERT_COLUMN, AUDIT_UPDATE_COLUMN, AUDIT_DELETE_COLUMN = AUDIT_COLUMN_NAMES
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True)
|
|
99
|
+
class CatalogueColumn:
|
|
100
|
+
"""One column of a catalogue table."""
|
|
101
|
+
|
|
102
|
+
name: str
|
|
103
|
+
type: str = STRING
|
|
104
|
+
#: Key and scope columns are not null because they are identity. A row that
|
|
105
|
+
#: could not say which installation it belonged to would be unusable.
|
|
106
|
+
not_null: bool = False
|
|
107
|
+
description: str = ""
|
|
108
|
+
#: Supplied by the installer when the row is written, rather than projected
|
|
109
|
+
#: from the declaration — so it is created and described like any other
|
|
110
|
+
#: column, but never appears in a projected row and never takes part in the
|
|
111
|
+
#: comparison that decides whether a row changed. See
|
|
112
|
+
#: :data:`BUILD_EPOCH`, the only one.
|
|
113
|
+
published: bool = False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class CatalogueTable:
|
|
118
|
+
"""One catalogue table's fixed representation.
|
|
119
|
+
|
|
120
|
+
``columns`` are the business columns in their declared order, ``signature``
|
|
121
|
+
last. ``key`` names the columns that identify a row; they always lead, and
|
|
122
|
+
always begin with the installation scope.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
name: str
|
|
126
|
+
description: str
|
|
127
|
+
key: tuple[str, ...]
|
|
128
|
+
columns: tuple[CatalogueColumn, ...]
|
|
129
|
+
|
|
130
|
+
def __post_init__(self) -> None:
|
|
131
|
+
names = [column.name for column in self.columns]
|
|
132
|
+
if len(set(names)) != len(names):
|
|
133
|
+
raise ValueError(f"{self.name}: duplicate column")
|
|
134
|
+
business = [column.name for column in self.columns if not column.published]
|
|
135
|
+
if business[-1] != SIGNATURE:
|
|
136
|
+
raise ValueError(f"{self.name}: signature must be the last business column")
|
|
137
|
+
if business[: len(self.key)] != list(self.key):
|
|
138
|
+
raise ValueError(f"{self.name}: key columns must lead, in key order")
|
|
139
|
+
if self.key[:2] != ITEM_SCOPE_COLUMNS:
|
|
140
|
+
raise ValueError(
|
|
141
|
+
f"{self.name}: every key opens with the logical item scope"
|
|
142
|
+
)
|
|
143
|
+
not_nullable = {column.name for column in self.columns if column.not_null}
|
|
144
|
+
missing = [name for name in self.key if name not in not_nullable]
|
|
145
|
+
if missing:
|
|
146
|
+
raise ValueError(f"{self.name}: key columns must be not null: {missing}")
|
|
147
|
+
if SIGNATURE in self.key:
|
|
148
|
+
# It must be a comparison column, because that is what makes a changed
|
|
149
|
+
# source file a changed row. In the key it would leave a table with
|
|
150
|
+
# nothing to compare, and the merge's MATCHED guard would be empty.
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"{self.name}: signature is a comparison column, never part of the key"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def qualified(self) -> str:
|
|
157
|
+
return f"{CATALOGUE_SCHEMA}.{self.name}"
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def column_names(self) -> tuple[str, ...]:
|
|
161
|
+
"""The business columns, in order — those a projection supplies."""
|
|
162
|
+
|
|
163
|
+
return tuple(column.name for column in self.columns if not column.published)
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def published_column_names(self) -> tuple[str, ...]:
|
|
167
|
+
"""Columns the installer supplies when it writes the row."""
|
|
168
|
+
|
|
169
|
+
return tuple(column.name for column in self.columns if column.published)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def comparison_columns(self) -> tuple[str, ...]:
|
|
173
|
+
"""Non-key columns, whose change makes a matched row an update.
|
|
174
|
+
|
|
175
|
+
``signature`` is one of them, which is the point: a row whose source file
|
|
176
|
+
changed differs here even when every projected value happens to match.
|
|
177
|
+
|
|
178
|
+
A published column is deliberately absent. One that compared would differ
|
|
179
|
+
on every build by construction — its value is new each time — so every
|
|
180
|
+
row would update every build and the no-op that makes an unchanged
|
|
181
|
+
installation cheap would be gone.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
return tuple(name for name in self.column_names if name not in self.key)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def physical_columns(self) -> tuple[str, ...]:
|
|
188
|
+
"""Every column the built table has: business, published, audit trio."""
|
|
189
|
+
|
|
190
|
+
return self.column_names + self.published_column_names + AUDIT_COLUMN_NAMES
|
|
191
|
+
|
|
192
|
+
def column(self, name: str) -> CatalogueColumn:
|
|
193
|
+
for column in self.columns:
|
|
194
|
+
if column.name == name:
|
|
195
|
+
return column
|
|
196
|
+
raise KeyError(f"{self.qualified} has no column {name!r}")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# --- the installation scope, shared by every table ---------------------------
|
|
200
|
+
|
|
201
|
+
SCOPE_ITEM_TYPE = "item_type"
|
|
202
|
+
SCOPE_ITEM_NAME = "item_name"
|
|
203
|
+
ITEM_SCOPE_COLUMNS = (SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _scope() -> tuple[CatalogueColumn, ...]:
|
|
207
|
+
return (
|
|
208
|
+
CatalogueColumn(
|
|
209
|
+
SCOPE_ITEM_TYPE,
|
|
210
|
+
not_null=True,
|
|
211
|
+
description="Logical Weaver item type.",
|
|
212
|
+
),
|
|
213
|
+
CatalogueColumn(
|
|
214
|
+
SCOPE_ITEM_NAME,
|
|
215
|
+
not_null=True,
|
|
216
|
+
description="Logical Weaver item name.",
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _object() -> tuple[CatalogueColumn, ...]:
|
|
222
|
+
return (
|
|
223
|
+
CatalogueColumn(
|
|
224
|
+
"schema_name", not_null=True, description="The object's schema."
|
|
225
|
+
),
|
|
226
|
+
CatalogueColumn(
|
|
227
|
+
"object_name", not_null=True, description="The object's name."
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _signature(what: str) -> CatalogueColumn:
|
|
233
|
+
return CatalogueColumn(
|
|
234
|
+
SIGNATURE,
|
|
235
|
+
not_null=True,
|
|
236
|
+
description=f"Content hash of {what}, so a change can be detected.",
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _described(*, what: str) -> tuple[CatalogueColumn, ...]:
|
|
241
|
+
"""A description and the pointer it was copied from, if it was copied.
|
|
242
|
+
|
|
243
|
+
A Weaver document ``Description`` is either prose or exactly one ``$Schema.Object``
|
|
244
|
+
reference. When it is a reference the prose is copied from the target and the
|
|
245
|
+
pointer is kept, so a reader can see both what it says and where it came
|
|
246
|
+
from.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
CatalogueColumn("description", description=f"What this {what} is."),
|
|
251
|
+
CatalogueColumn(
|
|
252
|
+
"description_reference",
|
|
253
|
+
description=(
|
|
254
|
+
"The $Schema.Object the description was copied from, when it was "
|
|
255
|
+
"declared as a reference rather than written here."
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _lineage() -> tuple[CatalogueColumn, ...]:
|
|
262
|
+
return (
|
|
263
|
+
CatalogueColumn("lineage", description="Where this object's data comes from."),
|
|
264
|
+
CatalogueColumn(
|
|
265
|
+
"lineage_reference",
|
|
266
|
+
description="The $Schema.Object the lineage was copied from, if any.",
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _behaviour() -> tuple[CatalogueColumn, ...]:
|
|
272
|
+
return (
|
|
273
|
+
CatalogueColumn(
|
|
274
|
+
"is_incremental",
|
|
275
|
+
BOOLEAN,
|
|
276
|
+
description="Whether load accumulates rows rather than replacing them.",
|
|
277
|
+
),
|
|
278
|
+
CatalogueColumn(
|
|
279
|
+
"is_static",
|
|
280
|
+
BOOLEAN,
|
|
281
|
+
description="Whether the object is loaded once rather than refreshed.",
|
|
282
|
+
),
|
|
283
|
+
CatalogueColumn(
|
|
284
|
+
"prohibit_rebuild",
|
|
285
|
+
BOOLEAN,
|
|
286
|
+
description="Whether build may drop and recreate this object.",
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# --- the tables --------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
INSTALLATION = CatalogueTable(
|
|
294
|
+
name="Installation",
|
|
295
|
+
description=(
|
|
296
|
+
"One row per repository installation — a repository against one physical "
|
|
297
|
+
"target type. The bound item's name is an attribute, never identity: "
|
|
298
|
+
"rebinding to a different Lakehouse updates this row rather than adding "
|
|
299
|
+
"a second installation."
|
|
300
|
+
),
|
|
301
|
+
key=(SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME),
|
|
302
|
+
columns=(
|
|
303
|
+
*_scope(),
|
|
304
|
+
CatalogueColumn(
|
|
305
|
+
"target_name",
|
|
306
|
+
not_null=True,
|
|
307
|
+
description="The physical item currently bound to this installation.",
|
|
308
|
+
),
|
|
309
|
+
CatalogueColumn(
|
|
310
|
+
"weaver_version",
|
|
311
|
+
not_null=True,
|
|
312
|
+
description="The Weaver version that last reconciled this installation.",
|
|
313
|
+
),
|
|
314
|
+
_signature("the repository as a whole"),
|
|
315
|
+
),
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
REGISTRY = CatalogueTable(
|
|
319
|
+
name="Registry",
|
|
320
|
+
description=(
|
|
321
|
+
"Objects Weaver currently certifies as installed. A physical table may "
|
|
322
|
+
"exist without a row here, and Weaver then does not treat it as valid. "
|
|
323
|
+
"Written last in a build, so its presence means everything the object "
|
|
324
|
+
"needed succeeded."
|
|
325
|
+
),
|
|
326
|
+
key=(SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME, "schema_name", "object_name"),
|
|
327
|
+
columns=(
|
|
328
|
+
*_scope(),
|
|
329
|
+
*_object(),
|
|
330
|
+
CatalogueColumn(
|
|
331
|
+
"object_type",
|
|
332
|
+
not_null=True,
|
|
333
|
+
description=(
|
|
334
|
+
"What was installed: folder, table, view, file or "
|
|
335
|
+
"stored_procedure."
|
|
336
|
+
),
|
|
337
|
+
),
|
|
338
|
+
CatalogueColumn(
|
|
339
|
+
"object_role",
|
|
340
|
+
not_null=True,
|
|
341
|
+
description=(
|
|
342
|
+
"What the object is for: data holds or shapes rows; load does "
|
|
343
|
+
"the work that fills one."
|
|
344
|
+
),
|
|
345
|
+
),
|
|
346
|
+
_signature("the object's source file"),
|
|
347
|
+
CatalogueColumn(
|
|
348
|
+
BUILD_EPOCH,
|
|
349
|
+
TIMESTAMP,
|
|
350
|
+
published=True,
|
|
351
|
+
description=(
|
|
352
|
+
"When this row was published, shared by every row one completed "
|
|
353
|
+
"build wrote. Null for a row published before epochs existed, "
|
|
354
|
+
"which orders as older than any epoch."
|
|
355
|
+
),
|
|
356
|
+
),
|
|
357
|
+
),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
SCHEMA_DICTIONARY = CatalogueTable(
|
|
361
|
+
name="SchemaDictionary",
|
|
362
|
+
description="The declared schemas an installation uses, and what they are for.",
|
|
363
|
+
key=(SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME, "schema_name"),
|
|
364
|
+
columns=(
|
|
365
|
+
*_scope(),
|
|
366
|
+
CatalogueColumn("schema_name", not_null=True, description="The schema."),
|
|
367
|
+
*_described(what="schema"),
|
|
368
|
+
_signature("the schema declaration"),
|
|
369
|
+
),
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
TABLE_DICTIONARY = CatalogueTable(
|
|
373
|
+
name="TableDictionary",
|
|
374
|
+
description=(
|
|
375
|
+
"Tables and views together — they are described the same way and a "
|
|
376
|
+
"reader asks the same questions of both. Everything here is declared in "
|
|
377
|
+
"Weaver document; nothing is read back from the physical object."
|
|
378
|
+
),
|
|
379
|
+
key=(SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME, "schema_name", "object_name"),
|
|
380
|
+
columns=(
|
|
381
|
+
*_scope(),
|
|
382
|
+
*_object(),
|
|
383
|
+
CatalogueColumn(
|
|
384
|
+
"object_type", not_null=True, description="table or view."
|
|
385
|
+
),
|
|
386
|
+
*_described(what="object"),
|
|
387
|
+
*_lineage(),
|
|
388
|
+
CatalogueColumn(
|
|
389
|
+
"primary_key",
|
|
390
|
+
description="The primary key's columns, in declared order.",
|
|
391
|
+
),
|
|
392
|
+
CatalogueColumn(
|
|
393
|
+
"not_null_columns",
|
|
394
|
+
description="Columns declared not null, beyond the primary key.",
|
|
395
|
+
),
|
|
396
|
+
CatalogueColumn(
|
|
397
|
+
"identity_column",
|
|
398
|
+
description="Weaver's managed surrogate column, when one is declared.",
|
|
399
|
+
),
|
|
400
|
+
CatalogueColumn(
|
|
401
|
+
"comparison_columns",
|
|
402
|
+
description="Columns whose change drives an upsert.",
|
|
403
|
+
),
|
|
404
|
+
*_behaviour(),
|
|
405
|
+
_signature("the object's source file"),
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
FOLDER_DICTIONARY = CatalogueTable(
|
|
410
|
+
name="FolderDictionary",
|
|
411
|
+
description=(
|
|
412
|
+
"Managed folders. A folder keeps its two-part Weaver document identity rather than "
|
|
413
|
+
"being reduced to a path, and its file key is the scope of what Weaver "
|
|
414
|
+
"manages inside it — reconciliation deletes nothing outside that."
|
|
415
|
+
),
|
|
416
|
+
key=(SCOPE_ITEM_TYPE, SCOPE_ITEM_NAME, "schema_name", "object_name"),
|
|
417
|
+
columns=(
|
|
418
|
+
*_scope(),
|
|
419
|
+
*_object(),
|
|
420
|
+
*_described(what="folder"),
|
|
421
|
+
*_lineage(),
|
|
422
|
+
CatalogueColumn(
|
|
423
|
+
"file_key",
|
|
424
|
+
description="The glob patterns Weaver manages, in declared order.",
|
|
425
|
+
),
|
|
426
|
+
*_behaviour(),
|
|
427
|
+
_signature("the object's source file"),
|
|
428
|
+
),
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
COLUMN_DICTIONARY = CatalogueTable(
|
|
432
|
+
name="ColumnDictionary",
|
|
433
|
+
description=(
|
|
434
|
+
"What an author said about a column, plus Weaver's own surrogate. "
|
|
435
|
+
"Purely descriptive: it holds the columns that carry a note, not every "
|
|
436
|
+
"column of every object. Ordinals, types and nullability are physical "
|
|
437
|
+
"and are recorded separately, so nothing here depends on reading a "
|
|
438
|
+
"built table."
|
|
439
|
+
),
|
|
440
|
+
key=(
|
|
441
|
+
SCOPE_ITEM_TYPE,
|
|
442
|
+
SCOPE_ITEM_NAME,
|
|
443
|
+
"schema_name",
|
|
444
|
+
"object_name",
|
|
445
|
+
"column_name",
|
|
446
|
+
),
|
|
447
|
+
columns=(
|
|
448
|
+
*_scope(),
|
|
449
|
+
*_object(),
|
|
450
|
+
CatalogueColumn("column_name", not_null=True, description="The column."),
|
|
451
|
+
*_described(what="column"),
|
|
452
|
+
CatalogueColumn(
|
|
453
|
+
"is_identity",
|
|
454
|
+
BOOLEAN,
|
|
455
|
+
description="Whether this is Weaver's managed surrogate column.",
|
|
456
|
+
),
|
|
457
|
+
_signature("the object's source file"),
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
INDEX_DICTIONARY = CatalogueTable(
|
|
462
|
+
name="IndexDictionary",
|
|
463
|
+
description=(
|
|
464
|
+
"Declared logical keys — the primary key and any alternate keys. Neither "
|
|
465
|
+
"is built and neither is enforced; they say which column sets identify a "
|
|
466
|
+
"row. A key is identified by its own columns, so it needs no name."
|
|
467
|
+
),
|
|
468
|
+
key=(
|
|
469
|
+
SCOPE_ITEM_TYPE,
|
|
470
|
+
SCOPE_ITEM_NAME,
|
|
471
|
+
"schema_name",
|
|
472
|
+
"object_name",
|
|
473
|
+
"index_type",
|
|
474
|
+
"column_set",
|
|
475
|
+
),
|
|
476
|
+
columns=(
|
|
477
|
+
*_scope(),
|
|
478
|
+
*_object(),
|
|
479
|
+
CatalogueColumn(
|
|
480
|
+
"index_type", not_null=True, description="primary_key or unique."
|
|
481
|
+
),
|
|
482
|
+
CatalogueColumn(
|
|
483
|
+
"column_set",
|
|
484
|
+
not_null=True,
|
|
485
|
+
description="The key's columns, comma-separated in declared order.",
|
|
486
|
+
),
|
|
487
|
+
_signature("the object's source file"),
|
|
488
|
+
),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
FOREIGN_KEY_DICTIONARY = CatalogueTable(
|
|
492
|
+
name="ForeignKeyDictionary",
|
|
493
|
+
description=(
|
|
494
|
+
"Declared relationships to parent objects — an ER model rather than "
|
|
495
|
+
"database constraints. Nothing is enforced. Because a relationship has "
|
|
496
|
+
"no name, the row is the edge: every column is part of the key, so two "
|
|
497
|
+
"objects may be related several times over and an object may reference "
|
|
498
|
+
"itself. The parent is named by its own logical item."
|
|
499
|
+
),
|
|
500
|
+
key=(
|
|
501
|
+
SCOPE_ITEM_TYPE,
|
|
502
|
+
SCOPE_ITEM_NAME,
|
|
503
|
+
"schema_name",
|
|
504
|
+
"object_name",
|
|
505
|
+
"column_set",
|
|
506
|
+
"reference_item_type",
|
|
507
|
+
"reference_item_name",
|
|
508
|
+
"reference_schema_name",
|
|
509
|
+
"reference_object_name",
|
|
510
|
+
"reference_column_set",
|
|
511
|
+
),
|
|
512
|
+
columns=(
|
|
513
|
+
*_scope(),
|
|
514
|
+
*_object(),
|
|
515
|
+
CatalogueColumn(
|
|
516
|
+
"column_set",
|
|
517
|
+
not_null=True,
|
|
518
|
+
description="This object's columns, comma-separated in declared order.",
|
|
519
|
+
),
|
|
520
|
+
CatalogueColumn(
|
|
521
|
+
"reference_item_type", not_null=True, description="The parent's item type."
|
|
522
|
+
),
|
|
523
|
+
CatalogueColumn(
|
|
524
|
+
"reference_item_name", not_null=True, description="The parent's item name."
|
|
525
|
+
),
|
|
526
|
+
CatalogueColumn(
|
|
527
|
+
"reference_schema_name", not_null=True, description="The parent's schema."
|
|
528
|
+
),
|
|
529
|
+
CatalogueColumn(
|
|
530
|
+
"reference_object_name", not_null=True, description="The parent's name."
|
|
531
|
+
),
|
|
532
|
+
CatalogueColumn(
|
|
533
|
+
"reference_column_set",
|
|
534
|
+
not_null=True,
|
|
535
|
+
description="The parent's columns, paired in order with this object's.",
|
|
536
|
+
),
|
|
537
|
+
_signature("the object's source file"),
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
DEPENDENCY = CatalogueTable(
|
|
542
|
+
name="Dependency",
|
|
543
|
+
description=(
|
|
544
|
+
"One row per resolved dependency edge, scoped to the consuming item and "
|
|
545
|
+
"keeping the reference exactly as the author wrote it. Crossing items or "
|
|
546
|
+
"engines is an alias, recorded separately, not a dependency that quietly "
|
|
547
|
+
"changes namespace."
|
|
548
|
+
),
|
|
549
|
+
key=(
|
|
550
|
+
SCOPE_ITEM_TYPE,
|
|
551
|
+
SCOPE_ITEM_NAME,
|
|
552
|
+
"schema_name",
|
|
553
|
+
"object_name",
|
|
554
|
+
"dependency_name",
|
|
555
|
+
),
|
|
556
|
+
columns=(
|
|
557
|
+
*_scope(),
|
|
558
|
+
*_object(),
|
|
559
|
+
CatalogueColumn(
|
|
560
|
+
"dependency_name",
|
|
561
|
+
not_null=True,
|
|
562
|
+
description="The dependency exactly as the owning document wrote it.",
|
|
563
|
+
),
|
|
564
|
+
CatalogueColumn(
|
|
565
|
+
"is_within_item",
|
|
566
|
+
BOOLEAN,
|
|
567
|
+
description="Whether the producer is owned by the same logical item.",
|
|
568
|
+
),
|
|
569
|
+
_signature("the owning object's source file"),
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
ALIAS = CatalogueTable(
|
|
574
|
+
name="Alias",
|
|
575
|
+
description=(
|
|
576
|
+
"The name one item presents for a document another item owns, "
|
|
577
|
+
"reproduced from the consuming item's own alias.yml. This is where the "
|
|
578
|
+
"estate's graph crosses items and engines, so it is kept apart from "
|
|
579
|
+
"Dependency — composing Dependency, Alias and Registry is what yields "
|
|
580
|
+
"the whole DAG, and only that composition may cross."
|
|
581
|
+
),
|
|
582
|
+
key=(
|
|
583
|
+
SCOPE_ITEM_TYPE,
|
|
584
|
+
SCOPE_ITEM_NAME,
|
|
585
|
+
"destination_schema_name",
|
|
586
|
+
"destination_object_name",
|
|
587
|
+
),
|
|
588
|
+
columns=(
|
|
589
|
+
*_scope(),
|
|
590
|
+
CatalogueColumn(
|
|
591
|
+
"destination_schema_name",
|
|
592
|
+
not_null=True,
|
|
593
|
+
description="The schema the consuming item presents the alias in.",
|
|
594
|
+
),
|
|
595
|
+
CatalogueColumn(
|
|
596
|
+
"destination_object_name",
|
|
597
|
+
not_null=True,
|
|
598
|
+
description="The name the consuming item presents.",
|
|
599
|
+
),
|
|
600
|
+
CatalogueColumn(
|
|
601
|
+
"source_item_type", not_null=True, description="The producer's item type."
|
|
602
|
+
),
|
|
603
|
+
CatalogueColumn(
|
|
604
|
+
"source_item_name", not_null=True, description="The producer's item name."
|
|
605
|
+
),
|
|
606
|
+
CatalogueColumn(
|
|
607
|
+
"source_schema_name", not_null=True, description="The producer's schema."
|
|
608
|
+
),
|
|
609
|
+
CatalogueColumn(
|
|
610
|
+
"source_object_name", not_null=True, description="The producer's name."
|
|
611
|
+
),
|
|
612
|
+
_signature("the alias declaration"),
|
|
613
|
+
),
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
#: Every dictionary table, in the order a build reconciles them. Order is fixed
|
|
618
|
+
#: so a bundle's payloads and a report's actions read the same way every time.
|
|
619
|
+
DICTIONARY_TABLES = (
|
|
620
|
+
SCHEMA_DICTIONARY,
|
|
621
|
+
FOLDER_DICTIONARY,
|
|
622
|
+
TABLE_DICTIONARY,
|
|
623
|
+
COLUMN_DICTIONARY,
|
|
624
|
+
INDEX_DICTIONARY,
|
|
625
|
+
FOREIGN_KEY_DICTIONARY,
|
|
626
|
+
DEPENDENCY,
|
|
627
|
+
ALIAS,
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
#: Every catalogue table, dictionaries first, then Installation, then Registry.
|
|
631
|
+
#: The order is the reconciliation order: dictionaries describe, Installation
|
|
632
|
+
#: records the binding, and Registry certifies — so Registry is last.
|
|
633
|
+
CATALOGUE_TABLES = DICTIONARY_TABLES + (INSTALLATION, REGISTRY)
|
|
634
|
+
|
|
635
|
+
TABLES_BY_NAME = {table.name: table for table in CATALOGUE_TABLES}
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def table(name: str) -> CatalogueTable:
|
|
639
|
+
"""One catalogue table by its object name."""
|
|
640
|
+
|
|
641
|
+
try:
|
|
642
|
+
return TABLES_BY_NAME[name]
|
|
643
|
+
except KeyError:
|
|
644
|
+
raise KeyError(
|
|
645
|
+
f"{name!r} is not a catalogue table — expected one of "
|
|
646
|
+
+ ", ".join(sorted(TABLES_BY_NAME))
|
|
647
|
+
) from None
|
|
648
|
+
|