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,238 @@
|
|
|
1
|
+
"""The built-in Weaver document repository that declares the catalogue tables.
|
|
2
|
+
|
|
3
|
+
Weaver's catalogue is built by Weaver, from ordinary Weaver document, through the ordinary
|
|
4
|
+
planner and installer. There is no second "create the control tables" path — that
|
|
5
|
+
recursion is the point, and it is the proof that a catalogue table is an ordinary
|
|
6
|
+
Weaver object rather than a privileged one.
|
|
7
|
+
|
|
8
|
+
The item is rendered from :mod:`weaver.catalogue.tables`, so the declaration and
|
|
9
|
+
the table definitions cannot drift: there is one source of truth and the text is
|
|
10
|
+
derived from it. ``Lakehouse/_weaver`` is composed into the parsed repository
|
|
11
|
+
in memory and built through the ordinary planner.
|
|
12
|
+
|
|
13
|
+
Every table declares:
|
|
14
|
+
|
|
15
|
+
``Static: true``
|
|
16
|
+
Its rows are not produced by a load. Catalogue rows are maintained only by
|
|
17
|
+
the DML a build appends.
|
|
18
|
+
|
|
19
|
+
``Prohibit rebuild: true``
|
|
20
|
+
This stops an ordinary build treating the catalogue as a disposable
|
|
21
|
+
application object.
|
|
22
|
+
|
|
23
|
+
``Dependencies: []``
|
|
24
|
+
Explicitly nothing. The body is literals, so there is nothing to discover
|
|
25
|
+
and nothing to declare.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import textwrap
|
|
31
|
+
from dataclasses import replace
|
|
32
|
+
|
|
33
|
+
from .tables import CATALOGUE_SCHEMA, CATALOGUE_TABLES, CatalogueColumn, CatalogueTable
|
|
34
|
+
|
|
35
|
+
#: The reserved item Weaver generates and manages inside the declaration.
|
|
36
|
+
ITEM_ROOT = "Lakehouse/_weaver"
|
|
37
|
+
SCHEMA_PATH = f"{ITEM_ROOT}/schemas/{CATALOGUE_SCHEMA}.yml"
|
|
38
|
+
|
|
39
|
+
#: The folder every top-level Weaver task writes its evidence beneath, and the
|
|
40
|
+
#: document that declares it. ``_`` + ``__`` + ``Log`` spells ``___Log``, which
|
|
41
|
+
#: the parser reads as ``_.Log`` — see
|
|
42
|
+
#: :func:`weaver.declaration.source.python_id_parts`.
|
|
43
|
+
LOG_FOLDER = "Log"
|
|
44
|
+
LOG_FOLDER_ID = f"{CATALOGUE_SCHEMA}.{LOG_FOLDER}"
|
|
45
|
+
LOG_PATH = f"{ITEM_ROOT}/Files/{CATALOGUE_SCHEMA}{'__'}{LOG_FOLDER}.py"
|
|
46
|
+
|
|
47
|
+
#: Where the folder puts its files, relative to the Weaver Lakehouse's ``Files``
|
|
48
|
+
#: area. Derived from the identity exactly as any Folder's location is, so the
|
|
49
|
+
#: logger addresses the *declared* folder rather than a path it happens to know.
|
|
50
|
+
LOG_ROOT = f"{CATALOGUE_SCHEMA}/{LOG_FOLDER}"
|
|
51
|
+
|
|
52
|
+
_LOG_CLASS = f"{CATALOGUE_SCHEMA}{'__'}{LOG_FOLDER}"
|
|
53
|
+
|
|
54
|
+
#: One sentence, the same on every table, saying where the rows come from. It is
|
|
55
|
+
#: not boilerplate: "never loaded" is the fact that makes ``Static: true``
|
|
56
|
+
#: correct, and a reader of any one file should be told it.
|
|
57
|
+
LINEAGE = (
|
|
58
|
+
"Projected from validated Weaver document declarations by Weaver's own build, and "
|
|
59
|
+
"maintained only by the catalogue DML a build appends. Never populated by a "
|
|
60
|
+
"load."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
SCHEMA_DESCRIPTION = (
|
|
64
|
+
"Weaver's own control plane. These tables record what Weaver has built and "
|
|
65
|
+
"what it certifies as installed; they are declared as ordinary Weaver document and built "
|
|
66
|
+
"by Weaver itself, and are never authored or loaded by hand."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
_WIDTH = 76
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _escaped(text: str) -> str:
|
|
73
|
+
"""Metadata text with dollars escaped.
|
|
74
|
+
|
|
75
|
+
A ``$`` opens a ``$Schema.Object`` reference, and several catalogue columns are
|
|
76
|
+
described in terms of one — ``description_reference`` holds "the
|
|
77
|
+
$Schema.Object the description was copied from". Written raw, that would parse
|
|
78
|
+
as a reference and be refused, so it is escaped the way Weaver document specifies.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
return text.replace("$", "$$")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _folded(key: str, text: str, *, indent: int = 0) -> str:
|
|
85
|
+
"""A YAML folded block, so prose can wrap without becoming multi-line text.
|
|
86
|
+
|
|
87
|
+
``>-`` folds newlines into spaces and strips the trailing one, which keeps the
|
|
88
|
+
parsed value a single sentence however it is laid out in the file. Used
|
|
89
|
+
uniformly rather than only when needed: a plain scalar is fine until a
|
|
90
|
+
description happens to contain a colon, and this removes the class of problem.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
pad = " " * indent
|
|
94
|
+
body = textwrap.fill(
|
|
95
|
+
_escaped(text), width=_WIDTH - indent - 2, initial_indent="", subsequent_indent=""
|
|
96
|
+
)
|
|
97
|
+
lines = "\n".join(f"{pad} {line}" for line in body.splitlines())
|
|
98
|
+
return f"{pad}{key}: >-\n{lines}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def render_schema_file() -> str:
|
|
102
|
+
"""The ``_schemas/_.yml`` declaration for the catalogue schema."""
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
f"Schema ID: {CATALOGUE_SCHEMA}\n"
|
|
106
|
+
"\n"
|
|
107
|
+
f"{_folded('Description', SCHEMA_DESCRIPTION)}\n"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _body(table: CatalogueTable) -> str:
|
|
112
|
+
"""A query that declares the shape and returns no rows.
|
|
113
|
+
|
|
114
|
+
``where 1 = 0`` with no ``FROM`` is valid Spark SQL and is the whole trick: the
|
|
115
|
+
executor resolves the query's schema to create the table, and resolving a
|
|
116
|
+
schema reads no rows. Build creates structure; this is the smallest possible
|
|
117
|
+
statement that describes one.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def line(column: CatalogueColumn, first: bool) -> str:
|
|
121
|
+
lead = "select" if first else " ,"
|
|
122
|
+
return f"{lead} cast(null as {column.type}) as `{column.name}`"
|
|
123
|
+
|
|
124
|
+
lines = [line(column, index == 0) for index, column in enumerate(table.columns)]
|
|
125
|
+
return "\n".join(lines) + "\n where 1 = 0\n"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def render_source(table: CatalogueTable) -> str:
|
|
129
|
+
"""The complete Weaver document source file for one catalogue table."""
|
|
130
|
+
|
|
131
|
+
not_null = [
|
|
132
|
+
column.name
|
|
133
|
+
for column in table.columns
|
|
134
|
+
if column.not_null and column.name not in table.key
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
sections: list[str] = [
|
|
138
|
+
f"Table ID: {table.qualified}",
|
|
139
|
+
_folded("Description", table.description),
|
|
140
|
+
_folded("Lineage", LINEAGE),
|
|
141
|
+
"Dependencies: []",
|
|
142
|
+
"Static: true",
|
|
143
|
+
"Prohibit rebuild: true",
|
|
144
|
+
# The key is declared as the primary key, so the catalogue's own tables
|
|
145
|
+
# describe themselves: Weaver document makes key columns not null, and the projection
|
|
146
|
+
# records the key in the catalogue like any other object's.
|
|
147
|
+
f"Primary key: {', '.join(table.key)}",
|
|
148
|
+
]
|
|
149
|
+
if not_null:
|
|
150
|
+
sections.append("Not null:\n" + "\n".join(f" - {name}" for name in not_null))
|
|
151
|
+
sections.append(
|
|
152
|
+
"Schema:\n"
|
|
153
|
+
+ "\n".join(f" {column.name}: {column.type}" for column in table.columns)
|
|
154
|
+
)
|
|
155
|
+
sections.append(
|
|
156
|
+
"Column notes:\n"
|
|
157
|
+
+ "\n".join(
|
|
158
|
+
_folded(column.name, column.description, indent=2)
|
|
159
|
+
for column in table.columns
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
header = "\n\n".join(sections)
|
|
164
|
+
return f"/*\n{header}\n*/\n{_body(table)}"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def render_log_file() -> str:
|
|
168
|
+
"""The declaration for ``Files/_/Log`` — where task evidence is written.
|
|
169
|
+
|
|
170
|
+
An ordinary Folder document, and that is the whole point. A task log could
|
|
171
|
+
have been a path the logger alone knew about, but then its creation, its
|
|
172
|
+
registration, its survival through a prune and its removal would each need a
|
|
173
|
+
rule of their own. Declared here it is claimed, projected, inventoried,
|
|
174
|
+
installed, converged and protected by the machinery that already exists, and
|
|
175
|
+
the logger asks the *folder* where to write rather than composing a path.
|
|
176
|
+
|
|
177
|
+
``Static: true`` because nothing loads into it: a task writes its own
|
|
178
|
+
evidence beneath it, exactly as a Folder object's authored code writes files
|
|
179
|
+
into its destination. ``Incremental: false`` for the same reason the runtime
|
|
180
|
+
tree declares it — the folder itself accumulates nothing that Weaver claims
|
|
181
|
+
file by file.
|
|
182
|
+
|
|
183
|
+
``File key`` claims everything beneath, because everything beneath *is*
|
|
184
|
+
Weaver's — task evidence and nothing else. It is an accurate statement of
|
|
185
|
+
ownership rather than a licence to delete: nothing loads this folder, and a
|
|
186
|
+
written task file is never rewritten.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
return f'''\
|
|
190
|
+
"""
|
|
191
|
+
Folder ID: {LOG_FOLDER_ID}
|
|
192
|
+
|
|
193
|
+
Description: >-
|
|
194
|
+
Where every top-level Weaver task — wipe, mirror, build, load and test —
|
|
195
|
+
writes its immutable evidence. One folder per task, partitioned by the UTC
|
|
196
|
+
date the task started.
|
|
197
|
+
|
|
198
|
+
Lineage: >-
|
|
199
|
+
Written by Weaver's own task logger as each top-level task runs. Never
|
|
200
|
+
authored, and never populated by a load.
|
|
201
|
+
|
|
202
|
+
File key: "**/*"
|
|
203
|
+
|
|
204
|
+
Incremental: false
|
|
205
|
+
|
|
206
|
+
Static: true
|
|
207
|
+
"""
|
|
208
|
+
from weaver import Folder
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class {_LOG_CLASS}(Folder):
|
|
212
|
+
def read(self):
|
|
213
|
+
return self.staging_folder(), []
|
|
214
|
+
'''
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def render_item_sources() -> dict[str, str]:
|
|
218
|
+
sources = {SCHEMA_PATH: render_schema_file(), LOG_PATH: render_log_file()}
|
|
219
|
+
for table in CATALOGUE_TABLES:
|
|
220
|
+
documented = replace(
|
|
221
|
+
table,
|
|
222
|
+
columns=tuple(
|
|
223
|
+
replace(
|
|
224
|
+
column,
|
|
225
|
+
description=column.description
|
|
226
|
+
or f"The catalogue value for {column.name.replace('_', ' ')}.",
|
|
227
|
+
)
|
|
228
|
+
for column in table.columns
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
sources[f"{ITEM_ROOT}/{table.qualified}.sql"] = render_source(documented)
|
|
232
|
+
return sources
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def item_repository_files() -> dict[str, bytes]:
|
|
236
|
+
return {
|
|
237
|
+
path: text.encode("utf-8") for path, text in render_item_sources().items()
|
|
238
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Explicit catalogue ownership rules for Weaver document types.
|
|
2
|
+
|
|
3
|
+
Catalogue rows are not owned merely because their table happens to contain an
|
|
4
|
+
object-shaped pair of columns. Each supported document type names every table
|
|
5
|
+
it may populate and the predicate by which rows in that table are its claims.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Mapping
|
|
12
|
+
|
|
13
|
+
from ..declaration.metadata import FOLDER, TABLE, VIEW
|
|
14
|
+
from ..declaration.model import WeaverDocumentId
|
|
15
|
+
from ..errors import BuildError
|
|
16
|
+
from .tables import (
|
|
17
|
+
COLUMN_DICTIONARY,
|
|
18
|
+
DEPENDENCY,
|
|
19
|
+
FOLDER_DICTIONARY,
|
|
20
|
+
FOREIGN_KEY_DICTIONARY,
|
|
21
|
+
INDEX_DICTIONARY,
|
|
22
|
+
OBJECT_TYPES,
|
|
23
|
+
REGISTRY,
|
|
24
|
+
TABLE_DICTIONARY,
|
|
25
|
+
CatalogueTable,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def catalogue_schema(identity: WeaverDocumentId) -> str:
|
|
30
|
+
"""The ``schema_name`` this identity is stored under.
|
|
31
|
+
|
|
32
|
+
A Folder carries its ``Files/`` prefix because that prefix is part of what
|
|
33
|
+
distinguishes it from a table of the same name. A load artefact does not get
|
|
34
|
+
one: its schema is already the real thing — the containing path for a file,
|
|
35
|
+
the Warehouse schema for a procedure — and prefixing it would store something
|
|
36
|
+
that is not the target's own name.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
prefix = "Files/" if identity.is_files else ""
|
|
40
|
+
return f"{prefix}{identity.object_id.schema}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class CatalogueClaimRule:
|
|
45
|
+
"""One table a document type may populate and how it owns its rows."""
|
|
46
|
+
|
|
47
|
+
table: CatalogueTable
|
|
48
|
+
predicate_columns: tuple[str, str] = ("schema_name", "object_name")
|
|
49
|
+
|
|
50
|
+
def values(self, identity: WeaverDocumentId) -> tuple[str, str]:
|
|
51
|
+
return catalogue_schema(identity), identity.object_id.object
|
|
52
|
+
|
|
53
|
+
def owns(self, row: Mapping[str, object], identity: WeaverDocumentId) -> bool:
|
|
54
|
+
expected = self.values(identity)
|
|
55
|
+
return all(
|
|
56
|
+
str(row.get(column)) == value
|
|
57
|
+
for column, value in zip(self.predicate_columns, expected)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class CatalogueClaim:
|
|
63
|
+
"""A concrete document claim collected for deletion."""
|
|
64
|
+
|
|
65
|
+
identity: WeaverDocumentId
|
|
66
|
+
rule: CatalogueClaimRule
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_COMMON_OBJECT_RULES = (
|
|
70
|
+
CatalogueClaimRule(REGISTRY),
|
|
71
|
+
CatalogueClaimRule(COLUMN_DICTIONARY),
|
|
72
|
+
CatalogueClaimRule(INDEX_DICTIONARY),
|
|
73
|
+
CatalogueClaimRule(FOREIGN_KEY_DICTIONARY),
|
|
74
|
+
CatalogueClaimRule(DEPENDENCY),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# This is deliberately exhaustive. Adding another Registry object_type requires
|
|
78
|
+
# an ownership declaration here before it can participate in reconciliation.
|
|
79
|
+
CATALOGUE_CLAIMS_BY_OBJECT_TYPE: Mapping[str, tuple[CatalogueClaimRule, ...]] = {
|
|
80
|
+
"folder": (
|
|
81
|
+
_COMMON_OBJECT_RULES[0],
|
|
82
|
+
CatalogueClaimRule(FOLDER_DICTIONARY),
|
|
83
|
+
*_COMMON_OBJECT_RULES[1:],
|
|
84
|
+
),
|
|
85
|
+
"table": (
|
|
86
|
+
_COMMON_OBJECT_RULES[0],
|
|
87
|
+
CatalogueClaimRule(TABLE_DICTIONARY),
|
|
88
|
+
*_COMMON_OBJECT_RULES[1:],
|
|
89
|
+
),
|
|
90
|
+
"view": (
|
|
91
|
+
_COMMON_OBJECT_RULES[0],
|
|
92
|
+
CatalogueClaimRule(TABLE_DICTIONARY),
|
|
93
|
+
*_COMMON_OBJECT_RULES[1:],
|
|
94
|
+
),
|
|
95
|
+
# A load artefact claims the Registry and nothing else. It declares no
|
|
96
|
+
# columns, no keys, no relationships and no dependencies — it is a deployed
|
|
97
|
+
# module or a generated statement, and the only thing the catalogue records
|
|
98
|
+
# about it is that Weaver installed it and at what signature.
|
|
99
|
+
"file": (CatalogueClaimRule(REGISTRY),),
|
|
100
|
+
"stored_procedure": (CatalogueClaimRule(REGISTRY),),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
OBJECT_TYPE_FOR_DOCUMENT_KIND = {FOLDER: "folder", TABLE: "table", VIEW: "view"}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def claim_rules_for_object_type(object_type: str) -> tuple[CatalogueClaimRule, ...]:
|
|
107
|
+
try:
|
|
108
|
+
return CATALOGUE_CLAIMS_BY_OBJECT_TYPE[object_type]
|
|
109
|
+
except KeyError as exc:
|
|
110
|
+
expected = ", ".join(OBJECT_TYPES)
|
|
111
|
+
raise BuildError(
|
|
112
|
+
f"Registry object_type must be one of {expected}, got {object_type!r}"
|
|
113
|
+
) from exc
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def claim_rules_for_document_kind(kind: str) -> tuple[CatalogueClaimRule, ...]:
|
|
117
|
+
try:
|
|
118
|
+
object_type = OBJECT_TYPE_FOR_DOCUMENT_KIND[kind]
|
|
119
|
+
except KeyError as exc:
|
|
120
|
+
raise BuildError(f"unsupported Weaver document kind {kind!r}") from exc
|
|
121
|
+
return claim_rules_for_object_type(object_type)
|