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,8 +1,15 @@
1
1
  import json
2
2
 
3
+ from datacontract.export.exporter import Exporter, _check_models_for_export
3
4
  from datacontract.model.data_contract_specification import Field
4
5
 
5
6
 
7
+ class AvroExporter(Exporter):
8
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
9
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
10
+ return to_avro_schema_json(model_name, model_value)
11
+
12
+
6
13
  def to_avro_schema(model_name, model) -> dict:
7
14
  return to_avro_record(model_name, model.fields, model.description, model.namespace)
8
15
 
@@ -47,7 +54,13 @@ def to_avro_type(field: Field, field_name: str) -> str | dict:
47
54
  if "avroLogicalType" in field.config and "avroType" in field.config:
48
55
  return {"type": field.config["avroType"], "logicalType": field.config["avroLogicalType"]}
49
56
  if "avroLogicalType" in field.config:
50
- if field.config["avroLogicalType"] in ["timestamp-millis", "timestamp-micros", "local-timestamp-millis", "local-timestamp-micros", "time-micros"]:
57
+ if field.config["avroLogicalType"] in [
58
+ "timestamp-millis",
59
+ "timestamp-micros",
60
+ "local-timestamp-millis",
61
+ "local-timestamp-micros",
62
+ "time-micros",
63
+ ]:
51
64
  return {"type": "long", "logicalType": field.config["avroLogicalType"]}
52
65
  if field.config["avroLogicalType"] in ["time-millis", "date"]:
53
66
  return {"type": "int", "logicalType": field.config["avroLogicalType"]}
@@ -82,8 +95,7 @@ def to_avro_type(field: Field, field_name: str) -> str | dict:
82
95
  elif field.type in ["binary"]:
83
96
  return "bytes"
84
97
  elif field.type in ["array"]:
85
- # TODO support array structs
86
- return "array"
98
+ return {"type": "array", "items": to_avro_type(field.items, field_name)}
87
99
  elif field.type in ["null"]:
88
100
  return "null"
89
101
  else:
@@ -7,28 +7,7 @@ from datacontract.lint.resolve import inline_definitions_into_data_contract
7
7
  from datacontract.model.data_contract_specification import DataContractSpecification, Field
8
8
  from datacontract.model.exceptions import DataContractException
9
9
 
10
-
11
- def to_avro_idl(contract: DataContractSpecification) -> str:
12
- """Serialize the provided data contract specification into an Avro IDL string.
13
-
14
- The data contract will be serialized as a protocol, with one record type
15
- for each contained model. Model fields are mapped one-to-one to Avro IDL
16
- record fields.
17
- """
18
- stream = StringIO()
19
- to_avro_idl_stream(contract, stream)
20
- return stream.getvalue()
21
-
22
-
23
- def to_avro_idl_stream(contract: DataContractSpecification, stream: typing.TextIO):
24
- """Serialize the provided data contract specification into Avro IDL."""
25
- ir = _contract_to_avro_idl_ir(contract)
26
- if ir.description:
27
- stream.write(f"/** {contract.info.description} */\n")
28
- stream.write(f"protocol {ir.name or 'Unnamed'} {{\n")
29
- for model_type in ir.model_types:
30
- _write_model_type(model_type, stream)
31
- stream.write("}\n")
10
+ from datacontract.export.exporter import Exporter
32
11
 
33
12
 
34
13
  class AvroPrimitiveType(Enum):
@@ -107,6 +86,34 @@ avro_primitive_types = set(
107
86
  )
108
87
 
109
88
 
89
+ class AvroIdlExporter(Exporter):
90
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
91
+ return to_avro_idl(data_contract)
92
+
93
+
94
+ def to_avro_idl(contract: DataContractSpecification) -> str:
95
+ """Serialize the provided data contract specification into an Avro IDL string.
96
+
97
+ The data contract will be serialized as a protocol, with one record type
98
+ for each contained model. Model fields are mapped one-to-one to Avro IDL
99
+ record fields.
100
+ """
101
+ stream = StringIO()
102
+ to_avro_idl_stream(contract, stream)
103
+ return stream.getvalue()
104
+
105
+
106
+ def to_avro_idl_stream(contract: DataContractSpecification, stream: typing.TextIO):
107
+ """Serialize the provided data contract specification into Avro IDL."""
108
+ ir = _contract_to_avro_idl_ir(contract)
109
+ if ir.description:
110
+ stream.write(f"/** {contract.info.description} */\n")
111
+ stream.write(f"protocol {ir.name or 'Unnamed'} {{\n")
112
+ for model_type in ir.model_types:
113
+ _write_model_type(model_type, stream)
114
+ stream.write("}\n")
115
+
116
+
110
117
  def _to_avro_primitive_logical_type(field_name: str, field: Field) -> AvroPrimitiveField:
111
118
  result = AvroPrimitiveField(field_name, field.required, field.description, AvroPrimitiveType.string)
112
119
  match field.type:
@@ -5,6 +5,21 @@ from typing import Dict, List
5
5
  from datacontract.model.data_contract_specification import Model, Field, Server
6
6
  from datacontract.model.exceptions import DataContractException
7
7
 
8
+ from datacontract.export.exporter import Exporter, _check_models_for_export
9
+
10
+
11
+ class BigQueryExporter(Exporter):
12
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
13
+ self.dict_args = export_args
14
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
15
+ found_server = data_contract.servers.get(server)
16
+ if found_server is None:
17
+ raise RuntimeError("Export to bigquery requires selecting a bigquery server from the data contract.")
18
+ if found_server.type != "bigquery":
19
+ raise RuntimeError("Export to bigquery requires selecting a bigquery server from the data contract.")
20
+
21
+ return to_bigquery_json(model_name, model_value, found_server)
22
+
8
23
 
9
24
  def to_bigquery_json(model_name: str, model_value: Model, server: Server) -> str:
10
25
  bigquery_table = to_bigquery_schema(model_name, model_value, server)
@@ -8,6 +8,15 @@ import datacontract.model.data_contract_specification as spec
8
8
  from datacontract.export.sql_type_converter import convert_to_sql_type
9
9
 
10
10
 
11
+ from datacontract.export.exporter import Exporter
12
+
13
+
14
+ class DbmlExporter(Exporter):
15
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
16
+ found_server = data_contract.servers.get(server)
17
+ return to_dbml_diagram(data_contract, found_server)
18
+
19
+
11
20
  def to_dbml_diagram(contract: spec.DataContractSpecification, server: spec.Server) -> str:
12
21
  result = ""
13
22
  result += add_generated_info(contract, server) + "\n"
@@ -5,6 +5,28 @@ import yaml
5
5
  from datacontract.export.sql_type_converter import convert_to_sql_type
6
6
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
7
7
 
8
+ from datacontract.export.exporter import Exporter, _check_models_for_export
9
+
10
+
11
+ class DbtExporter(Exporter):
12
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
13
+ return to_dbt_models_yaml(data_contract)
14
+
15
+
16
+ class DbtSourceExporter(Exporter):
17
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
18
+ return to_dbt_sources_yaml(data_contract, server)
19
+
20
+
21
+ class DbtStageExporter(Exporter):
22
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
23
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
24
+ return to_dbt_staging_sql(
25
+ data_contract,
26
+ model_name,
27
+ model_value,
28
+ )
29
+
8
30
 
9
31
  def to_dbt_models_yaml(data_contract_spec: DataContractSpecification):
10
32
  dbt = {
@@ -19,7 +41,10 @@ def to_dbt_models_yaml(data_contract_spec: DataContractSpecification):
19
41
 
20
42
  def to_dbt_staging_sql(data_contract_spec: DataContractSpecification, model_name: str, model_value: Model) -> str:
21
43
  if data_contract_spec.models is None or len(data_contract_spec.models.items()) != 1:
22
- print("Export to dbt-staging-sql currently only works with exactly one model in the data contract.")
44
+ print(
45
+ "Export to dbt-staging-sql currently only works with exactly one model in the data contract."
46
+ "Please specify the model name."
47
+ )
23
48
  return ""
24
49
 
25
50
  id = data_contract_spec.id
@@ -0,0 +1,88 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import Enum
3
+ import typing
4
+
5
+ from datacontract.model.data_contract_specification import DataContractSpecification
6
+
7
+
8
+ class Exporter(ABC):
9
+ def __init__(self, export_format) -> None:
10
+ self.export_format = export_format
11
+
12
+ @abstractmethod
13
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
14
+ pass
15
+
16
+
17
+ class ExportFormat(str, Enum):
18
+ jsonschema = "jsonschema"
19
+ pydantic_model = "pydantic-model"
20
+ sodacl = "sodacl"
21
+ dbt = "dbt"
22
+ dbt_sources = "dbt-sources"
23
+ dbt_staging_sql = "dbt-staging-sql"
24
+ odcs = "odcs"
25
+ rdf = "rdf"
26
+ avro = "avro"
27
+ protobuf = "protobuf"
28
+ great_expectations = "great-expectations"
29
+ terraform = "terraform"
30
+ avro_idl = "avro-idl"
31
+ sql = "sql"
32
+ sql_query = "sql-query"
33
+ html = "html"
34
+ go = "go"
35
+ bigquery = "bigquery"
36
+ dbml = "dbml"
37
+ spark = "spark"
38
+
39
+ @classmethod
40
+ def get_suported_formats(cls):
41
+ return list(map(lambda c: c.value, cls))
42
+
43
+
44
+ def _check_models_for_export(
45
+ data_contract: DataContractSpecification, model: str, export_format: str
46
+ ) -> typing.Tuple[str, str]:
47
+ if data_contract.models is None:
48
+ raise RuntimeError(f"Export to {export_format} requires models in the data contract.")
49
+
50
+ model_names = list(data_contract.models.keys())
51
+
52
+ if model == "all":
53
+ if len(data_contract.models.items()) != 1:
54
+ raise RuntimeError(
55
+ f"Export to {export_format} is model specific. Specify the model via --model $MODEL_NAME. Available models: {model_names}"
56
+ )
57
+
58
+ model_name, model_value = next(iter(data_contract.models.items()))
59
+ else:
60
+ model_name = model
61
+ model_value = data_contract.models.get(model_name)
62
+ if model_value is None:
63
+ raise RuntimeError(f"Model {model_name} not found in the data contract. Available models: {model_names}")
64
+
65
+ return model_name, model_value
66
+
67
+
68
+ def _determine_sql_server_type(data_contract: DataContractSpecification, sql_server_type: str, server: str = None):
69
+ if sql_server_type == "auto":
70
+ if data_contract.servers is None or len(data_contract.servers) == 0:
71
+ raise RuntimeError("Export with server_type='auto' requires servers in the data contract.")
72
+
73
+ if server is None:
74
+ server_types = set([server.type for server in data_contract.servers.values()])
75
+ else:
76
+ server_types = {data_contract.servers[server].type}
77
+
78
+ if "snowflake" in server_types:
79
+ return "snowflake"
80
+ elif "postgres" in server_types:
81
+ return "postgres"
82
+ elif "databricks" in server_types:
83
+ return "databricks"
84
+ else:
85
+ # default to snowflake dialect
86
+ return "snowflake"
87
+ else:
88
+ return sql_server_type
@@ -0,0 +1,145 @@
1
+ import importlib
2
+ import sys
3
+ from datacontract.export.exporter import ExportFormat, Exporter
4
+
5
+
6
+ class ExporterFactory:
7
+ def __init__(self):
8
+ self.dict_exporter = {}
9
+ self.dict_lazy_exporter = {}
10
+
11
+ def register_exporter(self, name: str, exporter: Exporter):
12
+ self.dict_exporter.update({name: exporter})
13
+
14
+ def register_lazy_exporter(self, name: str, module_path: str, class_name: str):
15
+ self.dict_lazy_exporter.update({name: (module_path, class_name)})
16
+
17
+ def create(self, name) -> Exporter:
18
+ exporters = self.dict_exporter.copy()
19
+ exporters.update(self.dict_lazy_exporter.copy())
20
+ if name not in exporters.keys():
21
+ raise ValueError(f"The '{name}' format is not supported.")
22
+ exporter_class = exporters[name]
23
+ if type(exporters[name]) is tuple:
24
+ exporter_class = load_module_class(module_path=exporters[name][0], class_name=exporters[name][1])
25
+ if not exporter_class:
26
+ raise ValueError(f"Module {name} could not be loaded.")
27
+ return exporter_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
+ exporter_factory = ExporterFactory()
48
+
49
+ exporter_factory.register_lazy_exporter(
50
+ name=ExportFormat.avro, module_path="datacontract.export.avro_converter", class_name="AvroExporter"
51
+ )
52
+
53
+ exporter_factory.register_lazy_exporter(
54
+ name=ExportFormat.avro_idl,
55
+ module_path="datacontract.export.avro_idl_converter",
56
+ class_name="AvroIdlExporter",
57
+ )
58
+
59
+ exporter_factory.register_lazy_exporter(
60
+ name=ExportFormat.bigquery,
61
+ module_path="datacontract.export.bigquery_converter",
62
+ class_name="BigQueryExporter",
63
+ )
64
+
65
+ exporter_factory.register_lazy_exporter(
66
+ name=ExportFormat.dbml, module_path="datacontract.export.dbml_converter", class_name="DbmlExporter"
67
+ )
68
+
69
+ exporter_factory.register_lazy_exporter(
70
+ name=ExportFormat.rdf, module_path="datacontract.export.rdf_converter", class_name="RdfExporter"
71
+ )
72
+
73
+ exporter_factory.register_lazy_exporter(
74
+ name=ExportFormat.dbt, module_path="datacontract.export.dbt_converter", class_name="DbtExporter"
75
+ )
76
+
77
+ exporter_factory.register_lazy_exporter(
78
+ name=ExportFormat.dbt_sources,
79
+ module_path="datacontract.export.dbt_converter",
80
+ class_name="DbtSourceExporter",
81
+ )
82
+
83
+ exporter_factory.register_lazy_exporter(
84
+ name=ExportFormat.dbt_staging_sql,
85
+ module_path="datacontract.export.dbt_converter",
86
+ class_name="DbtStageExporter",
87
+ )
88
+
89
+ exporter_factory.register_lazy_exporter(
90
+ name=ExportFormat.jsonschema,
91
+ module_path="datacontract.export.jsonschema_converter",
92
+ class_name="JsonSchemaExporter",
93
+ )
94
+
95
+ exporter_factory.register_lazy_exporter(
96
+ name=ExportFormat.odcs, module_path="datacontract.export.odcs_converter", class_name="OdcsExporter"
97
+ )
98
+
99
+ exporter_factory.register_lazy_exporter(
100
+ name=ExportFormat.go, module_path="datacontract.export.go_converter", class_name="GoExporter"
101
+ )
102
+
103
+ exporter_factory.register_lazy_exporter(
104
+ name=ExportFormat.great_expectations,
105
+ module_path="datacontract.export.great_expectations_converter",
106
+ class_name="GreateExpectationsExporter",
107
+ )
108
+
109
+ exporter_factory.register_lazy_exporter(
110
+ name=ExportFormat.html, module_path="datacontract.export.html_export", class_name="HtmlExporter"
111
+ )
112
+
113
+ exporter_factory.register_lazy_exporter(
114
+ name=ExportFormat.protobuf,
115
+ module_path="datacontract.export.protobuf_converter",
116
+ class_name="ProtoBufExporter",
117
+ )
118
+
119
+ exporter_factory.register_lazy_exporter(
120
+ name=ExportFormat.pydantic_model,
121
+ module_path="datacontract.export.pydantic_converter",
122
+ class_name="PydanticExporter",
123
+ )
124
+
125
+ exporter_factory.register_lazy_exporter(
126
+ name=ExportFormat.sodacl, module_path="datacontract.export.sodacl_converter", class_name="SodaExporter"
127
+ )
128
+
129
+ exporter_factory.register_lazy_exporter(
130
+ name=ExportFormat.sql, module_path="datacontract.export.sql_converter", class_name="SqlExporter"
131
+ )
132
+
133
+ exporter_factory.register_lazy_exporter(
134
+ name=ExportFormat.sql_query, module_path="datacontract.export.sql_converter", class_name="SqlQueryExporter"
135
+ )
136
+
137
+ exporter_factory.register_lazy_exporter(
138
+ name=ExportFormat.terraform,
139
+ module_path="datacontract.export.terraform_converter",
140
+ class_name="TerraformExporter",
141
+ )
142
+
143
+ exporter_factory.register_lazy_exporter(
144
+ name=ExportFormat.spark, module_path="datacontract.export.spark_converter", class_name="SparkExporter"
145
+ )
@@ -1,6 +1,12 @@
1
1
  import datacontract.model.data_contract_specification as spec
2
2
  from typing import List
3
3
  import re
4
+ from datacontract.export.exporter import Exporter
5
+
6
+
7
+ class GoExporter(Exporter):
8
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
9
+ return to_go_types(data_contract)
4
10
 
5
11
 
6
12
  def to_go_types(contract: spec.DataContractSpecification) -> str:
@@ -4,6 +4,16 @@ from typing import Dict, List, Any
4
4
  import yaml
5
5
 
6
6
  from datacontract.model.data_contract_specification import DataContractSpecification, Field, Quality
7
+ from datacontract.export.exporter import Exporter, _check_models_for_export
8
+
9
+
10
+ class GreateExpectationsExporter(Exporter):
11
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
12
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
13
+ return to_great_expectations(
14
+ data_contract,
15
+ model_name,
16
+ )
7
17
 
8
18
 
9
19
  def to_great_expectations(data_contract_spec: DataContractSpecification, model_key: str) -> str:
@@ -8,6 +8,12 @@ import yaml
8
8
  from jinja2 import Environment, PackageLoader, select_autoescape
9
9
 
10
10
  from datacontract.model.data_contract_specification import DataContractSpecification
11
+ from datacontract.export.exporter import Exporter
12
+
13
+
14
+ class HtmlExporter(Exporter):
15
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
16
+ return to_html(data_contract)
11
17
 
12
18
 
13
19
  def to_html(data_contract_spec: DataContractSpecification) -> str:
@@ -3,6 +3,14 @@ from typing import Dict
3
3
 
4
4
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
5
5
 
6
+ from datacontract.export.exporter import Exporter, _check_models_for_export
7
+
8
+
9
+ class JsonSchemaExporter(Exporter):
10
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
11
+ model_name, model_value = _check_models_for_export(data_contract, model, self.export_format)
12
+ return to_jsonschema_json(model_name, model_value)
13
+
6
14
 
7
15
  def to_jsonschemas(data_contract_spec: DataContractSpecification):
8
16
  jsonschmemas = {}
@@ -17,21 +25,6 @@ def to_jsonschema_json(model_key, model_value: Model) -> str:
17
25
  return json.dumps(jsonschema, indent=2)
18
26
 
19
27
 
20
- def to_jsonschema(model_key, model_value: Model) -> dict:
21
- model = {
22
- "$schema": "http://json-schema.org/draft-07/schema#",
23
- "type": "object",
24
- "properties": to_properties(model_value.fields),
25
- "required": to_required(model_value.fields),
26
- }
27
- if model_value.title:
28
- model["title"] = model_value.title
29
- if model_value.description:
30
- model["description"] = model_value.description
31
-
32
- return model
33
-
34
-
35
28
  def to_properties(fields: Dict[str, Field]) -> dict:
36
29
  properties = {}
37
30
  for field_name, field in fields.items():
@@ -65,27 +58,27 @@ def to_property(field: Field) -> dict:
65
58
  property["pattern"] = field.pattern
66
59
  if field.enum:
67
60
  property["enum"] = field.enum
68
- if field.minLength:
61
+ if field.minLength is not None:
69
62
  property["minLength"] = field.minLength
70
- if field.maxLength:
63
+ if field.maxLength is not None:
71
64
  property["maxLength"] = field.maxLength
72
65
  if field.title:
73
66
  property["title"] = field.title
74
67
  if field.description:
75
68
  property["description"] = field.description
76
- if field.exclusiveMinimum:
69
+ if field.exclusiveMinimum is not None:
77
70
  property["exclusiveMinimum"] = field.exclusiveMinimum
78
- if field.exclusiveMaximum:
71
+ if field.exclusiveMaximum is not None:
79
72
  property["exclusiveMaximum"] = field.exclusiveMaximum
80
- if field.minimum:
73
+ if field.minimum is not None:
81
74
  property["minimum"] = field.minimum
82
- if field.maximum:
75
+ if field.maximum is not None:
83
76
  property["maximum"] = field.maximum
84
77
  if field.tags:
85
78
  property["tags"] = field.tags
86
79
  if field.pii:
87
80
  property["pii"] = field.pii
88
- if field.classification:
81
+ if field.classification is not None:
89
82
  property["classification"] = field.classification
90
83
 
91
84
  # TODO: all constraints
@@ -126,7 +119,7 @@ def convert_type_format(type, format) -> (str, str):
126
119
  return None, None
127
120
 
128
121
 
129
- def convert_format(format):
122
+ def convert_format(self, format):
130
123
  if format is None:
131
124
  return None
132
125
  if format.lower() in ["uri"]:
@@ -138,3 +131,18 @@ def convert_format(format):
138
131
  if format.lower() in ["boolean"]:
139
132
  return "boolean"
140
133
  return None
134
+
135
+
136
+ def to_jsonschema(model_key, model_value: Model) -> dict:
137
+ model = {
138
+ "$schema": "http://json-schema.org/draft-07/schema#",
139
+ "type": "object",
140
+ "properties": to_properties(model_value.fields),
141
+ "required": to_required(model_value.fields),
142
+ }
143
+ if model_value.title:
144
+ model["title"] = model_value.title
145
+ if model_value.description:
146
+ model["description"] = model_value.description
147
+
148
+ return model
@@ -3,6 +3,12 @@ from typing import Dict
3
3
  import yaml
4
4
 
5
5
  from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
6
+ from datacontract.export.exporter import Exporter
7
+
8
+
9
+ class OdcsExporter(Exporter):
10
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
11
+ return to_odcs_yaml(data_contract)
6
12
 
7
13
 
8
14
  def to_odcs_yaml(data_contract_spec: DataContractSpecification):
@@ -24,13 +30,30 @@ def to_odcs_yaml(data_contract_spec: DataContractSpecification):
24
30
 
25
31
  if data_contract_spec.terms is not None:
26
32
  odcs["description"] = {
27
- "purpose": None,
33
+ "purpose": data_contract_spec.terms.description.strip()
34
+ if data_contract_spec.terms.description is not None
35
+ else None,
28
36
  "usage": data_contract_spec.terms.usage.strip() if data_contract_spec.terms.usage is not None else None,
29
37
  "limitations": data_contract_spec.terms.limitations.strip()
30
38
  if data_contract_spec.terms.limitations is not None
31
39
  else None,
32
40
  }
33
41
 
42
+ if data_contract_spec.servicelevels is not None:
43
+ slas = []
44
+ if data_contract_spec.servicelevels.availability is not None:
45
+ slas.append(
46
+ {
47
+ "property": "generalAvailability",
48
+ "value": data_contract_spec.servicelevels.availability.description,
49
+ }
50
+ )
51
+ if data_contract_spec.servicelevels.retention is not None:
52
+ slas.append({"property": "retention", "value": data_contract_spec.servicelevels.retention.period})
53
+
54
+ if len(slas) > 0:
55
+ odcs["slaProperties"] = slas
56
+
34
57
  odcs["type"] = "tables" # required, TODO read from models.type?
35
58
  odcs["dataset"] = []
36
59
 
@@ -1,4 +1,10 @@
1
1
  from datacontract.model.data_contract_specification import DataContractSpecification
2
+ from datacontract.export.exporter import Exporter
3
+
4
+
5
+ class ProtoBufExporter(Exporter):
6
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
7
+ return to_protobuf(data_contract)
2
8
 
3
9
 
4
10
  def to_protobuf(data_contract_spec: DataContractSpecification):
@@ -2,6 +2,12 @@ import ast
2
2
  import typing
3
3
 
4
4
  import datacontract.model.data_contract_specification as spec
5
+ from datacontract.export.exporter import Exporter
6
+
7
+
8
+ class PydanticExporter(Exporter):
9
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
10
+ return to_pydantic_model_str(data_contract)
5
11
 
6
12
 
7
13
  def to_pydantic_model_str(contract: spec.DataContractSpecification) -> str:
@@ -3,6 +3,15 @@ from rdflib import Graph, Literal, BNode, RDF, URIRef, Namespace
3
3
 
4
4
  from datacontract.model.data_contract_specification import DataContractSpecification
5
5
 
6
+ from datacontract.export.exporter import Exporter
7
+
8
+
9
+ class RdfExporter(Exporter):
10
+ def export(self, data_contract, model, server, sql_server_type, export_args) -> dict:
11
+ self.dict_args = export_args
12
+ rdf_base = self.dict_args.get("rdf_base")
13
+ return to_rdf_n3(data_contract_spec=data_contract, base=rdf_base)
14
+
6
15
 
7
16
  def is_literal(property_name):
8
17
  return property_name in [