entropy-data 0.3.6__tar.gz → 0.3.8__tar.gz
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-0.3.6 → entropy_data-0.3.8}/CHANGELOG.md +9 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/PKG-INFO +1 -1
- {entropy_data-0.3.6 → entropy_data-0.3.8}/README.md +1 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/pyproject.toml +1 -1
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/cli.py +2 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/access.py +21 -3
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/api_keys.py +3 -4
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/assets.py +11 -4
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/connection.py +3 -4
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/events.py +3 -4
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/gitconnections.py +2 -6
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/lineage.py +2 -6
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/notification_channels.py +2 -6
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/organization.py +3 -4
- entropy_data-0.3.8/src/entropy_data/commands/policies.py +79 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/search.py +3 -3
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/usage.py +2 -6
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/output.py +15 -4
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_access_request.py +36 -0
- entropy_data-0.3.8/tests/commands/test_policies.py +89 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.editorconfig +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/dependabot.yml +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/pull_request_template.md +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/workflows/ci.yaml +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/workflows/release.yaml +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.gitignore +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/.pre-commit-config.yaml +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/CLAUDE.md +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/Dockerfile +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/LICENSE +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/release +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/__init__.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/__main__.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/client.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/__init__.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/certifications.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/connectors.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/costs.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/datacontracts.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/dataproducts.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/definitions.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/example_data.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/git_credentials.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/import_export.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/semantics.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/settings.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/sourcesystems.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/tags.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/teams.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/test_results.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/config.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/util.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/__init__.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/__init__.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_api_keys.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_assets.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_connection.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_connectors.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_costs.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_datacontracts.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_dataproducts.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_git_credentials.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_gitconnections.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_lineage.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_notification_channels.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_organization.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_semantics.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_settings.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_tags.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_teams.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_usage.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/conftest.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/test_client.py +0 -0
- {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/test_config.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.8]
|
|
6
|
+
|
|
7
|
+
- Add `entropy-data policies list|get|put|delete` to manage policies. Wraps `/api/policies` and `/api/policies/{externalId}`.
|
|
8
|
+
|
|
9
|
+
## [0.3.7]
|
|
10
|
+
|
|
11
|
+
- Add `yaml` as a third value for `--output` / `-o` alongside `table` and `json`. Useful when piping results into editors or files that expect YAML (e.g. data contracts).
|
|
12
|
+
- `entropy-data access list` now supports `--provider-dataproduct`, `--consumer-dataproduct`, and `--consumer-type` filters, passed through to the existing `providerDataProductId`, `consumerDataProductId`, and `consumerType` query parameters of `GET /api/access`.
|
|
13
|
+
|
|
5
14
|
## [0.3.6]
|
|
6
15
|
|
|
7
16
|
- `entropy-data connection add` now sets the newly added connection as the default, overriding any previously set default.
|
|
@@ -87,6 +87,7 @@ entropy-data [--version] [--connection NAME] [--output table|json] [--debug]
|
|
|
87
87
|
sourcesystems list | get | put | delete
|
|
88
88
|
definitions list | get | put | delete
|
|
89
89
|
certifications list | get | put | delete
|
|
90
|
+
policies list | get | put | delete
|
|
90
91
|
example-data list | get | put | delete
|
|
91
92
|
test-results list | get | publish | delete
|
|
92
93
|
costs list | add | delete
|
|
@@ -105,6 +105,7 @@ from entropy_data.commands.example_data import example_data_app # noqa: E402
|
|
|
105
105
|
from entropy_data.commands.import_export import import_app # noqa: E402
|
|
106
106
|
from entropy_data.commands.lineage import lineage_app # noqa: E402
|
|
107
107
|
from entropy_data.commands.organization import organization_app # noqa: E402
|
|
108
|
+
from entropy_data.commands.policies import policies_app # noqa: E402
|
|
108
109
|
from entropy_data.commands.search import search_app # noqa: E402
|
|
109
110
|
from entropy_data.commands.semantics import semantics_app # noqa: E402
|
|
110
111
|
from entropy_data.commands.settings import settings_app # noqa: E402
|
|
@@ -122,6 +123,7 @@ app.add_typer(access_app, name="access", help="Manage access (data usage agreeme
|
|
|
122
123
|
app.add_typer(sourcesystems_app, name="sourcesystems", help="Manage source systems.")
|
|
123
124
|
app.add_typer(definitions_app, name="definitions", help="Manage definitions.")
|
|
124
125
|
app.add_typer(certifications_app, name="certifications", help="Manage certifications.")
|
|
126
|
+
app.add_typer(policies_app, name="policies", help="Manage policies.")
|
|
125
127
|
app.add_typer(example_data_app, name="example-data", help="Manage example data.")
|
|
126
128
|
app.add_typer(test_results_app, name="test-results", help="Manage test results.")
|
|
127
129
|
app.add_typer(costs_app, name="costs", help="Manage costs.")
|
|
@@ -17,6 +17,18 @@ RESOURCE_TYPE = "access"
|
|
|
17
17
|
@access_app.command("list")
|
|
18
18
|
def list_access(
|
|
19
19
|
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
20
|
+
provider_dataproduct: Annotated[
|
|
21
|
+
Optional[str],
|
|
22
|
+
typer.Option("--provider-dataproduct", help="Filter by provider data product ID."),
|
|
23
|
+
] = None,
|
|
24
|
+
consumer_dataproduct: Annotated[
|
|
25
|
+
Optional[str],
|
|
26
|
+
typer.Option("--consumer-dataproduct", help="Filter by consumer data product ID."),
|
|
27
|
+
] = None,
|
|
28
|
+
consumer_type: Annotated[
|
|
29
|
+
Optional[str],
|
|
30
|
+
typer.Option("--consumer-type", help="Filter by consumer type (e.g. team, user, dataProduct)."),
|
|
31
|
+
] = None,
|
|
20
32
|
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
21
33
|
) -> None:
|
|
22
34
|
"""List all access agreements."""
|
|
@@ -25,7 +37,14 @@ def list_access(
|
|
|
25
37
|
fmt = output or get_output_format()
|
|
26
38
|
try:
|
|
27
39
|
client = get_client()
|
|
28
|
-
|
|
40
|
+
params: dict = {"p": page}
|
|
41
|
+
if provider_dataproduct:
|
|
42
|
+
params["providerDataProductId"] = provider_dataproduct
|
|
43
|
+
if consumer_dataproduct:
|
|
44
|
+
params["consumerDataProductId"] = consumer_dataproduct
|
|
45
|
+
if consumer_type:
|
|
46
|
+
params["consumerType"] = consumer_type
|
|
47
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params=params)
|
|
29
48
|
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
|
|
30
49
|
except Exception as e:
|
|
31
50
|
handle_error(e)
|
|
@@ -166,8 +185,7 @@ def request_access(
|
|
|
166
185
|
consumer_count = sum(c is not None for c in (consumer_team, consumer_user, consumer_dataproduct))
|
|
167
186
|
if consumer_count != 1:
|
|
168
187
|
error_console.print(
|
|
169
|
-
"[red]Error: provide exactly one of --consumer-team, --consumer-user, "
|
|
170
|
-
"or --consumer-dataproduct.[/red]"
|
|
188
|
+
"[red]Error: provide exactly one of --consumer-team, --consumer-user, or --consumer-dataproduct.[/red]"
|
|
171
189
|
)
|
|
172
190
|
raise SystemExit(2)
|
|
173
191
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""API Keys commands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Annotated, Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
|
|
8
|
-
from entropy_data.output import OutputFormat, console, print_success
|
|
7
|
+
from entropy_data.output import OutputFormat, console, print_data, print_success
|
|
9
8
|
|
|
10
9
|
api_keys_app = typer.Typer(no_args_is_help=True)
|
|
11
10
|
RESOURCE_PATH = "api-keys"
|
|
@@ -38,8 +37,8 @@ def create_api_key(
|
|
|
38
37
|
|
|
39
38
|
_raise_for_status(response)
|
|
40
39
|
data = response.json()
|
|
41
|
-
if fmt
|
|
42
|
-
|
|
40
|
+
if fmt != OutputFormat.table:
|
|
41
|
+
print_data(data, fmt)
|
|
43
42
|
else:
|
|
44
43
|
print_success(f"API key created: {data.get('organizationApiKeyId')}")
|
|
45
44
|
key = data.get("key")
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"""Assets commands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Annotated, Optional
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
|
|
9
|
-
from entropy_data.output import
|
|
8
|
+
from entropy_data.output import (
|
|
9
|
+
OutputFormat,
|
|
10
|
+
console,
|
|
11
|
+
print_data,
|
|
12
|
+
print_link,
|
|
13
|
+
print_resource,
|
|
14
|
+
print_resource_list,
|
|
15
|
+
print_success,
|
|
16
|
+
)
|
|
10
17
|
from entropy_data.util import read_body
|
|
11
18
|
|
|
12
19
|
assets_app = typer.Typer(no_args_is_help=True)
|
|
@@ -101,8 +108,8 @@ def list_asset_tags(
|
|
|
101
108
|
)
|
|
102
109
|
_raise_for_status(response)
|
|
103
110
|
data = response.json()
|
|
104
|
-
if fmt
|
|
105
|
-
|
|
111
|
+
if fmt != OutputFormat.table:
|
|
112
|
+
print_data(data, fmt)
|
|
106
113
|
else:
|
|
107
114
|
for tag in data:
|
|
108
115
|
console.print(tag)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"""Connection management commands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Annotated, Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich.table import Table
|
|
8
7
|
|
|
9
8
|
from entropy_data import config as cfg
|
|
10
|
-
from entropy_data.output import OutputFormat, console, print_error, print_success
|
|
9
|
+
from entropy_data.output import OutputFormat, console, print_data, print_error, print_success
|
|
11
10
|
|
|
12
11
|
connection_app = typer.Typer(no_args_is_help=True)
|
|
13
12
|
|
|
@@ -102,7 +101,7 @@ def get_connection(
|
|
|
102
101
|
is_default = config.get("default_connection_name") == resolved_name
|
|
103
102
|
|
|
104
103
|
fmt = output or get_output_format()
|
|
105
|
-
if fmt
|
|
104
|
+
if fmt != OutputFormat.table:
|
|
106
105
|
payload = {
|
|
107
106
|
"name": resolved_name,
|
|
108
107
|
"host": conn.get("host", cfg.DEFAULT_HOST),
|
|
@@ -110,7 +109,7 @@ def get_connection(
|
|
|
110
109
|
"api_key": displayed_key,
|
|
111
110
|
"default": is_default,
|
|
112
111
|
}
|
|
113
|
-
|
|
112
|
+
print_data(payload, fmt)
|
|
114
113
|
return
|
|
115
114
|
|
|
116
115
|
table = Table(show_header=False)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""Events commands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Annotated, Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
|
|
8
|
-
from entropy_data.output import OutputFormat,
|
|
7
|
+
from entropy_data.output import OutputFormat, print_data, print_resource_list
|
|
9
8
|
|
|
10
9
|
events_app = typer.Typer(no_args_is_help=True)
|
|
11
10
|
|
|
@@ -24,8 +23,8 @@ def poll_events(
|
|
|
24
23
|
try:
|
|
25
24
|
client = get_client()
|
|
26
25
|
data = client.get_events(last_event_id=last_event_id)
|
|
27
|
-
if fmt
|
|
28
|
-
|
|
26
|
+
if fmt != OutputFormat.table:
|
|
27
|
+
print_data(data, fmt)
|
|
29
28
|
else:
|
|
30
29
|
print_resource_list(data, "events", fmt)
|
|
31
30
|
except Exception as e:
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""Git connection subcommands. Used as a sub-typer of dataproducts and datacontracts."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Annotated, Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
|
|
8
|
-
from entropy_data.output import OutputFormat, print_link, print_success
|
|
7
|
+
from entropy_data.output import OutputFormat, print_data, print_link, print_success
|
|
9
8
|
|
|
10
9
|
GIT_CONNECTION_TYPES = ("github", "gitlab", "bitbucket", "azuredevops")
|
|
11
10
|
|
|
@@ -31,10 +30,7 @@ def make_gitconnection_app(resource_path: str, resource_label: str) -> typer.Typ
|
|
|
31
30
|
try:
|
|
32
31
|
client = get_client()
|
|
33
32
|
data = client.get_gitconnection(resource_path, id)
|
|
34
|
-
|
|
35
|
-
print(json.dumps(data, indent=2))
|
|
36
|
-
else:
|
|
37
|
-
print(json.dumps(data, indent=2))
|
|
33
|
+
print_data(data, fmt)
|
|
38
34
|
except Exception as e:
|
|
39
35
|
handle_error(e)
|
|
40
36
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Lineage commands (OpenLineage events)."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Annotated, Optional
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
|
|
9
|
-
from entropy_data.output import OutputFormat,
|
|
8
|
+
from entropy_data.output import OutputFormat, print_resource_list, print_success
|
|
10
9
|
from entropy_data.util import read_body
|
|
11
10
|
|
|
12
11
|
lineage_app = typer.Typer(no_args_is_help=True)
|
|
@@ -46,10 +45,7 @@ def list_lineage(
|
|
|
46
45
|
params["dataProductId"] = data_product_id
|
|
47
46
|
client = get_client()
|
|
48
47
|
data, _ = client.list_resources(RESOURCE_PATH, params=params)
|
|
49
|
-
|
|
50
|
-
console.print_json(json.dumps(data))
|
|
51
|
-
else:
|
|
52
|
-
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
48
|
+
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
53
49
|
except Exception as e:
|
|
54
50
|
handle_error(e)
|
|
55
51
|
|
{entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/notification_channels.py
RENAMED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Team notification channel subcommands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Annotated, Optional
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
|
|
9
|
-
from entropy_data.output import OutputFormat,
|
|
8
|
+
from entropy_data.output import OutputFormat, print_data, print_success
|
|
10
9
|
from entropy_data.util import read_body
|
|
11
10
|
|
|
12
11
|
notifications_app = typer.Typer(no_args_is_help=True)
|
|
@@ -34,10 +33,7 @@ def get_channel(
|
|
|
34
33
|
response = client.session.get(_channel_url(client, team_id, channel_id), timeout=REQUEST_TIMEOUT)
|
|
35
34
|
_raise_for_status(response)
|
|
36
35
|
data = response.json()
|
|
37
|
-
|
|
38
|
-
console.print_json(json.dumps(data))
|
|
39
|
-
else:
|
|
40
|
-
console.print_json(json.dumps(data))
|
|
36
|
+
print_data(data, fmt)
|
|
41
37
|
except Exception as e:
|
|
42
38
|
handle_error(e)
|
|
43
39
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Organization commands."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from typing import Annotated, Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from rich.table import Table
|
|
8
7
|
|
|
9
|
-
from entropy_data.output import OutputFormat, console, print_resource, print_resource_list
|
|
8
|
+
from entropy_data.output import OutputFormat, console, print_data, print_resource, print_resource_list
|
|
10
9
|
|
|
11
10
|
organization_app = typer.Typer(no_args_is_help=True)
|
|
12
11
|
members_app = typer.Typer(no_args_is_help=True)
|
|
@@ -29,8 +28,8 @@ def get_organization(
|
|
|
29
28
|
)
|
|
30
29
|
_raise_for_status(response)
|
|
31
30
|
data = response.json()
|
|
32
|
-
if fmt
|
|
33
|
-
|
|
31
|
+
if fmt != OutputFormat.table:
|
|
32
|
+
print_data(data, fmt)
|
|
34
33
|
return
|
|
35
34
|
|
|
36
35
|
table = Table(show_header=False)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Policies 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
|
+
policies_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "policies"
|
|
13
|
+
RESOURCE_TYPE = "policies"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@policies_app.command("list")
|
|
17
|
+
def list_policies(
|
|
18
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""List all policies."""
|
|
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, has_next = client.list_resources(RESOURCE_PATH)
|
|
27
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
handle_error(e)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@policies_app.command("get")
|
|
33
|
+
def get_policy(
|
|
34
|
+
id: Annotated[str, typer.Argument(help="Policy ID.")],
|
|
35
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Get a policy by ID."""
|
|
38
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
39
|
+
|
|
40
|
+
fmt = output or get_output_format()
|
|
41
|
+
try:
|
|
42
|
+
client = get_client()
|
|
43
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
44
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
handle_error(e)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@policies_app.command("put")
|
|
50
|
+
def put_policy(
|
|
51
|
+
id: Annotated[str, typer.Argument(help="Policy ID.")],
|
|
52
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Create or update a policy."""
|
|
55
|
+
from entropy_data.cli import get_client, handle_error
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
body = read_body(file)
|
|
59
|
+
client = get_client()
|
|
60
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
61
|
+
print_success(f"Policy '{id}' saved.")
|
|
62
|
+
print_link(location)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
handle_error(e)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@policies_app.command("delete")
|
|
68
|
+
def delete_policy(
|
|
69
|
+
id: Annotated[str, typer.Argument(help="Policy ID.")],
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Delete a policy."""
|
|
72
|
+
from entropy_data.cli import get_client, handle_error
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
client = get_client()
|
|
76
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
77
|
+
print_success(f"Policy '{id}' deleted.")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
handle_error(e)
|
|
@@ -5,7 +5,7 @@ from typing import Annotated, Optional
|
|
|
5
5
|
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
|
-
from entropy_data.output import OutputFormat, console
|
|
8
|
+
from entropy_data.output import OutputFormat, console, print_data
|
|
9
9
|
|
|
10
10
|
search_app = typer.Typer(no_args_is_help=True)
|
|
11
11
|
|
|
@@ -29,8 +29,8 @@ def search_query(
|
|
|
29
29
|
params["resourceType"] = resource_type
|
|
30
30
|
data = client.search(query, **params)
|
|
31
31
|
|
|
32
|
-
if fmt
|
|
33
|
-
|
|
32
|
+
if fmt != OutputFormat.table:
|
|
33
|
+
print_data(data, fmt)
|
|
34
34
|
return
|
|
35
35
|
|
|
36
36
|
# Table output for search results
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Usage commands (OpenTelemetry traces)."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
from typing import Annotated, Optional
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
|
|
9
|
-
from entropy_data.output import OutputFormat,
|
|
8
|
+
from entropy_data.output import OutputFormat, print_resource_list, print_success
|
|
10
9
|
from entropy_data.util import read_body
|
|
11
10
|
|
|
12
11
|
usage_app = typer.Typer(no_args_is_help=True)
|
|
@@ -41,10 +40,7 @@ def list_usage(
|
|
|
41
40
|
params["dataContractId"] = data_contract_id
|
|
42
41
|
client = get_client()
|
|
43
42
|
data, _ = client.list_resources(RESOURCE_PATH, params=params)
|
|
44
|
-
|
|
45
|
-
console.print_json(json.dumps(data))
|
|
46
|
-
else:
|
|
47
|
-
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
43
|
+
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
48
44
|
except Exception as e:
|
|
49
45
|
handle_error(e)
|
|
50
46
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import json
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
|
+
import yaml
|
|
6
7
|
from rich.console import Console
|
|
7
8
|
from rich.table import Table
|
|
8
9
|
|
|
@@ -13,6 +14,15 @@ error_console = Console(stderr=True)
|
|
|
13
14
|
class OutputFormat(str, Enum):
|
|
14
15
|
table = "table"
|
|
15
16
|
json = "json"
|
|
17
|
+
yaml = "yaml"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def print_data(data, fmt: OutputFormat) -> None:
|
|
21
|
+
"""Print data structured as JSON or YAML."""
|
|
22
|
+
if fmt == OutputFormat.yaml:
|
|
23
|
+
console.print(yaml.safe_dump(data, sort_keys=False, default_flow_style=False), end="")
|
|
24
|
+
else:
|
|
25
|
+
console.print_json(json.dumps(data))
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
# Column definitions per resource type: list of (header, dict_key)
|
|
@@ -32,6 +42,7 @@ RESOURCE_COLUMNS: dict[str, list[tuple[str, str]]] = {
|
|
|
32
42
|
"sourcesystems": [("ID", "id"), ("Name", "name"), ("Owner", "owner")],
|
|
33
43
|
"definitions": [("ID", "id"), ("Name", "title"), ("Owner", "owner")],
|
|
34
44
|
"certifications": [("ID", "id"), ("Name", "name"), ("Rank", "rank"), ("Tag", "tag")],
|
|
45
|
+
"policies": [("ID", "id"), ("Name", "name"), ("Status", "status")],
|
|
35
46
|
"example-data": [("ID", "id"), ("Data Product", "dataProductId"), ("Schema", "schemaName")],
|
|
36
47
|
"test-results": [("ID", "id"), ("Data Contract", "dataContractId"), ("Result", "result")],
|
|
37
48
|
"events": [("ID", "id"), ("Type", "type"), ("Subject", "subject"), ("Time", "time")],
|
|
@@ -96,8 +107,8 @@ def _get_nested(data: dict, key: str) -> str:
|
|
|
96
107
|
|
|
97
108
|
def print_resource(data: dict, resource_type: str, fmt: OutputFormat) -> None:
|
|
98
109
|
"""Print a single resource."""
|
|
99
|
-
if fmt
|
|
100
|
-
|
|
110
|
+
if fmt != OutputFormat.table:
|
|
111
|
+
print_data(data, fmt)
|
|
101
112
|
return
|
|
102
113
|
|
|
103
114
|
columns = RESOURCE_COLUMNS.get(resource_type, [])
|
|
@@ -120,8 +131,8 @@ def print_resource_list(
|
|
|
120
131
|
title: str | None = None,
|
|
121
132
|
) -> None:
|
|
122
133
|
"""Print a list of resources."""
|
|
123
|
-
if fmt
|
|
124
|
-
|
|
134
|
+
if fmt != OutputFormat.table:
|
|
135
|
+
print_data(data, fmt)
|
|
125
136
|
return
|
|
126
137
|
|
|
127
138
|
columns = RESOURCE_COLUMNS.get(resource_type, [])
|
|
@@ -116,3 +116,39 @@ def test_request_multiple_consumers(monkeypatch, tmp_path):
|
|
|
116
116
|
],
|
|
117
117
|
)
|
|
118
118
|
assert result.exit_code == 2
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@responses.activate
|
|
122
|
+
def test_list_with_filters(monkeypatch, tmp_path):
|
|
123
|
+
_setup(monkeypatch, tmp_path)
|
|
124
|
+
responses.add(responses.GET, f"{BASE_URL}/api/access", json=[], status=200)
|
|
125
|
+
result = runner.invoke(
|
|
126
|
+
app,
|
|
127
|
+
[
|
|
128
|
+
"access",
|
|
129
|
+
"list",
|
|
130
|
+
"--provider-dataproduct",
|
|
131
|
+
"dp_provider",
|
|
132
|
+
"--consumer-dataproduct",
|
|
133
|
+
"dp_consumer",
|
|
134
|
+
"--consumer-type",
|
|
135
|
+
"team",
|
|
136
|
+
],
|
|
137
|
+
)
|
|
138
|
+
assert result.exit_code == 0
|
|
139
|
+
qs = responses.calls[0].request.url.split("?", 1)[1]
|
|
140
|
+
assert "providerDataProductId=dp_provider" in qs
|
|
141
|
+
assert "consumerDataProductId=dp_consumer" in qs
|
|
142
|
+
assert "consumerType=team" in qs
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@responses.activate
|
|
146
|
+
def test_list_without_filters(monkeypatch, tmp_path):
|
|
147
|
+
_setup(monkeypatch, tmp_path)
|
|
148
|
+
responses.add(responses.GET, f"{BASE_URL}/api/access", json=[], status=200)
|
|
149
|
+
result = runner.invoke(app, ["access", "list"])
|
|
150
|
+
assert result.exit_code == 0
|
|
151
|
+
qs = responses.calls[0].request.url.split("?", 1)[1]
|
|
152
|
+
assert "providerDataProductId" not in qs
|
|
153
|
+
assert "consumerDataProductId" not in qs
|
|
154
|
+
assert "consumerType" not in qs
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Tests for policies commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import responses
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
import entropy_data.config as cfg
|
|
9
|
+
from entropy_data.cli import app
|
|
10
|
+
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
BASE_URL = "https://api.entropy-data.com"
|
|
13
|
+
|
|
14
|
+
POLICIES_LIST = [
|
|
15
|
+
{"id": "pol_pii_handling", "name": "PII Handling", "status": "Published", "content": "..."},
|
|
16
|
+
{"id": "pol_retention", "name": "Data Retention", "status": "Draft", "content": "..."},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@responses.activate
|
|
21
|
+
def test_policies_list(monkeypatch, tmp_path):
|
|
22
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
23
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
24
|
+
responses.add(responses.GET, f"{BASE_URL}/api/policies", json=POLICIES_LIST, status=200)
|
|
25
|
+
result = runner.invoke(app, ["policies", "list"])
|
|
26
|
+
assert result.exit_code == 0
|
|
27
|
+
assert "PII Handling" in result.output
|
|
28
|
+
assert "Data Retention" in result.output
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@responses.activate
|
|
32
|
+
def test_policies_list_json(monkeypatch, tmp_path):
|
|
33
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
34
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
35
|
+
responses.add(responses.GET, f"{BASE_URL}/api/policies", json=POLICIES_LIST, status=200)
|
|
36
|
+
result = runner.invoke(app, ["policies", "list", "--output", "json"])
|
|
37
|
+
assert result.exit_code == 0
|
|
38
|
+
data = json.loads(result.output)
|
|
39
|
+
assert len(data) == 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@responses.activate
|
|
43
|
+
def test_policies_get(monkeypatch, tmp_path):
|
|
44
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
45
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
46
|
+
responses.add(
|
|
47
|
+
responses.GET,
|
|
48
|
+
f"{BASE_URL}/api/policies/pol_pii_handling",
|
|
49
|
+
json=POLICIES_LIST[0],
|
|
50
|
+
status=200,
|
|
51
|
+
)
|
|
52
|
+
result = runner.invoke(app, ["policies", "get", "pol_pii_handling"])
|
|
53
|
+
assert result.exit_code == 0
|
|
54
|
+
assert "PII Handling" in result.output
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@responses.activate
|
|
58
|
+
def test_policies_put(monkeypatch, tmp_path):
|
|
59
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
60
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
61
|
+
responses.add(
|
|
62
|
+
responses.PUT,
|
|
63
|
+
f"{BASE_URL}/api/policies/pol_pii_handling",
|
|
64
|
+
status=200,
|
|
65
|
+
)
|
|
66
|
+
policy_file = tmp_path / "policy.json"
|
|
67
|
+
policy_file.write_text(json.dumps({"id": "pol_pii_handling", "name": "PII Handling", "status": "Draft"}))
|
|
68
|
+
result = runner.invoke(app, ["policies", "put", "pol_pii_handling", "--file", str(policy_file)])
|
|
69
|
+
assert result.exit_code == 0
|
|
70
|
+
assert "saved" in result.output
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@responses.activate
|
|
74
|
+
def test_policies_delete(monkeypatch, tmp_path):
|
|
75
|
+
monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
|
|
76
|
+
monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
|
|
77
|
+
responses.add(responses.DELETE, f"{BASE_URL}/api/policies/pol_pii_handling", status=200)
|
|
78
|
+
result = runner.invoke(app, ["policies", "delete", "pol_pii_handling"])
|
|
79
|
+
assert result.exit_code == 0
|
|
80
|
+
assert "deleted" in result.output
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_policies_help():
|
|
84
|
+
result = runner.invoke(app, ["policies", "--help"])
|
|
85
|
+
assert result.exit_code == 0
|
|
86
|
+
assert "list" in result.output
|
|
87
|
+
assert "get" in result.output
|
|
88
|
+
assert "put" in result.output
|
|
89
|
+
assert "delete" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|