datacontract-cli 0.10.23__py3-none-any.whl → 0.10.25__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/__init__.py +13 -0
- datacontract/api.py +3 -3
- datacontract/catalog/catalog.py +2 -2
- datacontract/cli.py +1 -1
- datacontract/data_contract.py +5 -3
- datacontract/engines/data_contract_test.py +13 -4
- datacontract/engines/fastjsonschema/s3/s3_read_files.py +3 -2
- datacontract/engines/soda/check_soda_execute.py +16 -3
- datacontract/engines/soda/connections/duckdb_connection.py +61 -5
- datacontract/engines/soda/connections/kafka.py +3 -2
- datacontract/export/avro_converter.py +8 -1
- datacontract/export/bigquery_converter.py +1 -1
- datacontract/export/duckdb_type_converter.py +57 -0
- datacontract/export/great_expectations_converter.py +49 -2
- datacontract/export/odcs_v3_exporter.py +162 -136
- datacontract/export/protobuf_converter.py +163 -69
- datacontract/export/spark_converter.py +1 -1
- datacontract/imports/avro_importer.py +30 -5
- datacontract/imports/csv_importer.py +111 -57
- datacontract/imports/excel_importer.py +850 -0
- datacontract/imports/importer.py +5 -2
- datacontract/imports/importer_factory.py +10 -0
- datacontract/imports/odcs_v3_importer.py +226 -127
- datacontract/imports/protobuf_importer.py +264 -0
- datacontract/lint/linters/description_linter.py +1 -3
- datacontract/lint/linters/field_reference_linter.py +1 -2
- datacontract/lint/linters/notice_period_linter.py +2 -2
- datacontract/lint/linters/valid_constraints_linter.py +3 -3
- datacontract/lint/resolve.py +23 -8
- datacontract/model/data_contract_specification/__init__.py +1 -0
- datacontract/model/run.py +3 -0
- datacontract/output/__init__.py +0 -0
- datacontract/templates/datacontract.html +2 -1
- datacontract/templates/index.html +2 -1
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info}/METADATA +305 -195
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info}/RECORD +40 -38
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info}/WHEEL +1 -1
- datacontract/export/csv_type_converter.py +0 -36
- datacontract/lint/linters/quality_schema_linter.py +0 -52
- datacontract/model/data_contract_specification.py +0 -327
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info/licenses}/LICENSE +0 -0
- {datacontract_cli-0.10.23.dist-info → datacontract_cli-0.10.25.dist-info}/top_level.txt +0 -0
datacontract/imports/importer.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from enum import Enum
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from datacontract_specification.model import DataContractSpecification
|
|
5
|
+
from open_data_contract_standard.model import OpenDataContractStandard
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class Importer(ABC):
|
|
@@ -14,7 +15,7 @@ class Importer(ABC):
|
|
|
14
15
|
data_contract_specification: DataContractSpecification,
|
|
15
16
|
source: str,
|
|
16
17
|
import_args: dict,
|
|
17
|
-
) -> DataContractSpecification:
|
|
18
|
+
) -> DataContractSpecification | OpenDataContractStandard:
|
|
18
19
|
pass
|
|
19
20
|
|
|
20
21
|
|
|
@@ -32,6 +33,8 @@ class ImportFormat(str, Enum):
|
|
|
32
33
|
iceberg = "iceberg"
|
|
33
34
|
parquet = "parquet"
|
|
34
35
|
csv = "csv"
|
|
36
|
+
protobuf = "protobuf"
|
|
37
|
+
excel = "excel"
|
|
35
38
|
|
|
36
39
|
@classmethod
|
|
37
40
|
def get_supported_formats(cls):
|
|
@@ -109,3 +109,13 @@ importer_factory.register_lazy_importer(
|
|
|
109
109
|
module_path="datacontract.imports.csv_importer",
|
|
110
110
|
class_name="CsvImporter",
|
|
111
111
|
)
|
|
112
|
+
importer_factory.register_lazy_importer(
|
|
113
|
+
name=ImportFormat.protobuf,
|
|
114
|
+
module_path="datacontract.imports.protobuf_importer",
|
|
115
|
+
class_name="ProtoBufImporter",
|
|
116
|
+
)
|
|
117
|
+
importer_factory.register_lazy_importer(
|
|
118
|
+
name=ImportFormat.excel,
|
|
119
|
+
module_path="datacontract.imports.excel_importer",
|
|
120
|
+
class_name="ExcelImporter",
|
|
121
|
+
)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
from typing import Any, Dict, List
|
|
4
5
|
from venv import logger
|
|
5
6
|
|
|
6
|
-
import
|
|
7
|
+
from datacontract_specification.model import Quality
|
|
8
|
+
from open_data_contract_standard.model import CustomProperty, OpenDataContractStandard, SchemaProperty
|
|
7
9
|
|
|
8
10
|
from datacontract.imports.importer import Importer
|
|
9
11
|
from datacontract.lint.resources import read_resource
|
|
@@ -14,9 +16,9 @@ from datacontract.model.data_contract_specification import (
|
|
|
14
16
|
Field,
|
|
15
17
|
Info,
|
|
16
18
|
Model,
|
|
17
|
-
Quality,
|
|
18
19
|
Retention,
|
|
19
20
|
Server,
|
|
21
|
+
ServerRole,
|
|
20
22
|
ServiceLevel,
|
|
21
23
|
Terms,
|
|
22
24
|
)
|
|
@@ -39,7 +41,7 @@ def import_odcs_v3_from_str(
|
|
|
39
41
|
data_contract_specification: DataContractSpecification, source_str: str
|
|
40
42
|
) -> DataContractSpecification:
|
|
41
43
|
try:
|
|
42
|
-
|
|
44
|
+
odcs = OpenDataContractStandard.from_string(source_str)
|
|
43
45
|
except Exception as e:
|
|
44
46
|
raise DataContractException(
|
|
45
47
|
type="schema",
|
|
@@ -49,129 +51,140 @@ def import_odcs_v3_from_str(
|
|
|
49
51
|
original_exception=e,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
|
-
data_contract_specification
|
|
53
|
-
data_contract_specification.info = import_info(odcs_contract)
|
|
54
|
-
data_contract_specification.servers = import_servers(odcs_contract)
|
|
55
|
-
data_contract_specification.terms = import_terms(odcs_contract)
|
|
56
|
-
data_contract_specification.servicelevels = import_servicelevels(odcs_contract)
|
|
57
|
-
data_contract_specification.models = import_models(odcs_contract)
|
|
58
|
-
data_contract_specification.tags = import_tags(odcs_contract)
|
|
54
|
+
return import_from_odcs_model(data_contract_specification, odcs)
|
|
59
55
|
|
|
56
|
+
|
|
57
|
+
def import_from_odcs_model(data_contract_specification, odcs):
|
|
58
|
+
data_contract_specification.id = odcs.id
|
|
59
|
+
data_contract_specification.info = import_info(odcs)
|
|
60
|
+
data_contract_specification.servers = import_servers(odcs)
|
|
61
|
+
data_contract_specification.terms = import_terms(odcs)
|
|
62
|
+
data_contract_specification.servicelevels = import_servicelevels(odcs)
|
|
63
|
+
data_contract_specification.models = import_models(odcs)
|
|
64
|
+
data_contract_specification.tags = import_tags(odcs)
|
|
60
65
|
return data_contract_specification
|
|
61
66
|
|
|
62
67
|
|
|
63
|
-
def import_info(
|
|
68
|
+
def import_info(odcs: Any) -> Info:
|
|
64
69
|
info = Info()
|
|
65
70
|
|
|
66
|
-
info.title =
|
|
71
|
+
info.title = odcs.name if odcs.name is not None else ""
|
|
67
72
|
|
|
68
|
-
if
|
|
69
|
-
info.version =
|
|
73
|
+
if odcs.version is not None:
|
|
74
|
+
info.version = odcs.version
|
|
70
75
|
|
|
71
76
|
# odcs.description.purpose => datacontract.description
|
|
72
|
-
if
|
|
73
|
-
info.description =
|
|
77
|
+
if odcs.description is not None and odcs.description.purpose is not None:
|
|
78
|
+
info.description = odcs.description.purpose
|
|
74
79
|
|
|
75
80
|
# odcs.domain => datacontract.owner
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
owner = get_owner(odcs.customProperties)
|
|
82
|
+
if owner is not None:
|
|
83
|
+
info.owner = owner
|
|
78
84
|
|
|
79
85
|
# add dataProduct as custom property
|
|
80
|
-
if
|
|
81
|
-
info.dataProduct =
|
|
86
|
+
if odcs.dataProduct is not None:
|
|
87
|
+
info.dataProduct = odcs.dataProduct
|
|
82
88
|
|
|
83
89
|
# add tenant as custom property
|
|
84
|
-
if
|
|
85
|
-
info.tenant =
|
|
90
|
+
if odcs.tenant is not None:
|
|
91
|
+
info.tenant = odcs.tenant
|
|
86
92
|
|
|
87
93
|
return info
|
|
88
94
|
|
|
89
95
|
|
|
90
|
-
def
|
|
91
|
-
if
|
|
96
|
+
def import_server_roles(roles: List[Dict]) -> List[ServerRole] | None:
|
|
97
|
+
if roles is None:
|
|
98
|
+
return None
|
|
99
|
+
result = []
|
|
100
|
+
for role in roles:
|
|
101
|
+
server_role = ServerRole()
|
|
102
|
+
server_role.name = role.role
|
|
103
|
+
server_role.description = role.description
|
|
104
|
+
result.append(server_role)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def import_servers(odcs: OpenDataContractStandard) -> Dict[str, Server] | None:
|
|
108
|
+
if odcs.servers is None:
|
|
92
109
|
return None
|
|
93
110
|
servers = {}
|
|
94
|
-
for odcs_server in
|
|
95
|
-
server_name = odcs_server.
|
|
111
|
+
for odcs_server in odcs.servers:
|
|
112
|
+
server_name = odcs_server.server
|
|
96
113
|
if server_name is None:
|
|
97
114
|
logger.warning("Server name is missing, skipping server")
|
|
98
115
|
continue
|
|
99
116
|
|
|
100
117
|
server = Server()
|
|
101
|
-
server.type = odcs_server.
|
|
102
|
-
server.description = odcs_server.
|
|
103
|
-
server.environment = odcs_server.
|
|
104
|
-
server.format = odcs_server.
|
|
105
|
-
server.project = odcs_server.
|
|
106
|
-
server.dataset = odcs_server.
|
|
107
|
-
server.path = odcs_server.
|
|
108
|
-
server.delimiter = odcs_server.
|
|
109
|
-
server.endpointUrl = odcs_server.
|
|
110
|
-
server.location = odcs_server.
|
|
111
|
-
server.account = odcs_server.
|
|
112
|
-
server.database = odcs_server.
|
|
113
|
-
server.schema_ = odcs_server.
|
|
114
|
-
server.host = odcs_server.
|
|
115
|
-
server.port = odcs_server.
|
|
116
|
-
server.catalog = odcs_server.
|
|
117
|
-
server.topic = odcs_server
|
|
118
|
-
server.http_path = odcs_server
|
|
119
|
-
server.token = odcs_server
|
|
120
|
-
server.
|
|
121
|
-
server.
|
|
122
|
-
server.
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
server.type = odcs_server.type
|
|
119
|
+
server.description = odcs_server.description
|
|
120
|
+
server.environment = odcs_server.environment
|
|
121
|
+
server.format = odcs_server.format
|
|
122
|
+
server.project = odcs_server.project
|
|
123
|
+
server.dataset = odcs_server.dataset
|
|
124
|
+
server.path = odcs_server.path
|
|
125
|
+
server.delimiter = odcs_server.delimiter
|
|
126
|
+
server.endpointUrl = odcs_server.endpointUrl
|
|
127
|
+
server.location = odcs_server.location
|
|
128
|
+
server.account = odcs_server.account
|
|
129
|
+
server.database = odcs_server.database
|
|
130
|
+
server.schema_ = odcs_server.schema_
|
|
131
|
+
server.host = odcs_server.host
|
|
132
|
+
server.port = odcs_server.port
|
|
133
|
+
server.catalog = odcs_server.catalog
|
|
134
|
+
server.topic = getattr(odcs_server, "topic", None)
|
|
135
|
+
server.http_path = getattr(odcs_server, "http_path", None)
|
|
136
|
+
server.token = getattr(odcs_server, "token", None)
|
|
137
|
+
server.driver = getattr(odcs_server, "driver", None)
|
|
138
|
+
server.roles = import_server_roles(odcs_server.roles)
|
|
139
|
+
server.storageAccount = (
|
|
140
|
+
re.search(r"(?:@|://)([^.]+)\.", odcs_server.location, re.IGNORECASE) if server.type == "azure" else None
|
|
141
|
+
)
|
|
125
142
|
servers[server_name] = server
|
|
126
143
|
return servers
|
|
127
144
|
|
|
128
145
|
|
|
129
|
-
def import_terms(
|
|
130
|
-
if
|
|
146
|
+
def import_terms(odcs: Any) -> Terms | None:
|
|
147
|
+
if odcs.description is None:
|
|
131
148
|
return None
|
|
132
|
-
if
|
|
133
|
-
odcs_contract.get("description").get("usage") is not None
|
|
134
|
-
or odcs_contract.get("description").get("limitations") is not None
|
|
135
|
-
or odcs_contract.get("price") is not None
|
|
136
|
-
):
|
|
149
|
+
if odcs.description.usage is not None or odcs.description.limitations is not None or odcs.price is not None:
|
|
137
150
|
terms = Terms()
|
|
138
|
-
if
|
|
139
|
-
terms.usage =
|
|
140
|
-
if
|
|
141
|
-
terms.limitations =
|
|
142
|
-
if
|
|
143
|
-
terms.billing = f"{
|
|
151
|
+
if odcs.description.usage is not None:
|
|
152
|
+
terms.usage = odcs.description.usage
|
|
153
|
+
if odcs.description.limitations is not None:
|
|
154
|
+
terms.limitations = odcs.description.limitations
|
|
155
|
+
if odcs.price is not None:
|
|
156
|
+
terms.billing = f"{odcs.price.priceAmount} {odcs.price.priceCurrency} / {odcs.price.priceUnit}"
|
|
144
157
|
|
|
145
158
|
return terms
|
|
146
159
|
else:
|
|
147
160
|
return None
|
|
148
161
|
|
|
149
162
|
|
|
150
|
-
def import_servicelevels(
|
|
163
|
+
def import_servicelevels(odcs: Any) -> ServiceLevel:
|
|
151
164
|
# find the two properties we can map (based on the examples)
|
|
152
|
-
sla_properties =
|
|
153
|
-
availability = next((p for p in sla_properties if p
|
|
154
|
-
retention = next((p for p in sla_properties if p
|
|
165
|
+
sla_properties = odcs.slaProperties if odcs.slaProperties is not None else []
|
|
166
|
+
availability = next((p for p in sla_properties if p.property == "generalAvailability"), None)
|
|
167
|
+
retention = next((p for p in sla_properties if p.property == "retention"), None)
|
|
155
168
|
|
|
156
169
|
if availability is not None or retention is not None:
|
|
157
170
|
servicelevel = ServiceLevel()
|
|
158
171
|
|
|
159
172
|
if availability is not None:
|
|
160
|
-
value = availability.
|
|
173
|
+
value = availability.value
|
|
161
174
|
if isinstance(value, datetime.datetime):
|
|
162
175
|
value = value.isoformat()
|
|
163
176
|
servicelevel.availability = Availability(description=value)
|
|
164
177
|
|
|
165
178
|
if retention is not None:
|
|
166
|
-
servicelevel.retention = Retention(period=f"{retention.
|
|
179
|
+
servicelevel.retention = Retention(period=f"{retention.value}{retention.unit}")
|
|
167
180
|
|
|
168
181
|
return servicelevel
|
|
169
182
|
else:
|
|
170
183
|
return None
|
|
171
184
|
|
|
172
185
|
|
|
173
|
-
def get_server_type(
|
|
174
|
-
servers = import_servers(
|
|
186
|
+
def get_server_type(odcs: OpenDataContractStandard) -> str | None:
|
|
187
|
+
servers = import_servers(odcs)
|
|
175
188
|
if servers is None or len(servers) == 0:
|
|
176
189
|
return None
|
|
177
190
|
# get first server from map
|
|
@@ -179,49 +192,106 @@ def get_server_type(odcs_contract: Dict[str, Any]) -> str | None:
|
|
|
179
192
|
return server.type
|
|
180
193
|
|
|
181
194
|
|
|
182
|
-
def import_models(
|
|
183
|
-
custom_type_mappings = get_custom_type_mappings(
|
|
195
|
+
def import_models(odcs: Any) -> Dict[str, Model]:
|
|
196
|
+
custom_type_mappings = get_custom_type_mappings(odcs.customProperties)
|
|
184
197
|
|
|
185
|
-
odcs_schemas =
|
|
198
|
+
odcs_schemas = odcs.schema_ if odcs.schema_ is not None else []
|
|
186
199
|
result = {}
|
|
187
200
|
|
|
188
201
|
for odcs_schema in odcs_schemas:
|
|
189
|
-
schema_name = odcs_schema.
|
|
190
|
-
schema_physical_name = odcs_schema.
|
|
191
|
-
schema_description = odcs_schema.
|
|
202
|
+
schema_name = odcs_schema.name
|
|
203
|
+
schema_physical_name = odcs_schema.physicalName
|
|
204
|
+
schema_description = odcs_schema.description if odcs_schema.description is not None else ""
|
|
192
205
|
model_name = schema_physical_name if schema_physical_name is not None else schema_name
|
|
193
|
-
model = Model(description=" ".join(schema_description.splitlines()), type="table")
|
|
194
|
-
model.fields = import_fields(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if odcs_schema.get("quality") is not None:
|
|
198
|
-
# convert dict to pydantic model
|
|
199
|
-
|
|
200
|
-
model.quality = [Quality.model_validate(q) for q in odcs_schema.get("quality")]
|
|
206
|
+
model = Model(description=" ".join(schema_description.splitlines()) if schema_description else "", type="table")
|
|
207
|
+
model.fields = import_fields(odcs_schema.properties, custom_type_mappings, server_type=get_server_type(odcs))
|
|
208
|
+
if odcs_schema.quality is not None:
|
|
209
|
+
model.quality = convert_quality_list(odcs_schema.quality)
|
|
201
210
|
model.title = schema_name
|
|
202
|
-
if odcs_schema.
|
|
203
|
-
model.config = {"dataGranularityDescription": odcs_schema.
|
|
211
|
+
if odcs_schema.dataGranularityDescription is not None:
|
|
212
|
+
model.config = {"dataGranularityDescription": odcs_schema.dataGranularityDescription}
|
|
204
213
|
result[model_name] = model
|
|
205
214
|
|
|
206
215
|
return result
|
|
207
216
|
|
|
208
217
|
|
|
209
|
-
def
|
|
218
|
+
def convert_quality_list(odcs_quality_list):
|
|
219
|
+
"""Convert a list of ODCS DataQuality objects to datacontract Quality objects"""
|
|
220
|
+
quality_list = []
|
|
221
|
+
|
|
222
|
+
if odcs_quality_list is not None:
|
|
223
|
+
for odcs_quality in odcs_quality_list:
|
|
224
|
+
quality = Quality(type=odcs_quality.type)
|
|
225
|
+
|
|
226
|
+
if odcs_quality.description is not None:
|
|
227
|
+
quality.description = odcs_quality.description
|
|
228
|
+
if odcs_quality.query is not None:
|
|
229
|
+
quality.query = odcs_quality.query
|
|
230
|
+
if odcs_quality.mustBe is not None:
|
|
231
|
+
quality.mustBe = odcs_quality.mustBe
|
|
232
|
+
if odcs_quality.mustNotBe is not None:
|
|
233
|
+
quality.mustNotBe = odcs_quality.mustNotBe
|
|
234
|
+
if odcs_quality.mustBeGreaterThan is not None:
|
|
235
|
+
quality.mustBeGreaterThan = odcs_quality.mustBeGreaterThan
|
|
236
|
+
if odcs_quality.mustBeGreaterOrEqualTo is not None:
|
|
237
|
+
quality.mustBeGreaterThanOrEqualTo = odcs_quality.mustBeGreaterOrEqualTo
|
|
238
|
+
if odcs_quality.mustBeLessThan is not None:
|
|
239
|
+
quality.mustBeLessThan = odcs_quality.mustBeLessThan
|
|
240
|
+
if odcs_quality.mustBeLessOrEqualTo is not None:
|
|
241
|
+
quality.mustBeLessThanOrEqualTo = odcs_quality.mustBeLessOrEqualTo
|
|
242
|
+
if odcs_quality.mustBeBetween is not None:
|
|
243
|
+
quality.mustBeBetween = odcs_quality.mustBeBetween
|
|
244
|
+
if odcs_quality.mustNotBeBetween is not None:
|
|
245
|
+
quality.mustNotBeBetween = odcs_quality.mustNotBeBetween
|
|
246
|
+
if odcs_quality.engine is not None:
|
|
247
|
+
quality.engine = odcs_quality.engine
|
|
248
|
+
if odcs_quality.implementation is not None:
|
|
249
|
+
quality.implementation = odcs_quality.implementation
|
|
250
|
+
if odcs_quality.businessImpact is not None:
|
|
251
|
+
quality.model_extra["businessImpact"] = odcs_quality.businessImpact
|
|
252
|
+
if odcs_quality.dimension is not None:
|
|
253
|
+
quality.model_extra["dimension"] = odcs_quality.dimension
|
|
254
|
+
if odcs_quality.rule is not None:
|
|
255
|
+
quality.model_extra["rule"] = odcs_quality.rule
|
|
256
|
+
if odcs_quality.schedule is not None:
|
|
257
|
+
quality.model_extra["schedule"] = odcs_quality.schedule
|
|
258
|
+
if odcs_quality.scheduler is not None:
|
|
259
|
+
quality.model_extra["scheduler"] = odcs_quality.scheduler
|
|
260
|
+
if odcs_quality.severity is not None:
|
|
261
|
+
quality.model_extra["severity"] = odcs_quality.severity
|
|
262
|
+
if odcs_quality.method is not None:
|
|
263
|
+
quality.model_extra["method"] = odcs_quality.method
|
|
264
|
+
if odcs_quality.customProperties is not None:
|
|
265
|
+
quality.model_extra["customProperties"] = []
|
|
266
|
+
for item in odcs_quality.customProperties:
|
|
267
|
+
quality.model_extra["customProperties"].append(
|
|
268
|
+
{
|
|
269
|
+
"property": item.property,
|
|
270
|
+
"value": item.value,
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
quality_list.append(quality)
|
|
275
|
+
|
|
276
|
+
return quality_list
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def import_field_config(odcs_property: SchemaProperty, server_type=None) -> Dict[str, Any]:
|
|
210
280
|
config = {}
|
|
211
|
-
if odcs_property.
|
|
212
|
-
config["criticalDataElement"] = odcs_property.
|
|
213
|
-
if odcs_property.
|
|
214
|
-
config["encryptedName"] = odcs_property.
|
|
215
|
-
if odcs_property.
|
|
216
|
-
config["partitionKeyPosition"] = odcs_property.
|
|
217
|
-
if odcs_property.
|
|
218
|
-
config["partitioned"] = odcs_property.
|
|
219
|
-
|
|
220
|
-
if odcs_property.
|
|
221
|
-
for item in odcs_property.
|
|
222
|
-
config[item
|
|
223
|
-
|
|
224
|
-
physical_type = odcs_property.
|
|
281
|
+
if odcs_property.criticalDataElement is not None:
|
|
282
|
+
config["criticalDataElement"] = odcs_property.criticalDataElement
|
|
283
|
+
if odcs_property.encryptedName is not None:
|
|
284
|
+
config["encryptedName"] = odcs_property.encryptedName
|
|
285
|
+
if odcs_property.partitionKeyPosition is not None:
|
|
286
|
+
config["partitionKeyPosition"] = odcs_property.partitionKeyPosition
|
|
287
|
+
if odcs_property.partitioned is not None:
|
|
288
|
+
config["partitioned"] = odcs_property.partitioned
|
|
289
|
+
|
|
290
|
+
if odcs_property.customProperties is not None:
|
|
291
|
+
for item in odcs_property.customProperties:
|
|
292
|
+
config[item.property] = item.value
|
|
293
|
+
|
|
294
|
+
physical_type = odcs_property.physicalType
|
|
225
295
|
if physical_type is not None:
|
|
226
296
|
if server_type == "postgres" or server_type == "postgresql":
|
|
227
297
|
config["postgresType"] = physical_type
|
|
@@ -241,13 +311,13 @@ def import_field_config(odcs_property: Dict[str, Any], server_type=None) -> Dict
|
|
|
241
311
|
return config
|
|
242
312
|
|
|
243
313
|
|
|
244
|
-
def has_composite_primary_key(odcs_properties) -> bool:
|
|
245
|
-
primary_keys = [prop for prop in odcs_properties if prop.
|
|
314
|
+
def has_composite_primary_key(odcs_properties: List[SchemaProperty]) -> bool:
|
|
315
|
+
primary_keys = [prop for prop in odcs_properties if prop.primaryKey is not None and prop.primaryKey]
|
|
246
316
|
return len(primary_keys) > 1
|
|
247
317
|
|
|
248
318
|
|
|
249
319
|
def import_fields(
|
|
250
|
-
odcs_properties:
|
|
320
|
+
odcs_properties: List[SchemaProperty], custom_type_mappings: Dict[str, str], server_type
|
|
251
321
|
) -> Dict[str, Field]:
|
|
252
322
|
logger = logging.getLogger(__name__)
|
|
253
323
|
result = {}
|
|
@@ -256,31 +326,51 @@ def import_fields(
|
|
|
256
326
|
return result
|
|
257
327
|
|
|
258
328
|
for odcs_property in odcs_properties:
|
|
259
|
-
mapped_type = map_type(odcs_property.
|
|
329
|
+
mapped_type = map_type(odcs_property.logicalType, custom_type_mappings)
|
|
260
330
|
if mapped_type is not None:
|
|
261
|
-
property_name = odcs_property
|
|
262
|
-
description = odcs_property.
|
|
331
|
+
property_name = odcs_property.name
|
|
332
|
+
description = odcs_property.description if odcs_property.description is not None else None
|
|
263
333
|
field = Field(
|
|
264
334
|
description=" ".join(description.splitlines()) if description is not None else None,
|
|
265
335
|
type=mapped_type,
|
|
266
|
-
title=odcs_property.
|
|
267
|
-
required=
|
|
268
|
-
primaryKey=odcs_property.
|
|
269
|
-
if not has_composite_primary_key(odcs_properties) and odcs_property.
|
|
336
|
+
title=odcs_property.businessName,
|
|
337
|
+
required=odcs_property.required if odcs_property.required is not None else None,
|
|
338
|
+
primaryKey=odcs_property.primaryKey
|
|
339
|
+
if not has_composite_primary_key(odcs_properties) and odcs_property.primaryKey is not None
|
|
270
340
|
else False,
|
|
271
|
-
unique=odcs_property.
|
|
272
|
-
examples=odcs_property.
|
|
273
|
-
classification=odcs_property.
|
|
274
|
-
if odcs_property.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
341
|
+
unique=odcs_property.unique if odcs_property.unique else None,
|
|
342
|
+
examples=odcs_property.examples if odcs_property.examples is not None else None,
|
|
343
|
+
classification=odcs_property.classification if odcs_property.classification is not None else None,
|
|
344
|
+
tags=odcs_property.tags if odcs_property.tags is not None else None,
|
|
345
|
+
quality=convert_quality_list(odcs_property.quality),
|
|
346
|
+
fields=import_fields(odcs_property.properties, custom_type_mappings, server_type)
|
|
347
|
+
if odcs_property.properties is not None
|
|
348
|
+
else {},
|
|
278
349
|
config=import_field_config(odcs_property, server_type),
|
|
350
|
+
format=getattr(odcs_property, "format", None),
|
|
279
351
|
)
|
|
352
|
+
# mapped_type is array
|
|
353
|
+
if field.type == "array" and odcs_property.items is not None:
|
|
354
|
+
# nested array object
|
|
355
|
+
if odcs_property.items.logicalType == "object":
|
|
356
|
+
field.items = Field(
|
|
357
|
+
type="object",
|
|
358
|
+
fields=import_fields(odcs_property.items.properties, custom_type_mappings, server_type),
|
|
359
|
+
)
|
|
360
|
+
# array of simple type
|
|
361
|
+
elif odcs_property.items.logicalType is not None:
|
|
362
|
+
field.items = Field(type=odcs_property.items.logicalType)
|
|
363
|
+
|
|
364
|
+
# enum from quality validValues as enum
|
|
365
|
+
if field.type == "string":
|
|
366
|
+
for q in field.quality:
|
|
367
|
+
if hasattr(q, "validValues"):
|
|
368
|
+
field.enum = q.validValues
|
|
369
|
+
|
|
280
370
|
result[property_name] = field
|
|
281
371
|
else:
|
|
282
372
|
logger.info(
|
|
283
|
-
f"Can't map {odcs_property.
|
|
373
|
+
f"Can't map {odcs_property.name} to the Datacontract Mapping types, as there is no equivalent or special mapping. Consider introducing a customProperty 'dc_mapping_{odcs_property.logicalType}' that defines your expected type as the 'value'"
|
|
284
374
|
)
|
|
285
375
|
|
|
286
376
|
return result
|
|
@@ -298,19 +388,28 @@ def map_type(odcs_type: str, custom_mappings: Dict[str, str]) -> str | None:
|
|
|
298
388
|
return None
|
|
299
389
|
|
|
300
390
|
|
|
301
|
-
def get_custom_type_mappings(odcs_custom_properties: List[
|
|
391
|
+
def get_custom_type_mappings(odcs_custom_properties: List[CustomProperty]) -> Dict[str, str]:
|
|
302
392
|
result = {}
|
|
303
393
|
if odcs_custom_properties is not None:
|
|
304
394
|
for prop in odcs_custom_properties:
|
|
305
|
-
if prop
|
|
306
|
-
odcs_type_name = prop[
|
|
307
|
-
datacontract_type = prop
|
|
395
|
+
if prop.property.startswith("dc_mapping_"):
|
|
396
|
+
odcs_type_name = prop.property[11:] # Changed substring to slice
|
|
397
|
+
datacontract_type = prop.value
|
|
308
398
|
result[odcs_type_name] = datacontract_type
|
|
309
399
|
|
|
310
400
|
return result
|
|
311
401
|
|
|
312
402
|
|
|
313
|
-
def
|
|
314
|
-
if
|
|
403
|
+
def get_owner(odcs_custom_properties: List[CustomProperty]) -> str | None:
|
|
404
|
+
if odcs_custom_properties is not None:
|
|
405
|
+
for prop in odcs_custom_properties:
|
|
406
|
+
if prop.property == "owner":
|
|
407
|
+
return prop.value
|
|
408
|
+
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def import_tags(odcs: OpenDataContractStandard) -> List[str] | None:
|
|
413
|
+
if odcs.tags is None:
|
|
315
414
|
return None
|
|
316
|
-
return
|
|
415
|
+
return odcs.tags
|