datacontract-cli 0.10.13__py3-none-any.whl → 0.10.15__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 +227 -9
- datacontract/breaking/breaking_rules.py +24 -0
- datacontract/catalog/catalog.py +1 -1
- datacontract/cli.py +104 -32
- datacontract/data_contract.py +35 -5
- datacontract/engines/datacontract/check_that_datacontract_file_exists.py +1 -1
- datacontract/engines/fastjsonschema/check_jsonschema.py +114 -22
- datacontract/engines/soda/check_soda_execute.py +5 -3
- datacontract/engines/soda/connections/duckdb.py +1 -0
- datacontract/engines/soda/connections/kafka.py +38 -17
- datacontract/export/avro_converter.py +8 -1
- datacontract/export/avro_idl_converter.py +2 -2
- datacontract/export/bigquery_converter.py +4 -3
- datacontract/export/data_caterer_converter.py +1 -1
- datacontract/export/dbml_converter.py +2 -4
- datacontract/export/dbt_converter.py +2 -3
- datacontract/export/dcs_exporter.py +6 -0
- datacontract/export/exporter.py +5 -2
- datacontract/export/exporter_factory.py +16 -3
- datacontract/export/go_converter.py +3 -2
- datacontract/export/great_expectations_converter.py +202 -40
- datacontract/export/html_export.py +1 -1
- datacontract/export/jsonschema_converter.py +3 -2
- datacontract/export/{odcs_converter.py → odcs_v2_exporter.py} +5 -5
- datacontract/export/odcs_v3_exporter.py +294 -0
- datacontract/export/pandas_type_converter.py +40 -0
- datacontract/export/protobuf_converter.py +1 -1
- datacontract/export/rdf_converter.py +4 -5
- datacontract/export/sodacl_converter.py +86 -2
- datacontract/export/spark_converter.py +10 -7
- datacontract/export/sql_converter.py +1 -2
- datacontract/export/sql_type_converter.py +55 -11
- datacontract/export/sqlalchemy_converter.py +1 -2
- datacontract/export/terraform_converter.py +1 -1
- datacontract/imports/avro_importer.py +1 -1
- datacontract/imports/bigquery_importer.py +1 -1
- datacontract/imports/dbml_importer.py +2 -2
- datacontract/imports/dbt_importer.py +3 -2
- datacontract/imports/glue_importer.py +5 -3
- datacontract/imports/iceberg_importer.py +161 -0
- datacontract/imports/importer.py +2 -0
- datacontract/imports/importer_factory.py +12 -1
- datacontract/imports/jsonschema_importer.py +3 -2
- datacontract/imports/odcs_importer.py +25 -168
- datacontract/imports/odcs_v2_importer.py +177 -0
- datacontract/imports/odcs_v3_importer.py +309 -0
- datacontract/imports/parquet_importer.py +81 -0
- datacontract/imports/spark_importer.py +2 -1
- datacontract/imports/sql_importer.py +1 -1
- datacontract/imports/unity_importer.py +3 -3
- datacontract/integration/datamesh_manager.py +1 -1
- datacontract/integration/opentelemetry.py +0 -1
- datacontract/lint/lint.py +2 -1
- datacontract/lint/linters/description_linter.py +1 -0
- datacontract/lint/linters/example_model_linter.py +1 -0
- datacontract/lint/linters/field_pattern_linter.py +1 -0
- datacontract/lint/linters/field_reference_linter.py +1 -0
- datacontract/lint/linters/notice_period_linter.py +1 -0
- datacontract/lint/linters/quality_schema_linter.py +1 -0
- datacontract/lint/linters/valid_constraints_linter.py +1 -0
- datacontract/lint/resolve.py +14 -9
- datacontract/lint/resources.py +21 -0
- datacontract/lint/schema.py +1 -1
- datacontract/lint/urls.py +4 -2
- datacontract/model/data_contract_specification.py +83 -13
- datacontract/model/odcs.py +11 -0
- datacontract/model/run.py +21 -12
- datacontract/templates/index.html +6 -6
- datacontract/web.py +2 -3
- {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/METADATA +176 -93
- datacontract_cli-0.10.15.dist-info/RECORD +105 -0
- {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/WHEEL +1 -1
- datacontract/engines/datacontract/check_that_datacontract_str_is_valid.py +0 -48
- datacontract_cli-0.10.13.dist-info/RECORD +0 -97
- {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
import pydantic as pyd
|
|
5
5
|
import yaml
|
|
6
6
|
|
|
7
|
+
DATACONTRACT_TYPES = [
|
|
8
|
+
"string",
|
|
9
|
+
"text",
|
|
10
|
+
"varchar",
|
|
11
|
+
"number",
|
|
12
|
+
"decimal",
|
|
13
|
+
"numeric",
|
|
14
|
+
"int",
|
|
15
|
+
"integer",
|
|
16
|
+
"long",
|
|
17
|
+
"bigint",
|
|
18
|
+
"float",
|
|
19
|
+
"double",
|
|
20
|
+
"boolean",
|
|
21
|
+
"timestamp",
|
|
22
|
+
"timestamp_tz",
|
|
23
|
+
"timestamp_ntz",
|
|
24
|
+
"date",
|
|
25
|
+
"array",
|
|
26
|
+
"bytes",
|
|
27
|
+
"object",
|
|
28
|
+
"record",
|
|
29
|
+
"struct",
|
|
30
|
+
"null",
|
|
31
|
+
]
|
|
32
|
+
|
|
7
33
|
|
|
8
34
|
class Contact(pyd.BaseModel):
|
|
9
35
|
name: str = None
|
|
@@ -15,6 +41,14 @@ class Contact(pyd.BaseModel):
|
|
|
15
41
|
)
|
|
16
42
|
|
|
17
43
|
|
|
44
|
+
class ServerRole(pyd.BaseModel):
|
|
45
|
+
name: str = None
|
|
46
|
+
description: str = None
|
|
47
|
+
model_config = pyd.ConfigDict(
|
|
48
|
+
extra="allow",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
18
52
|
class Server(pyd.BaseModel):
|
|
19
53
|
type: str = None
|
|
20
54
|
description: str = None
|
|
@@ -38,6 +72,7 @@ class Server(pyd.BaseModel):
|
|
|
38
72
|
dataProductId: str = None
|
|
39
73
|
outputPortId: str = None
|
|
40
74
|
driver: str = None
|
|
75
|
+
roles: List[ServerRole] = None
|
|
41
76
|
|
|
42
77
|
model_config = pyd.ConfigDict(
|
|
43
78
|
extra="allow",
|
|
@@ -83,19 +118,41 @@ class Definition(pyd.BaseModel):
|
|
|
83
118
|
)
|
|
84
119
|
|
|
85
120
|
|
|
121
|
+
class Quality(pyd.BaseModel):
|
|
122
|
+
type: str = None
|
|
123
|
+
description: str = None
|
|
124
|
+
query: str = None
|
|
125
|
+
dialect: str = None
|
|
126
|
+
mustBe: int = None
|
|
127
|
+
mustNotBe: int = None
|
|
128
|
+
mustBeGreaterThan: int = None
|
|
129
|
+
mustBeGreaterThanOrEqualTo: int = None
|
|
130
|
+
mustBeLessThan: int = None
|
|
131
|
+
mustBeLessThanOrEqualTo: int = None
|
|
132
|
+
mustBeBetween: List[int] = None
|
|
133
|
+
mustNotBeBetween: List[int] = None
|
|
134
|
+
engine: str = None
|
|
135
|
+
implementation: str | Dict[str, Any] = None
|
|
136
|
+
|
|
137
|
+
model_config = pyd.ConfigDict(
|
|
138
|
+
extra="allow",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
86
142
|
class Field(pyd.BaseModel):
|
|
87
143
|
ref: str = pyd.Field(default=None, alias="$ref")
|
|
88
144
|
ref_obj: Definition = pyd.Field(default=None, exclude=True)
|
|
89
|
-
title: str = None
|
|
145
|
+
title: str | None = None
|
|
90
146
|
type: str = None
|
|
91
147
|
format: str = None
|
|
92
148
|
required: bool = None
|
|
93
149
|
primary: bool = None
|
|
94
|
-
|
|
150
|
+
primaryKey: bool = None
|
|
151
|
+
unique: bool | None = None
|
|
95
152
|
references: str = None
|
|
96
|
-
description: str = None
|
|
97
|
-
pii: bool = None
|
|
98
|
-
classification: str = None
|
|
153
|
+
description: str | None = None
|
|
154
|
+
pii: bool | None = None
|
|
155
|
+
classification: str | None = None
|
|
99
156
|
pattern: str = None
|
|
100
157
|
minLength: int = None
|
|
101
158
|
maxLength: int = None
|
|
@@ -103,8 +160,8 @@ class Field(pyd.BaseModel):
|
|
|
103
160
|
exclusiveMinimum: int = None
|
|
104
161
|
maximum: int = None
|
|
105
162
|
exclusiveMaximum: int = None
|
|
106
|
-
enum: List[str] = []
|
|
107
|
-
tags: List[str] = []
|
|
163
|
+
enum: List[str] | None = []
|
|
164
|
+
tags: List[str] | None = []
|
|
108
165
|
links: Dict[str, str] = {}
|
|
109
166
|
fields: Dict[str, "Field"] = {}
|
|
110
167
|
items: "Field" = None
|
|
@@ -113,7 +170,9 @@ class Field(pyd.BaseModel):
|
|
|
113
170
|
precision: int = None
|
|
114
171
|
scale: int = None
|
|
115
172
|
example: str = None
|
|
116
|
-
|
|
173
|
+
examples: List[Any] | None = None
|
|
174
|
+
quality: List[Quality] | None = []
|
|
175
|
+
config: Dict[str, Any] | None = None
|
|
117
176
|
|
|
118
177
|
model_config = pyd.ConfigDict(
|
|
119
178
|
extra="allow",
|
|
@@ -126,7 +185,13 @@ class Model(pyd.BaseModel):
|
|
|
126
185
|
namespace: Optional[str] = None
|
|
127
186
|
title: Optional[str] = None
|
|
128
187
|
fields: Dict[str, Field] = {}
|
|
188
|
+
quality: List[Quality] | None = []
|
|
129
189
|
config: Dict[str, Any] = None
|
|
190
|
+
tags: List[str] | None = None
|
|
191
|
+
|
|
192
|
+
model_config = pyd.ConfigDict(
|
|
193
|
+
extra="allow",
|
|
194
|
+
)
|
|
130
195
|
|
|
131
196
|
|
|
132
197
|
class Info(pyd.BaseModel):
|
|
@@ -218,9 +283,14 @@ class DataContractSpecification(pyd.BaseModel):
|
|
|
218
283
|
terms: Terms = None
|
|
219
284
|
models: Dict[str, Model] = {}
|
|
220
285
|
definitions: Dict[str, Definition] = {}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
286
|
+
examples: List[Example] = pyd.Field(
|
|
287
|
+
default_factory=list,
|
|
288
|
+
deprecated="Removed in Data Contract Specification " "v1.1.0. Use models.examples instead.",
|
|
289
|
+
)
|
|
290
|
+
quality: Quality = pyd.Field(
|
|
291
|
+
default=None,
|
|
292
|
+
deprecated="Removed in Data Contract Specification v1.1.0. Use " "model-level and field-level quality instead.",
|
|
293
|
+
)
|
|
224
294
|
servicelevels: Optional[ServiceLevel] = None
|
|
225
295
|
links: Dict[str, str] = {}
|
|
226
296
|
tags: List[str] = []
|
|
@@ -228,7 +298,7 @@ class DataContractSpecification(pyd.BaseModel):
|
|
|
228
298
|
@classmethod
|
|
229
299
|
def from_file(cls, file):
|
|
230
300
|
if not os.path.exists(file):
|
|
231
|
-
raise (f"The file '{file}' does not exist.")
|
|
301
|
+
raise FileNotFoundError(f"The file '{file}' does not exist.")
|
|
232
302
|
with open(file, "r") as file:
|
|
233
303
|
file_content = file.read()
|
|
234
304
|
return DataContractSpecification.from_string(file_content)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
def is_open_data_contract_standard(odcs: dict) -> bool:
|
|
2
|
+
"""
|
|
3
|
+
Check if the given dictionary is an OpenDataContractStandard.
|
|
4
|
+
|
|
5
|
+
Args:
|
|
6
|
+
odcs (dict): The dictionary to check.
|
|
7
|
+
|
|
8
|
+
Returns:
|
|
9
|
+
bool: True if the dictionary is an OpenDataContractStandard, False otherwise.
|
|
10
|
+
"""
|
|
11
|
+
return odcs.get("kind") == "DataContract" and odcs.get("apiVersion", "").startswith("v3")
|
datacontract/model/run.py
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
+
from enum import Enum
|
|
3
4
|
from typing import List, Optional
|
|
4
5
|
from uuid import UUID, uuid4
|
|
5
6
|
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
8
9
|
|
|
10
|
+
class ResultEnum(str, Enum):
|
|
11
|
+
passed = "passed"
|
|
12
|
+
warning = "warning"
|
|
13
|
+
failed = "failed"
|
|
14
|
+
error = "error"
|
|
15
|
+
unknown = "unknown"
|
|
16
|
+
|
|
17
|
+
|
|
9
18
|
class Check(BaseModel):
|
|
10
19
|
type: str
|
|
11
20
|
name: Optional[str]
|
|
12
|
-
result:
|
|
21
|
+
result: ResultEnum
|
|
13
22
|
engine: str
|
|
14
23
|
reason: Optional[str] = None
|
|
15
24
|
model: Optional[str] = None
|
|
@@ -33,29 +42,29 @@ class Run(BaseModel):
|
|
|
33
42
|
server: Optional[str] = None
|
|
34
43
|
timestampStart: datetime
|
|
35
44
|
timestampEnd: datetime
|
|
36
|
-
result:
|
|
45
|
+
result: ResultEnum = ResultEnum.unknown
|
|
37
46
|
checks: List[Check]
|
|
38
47
|
logs: List[Log]
|
|
39
48
|
|
|
40
49
|
def has_passed(self):
|
|
41
50
|
self.calculate_result()
|
|
42
|
-
return self.result ==
|
|
51
|
+
return self.result == ResultEnum.passed
|
|
43
52
|
|
|
44
53
|
def finish(self):
|
|
45
54
|
self.timestampEnd = datetime.now(timezone.utc)
|
|
46
55
|
self.calculate_result()
|
|
47
56
|
|
|
48
57
|
def calculate_result(self):
|
|
49
|
-
if any(check.result ==
|
|
50
|
-
self.result =
|
|
51
|
-
elif any(check.result ==
|
|
52
|
-
self.result =
|
|
53
|
-
elif any(check.result ==
|
|
54
|
-
self.result =
|
|
55
|
-
elif any(check.result ==
|
|
56
|
-
self.result =
|
|
58
|
+
if any(check.result == ResultEnum.error for check in self.checks):
|
|
59
|
+
self.result = ResultEnum.error
|
|
60
|
+
elif any(check.result == ResultEnum.failed for check in self.checks):
|
|
61
|
+
self.result = ResultEnum.failed
|
|
62
|
+
elif any(check.result == ResultEnum.warning for check in self.checks):
|
|
63
|
+
self.result = ResultEnum.warning
|
|
64
|
+
elif any(check.result == ResultEnum.passed for check in self.checks):
|
|
65
|
+
self.result = ResultEnum.passed
|
|
57
66
|
else:
|
|
58
|
-
self.result =
|
|
67
|
+
self.result = ResultEnum.unknown
|
|
59
68
|
|
|
60
69
|
def log_info(self, message: str):
|
|
61
70
|
logging.info(message)
|
|
@@ -78,17 +78,17 @@
|
|
|
78
78
|
|
|
79
79
|
<li class="col-span-1 rounded-lg bg-white shadow hover:bg-gray-50"
|
|
80
80
|
data-search="{{
|
|
81
|
-
contract.spec.info.title|lower }} {{
|
|
82
|
-
contract.spec.info.owner|lower if contract.spec.info.owner else '' }} {{
|
|
83
|
-
contract.spec.info.description|lower }} {%
|
|
81
|
+
contract.spec.info.title|lower|e }} {{
|
|
82
|
+
contract.spec.info.owner|lower|e if contract.spec.info.owner else '' }} {{
|
|
83
|
+
contract.spec.info.description|lower|e }} {%
|
|
84
84
|
for model_name, model in contract.spec.models.items() %}
|
|
85
|
-
{{ model.description|lower }} {%
|
|
85
|
+
{{ model.description|lower|e }} {%
|
|
86
86
|
for field_name, field in model.fields.items() %}
|
|
87
|
-
{{ field_name|lower }} {{ field.description|lower if field.description else '' }} {%
|
|
87
|
+
{{ field_name|lower|e }} {{ field.description|lower|e if field.description else '' }} {%
|
|
88
88
|
endfor %}
|
|
89
89
|
{% endfor %}
|
|
90
90
|
">
|
|
91
|
-
<a href="{{contract.html_link}}" >
|
|
91
|
+
<a href="{{contract.html_link|e}}" >
|
|
92
92
|
<div class="flex w-full justify-between space-x-1 p-6 pb-4">
|
|
93
93
|
<div class="flex-1 truncate">
|
|
94
94
|
<div class="flex items-center space-x-3">
|
datacontract/web.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
from typing import Annotated,
|
|
1
|
+
from typing import Annotated, Optional, Union
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
4
|
from fastapi import FastAPI, File
|
|
5
|
-
from fastapi.responses import HTMLResponse
|
|
5
|
+
from fastapi.responses import HTMLResponse, PlainTextResponse
|
|
6
6
|
|
|
7
7
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
8
|
-
from fastapi.responses import PlainTextResponse
|
|
9
8
|
|
|
10
9
|
app = FastAPI()
|
|
11
10
|
|