datacontract-cli 0.10.18__py3-none-any.whl → 0.10.20__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/cli.py +22 -30
- datacontract/data_contract.py +7 -8
- datacontract/engines/soda/connections/duckdb.py +22 -9
- datacontract/export/data_caterer_converter.py +20 -7
- datacontract/export/sodacl_converter.py +21 -4
- datacontract/export/sql_type_converter.py +7 -2
- datacontract/imports/csv_importer.py +89 -0
- datacontract/imports/importer.py +1 -0
- datacontract/imports/importer_factory.py +5 -0
- datacontract/init/init_template.py +20 -0
- datacontract/integration/datamesh_manager.py +5 -10
- datacontract/lint/linters/field_reference_linter.py +10 -1
- datacontract/lint/resolve.py +22 -1
- datacontract/lint/schema.py +10 -3
- datacontract/lint/urls.py +9 -5
- datacontract/model/data_contract_specification.py +2 -0
- datacontract/schemas/datacontract-1.1.0.init.yaml +91 -0
- datacontract/schemas/datacontract-1.1.0.schema.json +1975 -0
- datacontract/schemas/odcs-3.0.1.schema.json +2634 -0
- datacontract/templates/datacontract.html +20 -1
- datacontract/templates/partials/definition.html +15 -5
- datacontract/templates/partials/model_field.html +9 -0
- datacontract/web.py +170 -36
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/METADATA +448 -297
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/RECORD +29 -25
- datacontract/init/download_datacontract_file.py +0 -17
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/WHEEL +0 -0
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.18.dist-info → datacontract_cli-0.10.20.dist-info}/top_level.txt +0 -0
|
@@ -125,9 +125,18 @@
|
|
|
125
125
|
<thead class="bg-gray-50">
|
|
126
126
|
<tr>
|
|
127
127
|
<th scope="colgroup" colspan="3" class="py-2 pl-4 pr-3 text-left font-semibold text-gray-900 sm:pl-6">
|
|
128
|
-
|
|
128
|
+
{% if model.title %}
|
|
129
|
+
{{ model.title }}
|
|
130
|
+
{% endif %}
|
|
131
|
+
{% if model.title != model_name %}
|
|
132
|
+
<span class="font-mono font-medium">{{ model_name }}</span>
|
|
133
|
+
{% endif %}
|
|
129
134
|
<span class="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10">{{ model.type }}</span>
|
|
130
135
|
<div class="text-sm font-medium text-gray-500">{{ model.description }}</div>
|
|
136
|
+
{% for key, value in model.model_extra.items() %}
|
|
137
|
+
<span
|
|
138
|
+
class="inline-flex items-center rounded-md bg-blue-50 px-1 py-1 text-xs font-medium text-blue-600 ring-1 ring-inset ring-blue-500/10 mr-1 mt-1">{{ key }}: {{ value }}</span>
|
|
139
|
+
{% endfor %}
|
|
131
140
|
</th>
|
|
132
141
|
|
|
133
142
|
</tr>
|
|
@@ -137,6 +146,16 @@
|
|
|
137
146
|
{{ render_partial('partials/model_field.html', nested = False, field_name=field_name, field = field, level = 0) }}
|
|
138
147
|
{% endfor %}
|
|
139
148
|
</tbody>
|
|
149
|
+
{% if model.primaryKey %}
|
|
150
|
+
<tfoot class="bg-gray-50">
|
|
151
|
+
<tr>
|
|
152
|
+
<th scope="colgroup" colspan="4"
|
|
153
|
+
class="py-2 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
|
154
|
+
<span>Primary Key: {{ model.primaryKey }}</span>
|
|
155
|
+
</th>
|
|
156
|
+
</tr>
|
|
157
|
+
</tfoot>
|
|
158
|
+
{% endif %}
|
|
140
159
|
</table>
|
|
141
160
|
</div>
|
|
142
161
|
</div>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{% if definition.title %}
|
|
21
21
|
<div>{{ definition.title }}</div>
|
|
22
22
|
{% endif %}
|
|
23
|
-
<div class="font-mono">{{
|
|
23
|
+
<div class="font-mono">{{ definition_name }}</div>
|
|
24
24
|
</div>
|
|
25
25
|
</td>
|
|
26
26
|
<td class="whitespace-nowrap px-1 py-2 text-sm text-gray-500 w-1/12">
|
|
@@ -33,13 +33,23 @@
|
|
|
33
33
|
</td>
|
|
34
34
|
<td class="px-3 py-2 text-sm text-gray-500 w-9/12">
|
|
35
35
|
{% if definition.example %}
|
|
36
|
-
<div class="mt-1">
|
|
37
|
-
<span class="text-gray-600
|
|
36
|
+
<div class="mt-1 italic">
|
|
37
|
+
<span class="text-gray-600">Example:</span> <span class="font-mono">{{ definition.example }}</span>
|
|
38
38
|
</div>
|
|
39
39
|
{% endif %}
|
|
40
|
+
|
|
41
|
+
{% if definition.examples %}
|
|
42
|
+
<div class="mt-1 italic">
|
|
43
|
+
<span class="text-gray-600">Examples:</span>
|
|
44
|
+
{% for example in definition.examples %}
|
|
45
|
+
<span class="font-mono">{{ example }}</span>{% if not loop.last %}, {% endif %}
|
|
46
|
+
{% endfor %}
|
|
47
|
+
</div>
|
|
48
|
+
{% endif %}
|
|
49
|
+
|
|
40
50
|
{% if definition.tags %}
|
|
41
51
|
<div>
|
|
42
|
-
<span class="text-gray-600
|
|
52
|
+
<span class="text-gray-600 italic">Tags:</span>
|
|
43
53
|
{% for tag in definition.tags %}
|
|
44
54
|
<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">{{ tag }}</span>
|
|
45
55
|
{% endfor %}
|
|
@@ -47,7 +57,7 @@
|
|
|
47
57
|
{% endif %}
|
|
48
58
|
{% if definition.enum %}
|
|
49
59
|
<div class="py-2 text-sm">
|
|
50
|
-
<span class="text-gray-600
|
|
60
|
+
<span class="text-gray-600 italic">Enum:</span>
|
|
51
61
|
{% for value in definition.enum %}
|
|
52
62
|
<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">{{ value }}</span>
|
|
53
63
|
{% endfor %}
|
|
@@ -39,6 +39,15 @@
|
|
|
39
39
|
</div>
|
|
40
40
|
{% endif %}
|
|
41
41
|
|
|
42
|
+
{% if field.examples %}
|
|
43
|
+
<div class="mt-1 italic">
|
|
44
|
+
Examples:
|
|
45
|
+
{% for example in field.examples %}
|
|
46
|
+
<span class="font-mono">{{ example }}</span>{% if not loop.last %}, {% endif %}
|
|
47
|
+
{% endfor %}
|
|
48
|
+
</div>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
42
51
|
<div>
|
|
43
52
|
{% if field.primaryKey or field.primary %}
|
|
44
53
|
<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">primary</span>
|
datacontract/web.py
CHANGED
|
@@ -1,48 +1,183 @@
|
|
|
1
|
-
from typing import Annotated, Optional
|
|
1
|
+
from typing import Annotated, Optional
|
|
2
2
|
|
|
3
3
|
import typer
|
|
4
|
-
from fastapi import FastAPI,
|
|
5
|
-
from fastapi.responses import
|
|
4
|
+
from fastapi import Body, FastAPI, Query
|
|
5
|
+
from fastapi.responses import PlainTextResponse
|
|
6
6
|
|
|
7
7
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
8
|
+
from datacontract.model.run import Run
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
DATA_CONTRACT_EXAMPLE_PAYLOAD = """dataContractSpecification: 1.1.0
|
|
11
|
+
id: urn:datacontract:checkout:orders-latest
|
|
12
|
+
info:
|
|
13
|
+
title: Orders Latest
|
|
14
|
+
version: 2.0.0
|
|
15
|
+
owner: Sales Team
|
|
16
|
+
servers:
|
|
17
|
+
production:
|
|
18
|
+
type: s3
|
|
19
|
+
location: s3://datacontract-example-orders-latest/v2/{model}/*.json
|
|
20
|
+
format: json
|
|
21
|
+
delimiter: new_line
|
|
22
|
+
models:
|
|
23
|
+
orders:
|
|
24
|
+
description: One record per order. Includes cancelled and deleted orders.
|
|
25
|
+
type: table
|
|
26
|
+
fields:
|
|
27
|
+
order_id:
|
|
28
|
+
type: string
|
|
29
|
+
primaryKey: true
|
|
30
|
+
order_timestamp:
|
|
31
|
+
description: The business timestamp in UTC when the order was successfully registered in the source system and the payment was successful.
|
|
32
|
+
type: timestamp
|
|
33
|
+
required: true
|
|
34
|
+
examples:
|
|
35
|
+
- "2024-09-09T08:30:00Z"
|
|
36
|
+
order_total:
|
|
37
|
+
description: Total amount the smallest monetary unit (e.g., cents).
|
|
38
|
+
type: long
|
|
39
|
+
required: true
|
|
40
|
+
examples:
|
|
41
|
+
- 9999
|
|
42
|
+
quality:
|
|
43
|
+
- type: sql
|
|
44
|
+
description: 95% of all order total values are expected to be between 10 and 499 EUR.
|
|
45
|
+
query: |
|
|
46
|
+
SELECT quantile_cont(order_total, 0.95) AS percentile_95
|
|
47
|
+
FROM orders
|
|
48
|
+
mustBeBetween: [1000, 99900]
|
|
49
|
+
customer_id:
|
|
50
|
+
description: Unique identifier for the customer.
|
|
51
|
+
type: text
|
|
52
|
+
minLength: 10
|
|
53
|
+
maxLength: 20
|
|
54
|
+
"""
|
|
10
55
|
|
|
56
|
+
app = FastAPI(
|
|
57
|
+
docs_url="/",
|
|
58
|
+
title="Data Contract API",
|
|
59
|
+
summary="API to execute Data Contract CLI operations.",
|
|
60
|
+
license_info={
|
|
61
|
+
"name": "MIT License",
|
|
62
|
+
"identifier": "MIT",
|
|
63
|
+
},
|
|
64
|
+
contact={"name": "Data Contract CLI", "url": "https://cli.datacontract.com/"},
|
|
65
|
+
openapi_tags=[
|
|
66
|
+
{
|
|
67
|
+
"name": "test",
|
|
68
|
+
"externalDocs": {
|
|
69
|
+
"description": "Documentation",
|
|
70
|
+
"url": "https://cli.datacontract.com/#test",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "lint",
|
|
75
|
+
"externalDocs": {
|
|
76
|
+
"description": "Documentation",
|
|
77
|
+
"url": "https://cli.datacontract.com/#lint",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "export",
|
|
82
|
+
"externalDocs": {
|
|
83
|
+
"description": "Documentation",
|
|
84
|
+
"url": "https://cli.datacontract.com/#export",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
)
|
|
11
89
|
|
|
12
|
-
@app.get("/", response_class=HTMLResponse)
|
|
13
|
-
def index():
|
|
14
|
-
# TODO OpenAPI spec
|
|
15
|
-
return """
|
|
16
|
-
<html>
|
|
17
|
-
<body>
|
|
18
|
-
<h1>datacontract web server</h1>
|
|
19
|
-
<ul>
|
|
20
|
-
<li>POST /lint</li>
|
|
21
|
-
<li>POST /export</li>
|
|
22
|
-
</ul>
|
|
23
|
-
</body>
|
|
24
|
-
</html>
|
|
25
|
-
"""
|
|
26
90
|
|
|
91
|
+
@app.post(
|
|
92
|
+
"/test",
|
|
93
|
+
tags=["test"],
|
|
94
|
+
summary="Run data contract tests",
|
|
95
|
+
description="""
|
|
96
|
+
Run schema and quality tests. Data Contract CLI connects to the data sources configured in the server section.
|
|
97
|
+
This usually requires credentials to access the data sources.
|
|
98
|
+
Credentials must be provided via environment variables when running the web server.
|
|
99
|
+
POST the data contract YAML as payload.
|
|
100
|
+
""",
|
|
101
|
+
response_model_exclude_none=True,
|
|
102
|
+
response_model_exclude_unset=True,
|
|
103
|
+
)
|
|
104
|
+
async def test(
|
|
105
|
+
body: Annotated[
|
|
106
|
+
str,
|
|
107
|
+
Body(
|
|
108
|
+
title="Data Contract YAML",
|
|
109
|
+
media_type="application/yaml",
|
|
110
|
+
examples=[DATA_CONTRACT_EXAMPLE_PAYLOAD],
|
|
111
|
+
),
|
|
112
|
+
],
|
|
113
|
+
server: Annotated[
|
|
114
|
+
str | None,
|
|
115
|
+
Query(
|
|
116
|
+
example="production",
|
|
117
|
+
description="The server name to test. Optional, if there is only one server.",
|
|
118
|
+
),
|
|
119
|
+
] = None,
|
|
120
|
+
) -> Run:
|
|
121
|
+
return DataContract(data_contract_str=body, server=server).test()
|
|
27
122
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
123
|
+
|
|
124
|
+
@app.post(
|
|
125
|
+
"/lint",
|
|
126
|
+
tags=["lint"],
|
|
127
|
+
summary="Validate that the datacontract.yaml is correctly formatted.",
|
|
128
|
+
description="""Validate that the datacontract.yaml is correctly formatted.""",
|
|
129
|
+
)
|
|
130
|
+
async def lint(
|
|
131
|
+
body: Annotated[
|
|
132
|
+
str,
|
|
133
|
+
Body(
|
|
134
|
+
title="Data Contract YAML",
|
|
135
|
+
media_type="application/yaml",
|
|
136
|
+
examples=[DATA_CONTRACT_EXAMPLE_PAYLOAD],
|
|
137
|
+
),
|
|
138
|
+
],
|
|
139
|
+
schema: Annotated[
|
|
140
|
+
str | None,
|
|
141
|
+
Query(
|
|
142
|
+
example="https://datacontract.com/datacontract.schema.json",
|
|
143
|
+
description="The schema to use for validation. This must be a URL.",
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
):
|
|
147
|
+
data_contract = DataContract(data_contract_str=body, schema_location=schema)
|
|
148
|
+
lint_result = data_contract.lint()
|
|
32
149
|
return {"result": lint_result.result, "checks": lint_result.checks}
|
|
33
150
|
|
|
34
151
|
|
|
35
|
-
@app.post(
|
|
152
|
+
@app.post(
|
|
153
|
+
"/export",
|
|
154
|
+
tags=["export"],
|
|
155
|
+
summary="Convert data contract to a specific format.",
|
|
156
|
+
response_class=PlainTextResponse,
|
|
157
|
+
)
|
|
36
158
|
def export(
|
|
37
|
-
|
|
38
|
-
export_format: Annotated[ExportFormat, typer.Option(help="The export format.")],
|
|
39
|
-
server: Annotated[str, typer.Option(help="The server name to export.")] = None,
|
|
40
|
-
model: Annotated[
|
|
159
|
+
body: Annotated[
|
|
41
160
|
str,
|
|
42
|
-
|
|
43
|
-
|
|
161
|
+
Body(
|
|
162
|
+
title="Data Contract YAML",
|
|
163
|
+
media_type="application/yaml",
|
|
164
|
+
examples=[DATA_CONTRACT_EXAMPLE_PAYLOAD],
|
|
165
|
+
),
|
|
166
|
+
],
|
|
167
|
+
format: Annotated[ExportFormat, typer.Option(help="The export format.")],
|
|
168
|
+
server: Annotated[
|
|
169
|
+
str | None,
|
|
170
|
+
Query(
|
|
171
|
+
example="production",
|
|
172
|
+
description="The server name to export. Optional, if there is only one server.",
|
|
173
|
+
),
|
|
174
|
+
] = None,
|
|
175
|
+
model: Annotated[
|
|
176
|
+
str | None,
|
|
177
|
+
Query(
|
|
178
|
+
description="Use the key of the model in the data contract yaml file "
|
|
44
179
|
"to refer to a model, e.g., `orders`, or `all` for all "
|
|
45
|
-
"models (default)."
|
|
180
|
+
"models (default).",
|
|
46
181
|
),
|
|
47
182
|
] = "all",
|
|
48
183
|
rdf_base: Annotated[
|
|
@@ -51,14 +186,13 @@ def export(
|
|
|
51
186
|
] = None,
|
|
52
187
|
sql_server_type: Annotated[
|
|
53
188
|
Optional[str],
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
rich_help_panel="SQL Options",
|
|
189
|
+
Query(
|
|
190
|
+
description="[sql] The server type to determine the sql dialect. By default, it uses 'auto' to automatically detect the sql dialect via the specified servers in the data contract.",
|
|
57
191
|
),
|
|
58
|
-
] =
|
|
192
|
+
] = None,
|
|
59
193
|
):
|
|
60
|
-
result = DataContract(data_contract_str=
|
|
61
|
-
export_format=
|
|
194
|
+
result = DataContract(data_contract_str=body, server=server).export(
|
|
195
|
+
export_format=format,
|
|
62
196
|
model=model,
|
|
63
197
|
rdf_base=rdf_base,
|
|
64
198
|
sql_server_type=sql_server_type,
|