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