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