datacontract-cli 0.10.19__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 +2 -3
- datacontract/lint/urls.py +9 -5
- datacontract/web.py +170 -36
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/METADATA +5 -4
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/RECORD +9 -9
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/WHEEL +0 -0
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.19.dist-info → datacontract_cli-0.10.20.dist-info}/top_level.txt +0 -0
datacontract/cli.py
CHANGED
|
@@ -4,7 +4,6 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Iterable, List, Optional
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
|
-
import uvicorn
|
|
8
7
|
from click import Context
|
|
9
8
|
from rich import box
|
|
10
9
|
from rich.console import Console
|
|
@@ -12,7 +11,6 @@ from rich.table import Table
|
|
|
12
11
|
from typer.core import TyperGroup
|
|
13
12
|
from typing_extensions import Annotated
|
|
14
13
|
|
|
15
|
-
from datacontract import web
|
|
16
14
|
from datacontract.catalog.catalog import create_data_contract_html, create_index_html
|
|
17
15
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
18
16
|
from datacontract.imports.importer import ImportFormat
|
|
@@ -442,8 +440,9 @@ def serve(
|
|
|
442
440
|
"""
|
|
443
441
|
Start the datacontract web server.
|
|
444
442
|
"""
|
|
443
|
+
import uvicorn
|
|
445
444
|
|
|
446
|
-
uvicorn.run(web
|
|
445
|
+
uvicorn.run("datacontract.web:app", port=port, host=host, reload=True)
|
|
447
446
|
|
|
448
447
|
|
|
449
448
|
def _handle_result(run):
|
datacontract/lint/urls.py
CHANGED
|
@@ -27,8 +27,11 @@ def fetch_resource(url: str):
|
|
|
27
27
|
|
|
28
28
|
def _set_api_key(headers, url):
|
|
29
29
|
hostname = urlparse(url).hostname
|
|
30
|
+
|
|
31
|
+
datamesh_manager_api_key = os.getenv("DATAMESH_MANAGER_API_KEY")
|
|
32
|
+
datacontract_manager_api_key = os.getenv("DATACONTRACT_MANAGER_API_KEY")
|
|
33
|
+
|
|
30
34
|
if hostname == "datamesh-manager.com" or hostname.endswith(".datamesh-manager.com"):
|
|
31
|
-
datamesh_manager_api_key = os.getenv("DATAMESH_MANAGER_API_KEY")
|
|
32
35
|
if datamesh_manager_api_key is None or datamesh_manager_api_key == "":
|
|
33
36
|
print("Error: Data Mesh Manager API Key is not set. Set env variable DATAMESH_MANAGER_API_KEY.")
|
|
34
37
|
raise DataContractException(
|
|
@@ -40,7 +43,6 @@ def _set_api_key(headers, url):
|
|
|
40
43
|
)
|
|
41
44
|
headers["x-api-key"] = datamesh_manager_api_key
|
|
42
45
|
elif hostname == "datacontract-manager.com" or hostname.endswith(".datacontract-manager.com"):
|
|
43
|
-
datacontract_manager_api_key = os.getenv("DATACONTRACT_MANAGER_API_KEY")
|
|
44
46
|
if datacontract_manager_api_key is None or datacontract_manager_api_key == "":
|
|
45
47
|
print("Error: Data Contract Manager API Key is not set. Set env variable DATACONTRACT_MANAGER_API_KEY.")
|
|
46
48
|
raise DataContractException(
|
|
@@ -51,6 +53,8 @@ def _set_api_key(headers, url):
|
|
|
51
53
|
result="error",
|
|
52
54
|
)
|
|
53
55
|
headers["x-api-key"] = datacontract_manager_api_key
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
if datamesh_manager_api_key is not None and datamesh_manager_api_key != "":
|
|
58
|
+
headers["x-api-key"] = datamesh_manager_api_key
|
|
59
|
+
if datacontract_manager_api_key is not None and datacontract_manager_api_key != "":
|
|
60
|
+
headers["x-api-key"] = datacontract_manager_api_key
|
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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: datacontract-cli
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.20
|
|
4
4
|
Summary: The datacontract CLI is an open source command-line tool for working with Data Contracts. It uses data contract YAML files to lint the data contract, connect to data sources and execute schema and quality tests, detect breaking changes, and export to different formats. The tool is written in Python. It can be used as a standalone CLI tool, in a CI/CD pipeline, or directly as a Python library.
|
|
5
5
|
Author-email: Jochen Christ <jochen.christ@innoq.com>, Stefan Negele <stefan.negele@innoq.com>, Simon Harrer <simon.harrer@innoq.com>
|
|
6
6
|
Project-URL: Homepage, https://cli.datacontract.com
|
|
@@ -15,8 +15,6 @@ Requires-Dist: typer<0.16,>=0.15.1
|
|
|
15
15
|
Requires-Dist: pydantic<2.11.0,>=2.8.2
|
|
16
16
|
Requires-Dist: pyyaml~=6.0.1
|
|
17
17
|
Requires-Dist: requests<2.33,>=2.31
|
|
18
|
-
Requires-Dist: fastapi==0.115.6
|
|
19
|
-
Requires-Dist: uvicorn==0.34.0
|
|
20
18
|
Requires-Dist: fastjsonschema<2.22.0,>=2.19.1
|
|
21
19
|
Requires-Dist: fastparquet==2024.11.0
|
|
22
20
|
Requires-Dist: numpy<2.0.0,>=1.26.4
|
|
@@ -65,8 +63,11 @@ Provides-Extra: dbml
|
|
|
65
63
|
Requires-Dist: pydbml>=1.1.1; extra == "dbml"
|
|
66
64
|
Provides-Extra: parquet
|
|
67
65
|
Requires-Dist: pyarrow>=18.1.0; extra == "parquet"
|
|
66
|
+
Provides-Extra: web
|
|
67
|
+
Requires-Dist: fastapi==0.115.6; extra == "web"
|
|
68
|
+
Requires-Dist: uvicorn==0.34.0; extra == "web"
|
|
68
69
|
Provides-Extra: all
|
|
69
|
-
Requires-Dist: datacontract-cli[bigquery,csv,databricks,dbml,dbt,iceberg,kafka,parquet,postgres,s3,snowflake,sqlserver,trino]; extra == "all"
|
|
70
|
+
Requires-Dist: datacontract-cli[bigquery,csv,databricks,dbml,dbt,iceberg,kafka,parquet,postgres,s3,snowflake,sqlserver,trino,web]; extra == "all"
|
|
70
71
|
Provides-Extra: dev
|
|
71
72
|
Requires-Dist: datacontract-cli[all]; extra == "dev"
|
|
72
73
|
Requires-Dist: httpx==0.28.1; extra == "dev"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
datacontract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
datacontract/cli.py,sha256=
|
|
2
|
+
datacontract/cli.py,sha256=rL860b_rNv7bDNnckljewhiDN6CTjsdRPLGRrXYLCa8,17103
|
|
3
3
|
datacontract/data_contract.py,sha256=KzsF4xvMWZnyTrwL7YDE0_dYWrWG9Bn7efCmW3vK5SI,14767
|
|
4
4
|
datacontract/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
datacontract/web.py,sha256=
|
|
5
|
+
datacontract/web.py,sha256=EaAJunEkheyoaTx6Zxt4v6bQ-AuRNuaGYiDa0NiKURQ,6096
|
|
6
6
|
datacontract/breaking/breaking.py,sha256=vUjPZzGsOF_ufTjdKa2T_gjQgPtZrZKytmcatkUr7ck,20428
|
|
7
7
|
datacontract/breaking/breaking_rules.py,sha256=OKwYWCMkIob2uX8zxav7LbIhx_6RC4msioBjuNlox1k,4060
|
|
8
8
|
datacontract/catalog/catalog.py,sha256=wmv_2BBxHhNBlilAmQHHhNe4tK14DowkyIOVaQW2DWU,2691
|
|
@@ -75,7 +75,7 @@ datacontract/lint/lint.py,sha256=Ew0n3ooXxmCVnUxJ_cDoacsD82QdMZYnKrxnG9J0sWQ,507
|
|
|
75
75
|
datacontract/lint/resolve.py,sha256=_QtoAReUhiJ8I_4fyNimyUnHhpZGYmmDHFA59wUTeFw,10684
|
|
76
76
|
datacontract/lint/resources.py,sha256=nfeZmORh1aP7EKpMKCmfbS04Te8pQ0nz64vJVkHOq3c,647
|
|
77
77
|
datacontract/lint/schema.py,sha256=4pYX6JX6SkASftyqaWTodKFRVPi2qV0_Z60tvaCOk80,1813
|
|
78
|
-
datacontract/lint/urls.py,sha256=
|
|
78
|
+
datacontract/lint/urls.py,sha256=dJDxvhJ_WaijbnkEKsF0EI3ptyP5BWC7RzDHJAK6v3k,2529
|
|
79
79
|
datacontract/lint/linters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
80
|
datacontract/lint/linters/description_linter.py,sha256=7fla7FQwDa-1UrLFCFKFoeUzkR91e4o9W6ySKSW6_U8,1555
|
|
81
81
|
datacontract/lint/linters/example_model_linter.py,sha256=tmgxGxC-GzMUxFumTOuuASdz9ZlncBZHasPGJsWnDT8,3973
|
|
@@ -102,9 +102,9 @@ datacontract/templates/partials/example.html,sha256=F1dWbHDIXQScgfs4OVgqM1lR4uV4
|
|
|
102
102
|
datacontract/templates/partials/model_field.html,sha256=QA0U95hHthEc66AjSYk4Di24hU-pHZbef0Sz-hSpdGM,7215
|
|
103
103
|
datacontract/templates/partials/server.html,sha256=WkWFbz1ZvhIAUQQhH5Lkwb0HZRW907ehEnFmJSkpquQ,6235
|
|
104
104
|
datacontract/templates/style/output.css,sha256=F3oEhUpuv8kA_dWr4pJymBS_Ju6huIIZdLMkJzPzMmU,25647
|
|
105
|
-
datacontract_cli-0.10.
|
|
106
|
-
datacontract_cli-0.10.
|
|
107
|
-
datacontract_cli-0.10.
|
|
108
|
-
datacontract_cli-0.10.
|
|
109
|
-
datacontract_cli-0.10.
|
|
110
|
-
datacontract_cli-0.10.
|
|
105
|
+
datacontract_cli-0.10.20.dist-info/LICENSE,sha256=23h64qnSeIZ0DKeziWAKC-zBCt328iSbRbWBrXoYRb4,2210
|
|
106
|
+
datacontract_cli-0.10.20.dist-info/METADATA,sha256=3VPkCNDzKqXRh0jS5qi2WwKuhVfwERgKOsBnkSv_h-c,98771
|
|
107
|
+
datacontract_cli-0.10.20.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
108
|
+
datacontract_cli-0.10.20.dist-info/entry_points.txt,sha256=D3Eqy4q_Z6bHauGd4ppIyQglwbrm1AJnLau4Ppbw9Is,54
|
|
109
|
+
datacontract_cli-0.10.20.dist-info/top_level.txt,sha256=VIRjd8EIUrBYWjEXJJjtdUgc0UAJdPZjmLiOR8BRBYM,13
|
|
110
|
+
datacontract_cli-0.10.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|