datacontract-cli 0.10.35__py3-none-any.whl → 0.10.36__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/api.py +1 -1
- datacontract/cli.py +1 -1
- datacontract/data_contract.py +18 -51
- datacontract/engines/data_contract_checks.py +280 -19
- datacontract/export/dbt_converter.py +30 -4
- datacontract/export/dqx_converter.py +12 -7
- datacontract/export/excel_exporter.py +3 -3
- datacontract/export/markdown_converter.py +35 -16
- datacontract/export/rdf_converter.py +2 -2
- datacontract/export/sql_type_converter.py +6 -4
- datacontract/imports/odcs_v3_importer.py +71 -18
- datacontract/imports/unity_importer.py +16 -11
- datacontract/init/init_template.py +1 -1
- datacontract/lint/resolve.py +1 -1
- datacontract/lint/schema.py +1 -1
- datacontract/schemas/datacontract-1.1.0.init.yaml +1 -1
- datacontract/schemas/datacontract-1.2.0.init.yaml +1 -1
- datacontract/schemas/datacontract-1.2.1.init.yaml +91 -0
- datacontract/schemas/datacontract-1.2.1.schema.json +2058 -0
- datacontract/schemas/odcs-3.0.2.schema.json +2382 -0
- datacontract/templates/datacontract_odcs.html +60 -41
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/METADATA +27 -24
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/RECORD +27 -31
- datacontract/lint/lint.py +0 -142
- datacontract/lint/linters/__init__.py +0 -0
- datacontract/lint/linters/description_linter.py +0 -33
- datacontract/lint/linters/field_pattern_linter.py +0 -34
- datacontract/lint/linters/field_reference_linter.py +0 -47
- datacontract/lint/linters/notice_period_linter.py +0 -55
- datacontract/lint/linters/valid_constraints_linter.py +0 -100
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/WHEEL +0 -0
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/licenses/LICENSE +0 -0
- {datacontract_cli-0.10.35.dist-info → datacontract_cli-0.10.36.dist-info}/top_level.txt +0 -0
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
2
|
-
|
|
3
|
-
from ..lint import Linter, LinterResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class FieldReferenceLinter(Linter):
|
|
7
|
-
"""Checks that all references definitions in fields refer to existing
|
|
8
|
-
fields.
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def name(self):
|
|
14
|
-
return "Field references existing field"
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def id(self) -> str:
|
|
18
|
-
return "field-reference"
|
|
19
|
-
|
|
20
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
21
|
-
result = LinterResult()
|
|
22
|
-
for model_name, model in contract.models.items():
|
|
23
|
-
for field_name, field in model.fields.items():
|
|
24
|
-
if field.references:
|
|
25
|
-
reference_hierarchy = field.references.split(".")
|
|
26
|
-
if len(reference_hierarchy) != 2:
|
|
27
|
-
result = result.with_error(
|
|
28
|
-
f"Field '{field_name}' in model '{model_name}'"
|
|
29
|
-
f" references must follow the model.field syntax and refer to a field in a model in this data contract."
|
|
30
|
-
)
|
|
31
|
-
continue
|
|
32
|
-
ref_model = reference_hierarchy[0]
|
|
33
|
-
ref_field = reference_hierarchy[1]
|
|
34
|
-
|
|
35
|
-
if ref_model not in contract.models:
|
|
36
|
-
result = result.with_error(
|
|
37
|
-
f"Field '{field_name}' in model '{model_name}' references non-existing model '{ref_model}'."
|
|
38
|
-
)
|
|
39
|
-
else:
|
|
40
|
-
ref_model_obj = contract.models[ref_model]
|
|
41
|
-
if ref_field not in ref_model_obj.fields:
|
|
42
|
-
result = result.with_error(
|
|
43
|
-
f"Field '{field_name}' in model '{model_name}'"
|
|
44
|
-
f" references non-existing field '{ref_field}'"
|
|
45
|
-
f" in model '{ref_model}'."
|
|
46
|
-
)
|
|
47
|
-
return result
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
from datacontract.model.data_contract_specification import DataContractSpecification
|
|
4
|
-
|
|
5
|
-
from ..lint import Linter, LinterResult
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class NoticePeriodLinter(Linter):
|
|
9
|
-
@property
|
|
10
|
-
def name(self) -> str:
|
|
11
|
-
return "noticePeriod in ISO8601 format"
|
|
12
|
-
|
|
13
|
-
@property
|
|
14
|
-
def id(self) -> str:
|
|
15
|
-
return "notice-period"
|
|
16
|
-
|
|
17
|
-
# Regex matching the "simple" ISO8601 duration format
|
|
18
|
-
simple = re.compile(
|
|
19
|
-
r"""P # Introduces period
|
|
20
|
-
(:?[0-9\.,]+Y)? # Number of years
|
|
21
|
-
(:?[0-9\.,]+M)? # Number of months
|
|
22
|
-
(:?[0-9\.,]+W)? # Number of weeks
|
|
23
|
-
(:?[0-9\.,]+D)? # Number of days
|
|
24
|
-
(:? # Time part (optional)
|
|
25
|
-
T # Always starts with T
|
|
26
|
-
(:?[0-9\.,]+H)? # Number of hours
|
|
27
|
-
(:?[0-9\.,]+M)? # Number of minutes
|
|
28
|
-
(:?[0-9\.,]+S)? # Number of seconds
|
|
29
|
-
)?
|
|
30
|
-
""",
|
|
31
|
-
re.VERBOSE,
|
|
32
|
-
)
|
|
33
|
-
datetime_basic = re.compile(r"P\d{8}T\d{6}")
|
|
34
|
-
datetime_extended = re.compile(r"P\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}")
|
|
35
|
-
|
|
36
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
37
|
-
"""Check whether the notice period is specified using ISO8601 duration syntax."""
|
|
38
|
-
if not contract.terms:
|
|
39
|
-
return LinterResult.cautious("No terms defined.")
|
|
40
|
-
period = contract.terms.noticePeriod
|
|
41
|
-
if not period:
|
|
42
|
-
return LinterResult.cautious("No notice period defined.")
|
|
43
|
-
if not period.startswith("P"):
|
|
44
|
-
return LinterResult.erroneous(f"Notice period '{period}' is not a valid ISO8601 duration.")
|
|
45
|
-
if period == "P":
|
|
46
|
-
return LinterResult.erroneous(
|
|
47
|
-
"Notice period 'P' is not a valid ISO8601 duration, requires at least one duration to be specified."
|
|
48
|
-
)
|
|
49
|
-
if (
|
|
50
|
-
not self.simple.fullmatch(period)
|
|
51
|
-
and not self.datetime_basic.fullmatch(period)
|
|
52
|
-
and not self.datetime_extended.fullmatch(period)
|
|
53
|
-
):
|
|
54
|
-
return LinterResult.erroneous(f"Notice period '{period}' is not a valid ISO8601 duration.")
|
|
55
|
-
return LinterResult()
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
from datacontract.model.data_contract_specification import DataContractSpecification, Field
|
|
2
|
-
|
|
3
|
-
from ..lint import Linter, LinterResult
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class ValidFieldConstraintsLinter(Linter):
|
|
7
|
-
"""Check validity of field constraints.
|
|
8
|
-
|
|
9
|
-
More precisely, check that only numeric constraints are specified on
|
|
10
|
-
fields of numeric type and string constraints on fields of string type.
|
|
11
|
-
Additionally, the linter checks that defined constraints make sense.
|
|
12
|
-
Minimum values should not be greater than maximum values, exclusive and
|
|
13
|
-
non-exclusive minimum and maximum should not be combined and string
|
|
14
|
-
pattern and format should not be combined.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
valid_types_for_constraint = {
|
|
19
|
-
"pattern": set(["string", "text", "varchar"]),
|
|
20
|
-
"format": set(["string", "text", "varchar"]),
|
|
21
|
-
"minLength": set(["string", "text", "varchar"]),
|
|
22
|
-
"maxLength": set(["string", "text", "varchar"]),
|
|
23
|
-
"minimum": set(["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]),
|
|
24
|
-
"exclusiveMinimum": set(
|
|
25
|
-
["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]
|
|
26
|
-
),
|
|
27
|
-
"maximum": set(["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]),
|
|
28
|
-
"exclusiveMaximum": set(
|
|
29
|
-
["int", "integer", "number", "decimal", "numeric", "long", "bigint", "float", "double"]
|
|
30
|
-
),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
def check_minimum_maximum(self, field: Field, field_name: str, model_name: str) -> LinterResult:
|
|
34
|
-
(min, max, xmin, xmax) = (field.minimum, field.maximum, field.exclusiveMinimum, field.exclusiveMaximum)
|
|
35
|
-
match (
|
|
36
|
-
"minimum" in field.model_fields_set,
|
|
37
|
-
"maximum" in field.model_fields_set,
|
|
38
|
-
"exclusiveMinimum" in field.model_fields_set,
|
|
39
|
-
"exclusiveMaximum" in field.model_fields_set,
|
|
40
|
-
):
|
|
41
|
-
case (True, True, _, _) if min > max:
|
|
42
|
-
return LinterResult.erroneous(
|
|
43
|
-
f"Minimum {min} is greater than maximum {max} on field '{field_name}' in model '{model_name}'."
|
|
44
|
-
)
|
|
45
|
-
case (_, _, True, True) if xmin >= xmax:
|
|
46
|
-
return LinterResult.erroneous(
|
|
47
|
-
f"Exclusive minimum {xmin} is greater than exclusive"
|
|
48
|
-
f" maximum {xmax} on field '{field_name}' in model '{model_name}'."
|
|
49
|
-
)
|
|
50
|
-
case (True, True, True, True):
|
|
51
|
-
return LinterResult.erroneous(
|
|
52
|
-
f"Both exclusive and non-exclusive minimum and maximum are "
|
|
53
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
54
|
-
)
|
|
55
|
-
case (True, _, True, _):
|
|
56
|
-
return LinterResult.erroneous(
|
|
57
|
-
f"Both exclusive and non-exclusive minimum are "
|
|
58
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
59
|
-
)
|
|
60
|
-
case (_, True, _, True):
|
|
61
|
-
return LinterResult.erroneous(
|
|
62
|
-
f"Both exclusive and non-exclusive maximum are "
|
|
63
|
-
f"defined on field '{field_name}' in model '{model_name}'."
|
|
64
|
-
)
|
|
65
|
-
return LinterResult()
|
|
66
|
-
|
|
67
|
-
def check_string_constraints(self, field: Field, field_name: str, model_name: str) -> LinterResult:
|
|
68
|
-
result = LinterResult()
|
|
69
|
-
if field.minLength and field.maxLength and field.minLength > field.maxLength:
|
|
70
|
-
result = result.with_error(
|
|
71
|
-
f"Minimum length is greater that maximum length on field '{field_name}' in model '{model_name}'."
|
|
72
|
-
)
|
|
73
|
-
if field.pattern and field.format:
|
|
74
|
-
result = result.with_error(
|
|
75
|
-
f"Both a pattern and a format are defined for field '{field_name}' in model '{model_name}'."
|
|
76
|
-
)
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def name(self):
|
|
81
|
-
return "Fields use valid constraints"
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def id(self):
|
|
85
|
-
return "field-constraints"
|
|
86
|
-
|
|
87
|
-
def lint_implementation(self, contract: DataContractSpecification) -> LinterResult:
|
|
88
|
-
result = LinterResult()
|
|
89
|
-
for model_name, model in contract.models.items():
|
|
90
|
-
for field_name, field in model.fields.items():
|
|
91
|
-
for _property, allowed_types in self.valid_types_for_constraint.items():
|
|
92
|
-
if _property in field.model_fields_set and field.type not in allowed_types:
|
|
93
|
-
result = result.with_error(
|
|
94
|
-
f"Forbidden constraint '{_property}' defined on field "
|
|
95
|
-
f"'{field_name}' in model '{model_name}'. Field type "
|
|
96
|
-
f"is '{field.type}'."
|
|
97
|
-
)
|
|
98
|
-
result = result.combine(self.check_minimum_maximum(field, field_name, model_name))
|
|
99
|
-
result = result.combine(self.check_string_constraints(field, field_name, model_name))
|
|
100
|
-
return result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|