datacontract-cli 0.10.7__py3-none-any.whl → 0.10.9__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 (55) hide show
  1. datacontract/catalog/catalog.py +4 -2
  2. datacontract/cli.py +44 -15
  3. datacontract/data_contract.py +52 -206
  4. datacontract/engines/fastjsonschema/s3/s3_read_files.py +13 -1
  5. datacontract/engines/soda/check_soda_execute.py +9 -2
  6. datacontract/engines/soda/connections/bigquery.py +8 -1
  7. datacontract/engines/soda/connections/duckdb.py +28 -12
  8. datacontract/engines/soda/connections/trino.py +26 -0
  9. datacontract/export/__init__.py +0 -0
  10. datacontract/export/avro_converter.py +15 -3
  11. datacontract/export/avro_idl_converter.py +29 -22
  12. datacontract/export/bigquery_converter.py +15 -0
  13. datacontract/export/dbml_converter.py +9 -0
  14. datacontract/export/dbt_converter.py +26 -1
  15. datacontract/export/exporter.py +88 -0
  16. datacontract/export/exporter_factory.py +145 -0
  17. datacontract/export/go_converter.py +6 -0
  18. datacontract/export/great_expectations_converter.py +10 -0
  19. datacontract/export/html_export.py +6 -0
  20. datacontract/export/jsonschema_converter.py +31 -23
  21. datacontract/export/odcs_converter.py +24 -1
  22. datacontract/export/protobuf_converter.py +6 -0
  23. datacontract/export/pydantic_converter.py +6 -0
  24. datacontract/export/rdf_converter.py +9 -0
  25. datacontract/export/sodacl_converter.py +23 -12
  26. datacontract/export/spark_converter.py +211 -0
  27. datacontract/export/sql_converter.py +32 -2
  28. datacontract/export/sql_type_converter.py +32 -5
  29. datacontract/export/terraform_converter.py +6 -0
  30. datacontract/imports/avro_importer.py +8 -0
  31. datacontract/imports/bigquery_importer.py +47 -4
  32. datacontract/imports/glue_importer.py +122 -30
  33. datacontract/imports/importer.py +29 -0
  34. datacontract/imports/importer_factory.py +72 -0
  35. datacontract/imports/jsonschema_importer.py +8 -0
  36. datacontract/imports/odcs_importer.py +200 -0
  37. datacontract/imports/sql_importer.py +8 -0
  38. datacontract/imports/unity_importer.py +152 -0
  39. datacontract/lint/resolve.py +22 -1
  40. datacontract/model/data_contract_specification.py +36 -4
  41. datacontract/templates/datacontract.html +17 -2
  42. datacontract/templates/partials/datacontract_information.html +20 -0
  43. datacontract/templates/partials/datacontract_terms.html +7 -0
  44. datacontract/templates/partials/definition.html +9 -1
  45. datacontract/templates/partials/model_field.html +23 -6
  46. datacontract/templates/partials/server.html +113 -48
  47. datacontract/templates/style/output.css +51 -0
  48. datacontract/web.py +17 -0
  49. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/METADATA +298 -59
  50. datacontract_cli-0.10.9.dist-info/RECORD +93 -0
  51. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/WHEEL +1 -1
  52. datacontract_cli-0.10.7.dist-info/RECORD +0 -84
  53. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/LICENSE +0 -0
  54. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/entry_points.txt +0 -0
  55. {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  import boto3
2
2
  from typing import List
3
3
 
4
+ from datacontract.imports.importer import Importer
4
5
  from datacontract.model.data_contract_specification import (
5
6
  DataContractSpecification,
6
7
  Model,
@@ -9,7 +10,14 @@ from datacontract.model.data_contract_specification import (
9
10
  )
10
11
 
11
12
 
12
- def get_glue_database(datebase_name: str):
13
+ class GlueImporter(Importer):
14
+ def import_source(
15
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
16
+ ) -> dict:
17
+ return import_glue(data_contract_specification, source, import_args.get("glue_tables"))
18
+
19
+
20
+ def get_glue_database(database_name: str):
13
21
  """Get the details Glue database.
14
22
 
15
23
  Args:
@@ -18,31 +26,32 @@ def get_glue_database(datebase_name: str):
18
26
  Returns:
19
27
  set: catalogid and locationUri
20
28
  """
21
-
22
29
  glue = boto3.client("glue")
23
30
  try:
24
- response = glue.get_database(Name=datebase_name)
31
+ response = glue.get_database(Name=database_name)
25
32
  except glue.exceptions.EntityNotFoundException:
26
- print(f"Database not found {datebase_name}.")
33
+ print(f"Database not found {database_name}.")
27
34
  return (None, None)
28
35
  except Exception as e:
29
36
  # todo catch all
30
37
  print(f"Error: {e}")
31
38
  return (None, None)
32
39
 
33
- return (response["Database"]["CatalogId"], response["Database"].get("LocationUri", "None"))
40
+ return (
41
+ response["Database"]["CatalogId"],
42
+ response["Database"].get("LocationUri", "None"),
43
+ )
34
44
 
35
45
 
36
46
  def get_glue_tables(database_name: str) -> List[str]:
37
47
  """Get the list of tables in a Glue database.
38
48
 
39
49
  Args:
40
- database_name (str): glue database to request.
50
+ database_name (str): Glue database to request.
41
51
 
42
52
  Returns:
43
- List[string]: List of table names
53
+ List[str]: List of table names
44
54
  """
45
-
46
55
  glue = boto3.client("glue")
47
56
 
48
57
  # Set the paginator
@@ -107,9 +116,21 @@ def get_glue_table_schema(database_name: str, table_name: str):
107
116
  return table_schema
108
117
 
109
118
 
110
- def import_glue(data_contract_specification: DataContractSpecification, source: str, table_names: List[str]):
111
- """Import the schema of a Glue database."""
119
+ def import_glue(
120
+ data_contract_specification: DataContractSpecification,
121
+ source: str,
122
+ table_names: List[str],
123
+ ):
124
+ """Import the schema of a Glue database.
112
125
 
126
+ Args:
127
+ data_contract_specification (DataContractSpecification): The data contract specification to update.
128
+ source (str): The name of the Glue database.
129
+ table_names (List[str]): List of table names to import. If None, all tables in the database are imported.
130
+
131
+ Returns:
132
+ DataContractSpecification: The updated data contract specification.
133
+ """
113
134
  catalogid, location_uri = get_glue_database(source)
114
135
 
115
136
  # something went wrong
@@ -131,17 +152,21 @@ def import_glue(data_contract_specification: DataContractSpecification, source:
131
152
 
132
153
  fields = {}
133
154
  for column in table_schema:
134
- field = Field()
135
- field.type = map_type_from_sql(column["Type"])
155
+ field = create_typed_field(column["Type"])
136
156
 
137
157
  # hive partitons are required, but are not primary keys
138
158
  if column.get("Hive"):
139
159
  field.required = True
140
160
 
141
161
  field.description = column.get("Comment")
142
-
143
162
  fields[column["Name"]] = field
144
163
 
164
+ if "decimal" in column["Type"]:
165
+ # Extract precision and scale from the string
166
+ perc_scale = column["Type"][8:-1].split(",")
167
+ field.precision = int(perc_scale[0])
168
+ field.scale = int(perc_scale[1])
169
+
145
170
  data_contract_specification.models[table_name] = Model(
146
171
  type="table",
147
172
  fields=fields,
@@ -150,35 +175,102 @@ def import_glue(data_contract_specification: DataContractSpecification, source:
150
175
  return data_contract_specification
151
176
 
152
177
 
153
- def map_type_from_sql(sql_type: str):
178
+ def create_typed_field(dtype: str) -> Field:
179
+ """Create a typed field based on the given data type.
180
+
181
+ Args:
182
+ dtype (str): The data type of the field.
183
+
184
+ Returns:
185
+ Field: The created field with the appropriate type.
186
+ """
187
+ field = Field()
188
+ dtype = dtype.strip().lower().replace(" ", "")
189
+ if dtype.startswith(("array", "struct")):
190
+ orig_dtype: str = dtype
191
+ if dtype.startswith("array"):
192
+ field.type = "array"
193
+ field.items = create_typed_field(orig_dtype[6:-1])
194
+ elif dtype.startswith("struct"):
195
+ field.type = "struct"
196
+ for f in split_struct(orig_dtype[7:-1]):
197
+ field.fields[f.split(":", 1)[0].strip()] = create_typed_field(f.split(":", 1)[1])
198
+ else:
199
+ field.type = map_type_from_sql(dtype)
200
+ return field
201
+
202
+
203
+ def split_fields(s: str):
204
+ """Split a string of fields considering nested structures.
205
+
206
+ Args:
207
+ s (str): The string to split.
208
+
209
+ Yields:
210
+ str: The next field in the string.
211
+ """
212
+ counter: int = 0
213
+ last: int = 0
214
+ for i, x in enumerate(s):
215
+ if x in ("<", "("):
216
+ counter += 1
217
+ elif x in (">", ")"):
218
+ counter -= 1
219
+ elif x == "," and counter == 0:
220
+ yield s[last:i]
221
+ last = i + 1
222
+ yield s[last:]
223
+
224
+
225
+ def split_struct(s: str) -> List[str]:
226
+ """Split a struct string into individual fields.
227
+
228
+ Args:
229
+ s (str): The struct string to split.
230
+
231
+ Returns:
232
+ List[str]: List of individual fields in the struct.
233
+ """
234
+ return list(split_fields(s=s))
235
+
236
+
237
+ def map_type_from_sql(sql_type: str) -> str:
238
+ """Map an SQL type to a corresponding field type.
239
+
240
+ Args:
241
+ sql_type (str): The SQL type to map.
242
+
243
+ Returns:
244
+ str: The corresponding field type.
245
+ """
154
246
  if sql_type is None:
155
247
  return None
156
248
 
157
- if sql_type.lower().startswith("varchar"):
249
+ sql_type = sql_type.lower()
250
+ if sql_type.startswith("varchar"):
158
251
  return "varchar"
159
- if sql_type.lower().startswith("string"):
252
+ if sql_type.startswith("string"):
160
253
  return "string"
161
- if sql_type.lower().startswith("text"):
254
+ if sql_type.startswith("text"):
162
255
  return "text"
163
- elif sql_type.lower().startswith("byte"):
256
+ if sql_type.startswith("byte"):
164
257
  return "byte"
165
- elif sql_type.lower().startswith("short"):
258
+ if sql_type.startswith("short"):
166
259
  return "short"
167
- elif sql_type.lower().startswith("integer"):
260
+ if sql_type.startswith("integer") or sql_type.startswith("int"):
168
261
  return "integer"
169
- elif sql_type.lower().startswith("long"):
262
+ if sql_type.startswith("long") or sql_type.startswith("bigint"):
170
263
  return "long"
171
- elif sql_type.lower().startswith("bigint"):
172
- return "long"
173
- elif sql_type.lower().startswith("float"):
264
+ if sql_type.startswith("float"):
174
265
  return "float"
175
- elif sql_type.lower().startswith("double"):
266
+ if sql_type.startswith("double"):
176
267
  return "double"
177
- elif sql_type.lower().startswith("boolean"):
268
+ if sql_type.startswith("boolean"):
178
269
  return "boolean"
179
- elif sql_type.lower().startswith("timestamp"):
270
+ if sql_type.startswith("timestamp"):
180
271
  return "timestamp"
181
- elif sql_type.lower().startswith("date"):
272
+ if sql_type.startswith("date"):
182
273
  return "date"
183
- else:
184
- return "variant"
274
+ if sql_type.startswith("decimal"):
275
+ return "decimal"
276
+ return "variant"
@@ -0,0 +1,29 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import Enum
3
+
4
+ from datacontract.model.data_contract_specification import DataContractSpecification
5
+
6
+
7
+ class Importer(ABC):
8
+ def __init__(self, import_format) -> None:
9
+ self.import_format = import_format
10
+
11
+ @abstractmethod
12
+ def import_source(
13
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
14
+ ) -> dict:
15
+ pass
16
+
17
+
18
+ class ImportFormat(str, Enum):
19
+ sql = "sql"
20
+ avro = "avro"
21
+ glue = "glue"
22
+ jsonschema = "jsonschema"
23
+ bigquery = "bigquery"
24
+ odcs = "odcs"
25
+ unity = "unity"
26
+
27
+ @classmethod
28
+ def get_suported_formats(cls):
29
+ return list(map(lambda c: c.value, cls))
@@ -0,0 +1,72 @@
1
+ import importlib.util
2
+ import sys
3
+ from datacontract.imports.importer import ImportFormat, Importer
4
+
5
+
6
+ class ImporterFactory:
7
+ def __init__(self):
8
+ self.dict_importer = {}
9
+ self.dict_lazy_importer = {}
10
+
11
+ def register_importer(self, name, importer: Importer):
12
+ self.dict_importer.update({name: importer})
13
+
14
+ def register_lazy_importer(self, name: str, module_path: str, class_name: str):
15
+ self.dict_lazy_importer.update({name: (module_path, class_name)})
16
+
17
+ def create(self, name) -> Importer:
18
+ importers = self.dict_importer.copy()
19
+ importers.update(self.dict_lazy_importer.copy())
20
+ if name not in importers.keys():
21
+ raise ValueError(f"The '{name}' format is not suportted.")
22
+ importer_class = importers[name]
23
+ if type(importers[name]) is tuple:
24
+ importer_class = load_module_class(module_path=importers[name][0], class_name=importers[name][1])
25
+ if not importer_class:
26
+ raise ValueError(f"Module {name} could not be loaded.")
27
+ return importer_class(name)
28
+
29
+
30
+ def import_module(module_path):
31
+ if importlib.util.find_spec(module_path) is not None:
32
+ try:
33
+ module = importlib.import_module(module_path)
34
+ except ModuleNotFoundError:
35
+ return None
36
+ sys.modules[module_path] = module
37
+ return module
38
+
39
+
40
+ def load_module_class(module_path, class_name):
41
+ module = import_module(module_path)
42
+ if not module:
43
+ return None
44
+ return getattr(module, class_name)
45
+
46
+
47
+ importer_factory = ImporterFactory()
48
+ importer_factory.register_lazy_importer(
49
+ name=ImportFormat.avro, module_path="datacontract.imports.avro_importer", class_name="AvroImporter"
50
+ )
51
+ importer_factory.register_lazy_importer(
52
+ name=ImportFormat.bigquery,
53
+ module_path="datacontract.imports.bigquery_importer",
54
+ class_name="BigQueryImporter",
55
+ )
56
+ importer_factory.register_lazy_importer(
57
+ name=ImportFormat.glue, module_path="datacontract.imports.glue_importer", class_name="GlueImporter"
58
+ )
59
+ importer_factory.register_lazy_importer(
60
+ name=ImportFormat.jsonschema,
61
+ module_path="datacontract.imports.jsonschema_importer",
62
+ class_name="JsonSchemaImporter",
63
+ )
64
+ importer_factory.register_lazy_importer(
65
+ name=ImportFormat.odcs, module_path="datacontract.imports.odcs_importer", class_name="OdcsImporter"
66
+ )
67
+ importer_factory.register_lazy_importer(
68
+ name=ImportFormat.sql, module_path="datacontract.imports.sql_importer", class_name="SqlImporter"
69
+ )
70
+ importer_factory.register_lazy_importer(
71
+ name=ImportFormat.unity, module_path="datacontract.imports.unity_importer", class_name="UnityImporter"
72
+ )
@@ -2,10 +2,18 @@ import json
2
2
 
3
3
  import fastjsonschema
4
4
 
5
+ from datacontract.imports.importer import Importer
5
6
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field, Definition
6
7
  from datacontract.model.exceptions import DataContractException
7
8
 
8
9
 
10
+ class JsonSchemaImporter(Importer):
11
+ def import_source(
12
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
13
+ ) -> dict:
14
+ return import_jsonschema(data_contract_specification, source)
15
+
16
+
9
17
  def convert_json_schema_properties(properties, is_definition=False):
10
18
  fields = {}
11
19
  for field_name, field_schema in properties.items():
@@ -0,0 +1,200 @@
1
+ import datetime
2
+ import logging
3
+ from typing import Any, Dict, List
4
+ import yaml
5
+ from datacontract.imports.importer import Importer
6
+ from datacontract.model.data_contract_specification import (
7
+ Availability,
8
+ Contact,
9
+ DataContractSpecification,
10
+ Info,
11
+ Model,
12
+ Field,
13
+ Retention,
14
+ ServiceLevel,
15
+ Terms,
16
+ )
17
+ from datacontract.model.exceptions import DataContractException
18
+
19
+ DATACONTRACT_TYPES = [
20
+ "string",
21
+ "text",
22
+ "varchar",
23
+ "number",
24
+ "decimal",
25
+ "numeric",
26
+ "int",
27
+ "integer",
28
+ "long",
29
+ "bigint",
30
+ "float",
31
+ "double",
32
+ "boolean",
33
+ "timestamp",
34
+ "timestamp_tz",
35
+ "timestamp_ntz",
36
+ "date",
37
+ "array",
38
+ "bytes",
39
+ "object",
40
+ "record",
41
+ "struct",
42
+ "null",
43
+ ]
44
+
45
+
46
+ class OdcsImporter(Importer):
47
+ def import_source(
48
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
49
+ ) -> dict:
50
+ return import_odcs(data_contract_specification, source)
51
+
52
+
53
+ def import_odcs(data_contract_specification: DataContractSpecification, source: str) -> DataContractSpecification:
54
+ try:
55
+ with open(source, "r") as file:
56
+ odcs_contract = yaml.safe_load(file.read())
57
+
58
+ except Exception as e:
59
+ raise DataContractException(
60
+ type="schema",
61
+ name="Parse ODCS contract",
62
+ reason=f"Failed to parse odcs contract from {source}",
63
+ engine="datacontract",
64
+ original_exception=e,
65
+ )
66
+
67
+ data_contract_specification.id = odcs_contract["uuid"]
68
+ data_contract_specification.info = import_info(odcs_contract)
69
+ data_contract_specification.terms = import_terms(odcs_contract)
70
+ data_contract_specification.servicelevels = import_servicelevels(odcs_contract)
71
+ data_contract_specification.models = import_models(odcs_contract)
72
+
73
+ return data_contract_specification
74
+
75
+
76
+ def import_info(odcs_contract: Dict[str, Any]) -> Info:
77
+ info = Info(title=odcs_contract.get("quantumName"), version=odcs_contract.get("version"))
78
+
79
+ if odcs_contract.get("description").get("purpose") is not None:
80
+ info.description = odcs_contract.get("description").get("purpose")
81
+
82
+ if odcs_contract.get("datasetDomain") is not None:
83
+ info.owner = odcs_contract.get("datasetDomain")
84
+
85
+ if odcs_contract.get("productDl") is not None or odcs_contract.get("productFeedbackUrl") is not None:
86
+ contact = Contact()
87
+ if odcs_contract.get("productDl") is not None:
88
+ contact.name = odcs_contract.get("productDl")
89
+ if odcs_contract.get("productFeedbackUrl") is not None:
90
+ contact.url = odcs_contract.get("productFeedbackUrl")
91
+
92
+ info.contact = contact
93
+
94
+ return info
95
+
96
+
97
+ def import_terms(odcs_contract: Dict[str, Any]) -> Terms | None:
98
+ if (
99
+ odcs_contract.get("description").get("usage") is not None
100
+ or odcs_contract.get("description").get("limitations") is not None
101
+ or odcs_contract.get("price") is not None
102
+ ):
103
+ terms = Terms()
104
+ if odcs_contract.get("description").get("usage") is not None:
105
+ terms.usage = odcs_contract.get("description").get("usage")
106
+ if odcs_contract.get("description").get("limitations") is not None:
107
+ terms.limitations = odcs_contract.get("description").get("limitations")
108
+ if odcs_contract.get("price") is not None:
109
+ terms.billing = f"{odcs_contract.get('price').get('priceAmount')} {odcs_contract.get('price').get('priceCurrency')} / {odcs_contract.get('price').get('priceUnit')}"
110
+
111
+ return terms
112
+ else:
113
+ return None
114
+
115
+
116
+ def import_servicelevels(odcs_contract: Dict[str, Any]) -> ServiceLevel:
117
+ # find the two properties we can map (based on the examples)
118
+ sla_properties = odcs_contract.get("slaProperties") if odcs_contract.get("slaProperties") is not None else []
119
+ availability = next((p for p in sla_properties if p["property"] == "generalAvailability"), None)
120
+ retention = next((p for p in sla_properties if p["property"] == "retention"), None)
121
+
122
+ if availability is not None or retention is not None:
123
+ servicelevel = ServiceLevel()
124
+
125
+ if availability is not None:
126
+ value = availability.get("value")
127
+ if isinstance(value, datetime.datetime):
128
+ value = value.isoformat()
129
+ servicelevel.availability = Availability(description=value)
130
+
131
+ if retention is not None:
132
+ servicelevel.retention = Retention(period=f"{retention.get('value')}{retention.get('unit')}")
133
+
134
+ return servicelevel
135
+ else:
136
+ return None
137
+
138
+
139
+ def import_models(odcs_contract: Dict[str, Any]) -> Dict[str, Model]:
140
+ custom_type_mappings = get_custom_type_mappings(odcs_contract.get("customProperties"))
141
+
142
+ odcs_tables = odcs_contract.get("dataset") if odcs_contract.get("dataset") is not None else []
143
+ result = {}
144
+
145
+ for table in odcs_tables:
146
+ description = table.get("description") if table.get("description") is not None else ""
147
+ model = Model(description=" ".join(description.splitlines()), type="table")
148
+ model.fields = import_fields(table.get("columns"), custom_type_mappings)
149
+ result[table.get("table")] = model
150
+
151
+ return result
152
+
153
+
154
+ def import_fields(odcs_columns: Dict[str, Any], custom_type_mappings: Dict[str, str]) -> Dict[str, Field]:
155
+ logger = logging.getLogger(__name__)
156
+ result = {}
157
+
158
+ for column in odcs_columns:
159
+ mapped_type = map_type(column.get("logicalType"), custom_type_mappings)
160
+ if mapped_type is not None:
161
+ description = column.get("description") if column.get("description") is not None else ""
162
+ field = Field(
163
+ description=" ".join(description.splitlines()),
164
+ type=mapped_type,
165
+ title=column.get("businessName") if column.get("businessName") is not None else "",
166
+ required=not column.get("isNullable") if column.get("isNullable") is not None else False,
167
+ primary=column.get("isPrimary") if column.get("isPrimary") is not None else False,
168
+ unique=column.get("isUnique") if column.get("isUnique") is not None else False,
169
+ classification=column.get("classification") if column.get("classification") is not None else "",
170
+ tags=column.get("tags") if column.get("tags") is not None else [],
171
+ )
172
+ result[column["column"]] = field
173
+ else:
174
+ logger.info(
175
+ f"Can't properly map {column.get('column')} to the Datacontract Mapping types, as there is no equivalent or special mapping. Consider introducing a customProperty 'dc_mapping_{column.get('logicalName')}' that defines your expected type as the 'value'"
176
+ )
177
+
178
+ return result
179
+
180
+
181
+ def map_type(odcs_type: str, custom_mappings: Dict[str, str]) -> str | None:
182
+ t = odcs_type.lower()
183
+ if t in DATACONTRACT_TYPES:
184
+ return t
185
+ elif custom_mappings.get(t) is not None:
186
+ return custom_mappings.get(t)
187
+ else:
188
+ return None
189
+
190
+
191
+ def get_custom_type_mappings(odcs_custom_properties: List[Any]) -> Dict[str, str]:
192
+ result = {}
193
+ if odcs_custom_properties is not None:
194
+ for prop in odcs_custom_properties:
195
+ if prop["property"].startswith("dc_mapping_"):
196
+ odcs_type_name = prop["property"].substring(11)
197
+ datacontract_type = prop["value"]
198
+ result[odcs_type_name] = datacontract_type
199
+
200
+ return result
@@ -1,8 +1,16 @@
1
1
  from simple_ddl_parser import parse_from_file
2
2
 
3
+ from datacontract.imports.importer import Importer
3
4
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
4
5
 
5
6
 
7
+ class SqlImporter(Importer):
8
+ def import_source(
9
+ self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
10
+ ) -> dict:
11
+ return import_sql(data_contract_specification, self.import_format, source)
12
+
13
+
6
14
  def import_sql(data_contract_specification: DataContractSpecification, format: str, source: str):
7
15
  ddl = parse_from_file(source, group_by_type=True)
8
16
  tables = ddl["tables"]