entropy-data 0.3.2__tar.gz → 0.3.4__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.4/CHANGELOG.md +65 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/PKG-INFO +1 -1
- {entropy_data-0.3.2 → entropy_data-0.3.4}/README.md +8 -5
- {entropy_data-0.3.2 → entropy_data-0.3.4}/pyproject.toml +1 -1
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/cli.py +6 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/client.py +9 -2
- entropy_data-0.3.4/src/entropy_data/commands/assets.py +159 -0
- entropy_data-0.3.4/src/entropy_data/commands/connection.py +190 -0
- entropy_data-0.3.2/src/entropy_data/commands/teams.py → entropy_data-0.3.4/src/entropy_data/commands/connectors.py +23 -24
- entropy_data-0.3.4/src/entropy_data/commands/datacontracts.py +305 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/dataproducts.py +53 -0
- entropy_data-0.3.4/src/entropy_data/commands/git_credentials.py +456 -0
- entropy_data-0.3.4/src/entropy_data/commands/notification_channels.py +88 -0
- entropy_data-0.3.4/src/entropy_data/commands/organization.py +98 -0
- entropy_data-0.3.4/src/entropy_data/commands/semantics.py +262 -0
- entropy_data-0.3.4/src/entropy_data/commands/settings.py +111 -0
- entropy_data-0.3.4/src/entropy_data/commands/teams.py +116 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/config.py +28 -10
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/output.py +36 -2
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_assets.py +61 -0
- entropy_data-0.3.4/tests/commands/test_connection.py +176 -0
- entropy_data-0.3.4/tests/commands/test_connectors.py +73 -0
- entropy_data-0.3.4/tests/commands/test_datacontracts.py +191 -0
- entropy_data-0.3.4/tests/commands/test_dataproducts.py +78 -0
- entropy_data-0.3.4/tests/commands/test_git_credentials.py +239 -0
- entropy_data-0.3.4/tests/commands/test_notification_channels.py +75 -0
- entropy_data-0.3.4/tests/commands/test_organization.py +139 -0
- entropy_data-0.3.4/tests/commands/test_semantics.py +226 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_settings.py +69 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_teams.py +81 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/test_config.py +41 -0
- entropy_data-0.3.2/CHANGELOG.md +0 -43
- entropy_data-0.3.2/src/entropy_data/commands/assets.py +0 -80
- entropy_data-0.3.2/src/entropy_data/commands/connection.py +0 -90
- entropy_data-0.3.2/src/entropy_data/commands/datacontracts.py +0 -124
- entropy_data-0.3.2/src/entropy_data/commands/settings.py +0 -68
- entropy_data-0.3.2/tests/commands/test_connection.py +0 -71
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.editorconfig +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.github/dependabot.yml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.github/pull_request_template.md +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.github/workflows/ci.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.github/workflows/release.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.gitignore +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/.pre-commit-config.yaml +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/CLAUDE.md +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/Dockerfile +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/LICENSE +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/release +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/__main__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/access.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/api_keys.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/certifications.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/costs.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/definitions.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/events.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/example_data.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/gitconnections.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/import_export.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/lineage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/search.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/sourcesystems.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/tags.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/test_results.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/commands/usage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/src/entropy_data/util.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/__init__.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_api_keys.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_costs.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_gitconnections.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_lineage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_tags.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/commands/test_usage.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/conftest.py +0 -0
- {entropy_data-0.3.2 → entropy_data-0.3.4}/tests/test_client.py +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.3.4]
|
|
6
|
+
|
|
7
|
+
- Add `entropy-data datacontracts yaml <id>` to fetch a data contract as ODCS YAML (writes to stdout or `--file`). Backed by `GET /api/datacontracts/{id}.yaml`.
|
|
8
|
+
- Add `entropy-data datacontracts generate <id> --type <kind>` for code generation (`sql-select`, `sql-ddl`, `dbt-models`, `dbt-sources`, `json-schema`, `pydantic`, `custom`). With `--out-dir`, each returned file is written to disk; without it, the JSON response is printed.
|
|
9
|
+
- Add `entropy-data dataproducts import-from-git` and `entropy-data datacontracts import-from-git` to import resources from a Git repository (flags or `--file body.json`).
|
|
10
|
+
- Add `entropy-data organization members list` and `entropy-data organization members get <email>` to inspect organization membership.
|
|
11
|
+
- Add `entropy-data settings get-scim-mapping` and `entropy-data settings put-scim-mapping` to manage the SCIM group mapping (YAML or JSON, mirrors the customization commands).
|
|
12
|
+
- Add `entropy-data connectors list|get|put|delete` to manage connector state.
|
|
13
|
+
- Add `entropy-data assets tags list|add|remove` to manage tag assignments on data assets.
|
|
14
|
+
- Add `entropy-data organization git-credentials list|get|create|update|delete` and `entropy-data teams git-credentials list|get|create|update|delete <team-id>` to manage organization- and team-level git credentials. Pass `--authentication-token -` to read the secret from stdin.
|
|
15
|
+
- Add `entropy-data teams notifications get|put|delete <team-id> <channel-id>` to manage a team's notification channels.
|
|
16
|
+
- Add EXPERIMENTAL `entropy-data semantics namespaces list|get|put|delete`, `entropy-data semantics concepts list|get|put|delete <namespace> [<external-id>]`, and `entropy-data semantics relationships list|get|put|delete <namespace> [<external-id>]` covering the `/api/semantics/experimental/...` endpoints. PUT commands enforce that any `namespace`/`id` in the body matches the path argument.
|
|
17
|
+
|
|
18
|
+
## [0.3.3]
|
|
19
|
+
|
|
20
|
+
- Add `entropy-data organization get` to fetch organization settings (vanity URL, host, full name, plan, SSO) for the API key in use. Backed by the new `GET /api/organization/settings` endpoint.
|
|
21
|
+
- Add `entropy-data connection get [name]` to inspect a stored connection. API key is masked by default; pass `--show-api-key` to print it in clear text. Use `-o json` for scripting.
|
|
22
|
+
- `entropy-data connection add` auto-fetches the organization vanity URL via `/api/organization/settings` and stores it on the connection. Best-effort: older servers or network errors fall back to no vanity URL.
|
|
23
|
+
- `connection list` surfaces the stored vanity URL.
|
|
24
|
+
|
|
25
|
+
## [0.3.2]
|
|
26
|
+
|
|
27
|
+
- Add `git-connection` subcommands to `dataproducts` and `datacontracts`
|
|
28
|
+
|
|
29
|
+
## [0.3.1]
|
|
30
|
+
|
|
31
|
+
- Support Python 3.11 (lowered minimum from 3.12)
|
|
32
|
+
|
|
33
|
+
## [0.3.0]
|
|
34
|
+
|
|
35
|
+
- Rename PyPI package from `entropy-data-cli` to `entropy-data`. Install with `pip install entropy-data` (or `uv tool install entropy-data`). The CLI command remains `entropy-data`.
|
|
36
|
+
|
|
37
|
+
## [0.2.3]
|
|
38
|
+
|
|
39
|
+
- Add `.env` file support for project-specific configuration via `python-dotenv`
|
|
40
|
+
- Document release process in README
|
|
41
|
+
|
|
42
|
+
## [0.2.2]
|
|
43
|
+
|
|
44
|
+
- Fix version reporting to read from package metadata instead of hardcoded value
|
|
45
|
+
|
|
46
|
+
## [0.2.1]
|
|
47
|
+
|
|
48
|
+
- Add Docker Hub publish to CI and release workflows
|
|
49
|
+
|
|
50
|
+
## [0.2.0]
|
|
51
|
+
|
|
52
|
+
- Fix negative page numbers leaking SQL queries from the server
|
|
53
|
+
- Fix mismatched resource ID in body vs CLI argument silently using body ID
|
|
54
|
+
- Fix HTML error responses (e.g., from Tomcat) displayed as raw markup
|
|
55
|
+
- Add max resource ID length validation (256 characters)
|
|
56
|
+
- Add 30s HTTP request timeout to prevent hanging on unreachable hosts
|
|
57
|
+
|
|
58
|
+
## [0.1.0]
|
|
59
|
+
|
|
60
|
+
- Initial release
|
|
61
|
+
- CRUD commands for data products, data contracts, access, teams, source systems, definitions, certifications, example data, test results
|
|
62
|
+
- Access workflow commands: approve, reject, cancel
|
|
63
|
+
- Event polling and search
|
|
64
|
+
- Connection management with `~/.entropy-data/config.toml`
|
|
65
|
+
- Table and JSON output formats
|
|
@@ -80,23 +80,26 @@ entropy-data search query "customer orders"
|
|
|
80
80
|
entropy-data [--version] [--connection NAME] [--output table|json] [--debug]
|
|
81
81
|
|
|
82
82
|
connection list | add | remove | set-default | test
|
|
83
|
-
dataproducts list | get | put | delete
|
|
84
|
-
datacontracts list | get | put | test | delete
|
|
83
|
+
dataproducts list | get | put | delete | import-from-git | gitconnection ...
|
|
84
|
+
datacontracts list | get | put | test | delete | yaml | generate | import-from-git | gitconnection ...
|
|
85
85
|
access list | get | put | delete | approve | reject | cancel
|
|
86
|
-
teams list | get | put | delete
|
|
86
|
+
teams list | get | put | delete | git-credentials ... | notifications ...
|
|
87
87
|
sourcesystems list | get | put | delete
|
|
88
88
|
definitions list | get | put | delete
|
|
89
89
|
certifications list | get | put | delete
|
|
90
90
|
example-data list | get | put | delete
|
|
91
91
|
test-results list | get | publish | delete
|
|
92
92
|
costs list | add | delete
|
|
93
|
-
assets list | get | put | delete
|
|
93
|
+
assets list | get | put | delete | tags ...
|
|
94
94
|
tags list | get | put | delete
|
|
95
95
|
api-keys create | delete
|
|
96
|
-
|
|
96
|
+
connectors list | get | put | delete
|
|
97
|
+
organization get | members ... | git-credentials ...
|
|
98
|
+
settings get-customization | put-customization | get-scim-mapping | put-scim-mapping
|
|
97
99
|
events poll
|
|
98
100
|
lineage list | submit | delete
|
|
99
101
|
search query
|
|
102
|
+
semantics namespaces ... | concepts ... | relationships ... (EXPERIMENTAL)
|
|
100
103
|
usage list | submit | delete
|
|
101
104
|
import zip
|
|
102
105
|
```
|
|
@@ -95,6 +95,7 @@ from entropy_data.commands.api_keys import api_keys_app # noqa: E402
|
|
|
95
95
|
from entropy_data.commands.assets import assets_app # noqa: E402
|
|
96
96
|
from entropy_data.commands.certifications import certifications_app # noqa: E402
|
|
97
97
|
from entropy_data.commands.connection import connection_app # noqa: E402
|
|
98
|
+
from entropy_data.commands.connectors import connectors_app # noqa: E402
|
|
98
99
|
from entropy_data.commands.costs import costs_app # noqa: E402
|
|
99
100
|
from entropy_data.commands.datacontracts import datacontracts_app # noqa: E402
|
|
100
101
|
from entropy_data.commands.dataproducts import dataproducts_app # noqa: E402
|
|
@@ -103,7 +104,9 @@ from entropy_data.commands.events import events_app # noqa: E402
|
|
|
103
104
|
from entropy_data.commands.example_data import example_data_app # noqa: E402
|
|
104
105
|
from entropy_data.commands.import_export import import_app # noqa: E402
|
|
105
106
|
from entropy_data.commands.lineage import lineage_app # noqa: E402
|
|
107
|
+
from entropy_data.commands.organization import organization_app # noqa: E402
|
|
106
108
|
from entropy_data.commands.search import search_app # noqa: E402
|
|
109
|
+
from entropy_data.commands.semantics import semantics_app # noqa: E402
|
|
107
110
|
from entropy_data.commands.settings import settings_app # noqa: E402
|
|
108
111
|
from entropy_data.commands.sourcesystems import sourcesystems_app # noqa: E402
|
|
109
112
|
from entropy_data.commands.tags import tags_app # noqa: E402
|
|
@@ -125,9 +128,12 @@ app.add_typer(costs_app, name="costs", help="Manage costs.")
|
|
|
125
128
|
app.add_typer(assets_app, name="assets", help="Manage data assets.")
|
|
126
129
|
app.add_typer(tags_app, name="tags", help="Manage tags.")
|
|
127
130
|
app.add_typer(api_keys_app, name="api-keys", help="Manage API keys.")
|
|
131
|
+
app.add_typer(connectors_app, name="connectors", help="Manage connectors.")
|
|
132
|
+
app.add_typer(organization_app, name="organization", help="Get organization details.")
|
|
128
133
|
app.add_typer(settings_app, name="settings", help="Manage organization settings.")
|
|
129
134
|
app.add_typer(events_app, name="events", help="Poll events.")
|
|
130
135
|
app.add_typer(lineage_app, name="lineage", help="Manage lineage (OpenLineage events).")
|
|
131
136
|
app.add_typer(search_app, name="search", help="Search across resources.")
|
|
137
|
+
app.add_typer(semantics_app, name="semantics", help="EXPERIMENTAL semantics API.")
|
|
132
138
|
app.add_typer(usage_app, name="usage", help="Manage usage (OpenTelemetry traces).")
|
|
133
139
|
app.add_typer(import_app, name="import", help="Import organization exports.")
|
|
@@ -122,13 +122,20 @@ class EntropyDataClient:
|
|
|
122
122
|
return response.headers.get(RESPONSE_HEADER_LOCATION_HTML)
|
|
123
123
|
|
|
124
124
|
def post_action_json(
|
|
125
|
-
self,
|
|
125
|
+
self,
|
|
126
|
+
path: str,
|
|
127
|
+
resource_id: str,
|
|
128
|
+
action: str,
|
|
129
|
+
params: dict | None = None,
|
|
130
|
+
body: dict | None = None,
|
|
131
|
+
timeout: int = REQUEST_TIMEOUT,
|
|
126
132
|
) -> dict:
|
|
127
|
-
"""POST /api/{path}/{id}/{action}
|
|
133
|
+
"""POST /api/{path}/{id}/{action}. Returns response JSON."""
|
|
128
134
|
_validate_resource_id(resource_id)
|
|
129
135
|
response = self.session.post(
|
|
130
136
|
f"{self.base_url}/api/{path}/{resource_id}/{action}",
|
|
131
137
|
params=params,
|
|
138
|
+
json=body if body is not None else None,
|
|
132
139
|
timeout=timeout,
|
|
133
140
|
)
|
|
134
141
|
_raise_for_status(response)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Assets commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from entropy_data.output import OutputFormat, console, print_link, print_resource, print_resource_list, print_success
|
|
10
|
+
from entropy_data.util import read_body
|
|
11
|
+
|
|
12
|
+
assets_app = typer.Typer(no_args_is_help=True)
|
|
13
|
+
tags_app = typer.Typer(no_args_is_help=True)
|
|
14
|
+
RESOURCE_PATH = "assets"
|
|
15
|
+
RESOURCE_TYPE = "assets"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@assets_app.command("list")
|
|
19
|
+
def list_assets(
|
|
20
|
+
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
21
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""List all data assets."""
|
|
24
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
25
|
+
|
|
26
|
+
fmt = output or get_output_format()
|
|
27
|
+
try:
|
|
28
|
+
client = get_client()
|
|
29
|
+
data, has_next = client.list_resources(RESOURCE_PATH, params={"p": page})
|
|
30
|
+
print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
handle_error(e)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@assets_app.command("get")
|
|
36
|
+
def get_asset(
|
|
37
|
+
id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
38
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Get a data asset by ID."""
|
|
41
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
42
|
+
|
|
43
|
+
fmt = output or get_output_format()
|
|
44
|
+
try:
|
|
45
|
+
client = get_client()
|
|
46
|
+
data = client.get_resource(RESOURCE_PATH, id)
|
|
47
|
+
print_resource(data, RESOURCE_TYPE, fmt)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
handle_error(e)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@assets_app.command("put")
|
|
53
|
+
def put_asset(
|
|
54
|
+
id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
55
|
+
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Create or update a data asset."""
|
|
58
|
+
from entropy_data.cli import get_client, handle_error
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
body = read_body(file)
|
|
62
|
+
client = get_client()
|
|
63
|
+
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
64
|
+
print_success(f"Asset '{id}' saved.")
|
|
65
|
+
print_link(location)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
handle_error(e)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@assets_app.command("delete")
|
|
71
|
+
def delete_asset(
|
|
72
|
+
id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Delete a data asset."""
|
|
75
|
+
from entropy_data.cli import get_client, handle_error
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
client = get_client()
|
|
79
|
+
client.delete_resource(RESOURCE_PATH, id)
|
|
80
|
+
print_success(f"Asset '{id}' deleted.")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
handle_error(e)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@tags_app.command("list")
|
|
86
|
+
def list_asset_tags(
|
|
87
|
+
asset_id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
88
|
+
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""List tags assigned to an asset."""
|
|
91
|
+
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
92
|
+
from entropy_data.client import REQUEST_TIMEOUT, _raise_for_status, _validate_resource_id
|
|
93
|
+
|
|
94
|
+
fmt = output or get_output_format()
|
|
95
|
+
try:
|
|
96
|
+
client = get_client()
|
|
97
|
+
_validate_resource_id(asset_id)
|
|
98
|
+
response = client.session.get(
|
|
99
|
+
f"{client.base_url}/api/assets/{asset_id}/assigned-tags",
|
|
100
|
+
timeout=REQUEST_TIMEOUT,
|
|
101
|
+
)
|
|
102
|
+
_raise_for_status(response)
|
|
103
|
+
data = response.json()
|
|
104
|
+
if fmt == OutputFormat.json:
|
|
105
|
+
console.print_json(json.dumps(data))
|
|
106
|
+
else:
|
|
107
|
+
for tag in data:
|
|
108
|
+
console.print(tag)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
handle_error(e)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@tags_app.command("add")
|
|
114
|
+
def add_asset_tag(
|
|
115
|
+
asset_id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
116
|
+
tag_id: Annotated[str, typer.Argument(help="Tag ID (may be hierarchical, e.g. governance/PII).")],
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Assign a tag to an asset."""
|
|
119
|
+
from entropy_data.cli import get_client, handle_error
|
|
120
|
+
from entropy_data.client import REQUEST_TIMEOUT, _raise_for_status, _validate_resource_id
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
client = get_client()
|
|
124
|
+
_validate_resource_id(asset_id)
|
|
125
|
+
# tag_id can contain "/" (e.g. governance/PII) so we don't run it through the
|
|
126
|
+
# path-traversal-rejecting validator; the server validates anyway.
|
|
127
|
+
response = client.session.put(
|
|
128
|
+
f"{client.base_url}/api/assets/{asset_id}/assigned-tags/{tag_id}",
|
|
129
|
+
timeout=REQUEST_TIMEOUT,
|
|
130
|
+
)
|
|
131
|
+
_raise_for_status(response)
|
|
132
|
+
print_success(f"Tag '{tag_id}' assigned to asset '{asset_id}'.")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
handle_error(e)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@tags_app.command("remove")
|
|
138
|
+
def remove_asset_tag(
|
|
139
|
+
asset_id: Annotated[str, typer.Argument(help="Asset ID.")],
|
|
140
|
+
tag_id: Annotated[str, typer.Argument(help="Tag ID (may be hierarchical, e.g. governance/PII).")],
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Unassign a tag from an asset."""
|
|
143
|
+
from entropy_data.cli import get_client, handle_error
|
|
144
|
+
from entropy_data.client import REQUEST_TIMEOUT, _raise_for_status, _validate_resource_id
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
client = get_client()
|
|
148
|
+
_validate_resource_id(asset_id)
|
|
149
|
+
response = client.session.delete(
|
|
150
|
+
f"{client.base_url}/api/assets/{asset_id}/assigned-tags/{tag_id}",
|
|
151
|
+
timeout=REQUEST_TIMEOUT,
|
|
152
|
+
)
|
|
153
|
+
_raise_for_status(response)
|
|
154
|
+
print_success(f"Tag '{tag_id}' removed from asset '{asset_id}'.")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
handle_error(e)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
assets_app.add_typer(tags_app, name="tags", help="Manage tag assignments on an asset.")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Connection management commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from entropy_data import config as cfg
|
|
10
|
+
from entropy_data.output import OutputFormat, console, print_error, print_success
|
|
11
|
+
|
|
12
|
+
connection_app = typer.Typer(no_args_is_help=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _mask_api_key(api_key: str) -> str:
|
|
16
|
+
"""Mask an API key for display (first/last 4 visible)."""
|
|
17
|
+
if len(api_key) > 8:
|
|
18
|
+
return api_key[:4] + "..." + api_key[-4:]
|
|
19
|
+
return "****"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _fetch_vanity_url(api_key: str, host: str) -> str | None:
|
|
23
|
+
"""Best-effort fetch of the org vanity URL via /api/organization/settings.
|
|
24
|
+
|
|
25
|
+
Returns None on any failure (older server, network error, etc.) so callers
|
|
26
|
+
can fall back to None instead of failing the whole `connection add`.
|
|
27
|
+
"""
|
|
28
|
+
from entropy_data.client import REQUEST_TIMEOUT, EntropyDataClient
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
client = EntropyDataClient(cfg.ConnectionConfig(api_key=api_key, host=host))
|
|
32
|
+
response = client.session.get(
|
|
33
|
+
f"{client.base_url}/api/organization/settings",
|
|
34
|
+
timeout=REQUEST_TIMEOUT,
|
|
35
|
+
)
|
|
36
|
+
if response.ok:
|
|
37
|
+
return response.json().get("vanityUrl")
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@connection_app.command("list")
|
|
44
|
+
def list_connections() -> None:
|
|
45
|
+
"""List all configured connections."""
|
|
46
|
+
connections = cfg.list_connections()
|
|
47
|
+
if not connections:
|
|
48
|
+
console.print("No connections configured. Run: entropy-data connection add <name>")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
table = Table(show_header=True)
|
|
52
|
+
table.add_column("Name")
|
|
53
|
+
table.add_column("Host")
|
|
54
|
+
table.add_column("Vanity URL")
|
|
55
|
+
table.add_column("API Key")
|
|
56
|
+
table.add_column("Default")
|
|
57
|
+
for conn in connections:
|
|
58
|
+
table.add_row(
|
|
59
|
+
conn["name"],
|
|
60
|
+
conn["host"],
|
|
61
|
+
conn.get("vanity_url") or "",
|
|
62
|
+
conn["api_key"],
|
|
63
|
+
"*" if conn["default"] else "",
|
|
64
|
+
)
|
|
65
|
+
console.print(table)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@connection_app.command("get")
|
|
69
|
+
def get_connection(
|
|
70
|
+
name: Annotated[
|
|
71
|
+
Optional[str],
|
|
72
|
+
typer.Argument(help="Connection name. Defaults to the default connection."),
|
|
73
|
+
] = None,
|
|
74
|
+
show_api_key: Annotated[
|
|
75
|
+
bool,
|
|
76
|
+
typer.Option("--show-api-key", help="Print the API key in clear text (default: masked)."),
|
|
77
|
+
] = False,
|
|
78
|
+
output: Annotated[
|
|
79
|
+
Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")
|
|
80
|
+
] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Get details of a named connection (use --show-api-key to reveal the key)."""
|
|
83
|
+
from entropy_data.cli import get_output_format
|
|
84
|
+
|
|
85
|
+
config = cfg.load_config()
|
|
86
|
+
connections = config.get("connections", {})
|
|
87
|
+
|
|
88
|
+
resolved_name = name or config.get("default_connection_name")
|
|
89
|
+
if resolved_name is None:
|
|
90
|
+
print_error(
|
|
91
|
+
"No connection specified and no default set. "
|
|
92
|
+
"Run: entropy-data connection set-default <name>"
|
|
93
|
+
)
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
if resolved_name not in connections:
|
|
96
|
+
print_error(f"Connection '{resolved_name}' not found.")
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
|
|
99
|
+
conn = connections[resolved_name]
|
|
100
|
+
api_key_value = conn.get("api_key", "")
|
|
101
|
+
displayed_key = api_key_value if show_api_key else _mask_api_key(api_key_value)
|
|
102
|
+
is_default = config.get("default_connection_name") == resolved_name
|
|
103
|
+
|
|
104
|
+
fmt = output or get_output_format()
|
|
105
|
+
if fmt == OutputFormat.json:
|
|
106
|
+
payload = {
|
|
107
|
+
"name": resolved_name,
|
|
108
|
+
"host": conn.get("host", cfg.DEFAULT_HOST),
|
|
109
|
+
"vanity_url": conn.get("vanity_url"),
|
|
110
|
+
"api_key": displayed_key,
|
|
111
|
+
"default": is_default,
|
|
112
|
+
}
|
|
113
|
+
console.print_json(json.dumps(payload))
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
table = Table(show_header=False)
|
|
117
|
+
table.add_column("Field", style="cyan")
|
|
118
|
+
table.add_column("Value")
|
|
119
|
+
table.add_row("name", resolved_name)
|
|
120
|
+
table.add_row("host", conn.get("host", cfg.DEFAULT_HOST))
|
|
121
|
+
if conn.get("vanity_url"):
|
|
122
|
+
table.add_row("vanity_url", conn["vanity_url"])
|
|
123
|
+
table.add_row("api_key", displayed_key)
|
|
124
|
+
if is_default:
|
|
125
|
+
table.add_row("default", "yes")
|
|
126
|
+
console.print(table)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@connection_app.command("add")
|
|
130
|
+
def add_connection(
|
|
131
|
+
name: Annotated[str, typer.Argument(help="Connection name.")],
|
|
132
|
+
api_key: Annotated[str, typer.Option("--api-key", prompt="API key", help="The API key.")] = None,
|
|
133
|
+
host: Annotated[
|
|
134
|
+
str, typer.Option("--host", prompt="Host", prompt_required=False, help="API host URL.")
|
|
135
|
+
] = cfg.DEFAULT_HOST,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Add or update a named connection.
|
|
138
|
+
|
|
139
|
+
The organization vanity URL is read from /api/organization/settings using the
|
|
140
|
+
provided API key. Fetching is best-effort: older servers or network errors
|
|
141
|
+
fall back to no vanity URL on the stored connection.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
vanity_url = _fetch_vanity_url(api_key, host)
|
|
145
|
+
if vanity_url:
|
|
146
|
+
console.print(f"Fetched organization vanity URL '[cyan]{vanity_url}[/cyan]' from {host}.")
|
|
147
|
+
cfg.add_connection(name, api_key, host, vanity_url=vanity_url)
|
|
148
|
+
print_success(f"Connection '{name}' saved.")
|
|
149
|
+
except cfg.ConfigurationError as e:
|
|
150
|
+
print_error(str(e))
|
|
151
|
+
raise typer.Exit(1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@connection_app.command("remove")
|
|
155
|
+
def remove_connection(
|
|
156
|
+
name: Annotated[str, typer.Argument(help="Connection name to remove.")],
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Remove a named connection."""
|
|
159
|
+
try:
|
|
160
|
+
cfg.remove_connection(name)
|
|
161
|
+
print_success(f"Connection '{name}' removed.")
|
|
162
|
+
except cfg.ConfigurationError as e:
|
|
163
|
+
print_error(str(e))
|
|
164
|
+
raise typer.Exit(1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@connection_app.command("set-default")
|
|
168
|
+
def set_default(
|
|
169
|
+
name: Annotated[str, typer.Argument(help="Connection name to set as default.")],
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Set the default connection."""
|
|
172
|
+
try:
|
|
173
|
+
cfg.set_default_connection(name)
|
|
174
|
+
print_success(f"Default connection set to '{name}'.")
|
|
175
|
+
except cfg.ConfigurationError as e:
|
|
176
|
+
print_error(str(e))
|
|
177
|
+
raise typer.Exit(1)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@connection_app.command("test")
|
|
181
|
+
def test_connection() -> None:
|
|
182
|
+
"""Test the current connection by calling the API."""
|
|
183
|
+
from entropy_data.cli import get_client, handle_error
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
client = get_client()
|
|
187
|
+
client.list_resources("teams", params={"p": "0"})
|
|
188
|
+
print_success("Connection successful.")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
handle_error(e)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Connectors commands."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Annotated, Optional
|
|
@@ -8,34 +8,33 @@ import typer
|
|
|
8
8
|
from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
|
|
9
9
|
from entropy_data.util import read_body
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
RESOURCE_PATH = "
|
|
13
|
-
RESOURCE_TYPE = "
|
|
11
|
+
connectors_app = typer.Typer(no_args_is_help=True)
|
|
12
|
+
RESOURCE_PATH = "connectors"
|
|
13
|
+
RESOURCE_TYPE = "connectors"
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@
|
|
17
|
-
def
|
|
18
|
-
page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
|
|
16
|
+
@connectors_app.command("list")
|
|
17
|
+
def list_connectors(
|
|
19
18
|
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
20
19
|
) -> None:
|
|
21
|
-
"""List all
|
|
20
|
+
"""List all connectors."""
|
|
22
21
|
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
23
22
|
|
|
24
23
|
fmt = output or get_output_format()
|
|
25
24
|
try:
|
|
26
25
|
client = get_client()
|
|
27
|
-
data,
|
|
28
|
-
print_resource_list(data, RESOURCE_TYPE, fmt
|
|
26
|
+
data, _ = client.list_resources(RESOURCE_PATH)
|
|
27
|
+
print_resource_list(data, RESOURCE_TYPE, fmt)
|
|
29
28
|
except Exception as e:
|
|
30
29
|
handle_error(e)
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
@
|
|
34
|
-
def
|
|
35
|
-
id: Annotated[str, typer.Argument(help="
|
|
32
|
+
@connectors_app.command("get")
|
|
33
|
+
def get_connector(
|
|
34
|
+
id: Annotated[str, typer.Argument(help="Connector ID.")],
|
|
36
35
|
output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
|
|
37
36
|
) -> None:
|
|
38
|
-
"""Get a
|
|
37
|
+
"""Get a connector by ID."""
|
|
39
38
|
from entropy_data.cli import get_client, get_output_format, handle_error
|
|
40
39
|
|
|
41
40
|
fmt = output or get_output_format()
|
|
@@ -47,34 +46,34 @@ def get_team(
|
|
|
47
46
|
handle_error(e)
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
@
|
|
51
|
-
def
|
|
52
|
-
id: Annotated[str, typer.Argument(help="
|
|
49
|
+
@connectors_app.command("put")
|
|
50
|
+
def put_connector(
|
|
51
|
+
id: Annotated[str, typer.Argument(help="Connector ID.")],
|
|
53
52
|
file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
|
|
54
53
|
) -> None:
|
|
55
|
-
"""Create or update a
|
|
54
|
+
"""Create or update a connector."""
|
|
56
55
|
from entropy_data.cli import get_client, handle_error
|
|
57
56
|
|
|
58
57
|
try:
|
|
59
58
|
body = read_body(file)
|
|
60
59
|
client = get_client()
|
|
61
60
|
location = client.put_resource(RESOURCE_PATH, id, body)
|
|
62
|
-
print_success(f"
|
|
61
|
+
print_success(f"Connector '{id}' saved.")
|
|
63
62
|
print_link(location)
|
|
64
63
|
except Exception as e:
|
|
65
64
|
handle_error(e)
|
|
66
65
|
|
|
67
66
|
|
|
68
|
-
@
|
|
69
|
-
def
|
|
70
|
-
id: Annotated[str, typer.Argument(help="
|
|
67
|
+
@connectors_app.command("delete")
|
|
68
|
+
def delete_connector(
|
|
69
|
+
id: Annotated[str, typer.Argument(help="Connector ID.")],
|
|
71
70
|
) -> None:
|
|
72
|
-
"""Delete a
|
|
71
|
+
"""Delete a connector."""
|
|
73
72
|
from entropy_data.cli import get_client, handle_error
|
|
74
73
|
|
|
75
74
|
try:
|
|
76
75
|
client = get_client()
|
|
77
76
|
client.delete_resource(RESOURCE_PATH, id)
|
|
78
|
-
print_success(f"
|
|
77
|
+
print_success(f"Connector '{id}' deleted.")
|
|
79
78
|
except Exception as e:
|
|
80
79
|
handle_error(e)
|