datacontract-cli 0.10.8__py3-none-any.whl → 0.10.10__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 +36 -18
- datacontract/data_contract.py +13 -53
- datacontract/engines/soda/check_soda_execute.py +10 -2
- datacontract/engines/soda/connections/duckdb.py +32 -12
- datacontract/engines/soda/connections/trino.py +26 -0
- datacontract/export/avro_converter.py +1 -1
- datacontract/export/exporter.py +3 -2
- datacontract/export/exporter_factory.py +132 -39
- datacontract/export/jsonschema_converter.py +7 -7
- datacontract/export/sodacl_converter.py +17 -12
- datacontract/export/spark_converter.py +211 -0
- datacontract/export/sql_type_converter.py +28 -0
- datacontract/imports/avro_importer.py +149 -7
- datacontract/imports/bigquery_importer.py +17 -0
- datacontract/imports/dbt_importer.py +117 -0
- datacontract/imports/glue_importer.py +116 -33
- datacontract/imports/importer.py +34 -0
- datacontract/imports/importer_factory.py +90 -0
- datacontract/imports/jsonschema_importer.py +14 -3
- datacontract/imports/odcs_importer.py +8 -0
- datacontract/imports/spark_importer.py +134 -0
- datacontract/imports/sql_importer.py +8 -0
- datacontract/imports/unity_importer.py +23 -9
- datacontract/integration/publish_datamesh_manager.py +10 -5
- datacontract/lint/resolve.py +87 -21
- datacontract/lint/schema.py +24 -4
- datacontract/model/data_contract_specification.py +37 -4
- datacontract/templates/datacontract.html +18 -3
- datacontract/templates/index.html +1 -1
- 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 +49 -16
- datacontract/templates/style/output.css +42 -0
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/METADATA +310 -122
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/RECORD +42 -36
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/WHEEL +1 -1
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.8.dist-info → datacontract_cli-0.10.10.dist-info}/top_level.txt +0 -0
datacontract/lint/resolve.py
CHANGED
|
@@ -25,7 +25,7 @@ def resolve_data_contract(
|
|
|
25
25
|
data_contract_location, schema_location, inline_definitions, inline_quality
|
|
26
26
|
)
|
|
27
27
|
elif data_contract_str is not None:
|
|
28
|
-
return
|
|
28
|
+
return _resolve_data_contract_from_str(data_contract_str, schema_location, inline_definitions, inline_quality)
|
|
29
29
|
elif data_contract is not None:
|
|
30
30
|
return data_contract
|
|
31
31
|
else:
|
|
@@ -45,7 +45,7 @@ def resolve_data_contract_from_location(
|
|
|
45
45
|
data_contract_str = fetch_resource(location)
|
|
46
46
|
else:
|
|
47
47
|
data_contract_str = read_file(location)
|
|
48
|
-
return
|
|
48
|
+
return _resolve_data_contract_from_str(data_contract_str, schema_location, inline_definitions, inline_quality)
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def inline_definitions_into_data_contract(spec: DataContractSpecification):
|
|
@@ -55,23 +55,53 @@ def inline_definitions_into_data_contract(spec: DataContractSpecification):
|
|
|
55
55
|
if not field.ref and not field.ref_obj:
|
|
56
56
|
continue
|
|
57
57
|
|
|
58
|
-
definition =
|
|
58
|
+
definition = _resolve_definition_ref(field.ref, spec)
|
|
59
59
|
field.ref_obj = definition
|
|
60
60
|
|
|
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
|
-
def
|
|
67
|
-
|
|
68
|
-
definition_str = fetch_resource(ref)
|
|
69
|
-
definition_dict = to_yaml(definition_str)
|
|
70
|
-
return Definition(**definition_dict)
|
|
70
|
+
def _resolve_definition_ref(ref, spec) -> Definition:
|
|
71
|
+
logging.info(f"Resolving definition ref {ref}")
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
if "#" in ref:
|
|
74
|
+
path, definition_path = ref.split("#")
|
|
75
|
+
else:
|
|
76
|
+
path, definition_path = ref, None
|
|
77
|
+
|
|
78
|
+
if path.startswith("http://") or path.startswith("https://"):
|
|
79
|
+
logging.info(f"Resolving definition url {path}")
|
|
80
|
+
|
|
81
|
+
definition_str = fetch_resource(path)
|
|
82
|
+
definition_dict = _to_yaml(definition_str)
|
|
83
|
+
definition = Definition(**definition_dict)
|
|
84
|
+
if definition_path is not None:
|
|
85
|
+
return _find_by_path_in_definition(definition_path, definition)
|
|
86
|
+
else:
|
|
87
|
+
return definition
|
|
88
|
+
elif path.startswith("file://"):
|
|
89
|
+
logging.info(f"Resolving definition file path {path}")
|
|
90
|
+
|
|
91
|
+
path = path.replace("file://", "")
|
|
92
|
+
definition_str = _fetch_file(path)
|
|
93
|
+
definition_dict = _to_yaml(definition_str)
|
|
94
|
+
definition = Definition(**definition_dict)
|
|
95
|
+
if definition_path is not None:
|
|
96
|
+
return _find_by_path_in_definition(definition_path, definition)
|
|
97
|
+
else:
|
|
98
|
+
return definition
|
|
99
|
+
elif ref.startswith("#"):
|
|
100
|
+
logging.info(f"Resolving definition local path {path}")
|
|
101
|
+
|
|
102
|
+
definition_path = ref[1:]
|
|
103
|
+
|
|
104
|
+
return _find_by_path_in_spec(definition_path, spec)
|
|
75
105
|
else:
|
|
76
106
|
raise DataContractException(
|
|
77
107
|
type="lint",
|
|
@@ -82,7 +112,43 @@ def resolve_definition_ref(ref, definitions) -> Definition:
|
|
|
82
112
|
)
|
|
83
113
|
|
|
84
114
|
|
|
85
|
-
def
|
|
115
|
+
def _find_by_path_in_spec(definition_path: str, spec: DataContractSpecification):
|
|
116
|
+
path_elements = definition_path.split("/")
|
|
117
|
+
definition = spec.definitions[path_elements[2]]
|
|
118
|
+
definition = _find_subfield_in_definition(definition, path_elements[3:])
|
|
119
|
+
return definition
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _find_by_path_in_definition(definition_path: str, definition: Definition):
|
|
123
|
+
if definition_path == "" or definition_path == "/":
|
|
124
|
+
return definition
|
|
125
|
+
|
|
126
|
+
path_elements = definition_path.split("/")
|
|
127
|
+
return _find_subfield_in_definition(definition, path_elements[1:])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _find_subfield_in_definition(definition: Definition, path_elements):
|
|
131
|
+
while len(path_elements) > 0 and path_elements[0] == "fields":
|
|
132
|
+
definition = definition.fields[path_elements[1]]
|
|
133
|
+
path_elements = path_elements[2:]
|
|
134
|
+
|
|
135
|
+
return definition
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _fetch_file(path) -> str:
|
|
139
|
+
if not os.path.exists(path):
|
|
140
|
+
raise DataContractException(
|
|
141
|
+
type="export",
|
|
142
|
+
result="failed",
|
|
143
|
+
name="Check that data contract definition is valid",
|
|
144
|
+
reason=f"Cannot resolve reference {path}",
|
|
145
|
+
engine="datacontract",
|
|
146
|
+
)
|
|
147
|
+
with open(path, "r") as file:
|
|
148
|
+
return file.read()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _resolve_quality_ref(quality: Quality):
|
|
86
152
|
"""
|
|
87
153
|
Return the content of a ref file path
|
|
88
154
|
@param quality data contract quality specification
|
|
@@ -91,13 +157,13 @@ def resolve_quality_ref(quality: Quality):
|
|
|
91
157
|
specification = quality.specification
|
|
92
158
|
if quality.type == "great-expectations":
|
|
93
159
|
for model, model_quality in specification.items():
|
|
94
|
-
specification[model] =
|
|
160
|
+
specification[model] = _get_quality_ref_file(model_quality)
|
|
95
161
|
else:
|
|
96
162
|
if "$ref" in specification:
|
|
97
|
-
quality.specification =
|
|
163
|
+
quality.specification = _get_quality_ref_file(specification)
|
|
98
164
|
|
|
99
165
|
|
|
100
|
-
def
|
|
166
|
+
def _get_quality_ref_file(quality_spec: str | object) -> str | object:
|
|
101
167
|
"""
|
|
102
168
|
Get the file associated with a quality reference
|
|
103
169
|
@param quality_spec quality specification
|
|
@@ -118,23 +184,23 @@ def get_quality_ref_file(quality_spec: str | object) -> str | object:
|
|
|
118
184
|
return quality_spec
|
|
119
185
|
|
|
120
186
|
|
|
121
|
-
def
|
|
187
|
+
def _resolve_data_contract_from_str(
|
|
122
188
|
data_contract_str, schema_location: str = None, inline_definitions: bool = False, inline_quality: bool = False
|
|
123
189
|
) -> DataContractSpecification:
|
|
124
|
-
data_contract_yaml_dict =
|
|
125
|
-
|
|
190
|
+
data_contract_yaml_dict = _to_yaml(data_contract_str)
|
|
191
|
+
_validate(data_contract_yaml_dict, schema_location)
|
|
126
192
|
|
|
127
193
|
spec = DataContractSpecification(**data_contract_yaml_dict)
|
|
128
194
|
|
|
129
195
|
if inline_definitions:
|
|
130
196
|
inline_definitions_into_data_contract(spec)
|
|
131
197
|
if spec.quality and inline_quality:
|
|
132
|
-
|
|
198
|
+
_resolve_quality_ref(spec.quality)
|
|
133
199
|
|
|
134
200
|
return spec
|
|
135
201
|
|
|
136
202
|
|
|
137
|
-
def
|
|
203
|
+
def _to_yaml(data_contract_str):
|
|
138
204
|
try:
|
|
139
205
|
yaml_dict = yaml.safe_load(data_contract_str)
|
|
140
206
|
return yaml_dict
|
|
@@ -149,7 +215,7 @@ def to_yaml(data_contract_str):
|
|
|
149
215
|
)
|
|
150
216
|
|
|
151
217
|
|
|
152
|
-
def
|
|
218
|
+
def _validate(data_contract_yaml, schema_location: str = None):
|
|
153
219
|
schema = fetch_schema(schema_location)
|
|
154
220
|
try:
|
|
155
221
|
fastjsonschema.validate(schema, data_contract_yaml)
|
datacontract/lint/schema.py
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
+
from typing import Dict, Any
|
|
3
4
|
|
|
4
5
|
import requests
|
|
5
6
|
|
|
6
7
|
from datacontract.model.exceptions import DataContractException
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def fetch_schema(location: str = None):
|
|
10
|
+
def fetch_schema(location: str = None) -> Dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
Fetch and return a JSON schema from a given location.
|
|
13
|
+
|
|
14
|
+
This function retrieves a JSON schema either from a URL or a local file path.
|
|
15
|
+
If no location is provided, it defaults to the DataContract schema URL.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
location: The URL or file path of the schema.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The JSON schema as a dictionary.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
DataContractException: If the specified local file does not exist.
|
|
25
|
+
requests.RequestException: If there's an error fetching the schema from a URL.
|
|
26
|
+
json.JSONDecodeError: If there's an error decoding the JSON schema.
|
|
27
|
+
|
|
28
|
+
"""
|
|
10
29
|
if location is None:
|
|
11
30
|
location = "https://datacontract.com/datacontract.schema.json"
|
|
12
31
|
|
|
13
32
|
if location.startswith("http://") or location.startswith("https://"):
|
|
14
33
|
response = requests.get(location)
|
|
15
|
-
|
|
34
|
+
schema = response.json()
|
|
16
35
|
else:
|
|
17
36
|
if not os.path.exists(location):
|
|
18
37
|
raise DataContractException(
|
|
@@ -23,5 +42,6 @@ def fetch_schema(location: str = None):
|
|
|
23
42
|
result="error",
|
|
24
43
|
)
|
|
25
44
|
with open(location, "r") as file:
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
schema = json.load(file)
|
|
46
|
+
|
|
47
|
+
return schema
|
|
@@ -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,6 +39,10 @@ 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
|
|
@@ -41,6 +51,10 @@ class Terms(pyd.BaseModel):
|
|
|
41
51
|
noticePeriod: str = None
|
|
42
52
|
description: str = None
|
|
43
53
|
|
|
54
|
+
model_config = pyd.ConfigDict(
|
|
55
|
+
extra="allow",
|
|
56
|
+
)
|
|
57
|
+
|
|
44
58
|
|
|
45
59
|
class Definition(pyd.BaseModel):
|
|
46
60
|
domain: str = None
|
|
@@ -59,9 +73,15 @@ class Definition(pyd.BaseModel):
|
|
|
59
73
|
exclusiveMaximum: int = None
|
|
60
74
|
pii: bool = None
|
|
61
75
|
classification: str = None
|
|
76
|
+
fields: Dict[str, "Definition"] = {}
|
|
62
77
|
tags: List[str] = []
|
|
78
|
+
links: Dict[str, str] = {}
|
|
63
79
|
example: str = None
|
|
64
80
|
|
|
81
|
+
model_config = pyd.ConfigDict(
|
|
82
|
+
extra="allow",
|
|
83
|
+
)
|
|
84
|
+
|
|
65
85
|
|
|
66
86
|
class Field(pyd.BaseModel):
|
|
67
87
|
ref: str = pyd.Field(default=None, alias="$ref")
|
|
@@ -85,19 +105,26 @@ class Field(pyd.BaseModel):
|
|
|
85
105
|
exclusiveMaximum: int = None
|
|
86
106
|
enum: List[str] = []
|
|
87
107
|
tags: List[str] = []
|
|
108
|
+
links: Dict[str, str] = {}
|
|
88
109
|
fields: Dict[str, "Field"] = {}
|
|
89
110
|
items: "Field" = None
|
|
111
|
+
keys: "Field" = None
|
|
112
|
+
values: "Field" = None
|
|
90
113
|
precision: int = None
|
|
91
114
|
scale: int = None
|
|
92
115
|
example: str = None
|
|
93
116
|
config: Dict[str, Any] = None
|
|
94
117
|
|
|
118
|
+
model_config = pyd.ConfigDict(
|
|
119
|
+
extra="allow",
|
|
120
|
+
)
|
|
121
|
+
|
|
95
122
|
|
|
96
123
|
class Model(pyd.BaseModel):
|
|
97
|
-
description: str = None
|
|
98
|
-
type: str = None
|
|
99
|
-
namespace: str = None
|
|
100
|
-
title: str = None
|
|
124
|
+
description: Optional[str] = None
|
|
125
|
+
type: Optional[str] = None
|
|
126
|
+
namespace: Optional[str] = None
|
|
127
|
+
title: Optional[str] = None
|
|
101
128
|
fields: Dict[str, Field] = {}
|
|
102
129
|
config: Dict[str, Any] = None
|
|
103
130
|
|
|
@@ -110,6 +137,10 @@ class Info(pyd.BaseModel):
|
|
|
110
137
|
owner: str = None
|
|
111
138
|
contact: Contact = None
|
|
112
139
|
|
|
140
|
+
model_config = pyd.ConfigDict(
|
|
141
|
+
extra="allow",
|
|
142
|
+
)
|
|
143
|
+
|
|
113
144
|
|
|
114
145
|
class Example(pyd.BaseModel):
|
|
115
146
|
type: str = None
|
|
@@ -191,6 +222,8 @@ class DataContractSpecification(pyd.BaseModel):
|
|
|
191
222
|
examples: List[Example] = []
|
|
192
223
|
quality: Quality = None
|
|
193
224
|
servicelevels: Optional[ServiceLevel] = None
|
|
225
|
+
links: Dict[str, str] = {}
|
|
226
|
+
tags: List[str] = []
|
|
194
227
|
|
|
195
228
|
@classmethod
|
|
196
229
|
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 %}
|
|
@@ -235,7 +250,7 @@
|
|
|
235
250
|
</div>
|
|
236
251
|
<div class="mt-8 md:order-1 md:mt-0">
|
|
237
252
|
<p class="text-center leading-5 text-gray-400">
|
|
238
|
-
Supported with ❤️ by <a href="https://
|
|
253
|
+
Supported with ❤️ by <a href="https://datacontract-manager.com" class="text-gray-400 hover:text-gray-500">Data Contract Manager</a>
|
|
239
254
|
</p>
|
|
240
255
|
</div>
|
|
241
256
|
</div>
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
</div>
|
|
191
191
|
<div class="mt-8 md:order-1 md:mt-0">
|
|
192
192
|
<p class="text-center leading-5 text-gray-400">
|
|
193
|
-
Supported with ❤️ by <a href="https://
|
|
193
|
+
Supported with ❤️ by <a href="https://datacontract-manager.com" class="text-gray-400 hover:text-gray-500">Data Contract Manager</a>
|
|
194
194
|
</p>
|
|
195
195
|
</div>
|
|
196
196
|
</div>
|
|
@@ -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 %}
|