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.
- datacontract/catalog/catalog.py +4 -2
- datacontract/cli.py +44 -15
- datacontract/data_contract.py +52 -206
- datacontract/engines/fastjsonschema/s3/s3_read_files.py +13 -1
- datacontract/engines/soda/check_soda_execute.py +9 -2
- datacontract/engines/soda/connections/bigquery.py +8 -1
- datacontract/engines/soda/connections/duckdb.py +28 -12
- datacontract/engines/soda/connections/trino.py +26 -0
- datacontract/export/__init__.py +0 -0
- datacontract/export/avro_converter.py +15 -3
- datacontract/export/avro_idl_converter.py +29 -22
- datacontract/export/bigquery_converter.py +15 -0
- datacontract/export/dbml_converter.py +9 -0
- datacontract/export/dbt_converter.py +26 -1
- datacontract/export/exporter.py +88 -0
- datacontract/export/exporter_factory.py +145 -0
- datacontract/export/go_converter.py +6 -0
- datacontract/export/great_expectations_converter.py +10 -0
- datacontract/export/html_export.py +6 -0
- datacontract/export/jsonschema_converter.py +31 -23
- datacontract/export/odcs_converter.py +24 -1
- datacontract/export/protobuf_converter.py +6 -0
- datacontract/export/pydantic_converter.py +6 -0
- datacontract/export/rdf_converter.py +9 -0
- datacontract/export/sodacl_converter.py +23 -12
- datacontract/export/spark_converter.py +211 -0
- datacontract/export/sql_converter.py +32 -2
- datacontract/export/sql_type_converter.py +32 -5
- datacontract/export/terraform_converter.py +6 -0
- datacontract/imports/avro_importer.py +8 -0
- datacontract/imports/bigquery_importer.py +47 -4
- datacontract/imports/glue_importer.py +122 -30
- datacontract/imports/importer.py +29 -0
- datacontract/imports/importer_factory.py +72 -0
- datacontract/imports/jsonschema_importer.py +8 -0
- datacontract/imports/odcs_importer.py +200 -0
- datacontract/imports/sql_importer.py +8 -0
- datacontract/imports/unity_importer.py +152 -0
- datacontract/lint/resolve.py +22 -1
- datacontract/model/data_contract_specification.py +36 -4
- datacontract/templates/datacontract.html +17 -2
- datacontract/templates/partials/datacontract_information.html +20 -0
- datacontract/templates/partials/datacontract_terms.html +7 -0
- datacontract/templates/partials/definition.html +9 -1
- datacontract/templates/partials/model_field.html +23 -6
- datacontract/templates/partials/server.html +113 -48
- datacontract/templates/style/output.css +51 -0
- datacontract/web.py +17 -0
- {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/METADATA +298 -59
- datacontract_cli-0.10.9.dist-info/RECORD +93 -0
- {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/WHEEL +1 -1
- datacontract_cli-0.10.7.dist-info/RECORD +0 -84
- {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.7.dist-info → datacontract_cli-0.10.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from datacontract.imports.importer import Importer
|
|
7
|
+
from datacontract.model.data_contract_specification import DataContractSpecification, Model, Field
|
|
8
|
+
from datacontract.model.exceptions import DataContractException
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UnityImporter(Importer):
|
|
12
|
+
def import_source(
|
|
13
|
+
self, data_contract_specification: DataContractSpecification, source: str, import_args: dict
|
|
14
|
+
) -> dict:
|
|
15
|
+
if source is not None:
|
|
16
|
+
data_contract_specification = import_unity_from_json(data_contract_specification, source)
|
|
17
|
+
else:
|
|
18
|
+
data_contract_specification = import_unity_from_api(
|
|
19
|
+
data_contract_specification, import_args.get("unity_table_full_name")
|
|
20
|
+
)
|
|
21
|
+
return data_contract_specification
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def import_unity_from_json(
|
|
25
|
+
data_contract_specification: DataContractSpecification, source: str
|
|
26
|
+
) -> DataContractSpecification:
|
|
27
|
+
try:
|
|
28
|
+
with open(source, "r") as file:
|
|
29
|
+
unity_schema = json.loads(file.read())
|
|
30
|
+
except json.JSONDecodeError as e:
|
|
31
|
+
raise DataContractException(
|
|
32
|
+
type="schema",
|
|
33
|
+
name="Parse unity schema",
|
|
34
|
+
reason=f"Failed to parse unity schema from {source}",
|
|
35
|
+
engine="datacontract",
|
|
36
|
+
original_exception=e,
|
|
37
|
+
)
|
|
38
|
+
return convert_unity_schema(data_contract_specification, unity_schema)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def import_unity_from_api(
|
|
42
|
+
data_contract_specification: DataContractSpecification, unity_table_full_name: typing.Optional[str] = None
|
|
43
|
+
) -> DataContractSpecification:
|
|
44
|
+
databricks_instance = os.getenv("DATABRICKS_IMPORT_INSTANCE")
|
|
45
|
+
access_token = os.getenv("DATABRICKS_IMPORT_ACCESS_TOKEN")
|
|
46
|
+
|
|
47
|
+
if not databricks_instance or not access_token:
|
|
48
|
+
print("Missing environment variables for Databricks instance or access token.")
|
|
49
|
+
print("Both, $DATABRICKS_IMPORT_INSTANCE and $DATABRICKS_IMPORT_ACCESS_TOKEN must be set.")
|
|
50
|
+
exit(1) # Exit if variables are not set
|
|
51
|
+
|
|
52
|
+
api_url = f"{databricks_instance}/api/2.1/unity-catalog/tables/{unity_table_full_name}"
|
|
53
|
+
|
|
54
|
+
headers = {"Authorization": f"Bearer {access_token}"}
|
|
55
|
+
response = requests.get(api_url, headers=headers)
|
|
56
|
+
|
|
57
|
+
if response.status_code != 200:
|
|
58
|
+
raise DataContractException(
|
|
59
|
+
type="schema",
|
|
60
|
+
name="Retrieve unity catalog schema",
|
|
61
|
+
reason=f"Failed to retrieve unity catalog schema from databricks instance: {response.status_code} {response.text}",
|
|
62
|
+
engine="datacontract",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
convert_unity_schema(data_contract_specification, response.json())
|
|
66
|
+
|
|
67
|
+
return data_contract_specification
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def convert_unity_schema(
|
|
71
|
+
data_contract_specification: DataContractSpecification, unity_schema: dict
|
|
72
|
+
) -> DataContractSpecification:
|
|
73
|
+
if data_contract_specification.models is None:
|
|
74
|
+
data_contract_specification.models = {}
|
|
75
|
+
|
|
76
|
+
fields = import_table_fields(unity_schema.get("columns"))
|
|
77
|
+
|
|
78
|
+
table_id = unity_schema.get("table_id")
|
|
79
|
+
|
|
80
|
+
data_contract_specification.models[table_id] = Model(fields=fields, type="table")
|
|
81
|
+
|
|
82
|
+
if unity_schema.get("name") is not None:
|
|
83
|
+
data_contract_specification.models[table_id].title = unity_schema.get("name")
|
|
84
|
+
|
|
85
|
+
return data_contract_specification
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def import_table_fields(table_fields):
|
|
89
|
+
imported_fields = {}
|
|
90
|
+
for field in table_fields:
|
|
91
|
+
field_name = field.get("name")
|
|
92
|
+
imported_fields[field_name] = Field()
|
|
93
|
+
imported_fields[field_name].required = field.get("nullable") == "false"
|
|
94
|
+
imported_fields[field_name].description = field.get("comment")
|
|
95
|
+
|
|
96
|
+
# databricks api 2.1 specifies that type_name can be any of:
|
|
97
|
+
# BOOLEAN | BYTE | SHORT | INT | LONG | FLOAT | DOUBLE | DATE | TIMESTAMP | TIMESTAMP_NTZ | STRING
|
|
98
|
+
# | BINARY | DECIMAL | INTERVAL | ARRAY | STRUCT | MAP | CHAR | NULL | USER_DEFINED_TYPE | TABLE_TYPE
|
|
99
|
+
if field.get("type_name") in ["INTERVAL", "ARRAY", "STRUCT", "MAP", "USER_DEFINED_TYPE", "TABLE_TYPE"]:
|
|
100
|
+
# complex types are not supported, yet
|
|
101
|
+
raise DataContractException(
|
|
102
|
+
type="schema",
|
|
103
|
+
result="failed",
|
|
104
|
+
name="Map unity type to data contract type",
|
|
105
|
+
reason=f"type ${field.get('type_name')} is not supported yet for unity import",
|
|
106
|
+
engine="datacontract",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
imported_fields[field_name].type = map_type_from_unity(field.get("type_name"))
|
|
110
|
+
|
|
111
|
+
return imported_fields
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def map_type_from_unity(type_str: str):
|
|
115
|
+
if type_str == "BOOLEAN":
|
|
116
|
+
return "boolean"
|
|
117
|
+
elif type_str == "BYTE":
|
|
118
|
+
return "bytes"
|
|
119
|
+
elif type_str == "SHORT":
|
|
120
|
+
return "int"
|
|
121
|
+
elif type_str == "INT":
|
|
122
|
+
return "int"
|
|
123
|
+
elif type_str == "LONG":
|
|
124
|
+
return "long"
|
|
125
|
+
elif type_str == "FLOAT":
|
|
126
|
+
return "float"
|
|
127
|
+
elif type_str == "DOUBLE":
|
|
128
|
+
return "double"
|
|
129
|
+
elif type_str == "DATE":
|
|
130
|
+
return "date"
|
|
131
|
+
elif type_str == "TIMESTAMP":
|
|
132
|
+
return "timestamp"
|
|
133
|
+
elif type_str == "TIMESTAMP_NTZ":
|
|
134
|
+
return "timestamp_ntz"
|
|
135
|
+
elif type_str == "STRING":
|
|
136
|
+
return "string"
|
|
137
|
+
elif type_str == "BINARY":
|
|
138
|
+
return "bytes"
|
|
139
|
+
elif type_str == "DECIMAL":
|
|
140
|
+
return "decimal"
|
|
141
|
+
elif type_str == "CHAR":
|
|
142
|
+
return "varchar"
|
|
143
|
+
elif type_str == "NULL":
|
|
144
|
+
return "null"
|
|
145
|
+
else:
|
|
146
|
+
raise DataContractException(
|
|
147
|
+
type="schema",
|
|
148
|
+
result="failed",
|
|
149
|
+
name="Map unity type to data contract type",
|
|
150
|
+
reason=f"Unsupported type {type_str} in unity json definition.",
|
|
151
|
+
engine="datacontract",
|
|
152
|
+
)
|
datacontract/lint/resolve.py
CHANGED
|
@@ -61,6 +61,10 @@ def inline_definitions_into_data_contract(spec: DataContractSpecification):
|
|
|
61
61
|
for field_name in field.model_fields.keys():
|
|
62
62
|
if field_name in definition.model_fields_set and field_name not in field.model_fields_set:
|
|
63
63
|
setattr(field, field_name, getattr(definition, field_name))
|
|
64
|
+
# extras
|
|
65
|
+
for extra_field_name, extra_field_value in definition.model_extra.items():
|
|
66
|
+
if extra_field_name not in field.model_extra.keys():
|
|
67
|
+
setattr(field, extra_field_name, extra_field_value)
|
|
64
68
|
|
|
65
69
|
|
|
66
70
|
def resolve_definition_ref(ref, definitions) -> Definition:
|
|
@@ -68,7 +72,11 @@ def resolve_definition_ref(ref, definitions) -> Definition:
|
|
|
68
72
|
definition_str = fetch_resource(ref)
|
|
69
73
|
definition_dict = to_yaml(definition_str)
|
|
70
74
|
return Definition(**definition_dict)
|
|
71
|
-
|
|
75
|
+
elif ref.startswith("file://"):
|
|
76
|
+
path = ref.replace("file://", "")
|
|
77
|
+
definition_str = fetch_file(path)
|
|
78
|
+
definition_dict = to_yaml(definition_str)
|
|
79
|
+
return Definition(**definition_dict)
|
|
72
80
|
elif ref.startswith("#/definitions/"):
|
|
73
81
|
definition_name = ref.split("#/definitions/")[1]
|
|
74
82
|
return definitions[definition_name]
|
|
@@ -82,6 +90,19 @@ def resolve_definition_ref(ref, definitions) -> Definition:
|
|
|
82
90
|
)
|
|
83
91
|
|
|
84
92
|
|
|
93
|
+
def fetch_file(path) -> str:
|
|
94
|
+
if not os.path.exists(path):
|
|
95
|
+
raise DataContractException(
|
|
96
|
+
type="export",
|
|
97
|
+
result="failed",
|
|
98
|
+
name="Check that data contract definition is valid",
|
|
99
|
+
reason=f"Cannot resolve reference {path}",
|
|
100
|
+
engine="datacontract",
|
|
101
|
+
)
|
|
102
|
+
with open(path, "r") as file:
|
|
103
|
+
return file.read()
|
|
104
|
+
|
|
105
|
+
|
|
85
106
|
def resolve_quality_ref(quality: Quality):
|
|
86
107
|
"""
|
|
87
108
|
Return the content of a ref file path
|
|
@@ -10,9 +10,15 @@ class Contact(pyd.BaseModel):
|
|
|
10
10
|
url: str = None
|
|
11
11
|
email: str = None
|
|
12
12
|
|
|
13
|
+
model_config = pyd.ConfigDict(
|
|
14
|
+
extra="allow",
|
|
15
|
+
)
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
class Server(pyd.BaseModel):
|
|
15
19
|
type: str = None
|
|
20
|
+
description: str = None
|
|
21
|
+
environment: str = None
|
|
16
22
|
format: str = None
|
|
17
23
|
project: str = None
|
|
18
24
|
dataset: str = None
|
|
@@ -33,12 +39,21 @@ class Server(pyd.BaseModel):
|
|
|
33
39
|
outputPortId: str = None
|
|
34
40
|
driver: str = None
|
|
35
41
|
|
|
42
|
+
model_config = pyd.ConfigDict(
|
|
43
|
+
extra="allow",
|
|
44
|
+
)
|
|
45
|
+
|
|
36
46
|
|
|
37
47
|
class Terms(pyd.BaseModel):
|
|
38
48
|
usage: str = None
|
|
39
49
|
limitations: str = None
|
|
40
50
|
billing: str = None
|
|
41
51
|
noticePeriod: str = None
|
|
52
|
+
description: str = None
|
|
53
|
+
|
|
54
|
+
model_config = pyd.ConfigDict(
|
|
55
|
+
extra="allow",
|
|
56
|
+
)
|
|
42
57
|
|
|
43
58
|
|
|
44
59
|
class Definition(pyd.BaseModel):
|
|
@@ -59,8 +74,13 @@ class Definition(pyd.BaseModel):
|
|
|
59
74
|
pii: bool = None
|
|
60
75
|
classification: str = None
|
|
61
76
|
tags: List[str] = []
|
|
77
|
+
links: Dict[str, str] = {}
|
|
62
78
|
example: str = None
|
|
63
79
|
|
|
80
|
+
model_config = pyd.ConfigDict(
|
|
81
|
+
extra="allow",
|
|
82
|
+
)
|
|
83
|
+
|
|
64
84
|
|
|
65
85
|
class Field(pyd.BaseModel):
|
|
66
86
|
ref: str = pyd.Field(default=None, alias="$ref")
|
|
@@ -84,6 +104,7 @@ class Field(pyd.BaseModel):
|
|
|
84
104
|
exclusiveMaximum: int = None
|
|
85
105
|
enum: List[str] = []
|
|
86
106
|
tags: List[str] = []
|
|
107
|
+
links: Dict[str, str] = {}
|
|
87
108
|
fields: Dict[str, "Field"] = {}
|
|
88
109
|
items: "Field" = None
|
|
89
110
|
precision: int = None
|
|
@@ -91,13 +112,18 @@ class Field(pyd.BaseModel):
|
|
|
91
112
|
example: str = None
|
|
92
113
|
config: Dict[str, Any] = None
|
|
93
114
|
|
|
115
|
+
model_config = pyd.ConfigDict(
|
|
116
|
+
extra="allow",
|
|
117
|
+
)
|
|
118
|
+
|
|
94
119
|
|
|
95
120
|
class Model(pyd.BaseModel):
|
|
96
|
-
description: str = None
|
|
97
|
-
type: str = None
|
|
98
|
-
namespace: str = None
|
|
99
|
-
title: str = None
|
|
121
|
+
description: Optional[str] = None
|
|
122
|
+
type: Optional[str] = None
|
|
123
|
+
namespace: Optional[str] = None
|
|
124
|
+
title: Optional[str] = None
|
|
100
125
|
fields: Dict[str, Field] = {}
|
|
126
|
+
config: Dict[str, Any] = None
|
|
101
127
|
|
|
102
128
|
|
|
103
129
|
class Info(pyd.BaseModel):
|
|
@@ -108,6 +134,10 @@ class Info(pyd.BaseModel):
|
|
|
108
134
|
owner: str = None
|
|
109
135
|
contact: Contact = None
|
|
110
136
|
|
|
137
|
+
model_config = pyd.ConfigDict(
|
|
138
|
+
extra="allow",
|
|
139
|
+
)
|
|
140
|
+
|
|
111
141
|
|
|
112
142
|
class Example(pyd.BaseModel):
|
|
113
143
|
type: str = None
|
|
@@ -189,6 +219,8 @@ class DataContractSpecification(pyd.BaseModel):
|
|
|
189
219
|
examples: List[Example] = []
|
|
190
220
|
quality: Quality = None
|
|
191
221
|
servicelevels: Optional[ServiceLevel] = None
|
|
222
|
+
links: Dict[str, str] = {}
|
|
223
|
+
tags: List[str] = []
|
|
192
224
|
|
|
193
225
|
@classmethod
|
|
194
226
|
def from_file(cls, file):
|
|
@@ -41,6 +41,21 @@
|
|
|
41
41
|
<div class="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
|
|
42
42
|
{{ datacontract.id }}
|
|
43
43
|
</div>
|
|
44
|
+
<div class="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
|
|
45
|
+
{% if datacontract.tags %}
|
|
46
|
+
<div class="mt-2 flex items-center text-sm text-gray-500 whitespace-nowrap">
|
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400">
|
|
48
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" />
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
|
|
50
|
+
</svg>
|
|
51
|
+
{% for tag in datacontract.tags %}
|
|
52
|
+
<span class="inline-flex items-center rounded-full bg-indigo-100 px-2.5 py-0.5 text-xs font-medium text-gray-800 mr-1">
|
|
53
|
+
<span>{{ tag }}</span>
|
|
54
|
+
</span>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</div>
|
|
57
|
+
{% endif %}
|
|
58
|
+
</div>
|
|
44
59
|
</div>
|
|
45
60
|
<div class="mt-5 flex lg:mt-0 lg:ml-4 gap-3 items-center">
|
|
46
61
|
<button
|
|
@@ -129,14 +144,14 @@
|
|
|
129
144
|
</div>
|
|
130
145
|
{% endfor %}
|
|
131
146
|
</section>
|
|
132
|
-
|
|
147
|
+
|
|
133
148
|
{% if datacontract.definitions %}
|
|
134
149
|
<section id="definitions">
|
|
135
150
|
<div class="px-4 sm:px-0">
|
|
136
151
|
<h1 class="text-base font-semibold leading-6 text-gray-900">Definitions</h1>
|
|
137
152
|
<p class="text-sm text-gray-500">Domain specific definitions in the data contract</p>
|
|
138
153
|
</div>
|
|
139
|
-
|
|
154
|
+
|
|
140
155
|
{% for definition_name, definition in datacontract.definitions.items() %}
|
|
141
156
|
{{ render_partial('partials/definition.html', definition_name = definition_name, definition = definition)}}
|
|
142
157
|
{% endfor %}
|
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
<div class="px-4 py-5 sm:px-6">
|
|
8
8
|
|
|
9
|
+
{% if datacontract.links %}
|
|
10
|
+
<div class="flex flex-wrap gap-3">
|
|
11
|
+
{% for name, href in datacontract.links.items() %}
|
|
12
|
+
<a href="{{ href }}" class="flex flex-col text-center rounded-md bg-white px-2 py-2 text-sm font-medium text-gray-500 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 mb-6" target="_blank" style="min-width: 100px">
|
|
13
|
+
<div class="mx-auto w-8 h-8 my-2">
|
|
14
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M23.91,5.29a3.13,3.13,0,0,0-.72-1.23.35.35,0,0,0-.49,0,.34.34,0,0,0,0,.48,2.66,2.66,0,0,1,.54,1,1.56,1.56,0,0,1-.1,1,19.42,19.42,0,0,1-3.49,3.64,9.62,9.62,0,0,1-2.74,1.8,5.51,5.51,0,0,1-1.71.45.91.91,0,0,1-.73-.3c-.2-.22-.52-.52-.82-.83.66-.65,1.32-1.35,2.08-2.09a.3.3,0,0,0,0-.43.31.31,0,0,0-.43,0c-.84.6-1.6,1.14-2.32,1.69a7.18,7.18,0,0,1-.32-.81,5.23,5.23,0,0,1-.23-1,3.23,3.23,0,0,1,.94-2.5,31.64,31.64,0,0,1,3.65-3,5.24,5.24,0,0,1,1.63-1,2.56,2.56,0,0,1,1.77,0,2.08,2.08,0,0,1,.65.41,2.31,2.31,0,0,1,.49.61.3.3,0,0,0,.54-.26A2.78,2.78,0,0,0,21.52,2a2.91,2.91,0,0,0-.84-.57,3.23,3.23,0,0,0-1.81-.18,5.64,5.64,0,0,0-2.38,1.13,30.94,30.94,0,0,0-3.91,3,4.26,4.26,0,0,0-1.29,3.34,6.18,6.18,0,0,0,.27,1.23A11.36,11.36,0,0,0,12,11.06l0,.07-1.21,1c-.76.67-1.5,1.37-2.38,2.07a.34.34,0,0,0-.09.48.35.35,0,0,0,.48.09c1-.58,1.81-1.14,2.62-1.74.36-.26.71-.54,1-.83l.24-.21.82.82a2,2,0,0,0,1.6.67,6.72,6.72,0,0,0,2-.57,10.52,10.52,0,0,0,3-2.1,19.88,19.88,0,0,0,3.58-4.07A2.3,2.3,0,0,0,23.91,5.29Z" fill="#191919" fill-rule="evenodd"></path><path d="M11.86,14.07a.34.34,0,0,0,0,.48.81.81,0,0,1,.29.57,1.28,1.28,0,0,1-.27.65,29.38,29.38,0,0,1-3.6,3.65,9.07,9.07,0,0,1-2.73,1.79,5.43,5.43,0,0,1-1.71.45,1,1,0,0,1-.73-.31c-.22-.25-.6-.61-.93-1a2.71,2.71,0,0,1-.43-.56,9.71,9.71,0,0,1-.39-1,4.53,4.53,0,0,1-.22-1,3.21,3.21,0,0,1,1-2.51,30.47,30.47,0,0,1,3.66-3,5.14,5.14,0,0,1,1.65-1,2.62,2.62,0,0,1,1.79,0,.3.3,0,0,0,.4-.16.31.31,0,0,0-.17-.4,3.16,3.16,0,0,0-1.79-.19A5.63,5.63,0,0,0,5.27,11.7a30.41,30.41,0,0,0-3.93,2.94A4.21,4.21,0,0,0,0,18a6,6,0,0,0,.19,1A9.53,9.53,0,0,0,.7,20.3a3.53,3.53,0,0,0,.47.69c.36.41.83.84,1.09,1.13a2,2,0,0,0,1.59.68,6.21,6.21,0,0,0,2.05-.56,10,10,0,0,0,3-2.1,30.27,30.27,0,0,0,3.6-4,2,2,0,0,0,.36-1.07,1.46,1.46,0,0,0-.53-1A.35.35,0,0,0,11.86,14.07Z" fill="#191919" fill-rule="evenodd"></path><path d="M6.43,15.15l1.16-.79A.31.31,0,0,0,7.73,14a.3.3,0,0,0-.41-.13L6,14.49a7.39,7.39,0,0,0-2.3,1.83,2.35,2.35,0,0,0-.38,2.26,1,1,0,0,0,1,.68,2.41,2.41,0,0,0,1.17-.43,9.29,9.29,0,0,0,.91-.75c.66-.6,1.25-1.26,1.86-1.85a.35.35,0,0,0,0-.49.36.36,0,0,0-.49,0c-.57.45-1.13.95-1.73,1.42a10.58,10.58,0,0,1-1.17.81c-1.23.74-.36-.89-.3-1A7.43,7.43,0,0,1,6.43,15.15Z" fill="#0c6fff" fill-rule="evenodd"></path><path d="M19,7a6.13,6.13,0,0,1-1.26,1.28c-.21.15-.5.38-.78.56a1.9,1.9,0,0,1-.49.25.34.34,0,0,0-.25.41.34.34,0,0,0,.41.26,2.47,2.47,0,0,0,.55-.2c.35-.18.73-.44,1-.61a6.9,6.9,0,0,0,1.57-1.33A8.69,8.69,0,0,0,20.9,6a3.7,3.7,0,0,0,.43-1.13A1,1,0,0,0,21,4a1.36,1.36,0,0,0-.89-.39,3,3,0,0,0-1.08.17,6.4,6.4,0,0,0-1.63.82,15.32,15.32,0,0,0-2,1.7,4.54,4.54,0,0,0-.65.74,2.69,2.69,0,0,0-.31.61,3.5,3.5,0,0,0-.07.9.33.33,0,0,0,.17.24A.31.31,0,0,0,15,8.63c.18-.26-.17-.6.47-1.3A5.64,5.64,0,0,1,16,6.88a19,19,0,0,1,2-1.42,5.88,5.88,0,0,1,1.39-.61A1.87,1.87,0,0,1,20,4.73c.54,0,.12.31-.08.76A7.29,7.29,0,0,1,19,7Z" fill="#0c6fff" fill-rule="evenodd"></path></g></svg>
|
|
15
|
+
</div>
|
|
16
|
+
<div>{{ name }}</div>
|
|
17
|
+
</a>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</div>
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
9
22
|
<dl class="grid grid-cols-1 gap-x-4 gap-y-6 sm:grid-cols-2">
|
|
10
23
|
<div class="sm:col-span-1">
|
|
11
24
|
<dt class="text-sm font-medium text-gray-500">Title</dt>
|
|
@@ -42,6 +55,13 @@
|
|
|
42
55
|
</div>
|
|
43
56
|
{% endif %}
|
|
44
57
|
|
|
58
|
+
{% for key, value in datacontract.info.model_extra.items() %}
|
|
59
|
+
<div class="sm:col-span-1">
|
|
60
|
+
<dt class="text-sm font-medium text-gray-500">{{ key }}</dt>
|
|
61
|
+
<dd class="mt-1 text-sm text-gray-900">{{ value }}</dd>
|
|
62
|
+
</div>
|
|
63
|
+
{% endfor %}
|
|
64
|
+
|
|
45
65
|
{% if datacontract.info.contact %}
|
|
46
66
|
<div class="sm:col-span-1">
|
|
47
67
|
<dt class="text-sm font-medium text-gray-500">Contact</dt>
|
|
@@ -39,6 +39,13 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
{% endif %}
|
|
41
41
|
|
|
42
|
+
{% for key, value in datacontract.terms.model_extra.items() %}
|
|
43
|
+
<div class="sm:col-span-1">
|
|
44
|
+
<dt class="text-sm font-medium text-gray-500">{{ key }}</dt>
|
|
45
|
+
<dd class="mt-1 text-sm text-gray-900">{{ value }}</dd>
|
|
46
|
+
</div>
|
|
47
|
+
{% endfor %}
|
|
48
|
+
|
|
42
49
|
</dl>
|
|
43
50
|
</div>
|
|
44
51
|
</div>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
</tr>
|
|
15
15
|
</thead>
|
|
16
16
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
17
|
-
<tr>
|
|
17
|
+
<tr id="/definitions/{{ definition_name }}">
|
|
18
18
|
<td class="whitespace-nowrap py-2 pl-4 pr-2 text-sm font-medium text-gray-900 sm:pl-6 w-2/12">
|
|
19
19
|
<div class="py-2 text-sm">
|
|
20
20
|
{% if definition.title %}
|
|
@@ -87,6 +87,14 @@
|
|
|
87
87
|
{% if definition.pii %}
|
|
88
88
|
<span class="inline-flex items-center rounded-md bg-yellow-50 px-1 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-yellow-500/10 mr-1 mt-1">PII</span>
|
|
89
89
|
{% endif %}
|
|
90
|
+
{% for key, value in definition.model_extra.items() %}
|
|
91
|
+
<span class="inline-flex items-center rounded-md bg-gray-50 px-1 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 mr-1 mt-1">{{ key }}:{{ value }}</span>
|
|
92
|
+
{% endfor %}
|
|
93
|
+
{% if definition.links %}
|
|
94
|
+
{% for name,href in definition.links.items() %}
|
|
95
|
+
<a href="{{ href }}" class="inline-flex items-center px-1 py-1 mr-1 mt-1 text-sky-500 hover:text-gray-700 text-xs font-semibold">{{ name }}</a>
|
|
96
|
+
{% endfor %}
|
|
97
|
+
{% endif %}
|
|
90
98
|
</div>
|
|
91
99
|
</td>
|
|
92
100
|
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
{% endif %}
|
|
15
15
|
<div class="py-2 text-sm">
|
|
16
16
|
{% if field.title %}
|
|
17
|
-
<
|
|
17
|
+
<span>{{ field.title }}</span><br>
|
|
18
18
|
{% endif %}
|
|
19
|
-
<
|
|
19
|
+
<span class="font-mono flex">{{ field_name }}{% if field.ref %} <a href="{{ field.ref }}">
|
|
20
|
+
<svg title="Definition" class="mr-1.5 h-5 w-5 flex-shrink-0" xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24"><defs></defs><path d="M3.046875 5.15625h16.40625s0.9375 0 0.9375 0.9375v10.3125s0 0.9375 -0.9375 0.9375H3.046875s-0.9375 0 -0.9375 -0.9375v-10.3125s0 -0.9375 0.9375 -0.9375" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m12.568125 10.3125 4.6875 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="m12.568125 13.125 4.6875 0" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M5.068124999999999 8.4375h4.6875v4.6875h-4.6875Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>
|
|
21
|
+
</a>{% endif %}</span>
|
|
20
22
|
</div>
|
|
21
23
|
</td>
|
|
22
24
|
<td class="whitespace-nowrap px-1 py-2 text-sm text-gray-500 w-1/12">
|
|
@@ -83,15 +85,30 @@
|
|
|
83
85
|
{% if field.pii %}
|
|
84
86
|
<span class="inline-flex items-center rounded-md bg-yellow-50 px-1 py-1 text-xs font-medium text-yellow-600 ring-1 ring-inset ring-yellow-500/10 mr-1 mt-1">PII</span>
|
|
85
87
|
{% endif %}
|
|
88
|
+
{% for key, value in field.model_extra.items() %}
|
|
89
|
+
<span class="inline-flex items-center rounded-md bg-gray-50 px-1 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 mr-1 mt-1">{{ key }}:{{ value }}</span>
|
|
90
|
+
{% endfor %}
|
|
91
|
+
{% if field.links %}
|
|
92
|
+
{% for name,href in field.links.items() %}
|
|
93
|
+
<a href="{{ href }}" class="inline-flex items-center px-1 py-1 mr-1 mt-1 text-sky-500 hover:text-gray-700 text-xs font-semibold">{{ name }}</a>
|
|
94
|
+
{% endfor %}
|
|
95
|
+
{% endif %}
|
|
86
96
|
</div>
|
|
87
97
|
</td>
|
|
88
98
|
</tr>
|
|
89
99
|
|
|
100
|
+
{% macro render_nested_partial(field_name, field, level) %}
|
|
101
|
+
{{ render_partial('partials/model_field.html', nested=True, field_name=field_name, field=field, level=level + 1) }}
|
|
102
|
+
<!-- Mark the end of the contained fields -->
|
|
103
|
+
<tr style="--tw-divide-y-reverse: 2"></tr>
|
|
104
|
+
{% endmacro %}
|
|
105
|
+
|
|
90
106
|
{% if field.fields %}
|
|
91
107
|
{% for field_name, field in field.fields.items() %}
|
|
92
|
-
|
|
108
|
+
{{ render_nested_partial(field_name, field, level) }}
|
|
93
109
|
{% endfor %}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
{% endif %}
|
|
111
|
+
|
|
112
|
+
{% if field.items %}
|
|
113
|
+
{{ render_nested_partial("item", field.items, level) }}
|
|
97
114
|
{% endif %}
|