dbt-data-engineering-toolkit-compiler 3.0.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.
- dbt_data_engineering_toolkit_compiler/__init__.py +5 -0
- dbt_data_engineering_toolkit_compiler/__main__.py +3 -0
- dbt_data_engineering_toolkit_compiler/adapters.py +352 -0
- dbt_data_engineering_toolkit_compiler/brokers/__init__.py +16 -0
- dbt_data_engineering_toolkit_compiler/brokers/commands.py +46 -0
- dbt_data_engineering_toolkit_compiler/brokers/datacontracts.py +95 -0
- dbt_data_engineering_toolkit_compiler/brokers/dependencies.py +97 -0
- dbt_data_engineering_toolkit_compiler/brokers/files.py +92 -0
- dbt_data_engineering_toolkit_compiler/brokers/projects.py +128 -0
- dbt_data_engineering_toolkit_compiler/brokers/target_workbooks.py +622 -0
- dbt_data_engineering_toolkit_compiler/brokers/template_workbooks.py +989 -0
- dbt_data_engineering_toolkit_compiler/brokers/workbook_edits.py +106 -0
- dbt_data_engineering_toolkit_compiler/brokers/workbooks.py +92 -0
- dbt_data_engineering_toolkit_compiler/errors.py +128 -0
- dbt_data_engineering_toolkit_compiler/exposers/__init__.py +1 -0
- dbt_data_engineering_toolkit_compiler/exposers/cli.py +381 -0
- dbt_data_engineering_toolkit_compiler/generation.py +120 -0
- dbt_data_engineering_toolkit_compiler/models.py +271 -0
- dbt_data_engineering_toolkit_compiler/operational.py +59 -0
- dbt_data_engineering_toolkit_compiler/prove.py +176 -0
- dbt_data_engineering_toolkit_compiler/py.typed +1 -0
- dbt_data_engineering_toolkit_compiler/registry.py +118 -0
- dbt_data_engineering_toolkit_compiler/resources/data_product.xlsx +0 -0
- dbt_data_engineering_toolkit_compiler/resources/data_product_multi_source_sample.xlsx +0 -0
- dbt_data_engineering_toolkit_compiler/resources/data_product_sample.xlsx +0 -0
- dbt_data_engineering_toolkit_compiler/resources/operators.yml +240 -0
- dbt_data_engineering_toolkit_compiler/services/__init__.py +1 -0
- dbt_data_engineering_toolkit_compiler/services/application.py +466 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/__init__.py +5 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/common.py +61 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/contracts.py +217 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/generated_readme.py +117 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/project.py +119 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/project_config.py +120 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/schema.py +191 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/service.py +41 -0
- dbt_data_engineering_toolkit_compiler/services/emissions/sql.py +304 -0
- dbt_data_engineering_toolkit_compiler/services/generation.py +321 -0
- dbt_data_engineering_toolkit_compiler/services/imports/__init__.py +21 -0
- dbt_data_engineering_toolkit_compiler/services/imports/structures.py +554 -0
- dbt_data_engineering_toolkit_compiler/services/validation/__init__.py +5 -0
- dbt_data_engineering_toolkit_compiler/services/validation/common.py +128 -0
- dbt_data_engineering_toolkit_compiler/services/validation/context.py +83 -0
- dbt_data_engineering_toolkit_compiler/services/validation/mappings.py +264 -0
- dbt_data_engineering_toolkit_compiler/services/validation/metadata_sources.py +161 -0
- dbt_data_engineering_toolkit_compiler/services/validation/model_outputs.py +133 -0
- dbt_data_engineering_toolkit_compiler/services/validation/models_schema.py +110 -0
- dbt_data_engineering_toolkit_compiler/services/validation/quality_build.py +243 -0
- dbt_data_engineering_toolkit_compiler/services/validation/relationships.py +166 -0
- dbt_data_engineering_toolkit_compiler/services/validation/rules.py +134 -0
- dbt_data_engineering_toolkit_compiler/services/validation/service.py +51 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/__init__.py +5 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/common.py +206 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/governance.py +366 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/mappings_rules.py +177 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/product_schema.py +208 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/service.py +96 -0
- dbt_data_engineering_toolkit_compiler/services/workbooks/sources_models.py +106 -0
- dbt_data_engineering_toolkit_compiler/version.py +17 -0
- dbt_data_engineering_toolkit_compiler/workbook_layout.py +4 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/METADATA +105 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/RECORD +66 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/WHEEL +5 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/entry_points.txt +2 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/licenses/LICENSE +21 -0
- dbt_data_engineering_toolkit_compiler-3.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Explicit adapter capabilities used by validation and project emission."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from .operational import JsonValue
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _env(name: str, default: str | None = None, *, integer: bool = False) -> str:
|
|
11
|
+
default_argument = f", '{default}'" if default is not None else ""
|
|
12
|
+
cast = " | int" if integer else ""
|
|
13
|
+
return "{{ env_var('" + name + "'" + default_argument + ")" + cast + " }}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class AdapterProvider:
|
|
18
|
+
name: str
|
|
19
|
+
display_name: str
|
|
20
|
+
dbt_dependency: str
|
|
21
|
+
sqlfluff_dialect: str
|
|
22
|
+
logical_types: dict[str, str]
|
|
23
|
+
operational_types: dict[str, str]
|
|
24
|
+
supported_materializations: frozenset[str]
|
|
25
|
+
supports_incremental: bool
|
|
26
|
+
supports_enforced_contracts: bool
|
|
27
|
+
profile_output: dict[str, JsonValue]
|
|
28
|
+
|
|
29
|
+
def data_type(self, logical_type: str, physical_type: str | None) -> str:
|
|
30
|
+
return physical_type or self.logical_types.get(
|
|
31
|
+
logical_type.casefold(), logical_type.casefold()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def operational_type(self, field_name: str) -> str:
|
|
35
|
+
return self.operational_types.get(field_name, self.data_type("string", None))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AdapterRegistry:
|
|
39
|
+
"""Single extension point for warehouse-specific compiler behavior."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, providers: list[AdapterProvider]):
|
|
42
|
+
self._providers = {item.name: item for item in providers}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def default(cls) -> "AdapterRegistry":
|
|
46
|
+
common_materializations = frozenset({"view", "table", "ephemeral"})
|
|
47
|
+
portable = {
|
|
48
|
+
"string": "varchar",
|
|
49
|
+
"number": "numeric",
|
|
50
|
+
"numeric": "numeric",
|
|
51
|
+
"integer": "bigint",
|
|
52
|
+
"boolean": "boolean",
|
|
53
|
+
"date": "date",
|
|
54
|
+
"timestamp": "timestamp",
|
|
55
|
+
"time": "time",
|
|
56
|
+
}
|
|
57
|
+
return cls(
|
|
58
|
+
[
|
|
59
|
+
AdapterProvider(
|
|
60
|
+
name="athena",
|
|
61
|
+
display_name="Amazon Athena",
|
|
62
|
+
dbt_dependency="dbt-athena-community>=1.10,<2",
|
|
63
|
+
sqlfluff_dialect="athena",
|
|
64
|
+
logical_types={
|
|
65
|
+
**portable,
|
|
66
|
+
"string": "varchar",
|
|
67
|
+
"number": "double",
|
|
68
|
+
"numeric": "decimal(38,6)",
|
|
69
|
+
"timestamp": "timestamp",
|
|
70
|
+
},
|
|
71
|
+
operational_types={
|
|
72
|
+
"_det_rejections": "array(varchar)",
|
|
73
|
+
"_det_warnings": "array(varchar)",
|
|
74
|
+
"_det_loaded_at": "timestamp",
|
|
75
|
+
"_det_invocation_id": "varchar",
|
|
76
|
+
"_det_source_system": "varchar",
|
|
77
|
+
},
|
|
78
|
+
supported_materializations=common_materializations,
|
|
79
|
+
supports_incremental=False,
|
|
80
|
+
supports_enforced_contracts=False,
|
|
81
|
+
profile_output={
|
|
82
|
+
"type": "athena",
|
|
83
|
+
"database": _env("DET_ATHENA_DATABASE", "awsdatacatalog"),
|
|
84
|
+
"schema": "__TARGET_SCHEMA__",
|
|
85
|
+
"s3_staging_dir": _env("DET_ATHENA_S3_STAGING_DIR"),
|
|
86
|
+
"region_name": _env("DET_ATHENA_REGION", "us-east-1"),
|
|
87
|
+
"work_group": _env("DET_ATHENA_WORK_GROUP", "primary"),
|
|
88
|
+
"threads": 4,
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
AdapterProvider(
|
|
92
|
+
name="bigquery",
|
|
93
|
+
display_name="Google BigQuery",
|
|
94
|
+
dbt_dependency="dbt-bigquery>=1.10,<2",
|
|
95
|
+
sqlfluff_dialect="bigquery",
|
|
96
|
+
logical_types={
|
|
97
|
+
**portable,
|
|
98
|
+
"string": "string",
|
|
99
|
+
"number": "float64",
|
|
100
|
+
"numeric": "numeric",
|
|
101
|
+
"integer": "int64",
|
|
102
|
+
"boolean": "bool",
|
|
103
|
+
},
|
|
104
|
+
operational_types={
|
|
105
|
+
"_det_rejections": "array<string>",
|
|
106
|
+
"_det_warnings": "array<string>",
|
|
107
|
+
"_det_loaded_at": "timestamp",
|
|
108
|
+
"_det_invocation_id": "string",
|
|
109
|
+
"_det_source_system": "string",
|
|
110
|
+
},
|
|
111
|
+
supported_materializations=common_materializations,
|
|
112
|
+
supports_incremental=False,
|
|
113
|
+
supports_enforced_contracts=True,
|
|
114
|
+
profile_output={
|
|
115
|
+
"type": "bigquery",
|
|
116
|
+
"method": "oauth",
|
|
117
|
+
"project": _env("DET_BIGQUERY_PROJECT"),
|
|
118
|
+
"dataset": "__TARGET_SCHEMA__",
|
|
119
|
+
"location": _env("DET_BIGQUERY_LOCATION", "US"),
|
|
120
|
+
"threads": 4,
|
|
121
|
+
},
|
|
122
|
+
),
|
|
123
|
+
AdapterProvider(
|
|
124
|
+
name="clickhouse",
|
|
125
|
+
display_name="ClickHouse",
|
|
126
|
+
dbt_dependency="dbt-clickhouse>=1.10,<2",
|
|
127
|
+
sqlfluff_dialect="clickhouse",
|
|
128
|
+
logical_types={
|
|
129
|
+
**portable,
|
|
130
|
+
"string": "String",
|
|
131
|
+
"number": "Float64",
|
|
132
|
+
"numeric": "Decimal(38,6)",
|
|
133
|
+
"integer": "Int64",
|
|
134
|
+
"boolean": "Bool",
|
|
135
|
+
"date": "Date",
|
|
136
|
+
"timestamp": "DateTime64(6)",
|
|
137
|
+
"time": "String",
|
|
138
|
+
},
|
|
139
|
+
operational_types={
|
|
140
|
+
"_det_rejections": "Array(String)",
|
|
141
|
+
"_det_warnings": "Array(String)",
|
|
142
|
+
"_det_loaded_at": "DateTime64(6)",
|
|
143
|
+
"_det_invocation_id": "String",
|
|
144
|
+
"_det_source_system": "String",
|
|
145
|
+
},
|
|
146
|
+
supported_materializations=common_materializations,
|
|
147
|
+
supports_incremental=False,
|
|
148
|
+
supports_enforced_contracts=False,
|
|
149
|
+
profile_output={
|
|
150
|
+
"type": "clickhouse",
|
|
151
|
+
"host": _env("DET_CLICKHOUSE_HOST"),
|
|
152
|
+
"port": _env("DET_CLICKHOUSE_PORT", "8443", integer=True),
|
|
153
|
+
"user": _env("DET_CLICKHOUSE_USER", "default"),
|
|
154
|
+
"password": _env("DBT_ENV_SECRET_DET_CLICKHOUSE_PASSWORD", ""),
|
|
155
|
+
"schema": "__TARGET_SCHEMA__",
|
|
156
|
+
"secure": True,
|
|
157
|
+
"verify": True,
|
|
158
|
+
"threads": 4,
|
|
159
|
+
},
|
|
160
|
+
),
|
|
161
|
+
AdapterProvider(
|
|
162
|
+
name="databricks",
|
|
163
|
+
display_name="Databricks",
|
|
164
|
+
dbt_dependency="dbt-databricks>=1.10,<2",
|
|
165
|
+
sqlfluff_dialect="databricks",
|
|
166
|
+
logical_types={
|
|
167
|
+
**portable,
|
|
168
|
+
"string": "string",
|
|
169
|
+
"number": "double",
|
|
170
|
+
"numeric": "decimal(38,6)",
|
|
171
|
+
"time": "string",
|
|
172
|
+
},
|
|
173
|
+
operational_types={
|
|
174
|
+
"_det_rejections": "array<string>",
|
|
175
|
+
"_det_warnings": "array<string>",
|
|
176
|
+
"_det_loaded_at": "timestamp",
|
|
177
|
+
"_det_invocation_id": "string",
|
|
178
|
+
"_det_source_system": "string",
|
|
179
|
+
},
|
|
180
|
+
supported_materializations=common_materializations,
|
|
181
|
+
supports_incremental=False,
|
|
182
|
+
supports_enforced_contracts=True,
|
|
183
|
+
profile_output={
|
|
184
|
+
"type": "databricks",
|
|
185
|
+
"host": _env("DET_DATABRICKS_HOST"),
|
|
186
|
+
"http_path": _env("DET_DATABRICKS_HTTP_PATH"),
|
|
187
|
+
"token": _env("DBT_ENV_SECRET_DET_DATABRICKS_TOKEN"),
|
|
188
|
+
"catalog": _env("DET_DATABRICKS_CATALOG", "hive_metastore"),
|
|
189
|
+
"schema": "__TARGET_SCHEMA__",
|
|
190
|
+
"threads": 4,
|
|
191
|
+
},
|
|
192
|
+
),
|
|
193
|
+
AdapterProvider(
|
|
194
|
+
name="duckdb",
|
|
195
|
+
display_name="DuckDB",
|
|
196
|
+
dbt_dependency="dbt-duckdb>=1.10,<2",
|
|
197
|
+
sqlfluff_dialect="duckdb",
|
|
198
|
+
logical_types={
|
|
199
|
+
**portable,
|
|
200
|
+
"string": "varchar",
|
|
201
|
+
"number": "double",
|
|
202
|
+
"numeric": "decimal(38,6)",
|
|
203
|
+
},
|
|
204
|
+
operational_types={
|
|
205
|
+
"_det_rejections": "varchar[]",
|
|
206
|
+
"_det_warnings": "varchar[]",
|
|
207
|
+
"_det_loaded_at": "timestamp with time zone",
|
|
208
|
+
"_det_invocation_id": "varchar",
|
|
209
|
+
"_det_source_system": "varchar",
|
|
210
|
+
},
|
|
211
|
+
supported_materializations=common_materializations,
|
|
212
|
+
supports_incremental=False,
|
|
213
|
+
supports_enforced_contracts=True,
|
|
214
|
+
profile_output={
|
|
215
|
+
"type": "duckdb",
|
|
216
|
+
"path": "target/data_product.duckdb",
|
|
217
|
+
"schema": "__TARGET_SCHEMA__",
|
|
218
|
+
"threads": 4,
|
|
219
|
+
},
|
|
220
|
+
),
|
|
221
|
+
AdapterProvider(
|
|
222
|
+
name="postgres",
|
|
223
|
+
display_name="Postgres",
|
|
224
|
+
dbt_dependency="dbt-postgres>=1.10,<2",
|
|
225
|
+
sqlfluff_dialect="postgres",
|
|
226
|
+
logical_types={**portable, "string": "text"},
|
|
227
|
+
operational_types={
|
|
228
|
+
"_det_rejections": "text[]",
|
|
229
|
+
"_det_warnings": "text[]",
|
|
230
|
+
"_det_loaded_at": "timestamp with time zone",
|
|
231
|
+
"_det_invocation_id": "text",
|
|
232
|
+
"_det_source_system": "text",
|
|
233
|
+
},
|
|
234
|
+
supported_materializations=common_materializations,
|
|
235
|
+
supports_incremental=False,
|
|
236
|
+
supports_enforced_contracts=True,
|
|
237
|
+
profile_output={
|
|
238
|
+
"type": "postgres",
|
|
239
|
+
"host": _env("DET_POSTGRES_HOST"),
|
|
240
|
+
"port": _env("DET_POSTGRES_PORT", "5432", integer=True),
|
|
241
|
+
"user": _env("DET_POSTGRES_USER"),
|
|
242
|
+
"password": _env("DET_POSTGRES_PASSWORD"),
|
|
243
|
+
"dbname": _env("DET_POSTGRES_DATABASE"),
|
|
244
|
+
"schema": "__TARGET_SCHEMA__",
|
|
245
|
+
"threads": 4,
|
|
246
|
+
},
|
|
247
|
+
),
|
|
248
|
+
AdapterProvider(
|
|
249
|
+
name="redshift",
|
|
250
|
+
display_name="Amazon Redshift",
|
|
251
|
+
dbt_dependency="dbt-redshift>=1.10,<2",
|
|
252
|
+
sqlfluff_dialect="redshift",
|
|
253
|
+
logical_types={
|
|
254
|
+
**portable,
|
|
255
|
+
"string": "varchar",
|
|
256
|
+
"number": "double precision",
|
|
257
|
+
"numeric": "decimal(38,6)",
|
|
258
|
+
},
|
|
259
|
+
operational_types={
|
|
260
|
+
"_det_rejections": "super",
|
|
261
|
+
"_det_warnings": "super",
|
|
262
|
+
"_det_loaded_at": "timestamp",
|
|
263
|
+
"_det_invocation_id": "varchar",
|
|
264
|
+
"_det_source_system": "varchar",
|
|
265
|
+
},
|
|
266
|
+
supported_materializations=common_materializations,
|
|
267
|
+
supports_incremental=False,
|
|
268
|
+
supports_enforced_contracts=True,
|
|
269
|
+
profile_output={
|
|
270
|
+
"type": "redshift",
|
|
271
|
+
"host": _env("DET_REDSHIFT_HOST"),
|
|
272
|
+
"port": _env("DET_REDSHIFT_PORT", "5439", integer=True),
|
|
273
|
+
"user": _env("DET_REDSHIFT_USER"),
|
|
274
|
+
"password": _env("DBT_ENV_SECRET_DET_REDSHIFT_PASSWORD"),
|
|
275
|
+
"dbname": _env("DET_REDSHIFT_DATABASE"),
|
|
276
|
+
"schema": "__TARGET_SCHEMA__",
|
|
277
|
+
"threads": 4,
|
|
278
|
+
},
|
|
279
|
+
),
|
|
280
|
+
AdapterProvider(
|
|
281
|
+
name="snowflake",
|
|
282
|
+
display_name="Snowflake",
|
|
283
|
+
dbt_dependency="dbt-snowflake>=1.10,<2",
|
|
284
|
+
sqlfluff_dialect="snowflake",
|
|
285
|
+
logical_types={
|
|
286
|
+
**portable,
|
|
287
|
+
"string": "varchar",
|
|
288
|
+
"integer": "number(38,0)",
|
|
289
|
+
"timestamp": "timestamp_tz",
|
|
290
|
+
},
|
|
291
|
+
operational_types={
|
|
292
|
+
"_det_rejections": "array",
|
|
293
|
+
"_det_warnings": "array",
|
|
294
|
+
"_det_loaded_at": "timestamp_tz",
|
|
295
|
+
"_det_invocation_id": "varchar",
|
|
296
|
+
"_det_source_system": "varchar",
|
|
297
|
+
},
|
|
298
|
+
supported_materializations=common_materializations,
|
|
299
|
+
supports_incremental=False,
|
|
300
|
+
supports_enforced_contracts=True,
|
|
301
|
+
profile_output={
|
|
302
|
+
"type": "snowflake",
|
|
303
|
+
"account": _env("DET_SNOWFLAKE_ACCOUNT"),
|
|
304
|
+
"user": _env("DET_SNOWFLAKE_USER"),
|
|
305
|
+
"password": _env("DBT_ENV_SECRET_DET_SNOWFLAKE_PASSWORD"),
|
|
306
|
+
"role": _env("DET_SNOWFLAKE_ROLE"),
|
|
307
|
+
"database": _env("DET_SNOWFLAKE_DATABASE"),
|
|
308
|
+
"warehouse": _env("DET_SNOWFLAKE_WAREHOUSE"),
|
|
309
|
+
"schema": "__TARGET_SCHEMA__",
|
|
310
|
+
"threads": 4,
|
|
311
|
+
},
|
|
312
|
+
),
|
|
313
|
+
AdapterProvider(
|
|
314
|
+
name="spark",
|
|
315
|
+
display_name="Apache Spark",
|
|
316
|
+
dbt_dependency="dbt-spark[PyHive]>=1.10,<2",
|
|
317
|
+
sqlfluff_dialect="sparksql",
|
|
318
|
+
logical_types={
|
|
319
|
+
**portable,
|
|
320
|
+
"string": "string",
|
|
321
|
+
"number": "double",
|
|
322
|
+
"numeric": "decimal(38,6)",
|
|
323
|
+
"time": "string",
|
|
324
|
+
},
|
|
325
|
+
operational_types={
|
|
326
|
+
"_det_rejections": "array<string>",
|
|
327
|
+
"_det_warnings": "array<string>",
|
|
328
|
+
"_det_loaded_at": "timestamp",
|
|
329
|
+
"_det_invocation_id": "string",
|
|
330
|
+
"_det_source_system": "string",
|
|
331
|
+
},
|
|
332
|
+
supported_materializations=common_materializations,
|
|
333
|
+
supports_incremental=False,
|
|
334
|
+
supports_enforced_contracts=False,
|
|
335
|
+
profile_output={
|
|
336
|
+
"type": "spark",
|
|
337
|
+
"method": "thrift",
|
|
338
|
+
"host": _env("DET_SPARK_HOST"),
|
|
339
|
+
"port": _env("DET_SPARK_PORT", "10001", integer=True),
|
|
340
|
+
"user": _env("DET_SPARK_USER", "dbt"),
|
|
341
|
+
"schema": "__TARGET_SCHEMA__",
|
|
342
|
+
"threads": 4,
|
|
343
|
+
},
|
|
344
|
+
),
|
|
345
|
+
]
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def get(self, name: str) -> AdapterProvider | None:
|
|
349
|
+
return self._providers.get(name.casefold())
|
|
350
|
+
|
|
351
|
+
def names(self) -> tuple[str, ...]:
|
|
352
|
+
return tuple(sorted(self._providers))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Replaceable boundaries for files, workbooks, and command-line dependencies."""
|
|
2
|
+
|
|
3
|
+
from .commands import CommandBroker, SubprocessCommandBroker
|
|
4
|
+
from .files import FileBroker, LocalFileBroker
|
|
5
|
+
from .workbooks import OpenpyxlWorkbookBroker, SheetData, WorkbookBroker, WorkbookDocument
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CommandBroker",
|
|
9
|
+
"FileBroker",
|
|
10
|
+
"LocalFileBroker",
|
|
11
|
+
"OpenpyxlWorkbookBroker",
|
|
12
|
+
"SheetData",
|
|
13
|
+
"SubprocessCommandBroker",
|
|
14
|
+
"WorkbookBroker",
|
|
15
|
+
"WorkbookDocument",
|
|
16
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Broker for executable discovery and subprocess execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Iterable, Protocol
|
|
10
|
+
|
|
11
|
+
from ..operational import CommandResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CommandBroker(Protocol):
|
|
15
|
+
"""External command boundary consumed by dependency-specific brokers."""
|
|
16
|
+
|
|
17
|
+
def find(self, executable: str) -> str | None: ...
|
|
18
|
+
|
|
19
|
+
def run(self, command: Iterable[str], *, cwd: Path | None = None) -> CommandResult: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SubprocessCommandBroker:
|
|
23
|
+
"""Production command broker backed by the operating system."""
|
|
24
|
+
|
|
25
|
+
def find(self, executable: str) -> str | None:
|
|
26
|
+
suffix = ".exe" if sys.platform == "win32" else ""
|
|
27
|
+
sibling = Path(sys.executable).parent / f"{executable}{suffix}"
|
|
28
|
+
if sibling.is_file():
|
|
29
|
+
return str(sibling)
|
|
30
|
+
return shutil.which(executable)
|
|
31
|
+
|
|
32
|
+
def run(self, command: Iterable[str], *, cwd: Path | None = None) -> CommandResult:
|
|
33
|
+
arguments = tuple(str(item) for item in command)
|
|
34
|
+
completed = subprocess.run(
|
|
35
|
+
arguments,
|
|
36
|
+
cwd=cwd,
|
|
37
|
+
text=True,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
check=False,
|
|
40
|
+
)
|
|
41
|
+
return CommandResult(
|
|
42
|
+
command=arguments,
|
|
43
|
+
return_code=completed.returncode,
|
|
44
|
+
stdout=completed.stdout,
|
|
45
|
+
stderr=completed.stderr,
|
|
46
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Broker for the authoritative Data Contract CLI and Python API."""
|
|
2
|
+
# pyright: reportMissingImports=false
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from ..errors import MissingDependencyError
|
|
10
|
+
from ..operational import CommandResult
|
|
11
|
+
from .commands import CommandBroker, SubprocessCommandBroker
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataContractBroker(Protocol):
|
|
15
|
+
def lint(self, contract: Path) -> CommandResult: ...
|
|
16
|
+
|
|
17
|
+
def dbt_sync(self, contract: Path, project: Path, *, dry_run: bool) -> CommandResult: ...
|
|
18
|
+
|
|
19
|
+
def import_excel(self, workbook: Path, output: Path) -> CommandResult: ...
|
|
20
|
+
|
|
21
|
+
def export_excel(self, contract: Path, output: Path) -> CommandResult: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CliDataContractBroker:
|
|
25
|
+
"""Production Data Contract integration behind a local interface."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, commands: CommandBroker | None = None) -> None:
|
|
28
|
+
self.commands = commands or SubprocessCommandBroker()
|
|
29
|
+
|
|
30
|
+
def lint(self, contract: Path) -> CommandResult:
|
|
31
|
+
try:
|
|
32
|
+
from datacontract.data_contract import DataContract
|
|
33
|
+
except ModuleNotFoundError as exc:
|
|
34
|
+
raise MissingDependencyError(
|
|
35
|
+
"datacontract",
|
|
36
|
+
"Data Contract CLI is missing; reinstall the compiler.",
|
|
37
|
+
) from exc
|
|
38
|
+
result = DataContract(data_contract_file=str(contract)).lint()
|
|
39
|
+
return CommandResult(
|
|
40
|
+
command=("datacontract", "lint", str(contract)),
|
|
41
|
+
return_code=0 if result.has_passed() else 1,
|
|
42
|
+
stdout=str(result),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def dbt_sync(self, contract: Path, project: Path, *, dry_run: bool) -> CommandResult:
|
|
46
|
+
executable = self._executable()
|
|
47
|
+
command = [
|
|
48
|
+
executable,
|
|
49
|
+
"dbt",
|
|
50
|
+
"sync",
|
|
51
|
+
str(contract),
|
|
52
|
+
"--project-dir",
|
|
53
|
+
str(project),
|
|
54
|
+
]
|
|
55
|
+
if dry_run:
|
|
56
|
+
command.append("--dry-run")
|
|
57
|
+
return self.commands.run(command, cwd=project)
|
|
58
|
+
|
|
59
|
+
def import_excel(self, workbook: Path, output: Path) -> CommandResult:
|
|
60
|
+
"""Convert an official ODCS workbook with Data Contract CLI."""
|
|
61
|
+
|
|
62
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
command = [
|
|
64
|
+
self._executable(),
|
|
65
|
+
"import",
|
|
66
|
+
"excel",
|
|
67
|
+
"--source",
|
|
68
|
+
str(workbook),
|
|
69
|
+
"--output",
|
|
70
|
+
str(output),
|
|
71
|
+
]
|
|
72
|
+
return self.commands.run(command, cwd=workbook.parent)
|
|
73
|
+
|
|
74
|
+
def export_excel(self, contract: Path, output: Path) -> CommandResult:
|
|
75
|
+
"""Export canonical ODCS YAML using Data Contract CLI's official template."""
|
|
76
|
+
|
|
77
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
command = [
|
|
79
|
+
self._executable(),
|
|
80
|
+
"export",
|
|
81
|
+
"excel",
|
|
82
|
+
str(contract),
|
|
83
|
+
"--output",
|
|
84
|
+
str(output),
|
|
85
|
+
]
|
|
86
|
+
return self.commands.run(command, cwd=contract.parent)
|
|
87
|
+
|
|
88
|
+
def _executable(self) -> str:
|
|
89
|
+
executable = self.commands.find("datacontract")
|
|
90
|
+
if executable:
|
|
91
|
+
return executable
|
|
92
|
+
raise MissingDependencyError(
|
|
93
|
+
"datacontract",
|
|
94
|
+
"Data Contract CLI is missing; reinstall the compiler.",
|
|
95
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Dependency-specific brokers built on the shared command boundary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from ..errors import MissingDependencyError
|
|
9
|
+
from ..operational import CommandResult
|
|
10
|
+
from .commands import CommandBroker, SubprocessCommandBroker
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DbtBroker(Protocol):
|
|
14
|
+
def parse(self, project: Path) -> CommandResult: ...
|
|
15
|
+
|
|
16
|
+
def seed(
|
|
17
|
+
self, project: Path, *, selector: str | None = None, exclude: str | None = None
|
|
18
|
+
) -> CommandResult: ...
|
|
19
|
+
|
|
20
|
+
def build(self, project: Path, *, exclude: str | None = None) -> CommandResult: ...
|
|
21
|
+
|
|
22
|
+
def run(self, project: Path, *, selector: str | None = None) -> CommandResult: ...
|
|
23
|
+
|
|
24
|
+
def deps(self, project: Path) -> CommandResult: ...
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CliDbtBroker:
|
|
28
|
+
def __init__(self, commands: CommandBroker | None = None) -> None:
|
|
29
|
+
self.commands = commands or SubprocessCommandBroker()
|
|
30
|
+
|
|
31
|
+
def _dbt(self) -> str:
|
|
32
|
+
executable = self.commands.find("dbt")
|
|
33
|
+
if not executable:
|
|
34
|
+
raise MissingDependencyError(
|
|
35
|
+
"dbt", "dbt is not installed; install the compiler and target adapter."
|
|
36
|
+
)
|
|
37
|
+
return executable
|
|
38
|
+
|
|
39
|
+
def _run(self, project: Path, operation: str, *arguments: str) -> CommandResult:
|
|
40
|
+
return self.commands.run(
|
|
41
|
+
(
|
|
42
|
+
self._dbt(),
|
|
43
|
+
operation,
|
|
44
|
+
"--project-dir",
|
|
45
|
+
str(project),
|
|
46
|
+
"--profiles-dir",
|
|
47
|
+
str(project),
|
|
48
|
+
*arguments,
|
|
49
|
+
),
|
|
50
|
+
cwd=project,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def parse(self, project: Path) -> CommandResult:
|
|
54
|
+
return self._run(project, "parse")
|
|
55
|
+
|
|
56
|
+
def seed(
|
|
57
|
+
self, project: Path, *, selector: str | None = None, exclude: str | None = None
|
|
58
|
+
) -> CommandResult:
|
|
59
|
+
arguments: list[str] = []
|
|
60
|
+
if selector:
|
|
61
|
+
arguments.extend(("--select", selector))
|
|
62
|
+
if exclude:
|
|
63
|
+
arguments.extend(("--exclude", exclude))
|
|
64
|
+
return self._run(project, "seed", *arguments)
|
|
65
|
+
|
|
66
|
+
def build(self, project: Path, *, exclude: str | None = None) -> CommandResult:
|
|
67
|
+
arguments = ("--exclude", exclude) if exclude else ()
|
|
68
|
+
return self._run(project, "build", *arguments)
|
|
69
|
+
|
|
70
|
+
def run(self, project: Path, *, selector: str | None = None) -> CommandResult:
|
|
71
|
+
arguments = ("--select", selector) if selector else ()
|
|
72
|
+
return self._run(project, "run", *arguments)
|
|
73
|
+
|
|
74
|
+
def deps(self, project: Path) -> CommandResult:
|
|
75
|
+
return self._run(project, "deps")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SqlFluffBroker(Protocol):
|
|
79
|
+
def lint(self, project: Path) -> CommandResult: ...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CliSqlFluffBroker:
|
|
83
|
+
def __init__(self, commands: CommandBroker | None = None) -> None:
|
|
84
|
+
self.commands = commands or SubprocessCommandBroker()
|
|
85
|
+
|
|
86
|
+
def lint(self, project: Path) -> CommandResult:
|
|
87
|
+
executable = self.commands.find("sqlfluff")
|
|
88
|
+
if not executable:
|
|
89
|
+
raise MissingDependencyError(
|
|
90
|
+
"sqlfluff", "sqlfluff is not installed; reinstall the compiler."
|
|
91
|
+
)
|
|
92
|
+
config = project / ".sqlfluff"
|
|
93
|
+
targets = tuple(name for name in ("models", "tests") if (project / name).exists())
|
|
94
|
+
return self.commands.run(
|
|
95
|
+
(executable, "lint", *targets, "--config", str(config)),
|
|
96
|
+
cwd=project,
|
|
97
|
+
)
|