datacontract-cli 0.10.20__py3-none-any.whl → 0.10.21__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/{web.py → api.py} +55 -3
- datacontract/cli.py +27 -5
- datacontract/export/custom_converter.py +40 -0
- datacontract/export/exporter.py +1 -0
- datacontract/export/exporter_factory.py +4 -0
- datacontract/lint/urls.py +4 -4
- datacontract/model/data_contract_specification.py +130 -129
- datacontract/model/run.py +18 -18
- datacontract/templates/datacontract.html +16 -2
- datacontract/templates/partials/definition.html +3 -95
- datacontract/templates/partials/model_field.html +13 -0
- datacontract/templates/partials/quality.html +49 -0
- datacontract/templates/style/output.css +151 -152
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/METADATA +103 -22
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/RECORD +19 -17
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/LICENSE +0 -0
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/WHEEL +0 -0
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/entry_points.txt +0 -0
- {datacontract_cli-0.10.20.dist-info → datacontract_cli-0.10.21.dist-info}/top_level.txt +0 -0
datacontract/{web.py → api.py}
RENAMED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
1
3
|
from typing import Annotated, Optional
|
|
2
4
|
|
|
3
5
|
import typer
|
|
4
|
-
from fastapi import Body, FastAPI, Query
|
|
6
|
+
from fastapi import Body, Depends, FastAPI, HTTPException, Query, status
|
|
5
7
|
from fastapi.responses import PlainTextResponse
|
|
8
|
+
from fastapi.security.api_key import APIKeyHeader
|
|
6
9
|
|
|
7
10
|
from datacontract.data_contract import DataContract, ExportFormat
|
|
8
11
|
from datacontract.model.run import Run
|
|
@@ -55,8 +58,8 @@ models:
|
|
|
55
58
|
|
|
56
59
|
app = FastAPI(
|
|
57
60
|
docs_url="/",
|
|
58
|
-
title="Data Contract API",
|
|
59
|
-
summary="API to
|
|
61
|
+
title="Data Contract CLI API",
|
|
62
|
+
summary="You can use the API to test, export, and lint your data contracts.",
|
|
60
63
|
license_info={
|
|
61
64
|
"name": "MIT License",
|
|
62
65
|
"identifier": "MIT",
|
|
@@ -87,6 +90,32 @@ app = FastAPI(
|
|
|
87
90
|
],
|
|
88
91
|
)
|
|
89
92
|
|
|
93
|
+
api_key_header = APIKeyHeader(
|
|
94
|
+
name="x-api-key",
|
|
95
|
+
auto_error=False, # this makes authentication optional
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_api_key(api_key_header: str | None):
|
|
100
|
+
correct_api_key = os.getenv("DATACONTRACT_CLI_API_KEY")
|
|
101
|
+
if correct_api_key is None or correct_api_key == "":
|
|
102
|
+
logging.info("Environment variable DATACONTRACT_CLI_API_KEY is not set. Skip API key check.")
|
|
103
|
+
return
|
|
104
|
+
if api_key_header is None or api_key_header == "":
|
|
105
|
+
logging.info("The API key is missing.")
|
|
106
|
+
raise HTTPException(
|
|
107
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
108
|
+
detail="Missing API key. Use Header 'x-api-key' to provide the API key.",
|
|
109
|
+
)
|
|
110
|
+
if api_key_header != correct_api_key:
|
|
111
|
+
logging.info("The provided API key is not correct.")
|
|
112
|
+
raise HTTPException(
|
|
113
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
114
|
+
detail="The provided API key is not correct.",
|
|
115
|
+
)
|
|
116
|
+
logging.info("Request authenticated with API key.")
|
|
117
|
+
pass
|
|
118
|
+
|
|
90
119
|
|
|
91
120
|
@app.post(
|
|
92
121
|
"/test",
|
|
@@ -98,6 +127,25 @@ app = FastAPI(
|
|
|
98
127
|
Credentials must be provided via environment variables when running the web server.
|
|
99
128
|
POST the data contract YAML as payload.
|
|
100
129
|
""",
|
|
130
|
+
responses={
|
|
131
|
+
401: {
|
|
132
|
+
"description": "Unauthorized (when an environment variable DATACONTRACT_CLI_API_KEY is configured).",
|
|
133
|
+
"content": {
|
|
134
|
+
"application/json": {
|
|
135
|
+
"examples": {
|
|
136
|
+
"api_key_missing": {
|
|
137
|
+
"summary": "API key Missing",
|
|
138
|
+
"value": {"detail": "Missing API key. Use Header 'x-api-key' to provide the API key."},
|
|
139
|
+
},
|
|
140
|
+
"api_key_wrong": {
|
|
141
|
+
"summary": "API key Wrong",
|
|
142
|
+
"value": {"detail": "The provided API key is not correct."},
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
101
149
|
response_model_exclude_none=True,
|
|
102
150
|
response_model_exclude_unset=True,
|
|
103
151
|
)
|
|
@@ -110,6 +158,7 @@ async def test(
|
|
|
110
158
|
examples=[DATA_CONTRACT_EXAMPLE_PAYLOAD],
|
|
111
159
|
),
|
|
112
160
|
],
|
|
161
|
+
api_key: Annotated[str | None, Depends(api_key_header)] = None,
|
|
113
162
|
server: Annotated[
|
|
114
163
|
str | None,
|
|
115
164
|
Query(
|
|
@@ -118,6 +167,9 @@ async def test(
|
|
|
118
167
|
),
|
|
119
168
|
] = None,
|
|
120
169
|
) -> Run:
|
|
170
|
+
check_api_key(api_key)
|
|
171
|
+
logging.info("Testing data contract...")
|
|
172
|
+
logging.info(body)
|
|
121
173
|
return DataContract(data_contract_str=body, server=server).test()
|
|
122
174
|
|
|
123
175
|
|
datacontract/cli.py
CHANGED
|
@@ -196,6 +196,11 @@ def export(
|
|
|
196
196
|
Optional[str],
|
|
197
197
|
typer.Option(help="[engine] The engine used for great expection run."),
|
|
198
198
|
] = None,
|
|
199
|
+
# TODO: this should be a subcommand
|
|
200
|
+
template: Annotated[
|
|
201
|
+
Optional[Path],
|
|
202
|
+
typer.Option(help="[custom] The file path of Jinja template."),
|
|
203
|
+
] = None,
|
|
199
204
|
):
|
|
200
205
|
"""
|
|
201
206
|
Convert data contract to a specific format. Saves to file specified by `output` option if present, otherwise prints to stdout.
|
|
@@ -208,6 +213,7 @@ def export(
|
|
|
208
213
|
rdf_base=rdf_base,
|
|
209
214
|
sql_server_type=sql_server_type,
|
|
210
215
|
engine=engine,
|
|
216
|
+
template=template,
|
|
211
217
|
)
|
|
212
218
|
# Don't interpret console markup in output.
|
|
213
219
|
if output is None:
|
|
@@ -344,7 +350,7 @@ def catalog(
|
|
|
344
350
|
] = None,
|
|
345
351
|
):
|
|
346
352
|
"""
|
|
347
|
-
Create
|
|
353
|
+
Create a html catalog of data contracts.
|
|
348
354
|
"""
|
|
349
355
|
path = Path(output)
|
|
350
356
|
path.mkdir(parents=True, exist_ok=True)
|
|
@@ -433,16 +439,32 @@ def diff(
|
|
|
433
439
|
|
|
434
440
|
|
|
435
441
|
@app.command()
|
|
436
|
-
def
|
|
442
|
+
def api(
|
|
437
443
|
port: Annotated[int, typer.Option(help="Bind socket to this port.")] = 4242,
|
|
438
|
-
host: Annotated[
|
|
444
|
+
host: Annotated[
|
|
445
|
+
str, typer.Option(help="Bind socket to this host. Hint: For running in docker, set it to 0.0.0.0")
|
|
446
|
+
] = "127.0.0.1",
|
|
439
447
|
):
|
|
440
448
|
"""
|
|
441
|
-
Start the datacontract
|
|
449
|
+
Start the datacontract CLI as server application with REST API.
|
|
450
|
+
|
|
451
|
+
The OpenAPI documentation as Swagger UI is available on http://localhost:4242.
|
|
452
|
+
You can execute the commands directly from the Swagger UI.
|
|
453
|
+
|
|
454
|
+
To protect the API, you can set the environment variable DATACONTRACT_CLI_API_KEY to a secret API key.
|
|
455
|
+
To authenticate, requests must include the header 'x-api-key' with the correct API key.
|
|
456
|
+
This is highly recommended, as data contract tests may be subject to SQL injections or leak sensitive information.
|
|
457
|
+
|
|
458
|
+
To connect to servers (such as a Snowflake data source), set the credentials as environment variables as documented in
|
|
459
|
+
https://cli.datacontract.com/#test
|
|
442
460
|
"""
|
|
443
461
|
import uvicorn
|
|
462
|
+
from uvicorn.config import LOGGING_CONFIG
|
|
463
|
+
|
|
464
|
+
log_config = LOGGING_CONFIG
|
|
465
|
+
log_config["root"] = {"level": "INFO"}
|
|
444
466
|
|
|
445
|
-
uvicorn.run("datacontract.
|
|
467
|
+
uvicorn.run(app="datacontract.api:app", port=port, host=host, reload=True, log_config=LOGGING_CONFIG)
|
|
446
468
|
|
|
447
469
|
|
|
448
470
|
def _handle_result(run):
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from jinja2 import Environment, FileSystemLoader
|
|
4
|
+
|
|
5
|
+
from datacontract.export.exporter import Exporter
|
|
6
|
+
from datacontract.model.data_contract_specification import (
|
|
7
|
+
DataContractSpecification,
|
|
8
|
+
Model,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CustomExporter(Exporter):
|
|
13
|
+
"""Exporter implementation for converting data contracts to Markdown."""
|
|
14
|
+
|
|
15
|
+
def export(
|
|
16
|
+
self,
|
|
17
|
+
data_contract: DataContractSpecification,
|
|
18
|
+
model: Model,
|
|
19
|
+
server: str,
|
|
20
|
+
sql_server_type: str,
|
|
21
|
+
export_args: dict,
|
|
22
|
+
) -> str:
|
|
23
|
+
"""Exports a data contract to custom format with Jinja."""
|
|
24
|
+
template = export_args.get("template")
|
|
25
|
+
if template is None:
|
|
26
|
+
raise RuntimeError("Export to custom requires template argument.")
|
|
27
|
+
|
|
28
|
+
return to_custom(data_contract, template)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def to_custom(data_contract: DataContractSpecification, template_path: Path) -> str:
|
|
32
|
+
template = get_template(template_path)
|
|
33
|
+
rendered_sql = template.render(data_contract=data_contract)
|
|
34
|
+
return rendered_sql
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_template(path: Path):
|
|
38
|
+
abosolute_path = Path(path).resolve()
|
|
39
|
+
env = Environment(loader=FileSystemLoader(str(abosolute_path.parent)))
|
|
40
|
+
return env.get_template(path.name)
|
datacontract/export/exporter.py
CHANGED
|
@@ -206,3 +206,7 @@ exporter_factory.register_lazy_exporter(
|
|
|
206
206
|
exporter_factory.register_lazy_exporter(
|
|
207
207
|
name=ExportFormat.iceberg, module_path="datacontract.export.iceberg_converter", class_name="IcebergExporter"
|
|
208
208
|
)
|
|
209
|
+
|
|
210
|
+
exporter_factory.register_lazy_exporter(
|
|
211
|
+
name=ExportFormat.custom, module_path="datacontract.export.custom_converter", class_name="CustomExporter"
|
|
212
|
+
)
|
datacontract/lint/urls.py
CHANGED
|
@@ -33,22 +33,22 @@ def _set_api_key(headers, url):
|
|
|
33
33
|
|
|
34
34
|
if hostname == "datamesh-manager.com" or hostname.endswith(".datamesh-manager.com"):
|
|
35
35
|
if datamesh_manager_api_key is None or datamesh_manager_api_key == "":
|
|
36
|
-
print("Error: Data Mesh Manager API
|
|
36
|
+
print("Error: Data Mesh Manager API key is not set. Set env variable DATAMESH_MANAGER_API_KEY.")
|
|
37
37
|
raise DataContractException(
|
|
38
38
|
type="lint",
|
|
39
39
|
name=f"Reading data contract from {url}",
|
|
40
|
-
reason="Error: Data Mesh Manager API
|
|
40
|
+
reason="Error: Data Mesh Manager API key is not set. Set env variable DATAMESH_MANAGER_API_KEY.",
|
|
41
41
|
engine="datacontract",
|
|
42
42
|
result="error",
|
|
43
43
|
)
|
|
44
44
|
headers["x-api-key"] = datamesh_manager_api_key
|
|
45
45
|
elif hostname == "datacontract-manager.com" or hostname.endswith(".datacontract-manager.com"):
|
|
46
46
|
if datacontract_manager_api_key is None or datacontract_manager_api_key == "":
|
|
47
|
-
print("Error: Data Contract Manager API
|
|
47
|
+
print("Error: Data Contract Manager API key is not set. Set env variable DATACONTRACT_MANAGER_API_KEY.")
|
|
48
48
|
raise DataContractException(
|
|
49
49
|
type="lint",
|
|
50
50
|
name=f"Reading data contract from {url}",
|
|
51
|
-
reason="Error: Data Contract Manager API
|
|
51
|
+
reason="Error: Data Contract Manager API key is not set. Set env variable DATACONTRACT_MANAGER_API_KEY.",
|
|
52
52
|
engine="datacontract",
|
|
53
53
|
result="error",
|
|
54
54
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import Any, Dict, List
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
3
|
|
|
4
4
|
import pydantic as pyd
|
|
5
5
|
import yaml
|
|
@@ -32,9 +32,9 @@ DATACONTRACT_TYPES = [
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class Contact(pyd.BaseModel):
|
|
35
|
-
name: str = None
|
|
36
|
-
url: str = None
|
|
37
|
-
email: str = None
|
|
35
|
+
name: str | None = None
|
|
36
|
+
url: str | None = None
|
|
37
|
+
email: str | None = None
|
|
38
38
|
|
|
39
39
|
model_config = pyd.ConfigDict(
|
|
40
40
|
extra="allow",
|
|
@@ -42,37 +42,37 @@ class Contact(pyd.BaseModel):
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class ServerRole(pyd.BaseModel):
|
|
45
|
-
name: str = None
|
|
46
|
-
description: str = None
|
|
45
|
+
name: str | None = None
|
|
46
|
+
description: str | None = None
|
|
47
47
|
model_config = pyd.ConfigDict(
|
|
48
48
|
extra="allow",
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class Server(pyd.BaseModel):
|
|
53
|
-
type: str = None
|
|
54
|
-
description: str = None
|
|
55
|
-
environment: str = None
|
|
56
|
-
format: str = None
|
|
57
|
-
project: str = None
|
|
58
|
-
dataset: str = None
|
|
59
|
-
path: str = None
|
|
60
|
-
delimiter: str = None
|
|
61
|
-
endpointUrl: str = None
|
|
62
|
-
location: str = None
|
|
63
|
-
account: str = None
|
|
64
|
-
database: str = None
|
|
65
|
-
schema_: str = pyd.Field(default=None, alias="schema")
|
|
66
|
-
host: str = None
|
|
67
|
-
port: int = None
|
|
68
|
-
catalog: str = None
|
|
69
|
-
topic: str = None
|
|
70
|
-
http_path: str = None # Use ENV variable
|
|
71
|
-
token: str = None # Use ENV variable
|
|
72
|
-
dataProductId: str = None
|
|
73
|
-
outputPortId: str = None
|
|
74
|
-
driver: str = None
|
|
75
|
-
storageAccount: str = None
|
|
53
|
+
type: str | None = None
|
|
54
|
+
description: str | None = None
|
|
55
|
+
environment: str | None = None
|
|
56
|
+
format: str | None = None
|
|
57
|
+
project: str | None = None
|
|
58
|
+
dataset: str | None = None
|
|
59
|
+
path: str | None = None
|
|
60
|
+
delimiter: str | None = None
|
|
61
|
+
endpointUrl: str | None = None
|
|
62
|
+
location: str | None = None
|
|
63
|
+
account: str | None = None
|
|
64
|
+
database: str | None = None
|
|
65
|
+
schema_: str | None = pyd.Field(default=None, alias="schema")
|
|
66
|
+
host: str | None = None
|
|
67
|
+
port: int | None = None
|
|
68
|
+
catalog: str | None = None
|
|
69
|
+
topic: str | None = None
|
|
70
|
+
http_path: str | None = None # Use ENV variable
|
|
71
|
+
token: str | None = None # Use ENV variable
|
|
72
|
+
dataProductId: str | None = None
|
|
73
|
+
outputPortId: str | None = None
|
|
74
|
+
driver: str | None = None
|
|
75
|
+
storageAccount: str | None = None
|
|
76
76
|
roles: List[ServerRole] = None
|
|
77
77
|
|
|
78
78
|
model_config = pyd.ConfigDict(
|
|
@@ -81,11 +81,11 @@ class Server(pyd.BaseModel):
|
|
|
81
81
|
|
|
82
82
|
|
|
83
83
|
class Terms(pyd.BaseModel):
|
|
84
|
-
usage: str = None
|
|
85
|
-
limitations: str = None
|
|
86
|
-
billing: str = None
|
|
87
|
-
noticePeriod: str = None
|
|
88
|
-
description: str = None
|
|
84
|
+
usage: str | None = None
|
|
85
|
+
limitations: str | None = None
|
|
86
|
+
billing: str | None = None
|
|
87
|
+
noticePeriod: str | None = None
|
|
88
|
+
description: str | None = None
|
|
89
89
|
|
|
90
90
|
model_config = pyd.ConfigDict(
|
|
91
91
|
extra="allow",
|
|
@@ -93,26 +93,27 @@ class Terms(pyd.BaseModel):
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class Definition(pyd.BaseModel):
|
|
96
|
-
domain: str = None
|
|
97
|
-
name: str = None
|
|
98
|
-
title: str = None
|
|
99
|
-
description: str = None
|
|
100
|
-
type: str = None
|
|
96
|
+
domain: str | None = None
|
|
97
|
+
name: str | None = None
|
|
98
|
+
title: str | None = None
|
|
99
|
+
description: str | None = None
|
|
100
|
+
type: str | None = None
|
|
101
101
|
enum: List[str] = []
|
|
102
|
-
format: str = None
|
|
103
|
-
minLength: int = None
|
|
104
|
-
maxLength: int = None
|
|
105
|
-
pattern: str = None
|
|
106
|
-
minimum: int = None
|
|
107
|
-
exclusiveMinimum: int = None
|
|
108
|
-
maximum: int = None
|
|
109
|
-
exclusiveMaximum: int = None
|
|
110
|
-
pii: bool = None
|
|
111
|
-
classification: str = None
|
|
102
|
+
format: str | None = None
|
|
103
|
+
minLength: int | None = None
|
|
104
|
+
maxLength: int | None = None
|
|
105
|
+
pattern: str | None = None
|
|
106
|
+
minimum: int | None = None
|
|
107
|
+
exclusiveMinimum: int | None = None
|
|
108
|
+
maximum: int | None = None
|
|
109
|
+
exclusiveMaximum: int | None = None
|
|
110
|
+
pii: bool | None = None
|
|
111
|
+
classification: str | None = None
|
|
112
112
|
fields: Dict[str, "Field"] = {}
|
|
113
|
+
items: "Field" = None
|
|
113
114
|
tags: List[str] = []
|
|
114
115
|
links: Dict[str, str] = {}
|
|
115
|
-
example: str = None
|
|
116
|
+
example: str | None = None
|
|
116
117
|
examples: List[Any] | None = None
|
|
117
118
|
|
|
118
119
|
model_config = pyd.ConfigDict(
|
|
@@ -121,20 +122,20 @@ class Definition(pyd.BaseModel):
|
|
|
121
122
|
|
|
122
123
|
|
|
123
124
|
class Quality(pyd.BaseModel):
|
|
124
|
-
type: str = None
|
|
125
|
-
description: str = None
|
|
126
|
-
query: str = None
|
|
127
|
-
dialect: str = None
|
|
128
|
-
mustBe: int = None
|
|
129
|
-
mustNotBe: int = None
|
|
130
|
-
mustBeGreaterThan: int = None
|
|
131
|
-
mustBeGreaterThanOrEqualTo: int = None
|
|
132
|
-
mustBeLessThan: int = None
|
|
133
|
-
mustBeLessThanOrEqualTo: int = None
|
|
125
|
+
type: str | None = None
|
|
126
|
+
description: str | None = None
|
|
127
|
+
query: str | None = None
|
|
128
|
+
dialect: str | None = None
|
|
129
|
+
mustBe: int | None = None
|
|
130
|
+
mustNotBe: int | None = None
|
|
131
|
+
mustBeGreaterThan: int | None = None
|
|
132
|
+
mustBeGreaterThanOrEqualTo: int | None = None
|
|
133
|
+
mustBeLessThan: int | None = None
|
|
134
|
+
mustBeLessThanOrEqualTo: int | None = None
|
|
134
135
|
mustBeBetween: List[int] = None
|
|
135
136
|
mustNotBeBetween: List[int] = None
|
|
136
|
-
engine: str = None
|
|
137
|
-
implementation: str | Dict[str, Any] = None
|
|
137
|
+
engine: str | None = None
|
|
138
|
+
implementation: str | Dict[str, Any] | None = None
|
|
138
139
|
|
|
139
140
|
model_config = pyd.ConfigDict(
|
|
140
141
|
extra="allow",
|
|
@@ -144,26 +145,26 @@ class Quality(pyd.BaseModel):
|
|
|
144
145
|
class Field(pyd.BaseModel):
|
|
145
146
|
ref: str = pyd.Field(default=None, alias="$ref")
|
|
146
147
|
title: str | None = None
|
|
147
|
-
type: str = None
|
|
148
|
-
format: str = None
|
|
149
|
-
required: bool = None
|
|
148
|
+
type: str | None = None
|
|
149
|
+
format: str | None = None
|
|
150
|
+
required: bool | None = None
|
|
150
151
|
primary: bool = pyd.Field(
|
|
151
152
|
default=None,
|
|
152
153
|
deprecated="Removed in Data Contract Specification v1.1.0. Use primaryKey instead.",
|
|
153
154
|
)
|
|
154
155
|
primaryKey: bool | None = None
|
|
155
156
|
unique: bool | None = None
|
|
156
|
-
references: str = None
|
|
157
|
+
references: str | None = None
|
|
157
158
|
description: str | None = None
|
|
158
159
|
pii: bool | None = None
|
|
159
160
|
classification: str | None = None
|
|
160
|
-
pattern: str = None
|
|
161
|
-
minLength: int = None
|
|
162
|
-
maxLength: int = None
|
|
163
|
-
minimum: int = None
|
|
164
|
-
exclusiveMinimum: int = None
|
|
165
|
-
maximum: int = None
|
|
166
|
-
exclusiveMaximum: int = None
|
|
161
|
+
pattern: str | None = None
|
|
162
|
+
minLength: int | None = None
|
|
163
|
+
maxLength: int | None = None
|
|
164
|
+
minimum: int | None = None
|
|
165
|
+
exclusiveMinimum: int | None = None
|
|
166
|
+
maximum: int | None = None
|
|
167
|
+
exclusiveMaximum: int | None = None
|
|
167
168
|
enum: List[str] | None = []
|
|
168
169
|
tags: List[str] | None = []
|
|
169
170
|
links: Dict[str, str] = {}
|
|
@@ -171,11 +172,11 @@ class Field(pyd.BaseModel):
|
|
|
171
172
|
items: "Field" = None
|
|
172
173
|
keys: "Field" = None
|
|
173
174
|
values: "Field" = None
|
|
174
|
-
precision: int = None
|
|
175
|
-
scale: int = None
|
|
176
|
-
example:
|
|
175
|
+
precision: int | None = None
|
|
176
|
+
scale: int | None = None
|
|
177
|
+
example: Any | None = pyd.Field(
|
|
177
178
|
default=None,
|
|
178
|
-
deprecated="Removed in Data Contract Specification v1.1.0. Use
|
|
179
|
+
deprecated="Removed in Data Contract Specification v1.1.0. Use examples instead.",
|
|
179
180
|
)
|
|
180
181
|
examples: List[Any] | None = None
|
|
181
182
|
quality: List[Quality] | None = []
|
|
@@ -187,10 +188,10 @@ class Field(pyd.BaseModel):
|
|
|
187
188
|
|
|
188
189
|
|
|
189
190
|
class Model(pyd.BaseModel):
|
|
190
|
-
description:
|
|
191
|
-
type:
|
|
192
|
-
namespace:
|
|
193
|
-
title:
|
|
191
|
+
description: str | None = None
|
|
192
|
+
type: str | None = None
|
|
193
|
+
namespace: str | None = None
|
|
194
|
+
title: str | None = None
|
|
194
195
|
fields: Dict[str, Field] = {}
|
|
195
196
|
quality: List[Quality] | None = []
|
|
196
197
|
primaryKey: List[str] | None = []
|
|
@@ -204,12 +205,12 @@ class Model(pyd.BaseModel):
|
|
|
204
205
|
|
|
205
206
|
|
|
206
207
|
class Info(pyd.BaseModel):
|
|
207
|
-
title: str = None
|
|
208
|
-
version: str = None
|
|
209
|
-
status: str = None
|
|
210
|
-
description: str = None
|
|
211
|
-
owner: str = None
|
|
212
|
-
contact: Contact = None
|
|
208
|
+
title: str | None = None
|
|
209
|
+
version: str | None = None
|
|
210
|
+
status: str | None = None
|
|
211
|
+
description: str | None = None
|
|
212
|
+
owner: str | None = None
|
|
213
|
+
contact: Contact | None = None
|
|
213
214
|
|
|
214
215
|
model_config = pyd.ConfigDict(
|
|
215
216
|
extra="allow",
|
|
@@ -217,91 +218,91 @@ class Info(pyd.BaseModel):
|
|
|
217
218
|
|
|
218
219
|
|
|
219
220
|
class Example(pyd.BaseModel):
|
|
220
|
-
type: str = None
|
|
221
|
-
description: str = None
|
|
222
|
-
model: str = None
|
|
221
|
+
type: str | None = None
|
|
222
|
+
description: str | None = None
|
|
223
|
+
model: str | None = None
|
|
223
224
|
data: str | object = None
|
|
224
225
|
|
|
225
226
|
|
|
226
227
|
# Deprecated Quality class
|
|
227
228
|
class DeprecatedQuality(pyd.BaseModel):
|
|
228
|
-
type: str = None
|
|
229
|
+
type: str | None = None
|
|
229
230
|
specification: str | object = None
|
|
230
231
|
|
|
231
232
|
|
|
232
233
|
class Availability(pyd.BaseModel):
|
|
233
|
-
description:
|
|
234
|
-
percentage:
|
|
234
|
+
description: str | None = None
|
|
235
|
+
percentage: str | None = None
|
|
235
236
|
|
|
236
237
|
|
|
237
238
|
class Retention(pyd.BaseModel):
|
|
238
|
-
description:
|
|
239
|
-
period:
|
|
240
|
-
unlimited:
|
|
241
|
-
timestampField:
|
|
239
|
+
description: str | None = None
|
|
240
|
+
period: str | None = None
|
|
241
|
+
unlimited: bool | None = None
|
|
242
|
+
timestampField: str | None = None
|
|
242
243
|
|
|
243
244
|
|
|
244
245
|
class Latency(pyd.BaseModel):
|
|
245
|
-
description:
|
|
246
|
-
threshold:
|
|
247
|
-
sourceTimestampField:
|
|
248
|
-
processedTimestampField:
|
|
246
|
+
description: str | None = None
|
|
247
|
+
threshold: str | None = None
|
|
248
|
+
sourceTimestampField: str | None = None
|
|
249
|
+
processedTimestampField: str | None = None
|
|
249
250
|
|
|
250
251
|
|
|
251
252
|
class Freshness(pyd.BaseModel):
|
|
252
|
-
description:
|
|
253
|
-
threshold:
|
|
254
|
-
timestampField:
|
|
253
|
+
description: str | None = None
|
|
254
|
+
threshold: str | None = None
|
|
255
|
+
timestampField: str | None = None
|
|
255
256
|
|
|
256
257
|
|
|
257
258
|
class Frequency(pyd.BaseModel):
|
|
258
|
-
description:
|
|
259
|
-
type:
|
|
260
|
-
interval:
|
|
261
|
-
cron:
|
|
259
|
+
description: str | None = None
|
|
260
|
+
type: str | None = None
|
|
261
|
+
interval: str | None = None
|
|
262
|
+
cron: str | None = None
|
|
262
263
|
|
|
263
264
|
|
|
264
265
|
class Support(pyd.BaseModel):
|
|
265
|
-
description:
|
|
266
|
-
time:
|
|
267
|
-
responseTime:
|
|
266
|
+
description: str | None = None
|
|
267
|
+
time: str | None = None
|
|
268
|
+
responseTime: str | None = None
|
|
268
269
|
|
|
269
270
|
|
|
270
271
|
class Backup(pyd.BaseModel):
|
|
271
|
-
description:
|
|
272
|
-
interval:
|
|
273
|
-
cron:
|
|
274
|
-
recoveryTime:
|
|
275
|
-
recoveryPoint:
|
|
272
|
+
description: str | None = None
|
|
273
|
+
interval: str | None = None
|
|
274
|
+
cron: str | None = None
|
|
275
|
+
recoveryTime: str | None = None
|
|
276
|
+
recoveryPoint: str | None = None
|
|
276
277
|
|
|
277
278
|
|
|
278
279
|
class ServiceLevel(pyd.BaseModel):
|
|
279
|
-
availability:
|
|
280
|
-
retention:
|
|
281
|
-
latency:
|
|
282
|
-
freshness:
|
|
283
|
-
frequency:
|
|
284
|
-
support:
|
|
285
|
-
backup:
|
|
280
|
+
availability: Availability | None = None
|
|
281
|
+
retention: Retention | None = None
|
|
282
|
+
latency: Latency | None = None
|
|
283
|
+
freshness: Freshness | None = None
|
|
284
|
+
frequency: Frequency | None = None
|
|
285
|
+
support: Support | None = None
|
|
286
|
+
backup: Backup | None = None
|
|
286
287
|
|
|
287
288
|
|
|
288
289
|
class DataContractSpecification(pyd.BaseModel):
|
|
289
|
-
dataContractSpecification: str = None
|
|
290
|
-
id: str = None
|
|
291
|
-
info: Info = None
|
|
290
|
+
dataContractSpecification: str | None = None
|
|
291
|
+
id: str | None = None
|
|
292
|
+
info: Info | None = None
|
|
292
293
|
servers: Dict[str, Server] = {}
|
|
293
|
-
terms: Terms = None
|
|
294
|
+
terms: Terms | None = None
|
|
294
295
|
models: Dict[str, Model] = {}
|
|
295
296
|
definitions: Dict[str, Definition] = {}
|
|
296
297
|
examples: List[Example] = pyd.Field(
|
|
297
298
|
default_factory=list,
|
|
298
299
|
deprecated="Removed in Data Contract Specification " "v1.1.0. Use models.examples instead.",
|
|
299
300
|
)
|
|
300
|
-
quality: DeprecatedQuality = pyd.Field(
|
|
301
|
+
quality: DeprecatedQuality | None = pyd.Field(
|
|
301
302
|
default=None,
|
|
302
303
|
deprecated="Removed in Data Contract Specification v1.1.0. Use " "model-level and field-level quality instead.",
|
|
303
304
|
)
|
|
304
|
-
servicelevels:
|
|
305
|
+
servicelevels: ServiceLevel | None = None
|
|
305
306
|
links: Dict[str, str] = {}
|
|
306
307
|
tags: List[str] = []
|
|
307
308
|
|