entropy-data 0.3.0__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.
- entropy_data/__init__.py +5 -0
- entropy_data/__main__.py +3 -0
- entropy_data/cli.py +133 -0
- entropy_data/client.py +163 -0
- entropy_data/commands/__init__.py +0 -0
- entropy_data/commands/access.py +128 -0
- entropy_data/commands/api_keys.py +65 -0
- entropy_data/commands/assets.py +80 -0
- entropy_data/commands/certifications.py +80 -0
- entropy_data/commands/connection.py +90 -0
- entropy_data/commands/costs.py +61 -0
- entropy_data/commands/datacontracts.py +115 -0
- entropy_data/commands/dataproducts.py +93 -0
- entropy_data/commands/definitions.py +80 -0
- entropy_data/commands/events.py +32 -0
- entropy_data/commands/example_data.py +83 -0
- entropy_data/commands/import_export.py +134 -0
- entropy_data/commands/lineage.py +107 -0
- entropy_data/commands/search.py +55 -0
- entropy_data/commands/settings.py +68 -0
- entropy_data/commands/sourcesystems.py +80 -0
- entropy_data/commands/tags.py +84 -0
- entropy_data/commands/teams.py +80 -0
- entropy_data/commands/test_results.py +85 -0
- entropy_data/commands/usage.py +99 -0
- entropy_data/config.py +141 -0
- entropy_data/output.py +119 -0
- entropy_data/util.py +22 -0
- entropy_data-0.3.0.dist-info/METADATA +23 -0
- entropy_data-0.3.0.dist-info/RECORD +33 -0
- entropy_data-0.3.0.dist-info/WHEEL +4 -0
- entropy_data-0.3.0.dist-info/entry_points.txt +2 -0
- entropy_data-0.3.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Connection management commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from entropy_data import config as cfg
|
|
9
|
+
from entropy_data.output import console, print_error, print_success
|
|
10
|
+
|
|
11
|
+
connection_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@connection_app.command("list")
|
|
15
|
+
def list_connections() -> None:
|
|
16
|
+
"""List all configured connections."""
|
|
17
|
+
connections = cfg.list_connections()
|
|
18
|
+
if not connections:
|
|
19
|
+
console.print("No connections configured. Run: entropy-data connection add <name>")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
table = Table(show_header=True)
|
|
23
|
+
table.add_column("Name")
|
|
24
|
+
table.add_column("Host")
|
|
25
|
+
table.add_column("API Key")
|
|
26
|
+
table.add_column("Default")
|
|
27
|
+
for conn in connections:
|
|
28
|
+
table.add_row(
|
|
29
|
+
conn["name"],
|
|
30
|
+
conn["host"],
|
|
31
|
+
conn["api_key"],
|
|
32
|
+
"*" if conn["default"] else "",
|
|
33
|
+
)
|
|
34
|
+
console.print(table)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@connection_app.command("add")
|
|
38
|
+
def add_connection(
|
|
39
|
+
name: Annotated[str, typer.Argument(help="Connection name.")],
|
|
40
|
+
api_key: Annotated[str, typer.Option("--api-key", prompt="API key", help="The API key.")] = None,
|
|
41
|
+
host: Annotated[
|
|
42
|
+
str, typer.Option("--host", prompt="Host", prompt_required=False, help="API host URL.")
|
|
43
|
+
] = cfg.DEFAULT_HOST,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Add or update a named connection."""
|
|
46
|
+
try:
|
|
47
|
+
cfg.add_connection(name, api_key, host)
|
|
48
|
+
print_success(f"Connection '{name}' saved.")
|
|
49
|
+
except cfg.ConfigurationError as e:
|
|
50
|
+
print_error(str(e))
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@connection_app.command("remove")
|
|
55
|
+
def remove_connection(
|
|
56
|
+
name: Annotated[str, typer.Argument(help="Connection name to remove.")],
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Remove a named connection."""
|
|
59
|
+
try:
|
|
60
|
+
cfg.remove_connection(name)
|
|
61
|
+
print_success(f"Connection '{name}' removed.")
|
|
62
|
+
except cfg.ConfigurationError as e:
|
|
63
|
+
print_error(str(e))
|
|
64
|
+
raise typer.Exit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@connection_app.command("set-default")
|
|
68
|
+
def set_default(
|
|
69
|
+
name: Annotated[str, typer.Argument(help="Connection name to set as default.")],
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Set the default connection."""
|
|
72
|
+
try:
|
|
73
|
+
cfg.set_default_connection(name)
|
|
74
|
+
print_success(f"Default connection set to '{name}'.")
|
|
75
|
+
except cfg.ConfigurationError as e:
|
|
76
|
+
print_error(str(e))
|
|
77
|
+
raise typer.Exit(1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@connection_app.command("test")
|
|
81
|
+
def test_connection() -> None:
|
|
82
|
+
"""Test the current connection by calling the API."""
|
|
83
|
+
from entropy_data.cli import get_client, handle_error
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
client = get_client()
|
|
87
|
+
client.list_resources("teams", params={"p": "0"})
|
|
88
|
+
print_success("Connection successful.")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
handle_error(e)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Costs commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, print_resource_list, print_success
|
|
9
|
+
from entropy_data.util import read_body
|
|
10
|
+
|
|
11
|
+
costs_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "costs"
|
|
13
|
+
RESOURCE_TYPE = "costs"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@costs_app.command("list")
|
|
17
|
+
def list_costs(
|
|
18
|
+
data_product_id: Annotated[str, typer.Option("--data-product-id", help="Data product ID to list costs for.")],
|
|
19
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List all costs of a data product."""
|
|
22
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
23
|
+
|
|
24
|
+
fmt = output or get_output_format()
|
|
25
|
+
try:
|
|
26
|
+
client = get_client()
|
|
27
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params={"dataProductId": data_product_id})
|
|
28
|
+
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
handle_error(e)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@costs_app.command("add")
|
|
34
|
+
def add_cost(
|
|
35
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Add a cost."""
|
|
38
|
+
from entropy_data.cli import get_client, handle_error
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
body = read_body(file)
|
|
42
|
+
client = get_client()
|
|
43
|
+
client.post_resource(RESOURCE_PATH, body)
|
|
44
|
+
print_success("Cost added.")
|
|
45
|
+
except Exception as e:
|
|
46
|
+
handle_error(e)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@costs_app.command("delete")
|
|
50
|
+
def delete_cost(
|
|
51
|
+
id: Annotated[str, typer.Argument(help="Cost ID.")],
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Delete a cost."""
|
|
54
|
+
from entropy_data.cli import get_client, handle_error
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
client = get_client()
|
|
58
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
59
|
+
print_success(f"Cost '{id}' deleted.")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
handle_error(e)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Data contracts commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
|
|
9
|
+
from entropy_data.util import read_body
|
|
10
|
+
|
|
11
|
+
datacontracts_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "datacontracts"
|
|
13
|
+
RESOURCE_TYPE = "datacontracts"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@datacontracts_app.command("list")
|
|
17
|
+
def list_datacontracts(
|
|
18
|
+
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
19
|
+
query: Annotated[Optional[str], typer.Option("--query", "-q", help="Search term.")] = None,
|
|
20
|
+
owner: Annotated[Optional[str], typer.Option("--owner", help="Filter by owner.")] = None,
|
|
21
|
+
tag: Annotated[Optional[str], typer.Option("--tag", help="Filter by tag.")] = None,
|
|
22
|
+
sort: Annotated[Optional[str], typer.Option("--sort", help="Sort field.")] = None,
|
|
23
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""List all data contracts."""
|
|
26
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
27
|
+
|
|
28
|
+
fmt = output or get_output_format()
|
|
29
|
+
try:
|
|
30
|
+
client = get_client()
|
|
31
|
+
params = {"p": page}
|
|
32
|
+
if query:
|
|
33
|
+
params["q"] = query
|
|
34
|
+
if owner:
|
|
35
|
+
params["owner"] = owner
|
|
36
|
+
if tag:
|
|
37
|
+
params["tag"] = tag
|
|
38
|
+
if sort:
|
|
39
|
+
params["sort"] = sort
|
|
40
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params=params)
|
|
41
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
handle_error(e)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@datacontracts_app.command("get")
|
|
47
|
+
def get_datacontract(
|
|
48
|
+
id: Annotated[str, typer.Argument(help="Data contract ID.")],
|
|
49
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Get a data contract by ID."""
|
|
52
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
53
|
+
|
|
54
|
+
fmt = output or get_output_format()
|
|
55
|
+
try:
|
|
56
|
+
client = get_client()
|
|
57
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
58
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
handle_error(e)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@datacontracts_app.command("put")
|
|
64
|
+
def put_datacontract(
|
|
65
|
+
id: Annotated[str, typer.Argument(help="Data contract ID.")],
|
|
66
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Create or update a data contract."""
|
|
69
|
+
from entropy_data.cli import get_client, handle_error
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
body = read_body(file)
|
|
73
|
+
client = get_client()
|
|
74
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
75
|
+
print_success(f"Data contract '{id}' saved.")
|
|
76
|
+
print_link(location)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
handle_error(e)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@datacontracts_app.command("test")
|
|
82
|
+
def test_datacontract(
|
|
83
|
+
id: Annotated[str, typer.Argument(help="Data contract ID.")],
|
|
84
|
+
server: Annotated[Optional[str], typer.Option("--server", "-s", help="Server name to test against.")] = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Run a data contract test."""
|
|
87
|
+
import json
|
|
88
|
+
|
|
89
|
+
from entropy_data.cli import get_client, handle_error
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
client = get_client()
|
|
93
|
+
params = {}
|
|
94
|
+
if server:
|
|
95
|
+
params["server"] = server
|
|
96
|
+
# Data contract tests can take a long time (up to 30 minutes)
|
|
97
|
+
data = client.post_action_json(RESOURCE_PATH, id, "test", params=params, timeout=1800)
|
|
98
|
+
print(json.dumps(data, indent=2))
|
|
99
|
+
except Exception as e:
|
|
100
|
+
handle_error(e)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@datacontracts_app.command("delete")
|
|
104
|
+
def delete_datacontract(
|
|
105
|
+
id: Annotated[str, typer.Argument(help="Data contract ID.")],
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Delete a data contract."""
|
|
108
|
+
from entropy_data.cli import get_client, handle_error
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
client = get_client()
|
|
112
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
113
|
+
print_success(f"Data contract '{id}' deleted.")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
handle_error(e)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Data products commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
|
|
9
|
+
from entropy_data.util import read_body
|
|
10
|
+
|
|
11
|
+
dataproducts_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "dataproducts"
|
|
13
|
+
RESOURCE_TYPE = "dataproducts"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataproducts_app.command("list")
|
|
17
|
+
def list_dataproducts(
|
|
18
|
+
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
19
|
+
query: Annotated[Optional[str], typer.Option("--query", "-q", help="Search term.")] = None,
|
|
20
|
+
status: Annotated[Optional[str], typer.Option("--status", help="Filter by status.")] = None,
|
|
21
|
+
tag: Annotated[Optional[str], typer.Option("--tag", help="Filter by tag.")] = None,
|
|
22
|
+
sort: Annotated[Optional[str], typer.Option("--sort", help="Sort field.")] = None,
|
|
23
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""List all data products."""
|
|
26
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
27
|
+
|
|
28
|
+
fmt = output or get_output_format()
|
|
29
|
+
try:
|
|
30
|
+
client = get_client()
|
|
31
|
+
params = {"p": page}
|
|
32
|
+
if query:
|
|
33
|
+
params["q"] = query
|
|
34
|
+
if status:
|
|
35
|
+
params["status"] = status
|
|
36
|
+
if tag:
|
|
37
|
+
params["tag"] = tag
|
|
38
|
+
if sort:
|
|
39
|
+
params["sort"] = sort
|
|
40
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params=params)
|
|
41
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
handle_error(e)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataproducts_app.command("get")
|
|
47
|
+
def get_dataproduct(
|
|
48
|
+
id: Annotated[str, typer.Argument(help="Data product ID.")],
|
|
49
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Get a data product by ID."""
|
|
52
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
53
|
+
|
|
54
|
+
fmt = output or get_output_format()
|
|
55
|
+
try:
|
|
56
|
+
client = get_client()
|
|
57
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
58
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
handle_error(e)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataproducts_app.command("put")
|
|
64
|
+
def put_dataproduct(
|
|
65
|
+
id: Annotated[str, typer.Argument(help="Data product ID.")],
|
|
66
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Create or update a data product."""
|
|
69
|
+
from entropy_data.cli import get_client, handle_error
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
body = read_body(file)
|
|
73
|
+
client = get_client()
|
|
74
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
75
|
+
print_success(f"Data product '{id}' saved.")
|
|
76
|
+
print_link(location)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
handle_error(e)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataproducts_app.command("delete")
|
|
82
|
+
def delete_dataproduct(
|
|
83
|
+
id: Annotated[str, typer.Argument(help="Data product ID.")],
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Delete a data product."""
|
|
86
|
+
from entropy_data.cli import get_client, handle_error
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
client = get_client()
|
|
90
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
91
|
+
print_success(f"Data product '{id}' deleted.")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
handle_error(e)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Definitions commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
|
|
9
|
+
from entropy_data.util import read_body
|
|
10
|
+
|
|
11
|
+
definitions_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "definitions"
|
|
13
|
+
RESOURCE_TYPE = "definitions"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@definitions_app.command("list")
|
|
17
|
+
def list_definitions(
|
|
18
|
+
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
19
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List all definitions."""
|
|
22
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
23
|
+
|
|
24
|
+
fmt = output or get_output_format()
|
|
25
|
+
try:
|
|
26
|
+
client = get_client()
|
|
27
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params={"p": page})
|
|
28
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
handle_error(e)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@definitions_app.command("get")
|
|
34
|
+
def get_definition(
|
|
35
|
+
id: Annotated[str, typer.Argument(help="Definition ID.")],
|
|
36
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Get a definition by ID."""
|
|
39
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
40
|
+
|
|
41
|
+
fmt = output or get_output_format()
|
|
42
|
+
try:
|
|
43
|
+
client = get_client()
|
|
44
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
45
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
handle_error(e)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@definitions_app.command("put")
|
|
51
|
+
def put_definition(
|
|
52
|
+
id: Annotated[str, typer.Argument(help="Definition ID.")],
|
|
53
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Create or update a definition."""
|
|
56
|
+
from entropy_data.cli import get_client, handle_error
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
body = read_body(file)
|
|
60
|
+
client = get_client()
|
|
61
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
62
|
+
print_success(f"Definition '{id}' saved.")
|
|
63
|
+
print_link(location)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
handle_error(e)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@definitions_app.command("delete")
|
|
69
|
+
def delete_definition(
|
|
70
|
+
id: Annotated[str, typer.Argument(help="Definition ID.")],
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Delete a definition."""
|
|
73
|
+
from entropy_data.cli import get_client, handle_error
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
client = get_client()
|
|
77
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
78
|
+
print_success(f"Definition '{id}' deleted.")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
handle_error(e)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Events commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, console, print_resource_list
|
|
9
|
+
|
|
10
|
+
events_app = typer.Typer(no_args_is_help=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@events_app.command("poll")
|
|
14
|
+
def poll_events(
|
|
15
|
+
last_event_id: Annotated[
|
|
16
|
+
Optional[str], typer.Option("--last-event-id", help="Last event ID for incremental polling.")
|
|
17
|
+
] = None,
|
|
18
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Poll for events."""
|
|
21
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
22
|
+
|
|
23
|
+
fmt = output or get_output_format()
|
|
24
|
+
try:
|
|
25
|
+
client = get_client()
|
|
26
|
+
data = client.get_events(last_event_id=last_event_id)
|
|
27
|
+
if fmt == OutputFormat.json:
|
|
28
|
+
console.print_json(json.dumps(data))
|
|
29
|
+
else:
|
|
30
|
+
print_resource_list(data, "events", fmt)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
handle_error(e)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Example data commands."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
|
|
9
|
+
from entropy_data.util import read_body
|
|
10
|
+
|
|
11
|
+
example_data_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "example-data"
|
|
13
|
+
RESOURCE_TYPE = "example-data"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@example_data_app.command("list")
|
|
17
|
+
def list_example_data(
|
|
18
|
+
data_product_id: Annotated[Optional[str], typer.Option("--data-product-id", help="Filter by data product.")] = None,
|
|
19
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List all example data entries."""
|
|
22
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
23
|
+
|
|
24
|
+
fmt = output or get_output_format()
|
|
25
|
+
try:
|
|
26
|
+
client = get_client()
|
|
27
|
+
params = {}
|
|
28
|
+
if data_product_id:
|
|
29
|
+
params["dataProductId"] = data_product_id
|
|
30
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params=params or None)
|
|
31
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
handle_error(e)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@example_data_app.command("get")
|
|
37
|
+
def get_example_data(
|
|
38
|
+
id: Annotated[str, typer.Argument(help="Example data ID.")],
|
|
39
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Get example data by ID."""
|
|
42
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
43
|
+
|
|
44
|
+
fmt = output or get_output_format()
|
|
45
|
+
try:
|
|
46
|
+
client = get_client()
|
|
47
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
48
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
handle_error(e)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@example_data_app.command("put")
|
|
54
|
+
def put_example_data(
|
|
55
|
+
id: Annotated[str, typer.Argument(help="Example data ID.")],
|
|
56
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Create or update example data."""
|
|
59
|
+
from entropy_data.cli import get_client, handle_error
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
body = read_body(file)
|
|
63
|
+
client = get_client()
|
|
64
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
65
|
+
print_success(f"Example data '{id}' saved.")
|
|
66
|
+
print_link(location)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
handle_error(e)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@example_data_app.command("delete")
|
|
72
|
+
def delete_example_data(
|
|
73
|
+
id: Annotated[str, typer.Argument(help="Example data ID.")],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Delete example data."""
|
|
76
|
+
from entropy_data.cli import get_client, handle_error
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
client = get_client()
|
|
80
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
81
|
+
print_success(f"Example data '{id}' deleted.")
|
|
82
|
+
except Exception as e:
|
|
83
|
+
handle_error(e)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Import command for organization exports."""
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
import zipfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from entropy_data.client import ApiError
|
|
12
|
+
from entropy_data.output import console, error_console
|
|
13
|
+
|
|
14
|
+
import_app = typer.Typer(no_args_is_help=True)
|
|
15
|
+
|
|
16
|
+
# Import order respects resource dependencies:
|
|
17
|
+
# teams (parent→child), tags (→teams), definitions (→teams),
|
|
18
|
+
# assets (→teams), datacontracts (→teams, assets),
|
|
19
|
+
# dataproducts (→teams, datacontracts, assets), access (→dataproducts)
|
|
20
|
+
RESOURCE_ORDER = [
|
|
21
|
+
("teams", "teams"),
|
|
22
|
+
("tags", "tags"),
|
|
23
|
+
("definitions", "definitions"),
|
|
24
|
+
("assets", "assets"),
|
|
25
|
+
("datacontracts", "datacontracts"),
|
|
26
|
+
("dataproducts", "dataproducts"),
|
|
27
|
+
("access", "access"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _import_teams(teams_dir: Path, client) -> tuple[int, int]:
|
|
32
|
+
"""Import teams in topological order (parents before children), stripping members."""
|
|
33
|
+
teams = {}
|
|
34
|
+
for f in sorted(teams_dir.glob("*.yaml")):
|
|
35
|
+
data = yaml.safe_load(f.read_text())
|
|
36
|
+
data["members"] = []
|
|
37
|
+
teams[data["id"]] = {"data": data, "parent": data.get("parent")}
|
|
38
|
+
|
|
39
|
+
imported: set[str] = set()
|
|
40
|
+
success_count = 0
|
|
41
|
+
error_count = 0
|
|
42
|
+
|
|
43
|
+
while len(imported) < len(teams):
|
|
44
|
+
progress = False
|
|
45
|
+
for tid, t in teams.items():
|
|
46
|
+
if tid in imported:
|
|
47
|
+
continue
|
|
48
|
+
if t["parent"] is None or t["parent"] in imported:
|
|
49
|
+
try:
|
|
50
|
+
client.put_resource("teams", tid, t["data"])
|
|
51
|
+
console.print(f" [green]OK[/green] {tid}")
|
|
52
|
+
success_count += 1
|
|
53
|
+
except ApiError as e:
|
|
54
|
+
error_console.print(f" [red]FAIL[/red] {tid}: {e}")
|
|
55
|
+
error_count += 1
|
|
56
|
+
imported.add(tid)
|
|
57
|
+
progress = True
|
|
58
|
+
|
|
59
|
+
if not progress:
|
|
60
|
+
remaining = set(teams.keys()) - imported
|
|
61
|
+
error_console.print(f" [red]ERROR: circular or broken parent references: {remaining}[/red]")
|
|
62
|
+
error_count += len(remaining)
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
return success_count, error_count
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _import_simple(resource_dir: Path, api_path: str, client) -> tuple[int, int]:
|
|
69
|
+
"""Import resources from a directory using PUT."""
|
|
70
|
+
success_count = 0
|
|
71
|
+
error_count = 0
|
|
72
|
+
|
|
73
|
+
for f in sorted(resource_dir.glob("*.yaml")):
|
|
74
|
+
data = yaml.safe_load(f.read_text())
|
|
75
|
+
resource_id = data["id"]
|
|
76
|
+
try:
|
|
77
|
+
client.put_resource(api_path, resource_id, data)
|
|
78
|
+
console.print(f" [green]OK[/green] {resource_id}")
|
|
79
|
+
success_count += 1
|
|
80
|
+
except ApiError as e:
|
|
81
|
+
error_console.print(f" [red]FAIL[/red] {resource_id}: {e}")
|
|
82
|
+
error_count += 1
|
|
83
|
+
|
|
84
|
+
return success_count, error_count
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@import_app.command("zip")
|
|
88
|
+
def import_zip(
|
|
89
|
+
file: Annotated[Path, typer.Argument(help="Path to the export zip file.")],
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Import an organization export zip file."""
|
|
92
|
+
from entropy_data.cli import get_client, handle_error
|
|
93
|
+
|
|
94
|
+
if not file.is_file():
|
|
95
|
+
error_console.print(f"[red]Error: {file} not found[/red]")
|
|
96
|
+
raise typer.Exit(1)
|
|
97
|
+
|
|
98
|
+
if not zipfile.is_zipfile(file):
|
|
99
|
+
error_console.print(f"[red]Error: {file} is not a valid zip file[/red]")
|
|
100
|
+
raise typer.Exit(1)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
client = get_client()
|
|
104
|
+
except Exception as e:
|
|
105
|
+
handle_error(e)
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
109
|
+
export_dir = Path(tmpdir)
|
|
110
|
+
console.print(f"Extracting {file}...")
|
|
111
|
+
with zipfile.ZipFile(file) as zf:
|
|
112
|
+
zf.extractall(export_dir)
|
|
113
|
+
|
|
114
|
+
total_ok = 0
|
|
115
|
+
total_fail = 0
|
|
116
|
+
|
|
117
|
+
for directory, api_path in RESOURCE_ORDER:
|
|
118
|
+
resource_dir = export_dir / directory
|
|
119
|
+
if not resource_dir.is_dir():
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
console.print(f"\n[bold]{api_path}[/bold]")
|
|
123
|
+
|
|
124
|
+
if directory == "teams":
|
|
125
|
+
ok, fail = _import_teams(resource_dir, client)
|
|
126
|
+
else:
|
|
127
|
+
ok, fail = _import_simple(resource_dir, api_path, client)
|
|
128
|
+
|
|
129
|
+
total_ok += ok
|
|
130
|
+
total_fail += fail
|
|
131
|
+
|
|
132
|
+
console.print(f"\n[bold]Summary:[/bold] {total_ok} succeeded, {total_fail} failed")
|
|
133
|
+
if total_fail > 0:
|
|
134
|
+
raise typer.Exit(1)
|