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.

Files changed (77) hide show
  1. datacontract/breaking/breaking.py +227 -9
  2. datacontract/breaking/breaking_rules.py +24 -0
  3. datacontract/catalog/catalog.py +1 -1
  4. datacontract/cli.py +104 -32
  5. datacontract/data_contract.py +35 -5
  6. datacontract/engines/datacontract/check_that_datacontract_file_exists.py +1 -1
  7. datacontract/engines/fastjsonschema/check_jsonschema.py +114 -22
  8. datacontract/engines/soda/check_soda_execute.py +5 -3
  9. datacontract/engines/soda/connections/duckdb.py +1 -0
  10. datacontract/engines/soda/connections/kafka.py +38 -17
  11. datacontract/export/avro_converter.py +8 -1
  12. datacontract/export/avro_idl_converter.py +2 -2
  13. datacontract/export/bigquery_converter.py +4 -3
  14. datacontract/export/data_caterer_converter.py +1 -1
  15. datacontract/export/dbml_converter.py +2 -4
  16. datacontract/export/dbt_converter.py +2 -3
  17. datacontract/export/dcs_exporter.py +6 -0
  18. datacontract/export/exporter.py +5 -2
  19. datacontract/export/exporter_factory.py +16 -3
  20. datacontract/export/go_converter.py +3 -2
  21. datacontract/export/great_expectations_converter.py +202 -40
  22. datacontract/export/html_export.py +1 -1
  23. datacontract/export/jsonschema_converter.py +3 -2
  24. datacontract/export/{odcs_converter.py → odcs_v2_exporter.py} +5 -5
  25. datacontract/export/odcs_v3_exporter.py +294 -0
  26. datacontract/export/pandas_type_converter.py +40 -0
  27. datacontract/export/protobuf_converter.py +1 -1
  28. datacontract/export/rdf_converter.py +4 -5
  29. datacontract/export/sodacl_converter.py +86 -2
  30. datacontract/export/spark_converter.py +10 -7
  31. datacontract/export/sql_converter.py +1 -2
  32. datacontract/export/sql_type_converter.py +55 -11
  33. datacontract/export/sqlalchemy_converter.py +1 -2
  34. datacontract/export/terraform_converter.py +1 -1
  35. datacontract/imports/avro_importer.py +1 -1
  36. datacontract/imports/bigquery_importer.py +1 -1
  37. datacontract/imports/dbml_importer.py +2 -2
  38. datacontract/imports/dbt_importer.py +3 -2
  39. datacontract/imports/glue_importer.py +5 -3
  40. datacontract/imports/iceberg_importer.py +161 -0
  41. datacontract/imports/importer.py +2 -0
  42. datacontract/imports/importer_factory.py +12 -1
  43. datacontract/imports/jsonschema_importer.py +3 -2
  44. datacontract/imports/odcs_importer.py +25 -168
  45. datacontract/imports/odcs_v2_importer.py +177 -0
  46. datacontract/imports/odcs_v3_importer.py +309 -0
  47. datacontract/imports/parquet_importer.py +81 -0
  48. datacontract/imports/spark_importer.py +2 -1
  49. datacontract/imports/sql_importer.py +1 -1
  50. datacontract/imports/unity_importer.py +3 -3
  51. datacontract/integration/datamesh_manager.py +1 -1
  52. datacontract/integration/opentelemetry.py +0 -1
  53. datacontract/lint/lint.py +2 -1
  54. datacontract/lint/linters/description_linter.py +1 -0
  55. datacontract/lint/linters/example_model_linter.py +1 -0
  56. datacontract/lint/linters/field_pattern_linter.py +1 -0
  57. datacontract/lint/linters/field_reference_linter.py +1 -0
  58. datacontract/lint/linters/notice_period_linter.py +1 -0
  59. datacontract/lint/linters/quality_schema_linter.py +1 -0
  60. datacontract/lint/linters/valid_constraints_linter.py +1 -0
  61. datacontract/lint/resolve.py +14 -9
  62. datacontract/lint/resources.py +21 -0
  63. datacontract/lint/schema.py +1 -1
  64. datacontract/lint/urls.py +4 -2
  65. datacontract/model/data_contract_specification.py +83 -13
  66. datacontract/model/odcs.py +11 -0
  67. datacontract/model/run.py +21 -12
  68. datacontract/templates/index.html +6 -6
  69. datacontract/web.py +2 -3
  70. {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/METADATA +176 -93
  71. datacontract_cli-0.10.15.dist-info/RECORD +105 -0
  72. {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/WHEEL +1 -1
  73. datacontract/engines/datacontract/check_that_datacontract_str_is_valid.py +0 -48
  74. datacontract_cli-0.10.13.dist-info/RECORD +0 -97
  75. {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/LICENSE +0 -0
  76. {datacontract_cli-0.10.13.dist-info → datacontract_cli-0.10.15.dist-info}/entry_points.txt +0 -0
  77. {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 List, Dict, Optional, Any
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
- unique: bool = None
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
- config: Dict[str, Any] = None
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
- # schema: Dict[str, str]
222
- examples: List[Example] = []
223
- quality: Quality = None
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: str # passed, failed, warning, unknown
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: str = "unknown" # passed, warning, failed, error, unknown
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 == "passed"
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 == "error" for check in self.checks):
50
- self.result = "error"
51
- elif any(check.result == "failed" for check in self.checks):
52
- self.result = "failed"
53
- elif any(check.result == "warning" for check in self.checks):
54
- self.result = "warning"
55
- elif any(check.result == "passed" for check in self.checks):
56
- self.result = "passed"
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 = "unknown"
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, Union, Optional
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