datacontract-cli 0.9.7__py3-none-any.whl → 0.9.9__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.
Potentially problematic release.
This version of datacontract-cli might be problematic. Click here for more details.
- datacontract/breaking/breaking.py +48 -57
- datacontract/cli.py +100 -80
- datacontract/data_contract.py +178 -128
- datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py +5 -1
- datacontract/engines/datacontract/check_that_datacontract_file_exists.py +9 -8
- datacontract/engines/datacontract/check_that_datacontract_str_is_valid.py +26 -22
- datacontract/engines/fastjsonschema/check_jsonschema.py +31 -25
- datacontract/engines/fastjsonschema/s3/s3_read_files.py +8 -6
- datacontract/engines/soda/check_soda_execute.py +58 -36
- datacontract/engines/soda/connections/bigquery.py +5 -3
- datacontract/engines/soda/connections/dask.py +0 -1
- datacontract/engines/soda/connections/databricks.py +2 -2
- datacontract/engines/soda/connections/duckdb.py +25 -8
- datacontract/engines/soda/connections/kafka.py +36 -17
- datacontract/engines/soda/connections/postgres.py +3 -3
- datacontract/engines/soda/connections/snowflake.py +4 -4
- datacontract/export/avro_converter.py +9 -11
- datacontract/export/avro_idl_converter.py +65 -42
- datacontract/export/csv_type_converter.py +36 -0
- datacontract/export/dbt_converter.py +43 -32
- datacontract/export/great_expectations_converter.py +141 -0
- datacontract/export/html_export.py +46 -0
- datacontract/export/jsonschema_converter.py +3 -1
- datacontract/export/odcs_converter.py +5 -7
- datacontract/export/protobuf_converter.py +12 -10
- datacontract/export/pydantic_converter.py +131 -0
- datacontract/export/rdf_converter.py +34 -11
- datacontract/export/sodacl_converter.py +118 -21
- datacontract/export/sql_converter.py +30 -8
- datacontract/export/sql_type_converter.py +44 -4
- datacontract/export/terraform_converter.py +4 -3
- datacontract/imports/avro_importer.py +65 -18
- datacontract/imports/sql_importer.py +0 -2
- datacontract/init/download_datacontract_file.py +2 -2
- datacontract/integration/publish_datamesh_manager.py +6 -12
- datacontract/integration/publish_opentelemetry.py +30 -16
- datacontract/lint/files.py +2 -2
- datacontract/lint/lint.py +26 -31
- datacontract/lint/linters/description_linter.py +12 -21
- datacontract/lint/linters/example_model_linter.py +28 -29
- datacontract/lint/linters/field_pattern_linter.py +8 -8
- datacontract/lint/linters/field_reference_linter.py +11 -10
- datacontract/lint/linters/notice_period_linter.py +18 -22
- datacontract/lint/linters/quality_schema_linter.py +16 -20
- datacontract/lint/linters/valid_constraints_linter.py +42 -37
- datacontract/lint/resolve.py +50 -14
- datacontract/lint/schema.py +2 -3
- datacontract/lint/urls.py +4 -5
- datacontract/model/breaking_change.py +2 -1
- datacontract/model/data_contract_specification.py +8 -7
- datacontract/model/exceptions.py +13 -2
- datacontract/model/run.py +3 -2
- datacontract/web.py +3 -7
- datacontract_cli-0.9.9.dist-info/METADATA +951 -0
- datacontract_cli-0.9.9.dist-info/RECORD +64 -0
- datacontract/lint/linters/primary_field_linter.py +0 -30
- datacontract_cli-0.9.7.dist-info/METADATA +0 -603
- datacontract_cli-0.9.7.dist-info/RECORD +0 -61
- {datacontract_cli-0.9.7.dist-info → datacontract_cli-0.9.9.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.9.7.dist-info → datacontract_cli-0.9.9.dist-info}/WHEEL +0 -0
- {datacontract_cli-0.9.7.dist-info → datacontract_cli-0.9.9.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.9.7.dist-info → datacontract_cli-0.9.9.dist-info}/top_level.txt +0 -0
datacontract/data_contract.py
CHANGED
|
@@ -4,62 +4,53 @@ import tempfile
|
|
|
4
4
|
import typing
|
|
5
5
|
|
|
6
6
|
import yaml
|
|
7
|
+
from pyspark.sql import SparkSession
|
|
7
8
|
|
|
8
|
-
from datacontract.breaking.breaking import models_breaking_changes,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
from datacontract.breaking.breaking import models_breaking_changes, \
|
|
10
|
+
quality_breaking_changes
|
|
11
|
+
from datacontract.engines.datacontract.check_that_datacontract_contains_valid_servers_configuration import (
|
|
12
|
+
check_that_datacontract_contains_valid_server_configuration,
|
|
13
|
+
)
|
|
11
14
|
from datacontract.engines.fastjsonschema.check_jsonschema import \
|
|
12
15
|
check_jsonschema
|
|
13
16
|
from datacontract.engines.soda.check_soda_execute import check_soda_execute
|
|
14
|
-
from datacontract.export.avro_converter import
|
|
17
|
+
from datacontract.export.avro_converter import to_avro_schema_json
|
|
15
18
|
from datacontract.export.avro_idl_converter import to_avro_idl
|
|
16
19
|
from datacontract.export.dbt_converter import to_dbt_models_yaml, \
|
|
17
20
|
to_dbt_sources_yaml, to_dbt_staging_sql
|
|
18
|
-
from datacontract.export.
|
|
21
|
+
from datacontract.export.great_expectations_converter import \
|
|
22
|
+
to_great_expectations
|
|
23
|
+
from datacontract.export.html_export import to_html
|
|
24
|
+
from datacontract.export.jsonschema_converter import to_jsonschema_json
|
|
19
25
|
from datacontract.export.odcs_converter import to_odcs_yaml
|
|
20
26
|
from datacontract.export.protobuf_converter import to_protobuf
|
|
27
|
+
from datacontract.export.pydantic_converter import to_pydantic_model_str
|
|
21
28
|
from datacontract.export.rdf_converter import to_rdf_n3
|
|
22
29
|
from datacontract.export.sodacl_converter import to_sodacl_yaml
|
|
23
|
-
from datacontract.imports.avro_importer import import_avro
|
|
24
30
|
from datacontract.export.sql_converter import to_sql_ddl, to_sql_query
|
|
25
31
|
from datacontract.export.terraform_converter import to_terraform
|
|
32
|
+
from datacontract.imports.avro_importer import import_avro
|
|
26
33
|
from datacontract.imports.sql_importer import import_sql
|
|
27
34
|
from datacontract.integration.publish_datamesh_manager import \
|
|
28
35
|
publish_datamesh_manager
|
|
29
36
|
from datacontract.integration.publish_opentelemetry import publish_opentelemetry
|
|
30
37
|
from datacontract.lint import resolve
|
|
31
|
-
|
|
32
|
-
from datacontract.model.breaking_change import BreakingChanges, BreakingChange, Severity
|
|
33
38
|
from datacontract.lint.linters.description_linter import DescriptionLinter
|
|
34
39
|
from datacontract.lint.linters.example_model_linter import ExampleModelLinter
|
|
35
|
-
from datacontract.lint.linters.valid_constraints_linter import ValidFieldConstraintsLinter
|
|
36
40
|
from datacontract.lint.linters.field_pattern_linter import FieldPatternLinter
|
|
37
|
-
from datacontract.lint.linters.field_reference_linter import
|
|
41
|
+
from datacontract.lint.linters.field_reference_linter import \
|
|
42
|
+
FieldReferenceLinter
|
|
38
43
|
from datacontract.lint.linters.notice_period_linter import NoticePeriodLinter
|
|
39
|
-
from datacontract.lint.linters.
|
|
40
|
-
|
|
44
|
+
from datacontract.lint.linters.quality_schema_linter import \
|
|
45
|
+
QualityUsesSchemaLinter
|
|
46
|
+
from datacontract.lint.linters.valid_constraints_linter import \
|
|
47
|
+
ValidFieldConstraintsLinter
|
|
48
|
+
from datacontract.model.breaking_change import BreakingChanges, BreakingChange, \
|
|
49
|
+
Severity
|
|
41
50
|
from datacontract.model.data_contract_specification import \
|
|
42
51
|
DataContractSpecification, Server
|
|
43
52
|
from datacontract.model.exceptions import DataContractException
|
|
44
|
-
from datacontract.model.run import
|
|
45
|
-
Run, Check
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _determine_sql_server_type(data_contract, sql_server_type):
|
|
49
|
-
if sql_server_type == "auto":
|
|
50
|
-
if data_contract.servers is None or len(data_contract.servers) == 0:
|
|
51
|
-
raise RuntimeError(f"Export with server_type='auto' requires servers in the data contract.")
|
|
52
|
-
|
|
53
|
-
server_types = set([server.type for server in data_contract.servers.values()])
|
|
54
|
-
if "snowflake" in server_types:
|
|
55
|
-
return "snowflake"
|
|
56
|
-
elif "postgres" in server_types:
|
|
57
|
-
return "postgres"
|
|
58
|
-
else:
|
|
59
|
-
# default to snowflake dialect
|
|
60
|
-
return "snowflake"
|
|
61
|
-
else:
|
|
62
|
-
return sql_server_type
|
|
53
|
+
from datacontract.model.run import Run, Check
|
|
63
54
|
|
|
64
55
|
|
|
65
56
|
class DataContract:
|
|
@@ -73,7 +64,7 @@ class DataContract:
|
|
|
73
64
|
examples: bool = False,
|
|
74
65
|
publish_url: str = None,
|
|
75
66
|
publish_to_opentelemetry: bool = False,
|
|
76
|
-
spark:
|
|
67
|
+
spark: SparkSession = None,
|
|
77
68
|
inline_definitions: bool = False,
|
|
78
69
|
):
|
|
79
70
|
self._data_contract_file = data_contract_file
|
|
@@ -92,9 +83,8 @@ class DataContract:
|
|
|
92
83
|
FieldPatternLinter(),
|
|
93
84
|
FieldReferenceLinter(),
|
|
94
85
|
NoticePeriodLinter(),
|
|
95
|
-
PrimaryFieldUniqueRequired(),
|
|
96
86
|
ValidFieldConstraintsLinter(),
|
|
97
|
-
DescriptionLinter()
|
|
87
|
+
DescriptionLinter(),
|
|
98
88
|
}
|
|
99
89
|
|
|
100
90
|
@classmethod
|
|
@@ -104,60 +94,59 @@ class DataContract:
|
|
|
104
94
|
def lint(self, enabled_linters: typing.Union[str, set[str]] = "all") -> Run:
|
|
105
95
|
"""Lint the data contract by deserializing the contract and checking the schema, as well as calling the configured linters.
|
|
106
96
|
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
enabled_linters can be either "all" or "none", or a set of linter IDs. The "schema" linter is always enabled, even with enabled_linters="none".
|
|
98
|
+
"""
|
|
109
99
|
run = Run.create_run()
|
|
110
100
|
try:
|
|
111
101
|
run.log_info("Linting data contract")
|
|
112
|
-
data_contract = resolve.resolve_data_contract(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
102
|
+
data_contract = resolve.resolve_data_contract(
|
|
103
|
+
self._data_contract_file,
|
|
104
|
+
self._data_contract_str,
|
|
105
|
+
self._data_contract,
|
|
106
|
+
self._schema_location,
|
|
107
|
+
inline_definitions=True,
|
|
108
|
+
)
|
|
109
|
+
run.checks.append(
|
|
110
|
+
Check(type="lint", result="passed", name="Data contract is syntactically valid", engine="datacontract")
|
|
111
|
+
)
|
|
121
112
|
if enabled_linters == "none":
|
|
122
113
|
linters_to_check = set()
|
|
123
114
|
elif enabled_linters == "all":
|
|
124
115
|
linters_to_check = self.all_linters
|
|
125
116
|
elif isinstance(enabled_linters, set):
|
|
126
|
-
linters_to_check = {linter for linter in self.all_linters
|
|
127
|
-
if linter.id in enabled_linters}
|
|
117
|
+
linters_to_check = {linter for linter in self.all_linters if linter.id in enabled_linters}
|
|
128
118
|
else:
|
|
129
119
|
raise RuntimeError(f"Unknown argument enabled_linters={enabled_linters} for lint()")
|
|
130
120
|
for linter in linters_to_check:
|
|
131
121
|
try:
|
|
132
122
|
run.checks.extend(linter.lint(data_contract))
|
|
133
123
|
except Exception as e:
|
|
134
|
-
run.checks.append(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
run.checks.append(
|
|
125
|
+
Check(
|
|
126
|
+
type="general",
|
|
127
|
+
result="error",
|
|
128
|
+
name=f"Linter '{linter.name}'",
|
|
129
|
+
reason=str(e),
|
|
130
|
+
engine="datacontract",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
141
133
|
run.dataContractId = data_contract.id
|
|
142
134
|
run.dataContractVersion = data_contract.info.version
|
|
143
135
|
except DataContractException as e:
|
|
144
|
-
run.checks.append(
|
|
145
|
-
type=e.type,
|
|
146
|
-
|
|
147
|
-
name=e.name,
|
|
148
|
-
reason=e.reason,
|
|
149
|
-
engine=e.engine,
|
|
150
|
-
details=""
|
|
151
|
-
))
|
|
136
|
+
run.checks.append(
|
|
137
|
+
Check(type=e.type, result=e.result, name=e.name, reason=e.reason, engine=e.engine, details="")
|
|
138
|
+
)
|
|
152
139
|
run.log_error(str(e))
|
|
153
140
|
except Exception as e:
|
|
154
|
-
run.checks.append(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
run.checks.append(
|
|
142
|
+
Check(
|
|
143
|
+
type="general",
|
|
144
|
+
result="error",
|
|
145
|
+
name="Check Data Contract",
|
|
146
|
+
reason=str(e),
|
|
147
|
+
engine="datacontract",
|
|
148
|
+
)
|
|
149
|
+
)
|
|
161
150
|
run.log_error(str(e))
|
|
162
151
|
run.finish()
|
|
163
152
|
return run
|
|
@@ -165,9 +154,10 @@ class DataContract:
|
|
|
165
154
|
def test(self) -> Run:
|
|
166
155
|
run = Run.create_run()
|
|
167
156
|
try:
|
|
168
|
-
run.log_info(
|
|
169
|
-
data_contract = resolve.resolve_data_contract(
|
|
170
|
-
|
|
157
|
+
run.log_info("Testing data contract")
|
|
158
|
+
data_contract = resolve.resolve_data_contract(
|
|
159
|
+
self._data_contract_file, self._data_contract_str, self._data_contract, self._schema_location
|
|
160
|
+
)
|
|
171
161
|
|
|
172
162
|
if data_contract.models is None or len(data_contract.models) == 0:
|
|
173
163
|
raise DataContractException(
|
|
@@ -213,23 +203,20 @@ class DataContract:
|
|
|
213
203
|
check_soda_execute(run, data_contract, server, self._spark, tmp_dir)
|
|
214
204
|
|
|
215
205
|
except DataContractException as e:
|
|
216
|
-
run.checks.append(
|
|
217
|
-
type=e.type,
|
|
218
|
-
|
|
219
|
-
name=e.name,
|
|
220
|
-
reason=e.reason,
|
|
221
|
-
engine=e.engine,
|
|
222
|
-
details=""
|
|
223
|
-
))
|
|
206
|
+
run.checks.append(
|
|
207
|
+
Check(type=e.type, result=e.result, name=e.name, reason=e.reason, engine=e.engine, details="")
|
|
208
|
+
)
|
|
224
209
|
run.log_error(str(e))
|
|
225
210
|
except Exception as e:
|
|
226
|
-
run.checks.append(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
211
|
+
run.checks.append(
|
|
212
|
+
Check(
|
|
213
|
+
type="general",
|
|
214
|
+
result="error",
|
|
215
|
+
name="Test Data Contract",
|
|
216
|
+
reason=str(e),
|
|
217
|
+
engine="datacontract",
|
|
218
|
+
)
|
|
219
|
+
)
|
|
233
220
|
logging.exception("Exception occurred")
|
|
234
221
|
run.log_error(str(e))
|
|
235
222
|
|
|
@@ -238,45 +225,44 @@ class DataContract:
|
|
|
238
225
|
if self._publish_url is not None:
|
|
239
226
|
try:
|
|
240
227
|
publish_datamesh_manager(run, self._publish_url)
|
|
241
|
-
except:
|
|
242
|
-
|
|
228
|
+
except Exception:
|
|
229
|
+
run.log_error("Failed to publish to datamesh manager")
|
|
243
230
|
if self._publish_to_opentelemetry:
|
|
244
231
|
try:
|
|
245
232
|
publish_opentelemetry(run)
|
|
246
|
-
except:
|
|
247
|
-
|
|
233
|
+
except Exception:
|
|
234
|
+
run.log_error("Failed to publish to opentelemetry")
|
|
248
235
|
|
|
249
236
|
return run
|
|
250
237
|
|
|
251
|
-
def breaking(self, other:
|
|
252
|
-
return self.changelog(
|
|
253
|
-
other,
|
|
254
|
-
include_severities=[Severity.ERROR, Severity.WARNING]
|
|
255
|
-
)
|
|
238
|
+
def breaking(self, other: "DataContract") -> BreakingChanges:
|
|
239
|
+
return self.changelog(other, include_severities=[Severity.ERROR, Severity.WARNING])
|
|
256
240
|
|
|
257
241
|
def changelog(
|
|
258
|
-
self,
|
|
259
|
-
other: 'DataContract',
|
|
260
|
-
include_severities: [Severity] = (Severity.ERROR, Severity.WARNING, Severity.INFO)
|
|
242
|
+
self, other: "DataContract", include_severities: [Severity] = (Severity.ERROR, Severity.WARNING, Severity.INFO)
|
|
261
243
|
) -> BreakingChanges:
|
|
262
244
|
old = self.get_data_contract_specification()
|
|
263
245
|
new = other.get_data_contract_specification()
|
|
264
246
|
|
|
265
247
|
breaking_changes = list[BreakingChange]()
|
|
266
248
|
|
|
267
|
-
breaking_changes.extend(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
249
|
+
breaking_changes.extend(
|
|
250
|
+
quality_breaking_changes(
|
|
251
|
+
old_quality=old.quality,
|
|
252
|
+
new_quality=new.quality,
|
|
253
|
+
new_path=other._data_contract_file,
|
|
254
|
+
include_severities=include_severities,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
273
257
|
|
|
274
|
-
breaking_changes.extend(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
258
|
+
breaking_changes.extend(
|
|
259
|
+
models_breaking_changes(
|
|
260
|
+
old_models=old.models,
|
|
261
|
+
new_models=new.models,
|
|
262
|
+
new_path=other._data_contract_file,
|
|
263
|
+
include_severities=include_severities,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
280
266
|
|
|
281
267
|
return BreakingChanges(breaking_changes=breaking_changes)
|
|
282
268
|
|
|
@@ -290,17 +276,20 @@ class DataContract:
|
|
|
290
276
|
)
|
|
291
277
|
|
|
292
278
|
def export(self, export_format, model: str = "all", rdf_base: str = None, sql_server_type: str = "auto") -> str:
|
|
293
|
-
data_contract = resolve.resolve_data_contract(
|
|
294
|
-
|
|
279
|
+
data_contract = resolve.resolve_data_contract(
|
|
280
|
+
self._data_contract_file, self._data_contract_str, self._data_contract, inline_definitions=True
|
|
281
|
+
)
|
|
295
282
|
if export_format == "jsonschema":
|
|
296
283
|
if data_contract.models is None:
|
|
297
|
-
raise RuntimeError(
|
|
284
|
+
raise RuntimeError(f"Export to {export_format} requires models in the data contract.")
|
|
298
285
|
|
|
299
286
|
model_names = list(data_contract.models.keys())
|
|
300
287
|
|
|
301
288
|
if model == "all":
|
|
302
289
|
if len(data_contract.models.items()) != 1:
|
|
303
|
-
raise RuntimeError(
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
f"Export to {export_format} is model specific. Specify the model via --model $MODEL_NAME. Available models: {model_names}"
|
|
292
|
+
)
|
|
304
293
|
|
|
305
294
|
model_name, model_value = next(iter(data_contract.models.items()))
|
|
306
295
|
return to_jsonschema_json(model_name, model_value)
|
|
@@ -308,7 +297,9 @@ class DataContract:
|
|
|
308
297
|
model_name = model
|
|
309
298
|
model_value = data_contract.models.get(model_name)
|
|
310
299
|
if model_value is None:
|
|
311
|
-
raise RuntimeError(
|
|
300
|
+
raise RuntimeError(
|
|
301
|
+
f"Model {model_name} not found in the data contract. Available models: {model_names}"
|
|
302
|
+
)
|
|
312
303
|
|
|
313
304
|
return to_jsonschema_json(model_name, model_value)
|
|
314
305
|
if export_format == "sodacl":
|
|
@@ -325,7 +316,9 @@ class DataContract:
|
|
|
325
316
|
|
|
326
317
|
if model == "all":
|
|
327
318
|
if len(data_contract.models.items()) != 1:
|
|
328
|
-
raise RuntimeError(
|
|
319
|
+
raise RuntimeError(
|
|
320
|
+
f"Export to {export_format} is model specific. Specify the model via --model $MODEL_NAME. Available models: {model_names}"
|
|
321
|
+
)
|
|
329
322
|
|
|
330
323
|
model_name, model_value = next(iter(data_contract.models.items()))
|
|
331
324
|
return to_dbt_staging_sql(data_contract, model_name, model_value)
|
|
@@ -333,7 +326,9 @@ class DataContract:
|
|
|
333
326
|
model_name = model
|
|
334
327
|
model_value = data_contract.models.get(model_name)
|
|
335
328
|
if model_value is None:
|
|
336
|
-
raise RuntimeError(
|
|
329
|
+
raise RuntimeError(
|
|
330
|
+
f"Model {model_name} not found in the data contract. Available models: {model_names}"
|
|
331
|
+
)
|
|
337
332
|
|
|
338
333
|
return to_dbt_staging_sql(data_contract, model_name, model_value)
|
|
339
334
|
if export_format == "odcs":
|
|
@@ -350,7 +345,9 @@ class DataContract:
|
|
|
350
345
|
|
|
351
346
|
if model == "all":
|
|
352
347
|
if len(data_contract.models.items()) != 1:
|
|
353
|
-
raise RuntimeError(
|
|
348
|
+
raise RuntimeError(
|
|
349
|
+
f"Export to {export_format} is model specific. Specify the model via --model $MODEL_NAME. Available models: {model_names}"
|
|
350
|
+
)
|
|
354
351
|
|
|
355
352
|
model_name, model_value = next(iter(data_contract.models.items()))
|
|
356
353
|
return to_avro_schema_json(model_name, model_value)
|
|
@@ -358,7 +355,9 @@ class DataContract:
|
|
|
358
355
|
model_name = model
|
|
359
356
|
model_value = data_contract.models.get(model_name)
|
|
360
357
|
if model_value is None:
|
|
361
|
-
raise RuntimeError(
|
|
358
|
+
raise RuntimeError(
|
|
359
|
+
f"Model {model_name} not found in the data contract. Available models: {model_names}"
|
|
360
|
+
)
|
|
362
361
|
|
|
363
362
|
return to_avro_schema_json(model_name, model_value)
|
|
364
363
|
if export_format == "avro-idl":
|
|
@@ -366,19 +365,21 @@ class DataContract:
|
|
|
366
365
|
if export_format == "terraform":
|
|
367
366
|
return to_terraform(data_contract)
|
|
368
367
|
if export_format == "sql":
|
|
369
|
-
server_type = _determine_sql_server_type(data_contract, sql_server_type)
|
|
368
|
+
server_type = self._determine_sql_server_type(data_contract, sql_server_type)
|
|
370
369
|
return to_sql_ddl(data_contract, server_type=server_type)
|
|
371
370
|
if export_format == "sql-query":
|
|
372
371
|
if data_contract.models is None:
|
|
373
372
|
raise RuntimeError(f"Export to {export_format} requires models in the data contract.")
|
|
374
373
|
|
|
375
|
-
server_type = _determine_sql_server_type(data_contract, sql_server_type)
|
|
374
|
+
server_type = self._determine_sql_server_type(data_contract, sql_server_type)
|
|
376
375
|
|
|
377
376
|
model_names = list(data_contract.models.keys())
|
|
378
377
|
|
|
379
378
|
if model == "all":
|
|
380
379
|
if len(data_contract.models.items()) != 1:
|
|
381
|
-
raise RuntimeError(
|
|
380
|
+
raise RuntimeError(
|
|
381
|
+
f"Export to {export_format} is model specific. Specify the model via --model $MODEL_NAME. Available models: {model_names}"
|
|
382
|
+
)
|
|
382
383
|
|
|
383
384
|
model_name, model_value = next(iter(data_contract.models.items()))
|
|
384
385
|
return to_sql_query(data_contract, model_name, model_value, server_type)
|
|
@@ -386,13 +387,62 @@ class DataContract:
|
|
|
386
387
|
model_name = model
|
|
387
388
|
model_value = data_contract.models.get(model_name)
|
|
388
389
|
if model_value is None:
|
|
389
|
-
raise RuntimeError(
|
|
390
|
+
raise RuntimeError(
|
|
391
|
+
f"Model {model_name} not found in the data contract. Available models: {model_names}"
|
|
392
|
+
)
|
|
390
393
|
|
|
391
394
|
return to_sql_query(data_contract, model_name, model_value, server_type)
|
|
395
|
+
|
|
396
|
+
if export_format == "great-expectations":
|
|
397
|
+
if data_contract.models is None:
|
|
398
|
+
raise RuntimeError(f"Export to {export_format} requires models in the data contract.")
|
|
399
|
+
|
|
400
|
+
model_names = list(data_contract.models.keys())
|
|
401
|
+
|
|
402
|
+
if model == "all":
|
|
403
|
+
if len(data_contract.models.items()) != 1:
|
|
404
|
+
raise RuntimeError(
|
|
405
|
+
f"Export to {export_format} is model specific. Specify the model via --model "
|
|
406
|
+
f"$MODEL_NAME. Available models: {model_names}"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
model_name, model_value = next(iter(data_contract.models.items()))
|
|
410
|
+
return to_great_expectations(data_contract, model_name)
|
|
411
|
+
else:
|
|
412
|
+
model_name = model
|
|
413
|
+
model_value = data_contract.models.get(model_name)
|
|
414
|
+
if model_value is None:
|
|
415
|
+
raise RuntimeError(
|
|
416
|
+
f"Model {model_name} not found in the data contract. " f"Available models: {model_names}"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return to_great_expectations(data_contract, model_name)
|
|
420
|
+
if export_format == "pydantic-model":
|
|
421
|
+
return to_pydantic_model_str(data_contract)
|
|
422
|
+
if export_format == "html":
|
|
423
|
+
return to_html(data_contract)
|
|
392
424
|
else:
|
|
393
425
|
print(f"Export format {export_format} not supported.")
|
|
394
426
|
return ""
|
|
395
427
|
|
|
428
|
+
def _determine_sql_server_type(self, data_contract: DataContractSpecification, sql_server_type: str):
|
|
429
|
+
if sql_server_type == "auto":
|
|
430
|
+
if data_contract.servers is None or len(data_contract.servers) == 0:
|
|
431
|
+
raise RuntimeError("Export with server_type='auto' requires servers in the data contract.")
|
|
432
|
+
|
|
433
|
+
server_types = set([server.type for server in data_contract.servers.values()])
|
|
434
|
+
if "snowflake" in server_types:
|
|
435
|
+
return "snowflake"
|
|
436
|
+
elif "postgres" in server_types:
|
|
437
|
+
return "postgres"
|
|
438
|
+
elif "databricks" in server_types:
|
|
439
|
+
return "databricks"
|
|
440
|
+
else:
|
|
441
|
+
# default to snowflake dialect
|
|
442
|
+
return "snowflake"
|
|
443
|
+
else:
|
|
444
|
+
return sql_server_type
|
|
445
|
+
|
|
396
446
|
def _get_examples_server(self, data_contract, run, tmp_dir):
|
|
397
447
|
run.log_info(f"Copying examples to files in temporary directory {tmp_dir}")
|
|
398
448
|
format = "json"
|
|
@@ -402,13 +452,13 @@ class DataContract:
|
|
|
402
452
|
run.log_info(f"Creating example file {p}")
|
|
403
453
|
with open(p, "w") as f:
|
|
404
454
|
content = ""
|
|
405
|
-
if format == "json" and
|
|
455
|
+
if format == "json" and isinstance(example.data, list):
|
|
406
456
|
content = json.dumps(example.data)
|
|
407
|
-
elif format == "json" and
|
|
457
|
+
elif format == "json" and isinstance(example.data, str):
|
|
408
458
|
content = example.data
|
|
409
|
-
elif format == "yaml" and
|
|
459
|
+
elif format == "yaml" and isinstance(example.data, list):
|
|
410
460
|
content = yaml.dump(example.data, allow_unicode=True)
|
|
411
|
-
elif format == "yaml" and
|
|
461
|
+
elif format == "yaml" and isinstance(example.data, str):
|
|
412
462
|
content = example.data
|
|
413
463
|
elif format == "csv":
|
|
414
464
|
content = example.data
|
datacontract/engines/datacontract/check_that_datacontract_contains_valid_servers_configuration.py
CHANGED
|
@@ -3,7 +3,9 @@ from datacontract.model.exceptions import DataContractException
|
|
|
3
3
|
from datacontract.model.run import Run
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def check_that_datacontract_contains_valid_server_configuration(
|
|
6
|
+
def check_that_datacontract_contains_valid_server_configuration(
|
|
7
|
+
run: Run, data_contract: DataContractSpecification, server_name: str
|
|
8
|
+
):
|
|
7
9
|
if data_contract.servers is None:
|
|
8
10
|
raise DataContractException(
|
|
9
11
|
type="lint",
|
|
@@ -28,4 +30,6 @@ def check_that_datacontract_contains_valid_server_configuration(run: Run, data_c
|
|
|
28
30
|
reason=f"Cannot find server '{server_name}' in the data contract servers configuration. Skip executing tests.",
|
|
29
31
|
engine="datacontract",
|
|
30
32
|
)
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
# TODO check for server.type, if all required fields are present
|
|
@@ -9,12 +9,13 @@ def check_that_datacontract_file_exists(run: Run, file_path: str):
|
|
|
9
9
|
if file_path.startswith("http://") or file_path.startswith("https://"):
|
|
10
10
|
return
|
|
11
11
|
if not os.path.exists(file_path):
|
|
12
|
-
run.checks.append(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
run.checks.append(
|
|
13
|
+
Check(
|
|
14
|
+
type="lint",
|
|
15
|
+
name="Check that data contract file exists",
|
|
16
|
+
result="failed",
|
|
17
|
+
reason=f"The file '{file_path}' does not exist.",
|
|
18
|
+
engine="datacontract-cli",
|
|
19
|
+
)
|
|
20
|
+
)
|
|
19
21
|
raise Exception(f"The file '{file_path}' does not exist.")
|
|
20
|
-
|
|
@@ -14,31 +14,35 @@ def check_that_datacontract_str_is_valid(run: Run, data_contract_str: str):
|
|
|
14
14
|
try:
|
|
15
15
|
fastjsonschema.validate(schema, data_contract_yaml)
|
|
16
16
|
logging.debug("YAML data is valid.")
|
|
17
|
-
run.checks.append(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
run.checks.append(
|
|
18
|
+
Check(
|
|
19
|
+
type="lint",
|
|
20
|
+
result="passed",
|
|
21
|
+
name="Check that data contract YAML is valid",
|
|
22
|
+
engine="datacontract",
|
|
23
|
+
)
|
|
24
|
+
)
|
|
23
25
|
except JsonSchemaValueException as e:
|
|
24
26
|
logging.warning("YAML data is invalid.")
|
|
25
27
|
logging.warning(f"Validation error: {e.message}")
|
|
26
|
-
run.checks.append(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
run.checks.append(
|
|
29
|
+
Check(
|
|
30
|
+
type="lint",
|
|
31
|
+
result="failed",
|
|
32
|
+
name="Check that data contract YAML is valid",
|
|
33
|
+
reason=e.message,
|
|
34
|
+
engine="datacontract",
|
|
35
|
+
)
|
|
36
|
+
)
|
|
33
37
|
except Exception as e:
|
|
34
38
|
logging.warning("YAML data is invalid.")
|
|
35
39
|
logging.warning(f"Validation error: {str(e)}")
|
|
36
|
-
run.checks.append(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
run.checks.append(
|
|
41
|
+
Check(
|
|
42
|
+
type="lint",
|
|
43
|
+
result="failed",
|
|
44
|
+
name="Check that data contract YAML is valid",
|
|
45
|
+
reason=str(e),
|
|
46
|
+
engine="datacontract",
|
|
47
|
+
)
|
|
48
|
+
)
|