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/cli.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Iterable, Optional
|
|
|
5
5
|
import typer
|
|
6
6
|
from click import Context
|
|
7
7
|
from rich import box
|
|
8
|
-
from rich import
|
|
8
|
+
from rich.console import Console
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
from typer.core import TyperGroup
|
|
11
11
|
from typing_extensions import Annotated
|
|
@@ -14,6 +14,7 @@ from datacontract.data_contract import DataContract
|
|
|
14
14
|
from datacontract.init.download_datacontract_file import \
|
|
15
15
|
download_datacontract_file, FileExistsException
|
|
16
16
|
|
|
17
|
+
console = Console()
|
|
17
18
|
|
|
18
19
|
class OrderedCommands(TyperGroup):
|
|
19
20
|
def list_commands(self, ctx: Context) -> Iterable[str]:
|
|
@@ -29,15 +30,16 @@ app = typer.Typer(
|
|
|
29
30
|
|
|
30
31
|
def version_callback(value: bool):
|
|
31
32
|
if value:
|
|
32
|
-
print(metadata.version("datacontract-cli"))
|
|
33
|
+
console.print(metadata.version("datacontract-cli"))
|
|
33
34
|
raise typer.Exit()
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@app.callback()
|
|
37
38
|
def common(
|
|
38
39
|
ctx: typer.Context,
|
|
39
|
-
version: bool = typer.Option(
|
|
40
|
-
|
|
40
|
+
version: bool = typer.Option(
|
|
41
|
+
None, "--version", help="Prints the current version.", callback=version_callback, is_eager=True
|
|
42
|
+
),
|
|
41
43
|
):
|
|
42
44
|
"""
|
|
43
45
|
The datacontract CLI is an open source command-line tool for working with Data Contracts (https://datacontract.com).
|
|
@@ -51,10 +53,12 @@ def common(
|
|
|
51
53
|
|
|
52
54
|
@app.command()
|
|
53
55
|
def init(
|
|
54
|
-
location: Annotated[
|
|
55
|
-
help="The location (url or path) of the data contract yaml to create.")
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
location: Annotated[
|
|
57
|
+
str, typer.Argument(help="The location (url or path) of the data contract yaml to create.")
|
|
58
|
+
] = "datacontract.yaml",
|
|
59
|
+
template: Annotated[
|
|
60
|
+
str, typer.Option(help="URL of a template or data contract")
|
|
61
|
+
] = "https://datacontract.com/datacontract.init.yaml",
|
|
58
62
|
overwrite: Annotated[bool, typer.Option(help="Replace the existing datacontract.yaml")] = False,
|
|
59
63
|
):
|
|
60
64
|
"""
|
|
@@ -63,19 +67,20 @@ def init(
|
|
|
63
67
|
try:
|
|
64
68
|
download_datacontract_file(location, template, overwrite)
|
|
65
69
|
except FileExistsException:
|
|
66
|
-
print("File already exists, use --overwrite to overwrite")
|
|
70
|
+
console.print("File already exists, use --overwrite to overwrite")
|
|
67
71
|
raise typer.Exit(code=1)
|
|
68
72
|
else:
|
|
69
|
-
print("📄 data contract written to " + location)
|
|
73
|
+
console.print("📄 data contract written to " + location)
|
|
70
74
|
|
|
71
75
|
|
|
72
76
|
@app.command()
|
|
73
77
|
def lint(
|
|
74
78
|
location: Annotated[
|
|
75
|
-
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
79
|
+
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
80
|
+
] = "datacontract.yaml",
|
|
76
81
|
schema: Annotated[
|
|
77
|
-
str, typer.Option(
|
|
78
|
-
|
|
82
|
+
str, typer.Option(help="The location (url or path) of the Data Contract Specification JSON Schema")
|
|
83
|
+
] = "https://datacontract.com/datacontract.schema.json",
|
|
79
84
|
):
|
|
80
85
|
"""
|
|
81
86
|
Validate that the datacontract.yaml is correctly formatted.
|
|
@@ -87,30 +92,46 @@ def lint(
|
|
|
87
92
|
@app.command()
|
|
88
93
|
def test(
|
|
89
94
|
location: Annotated[
|
|
90
|
-
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
95
|
+
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
96
|
+
] = "datacontract.yaml",
|
|
91
97
|
schema: Annotated[
|
|
92
|
-
str, typer.Option(
|
|
93
|
-
|
|
94
|
-
server: Annotated[
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
help="
|
|
98
|
+
str, typer.Option(help="The location (url or path) of the Data Contract Specification JSON Schema")
|
|
99
|
+
] = "https://datacontract.com/datacontract.schema.json",
|
|
100
|
+
server: Annotated[
|
|
101
|
+
str,
|
|
102
|
+
typer.Option(
|
|
103
|
+
help="The server configuration to run the schema and quality tests. "
|
|
104
|
+
"Use the key of the server object in the data contract yaml file "
|
|
105
|
+
"to refer to a server, e.g., `production`, or `all` for all "
|
|
106
|
+
"servers (default)."
|
|
107
|
+
),
|
|
108
|
+
] = "all",
|
|
109
|
+
examples: Annotated[
|
|
110
|
+
bool, typer.Option(help="Run the schema and quality tests on the example data within the data contract.")
|
|
111
|
+
] = None,
|
|
112
|
+
publish: Annotated[str, typer.Option(help="The url to publish the results after the test")] = None,
|
|
113
|
+
publish_to_opentelemetry: Annotated[
|
|
114
|
+
bool,
|
|
115
|
+
typer.Option(
|
|
116
|
+
help="Publish the results to opentelemetry. Use environment variables to configure the OTLP endpoint, headers, etc."
|
|
117
|
+
),
|
|
118
|
+
] = False,
|
|
119
|
+
logs: Annotated[bool, typer.Option(help="Print logs")] = False,
|
|
105
120
|
):
|
|
106
121
|
"""
|
|
107
122
|
Run schema and quality tests on configured servers.
|
|
108
123
|
"""
|
|
109
|
-
print(f"Testing {location}")
|
|
124
|
+
console.print(f"Testing {location}")
|
|
110
125
|
if server == "all":
|
|
111
126
|
server = None
|
|
112
|
-
run = DataContract(
|
|
113
|
-
|
|
127
|
+
run = DataContract(
|
|
128
|
+
data_contract_file=location,
|
|
129
|
+
schema_location=schema,
|
|
130
|
+
publish_url=publish,
|
|
131
|
+
publish_to_opentelemetry=publish_to_opentelemetry,
|
|
132
|
+
server=server,
|
|
133
|
+
examples=examples,
|
|
134
|
+
).test()
|
|
114
135
|
if logs:
|
|
115
136
|
_print_logs(run)
|
|
116
137
|
_handle_result(run)
|
|
@@ -118,6 +139,7 @@ def test(
|
|
|
118
139
|
|
|
119
140
|
class ExportFormat(str, Enum):
|
|
120
141
|
jsonschema = "jsonschema"
|
|
142
|
+
pydantic_model = "pydantic-model"
|
|
121
143
|
sodacl = "sodacl"
|
|
122
144
|
dbt = "dbt"
|
|
123
145
|
dbt_sources = "dbt-sources"
|
|
@@ -125,26 +147,58 @@ class ExportFormat(str, Enum):
|
|
|
125
147
|
odcs = "odcs"
|
|
126
148
|
rdf = "rdf"
|
|
127
149
|
avro = "avro"
|
|
150
|
+
protobuf = "protobuf"
|
|
151
|
+
great_expectations = "great-expectations"
|
|
152
|
+
terraform = "terraform"
|
|
153
|
+
avro_idl = "avro-idl"
|
|
154
|
+
sql = "sql"
|
|
155
|
+
sql_query = "sql-query"
|
|
128
156
|
|
|
129
157
|
|
|
130
158
|
@app.command()
|
|
131
159
|
def export(
|
|
132
160
|
format: Annotated[ExportFormat, typer.Option(help="The export format.")],
|
|
133
161
|
server: Annotated[str, typer.Option(help="The server name to export.")] = None,
|
|
134
|
-
|
|
162
|
+
model: Annotated[
|
|
163
|
+
str,
|
|
164
|
+
typer.Option(
|
|
165
|
+
help="Use the key of the model in the data contract yaml file "
|
|
166
|
+
"to refer to a model, e.g., `orders`, or `all` for all "
|
|
167
|
+
"models (default)."
|
|
168
|
+
),
|
|
169
|
+
] = "all",
|
|
170
|
+
rdf_base: Annotated[
|
|
171
|
+
Optional[str],
|
|
172
|
+
typer.Option(help="[rdf] The base URI used to generate the RDF graph.", rich_help_panel="RDF Options"),
|
|
173
|
+
] = None,
|
|
174
|
+
sql_server_type: Annotated[
|
|
175
|
+
Optional[str],
|
|
176
|
+
typer.Option(
|
|
177
|
+
help="[sql] The server type to determine the sql dialect. By default, it uses 'auto' to automatically detect the sql dialect via the specified servers in the data contract.",
|
|
178
|
+
rich_help_panel="SQL Options",
|
|
179
|
+
),
|
|
180
|
+
] = "auto",
|
|
135
181
|
location: Annotated[
|
|
136
|
-
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
182
|
+
str, typer.Argument(help="The location (url or path) of the data contract yaml.")
|
|
183
|
+
] = "datacontract.yaml",
|
|
137
184
|
):
|
|
138
185
|
"""
|
|
139
|
-
Convert data contract to a specific format.
|
|
186
|
+
Convert data contract to a specific format. console.prints to stdout.
|
|
140
187
|
"""
|
|
141
188
|
# TODO exception handling
|
|
142
|
-
result = DataContract(data_contract_file=location, server=server).export(
|
|
143
|
-
|
|
189
|
+
result = DataContract(data_contract_file=location, server=server).export(
|
|
190
|
+
export_format=format,
|
|
191
|
+
model=model,
|
|
192
|
+
rdf_base=rdf_base,
|
|
193
|
+
sql_server_type=sql_server_type,
|
|
194
|
+
)
|
|
195
|
+
# Don't interpret console markup in output.
|
|
196
|
+
console.print(result, markup=False)
|
|
144
197
|
|
|
145
198
|
|
|
146
199
|
class ImportFormat(str, Enum):
|
|
147
200
|
sql = "sql"
|
|
201
|
+
avro = "avro"
|
|
148
202
|
|
|
149
203
|
|
|
150
204
|
@app.command(name="import")
|
|
@@ -156,7 +210,7 @@ def import_(
|
|
|
156
210
|
Create a data contract from the given source file. Prints to stdout.
|
|
157
211
|
"""
|
|
158
212
|
result = DataContract().import_from_source(format, source)
|
|
159
|
-
print(result.to_yaml())
|
|
213
|
+
console.print(result.to_yaml())
|
|
160
214
|
|
|
161
215
|
|
|
162
216
|
@app.command()
|
|
@@ -169,23 +223,62 @@ def breaking(
|
|
|
169
223
|
"""
|
|
170
224
|
|
|
171
225
|
# TODO exception handling
|
|
172
|
-
result = DataContract(data_contract_file=location_old).breaking(
|
|
173
|
-
|
|
226
|
+
result = DataContract(data_contract_file=location_old, inline_definitions=True).breaking(
|
|
227
|
+
DataContract(data_contract_file=location_new, inline_definitions=True)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
console.print(result.breaking_str())
|
|
231
|
+
|
|
174
232
|
if not result.passed_checks():
|
|
175
233
|
raise typer.Exit(code=1)
|
|
176
234
|
|
|
177
235
|
|
|
236
|
+
@app.command()
|
|
237
|
+
def changelog(
|
|
238
|
+
location_old: Annotated[str, typer.Argument(help="The location (url or path) of the old data contract yaml.")],
|
|
239
|
+
location_new: Annotated[str, typer.Argument(help="The location (url or path) of the new data contract yaml.")],
|
|
240
|
+
):
|
|
241
|
+
"""
|
|
242
|
+
Generate a changelog between data contracts. Prints to stdout.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
# TODO exception handling
|
|
246
|
+
result = DataContract(data_contract_file=location_old, inline_definitions=True).changelog(
|
|
247
|
+
DataContract(data_contract_file=location_new, inline_definitions=True)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
console.print(result.changelog_str())
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@app.command()
|
|
254
|
+
def diff(
|
|
255
|
+
location_old: Annotated[str, typer.Argument(help="The location (url or path) of the old data contract yaml.")],
|
|
256
|
+
location_new: Annotated[str, typer.Argument(help="The location (url or path) of the new data contract yaml.")],
|
|
257
|
+
):
|
|
258
|
+
"""
|
|
259
|
+
PLACEHOLDER. Currently works as 'changelog' does.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
# TODO change to diff output, not the changelog entries
|
|
263
|
+
result = DataContract(data_contract_file=location_old, inline_definitions=True).changelog(
|
|
264
|
+
DataContract(data_contract_file=location_new, inline_definitions=True)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
console.print(result.changelog_str())
|
|
268
|
+
|
|
269
|
+
|
|
178
270
|
def _handle_result(run):
|
|
179
271
|
_print_table(run)
|
|
180
272
|
if run.result == "passed":
|
|
181
|
-
print(
|
|
182
|
-
f"🟢 data contract is valid. Run {len(run.checks)} checks. Took {(run.timestampEnd - run.timestampStart).total_seconds()} seconds."
|
|
273
|
+
console.print(
|
|
274
|
+
f"🟢 data contract is valid. Run {len(run.checks)} checks. Took {(run.timestampEnd - run.timestampStart).total_seconds()} seconds."
|
|
275
|
+
)
|
|
183
276
|
else:
|
|
184
|
-
print("🔴 data contract is invalid, found the following errors:")
|
|
277
|
+
console.print("🔴 data contract is invalid, found the following errors:")
|
|
185
278
|
i = 1
|
|
186
279
|
for check in run.checks:
|
|
187
280
|
if check.result != "passed":
|
|
188
|
-
print(str(++i) + ") " + check.reason)
|
|
281
|
+
console.print(str(++i) + ") " + check.reason)
|
|
189
282
|
raise typer.Exit(code=1)
|
|
190
283
|
|
|
191
284
|
|
|
@@ -197,7 +290,7 @@ def _print_table(run):
|
|
|
197
290
|
table.add_column("Details", max_width=50)
|
|
198
291
|
for check in run.checks:
|
|
199
292
|
table.add_row(with_markup(check.result), check.name, to_field(run, check), check.reason)
|
|
200
|
-
print(table)
|
|
293
|
+
console.print(table)
|
|
201
294
|
|
|
202
295
|
|
|
203
296
|
def to_field(run, check):
|
|
@@ -211,9 +304,9 @@ def to_field(run, check):
|
|
|
211
304
|
|
|
212
305
|
|
|
213
306
|
def _print_logs(run):
|
|
214
|
-
print("\nLogs:")
|
|
307
|
+
console.print("\nLogs:")
|
|
215
308
|
for log in run.logs:
|
|
216
|
-
print(log.timestamp.strftime("%y-%m-%d %H:%M:%S"), log.level.ljust(5), log.message)
|
|
309
|
+
console.print(log.timestamp.strftime("%y-%m-%d %H:%M:%S"), log.level.ljust(5), log.message)
|
|
217
310
|
|
|
218
311
|
|
|
219
312
|
def with_markup(result):
|