entropy-data 0.3.1__tar.gz → 0.3.3__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.
Files changed (63) hide show
  1. {entropy_data-0.3.1 → entropy_data-0.3.3}/CHANGELOG.md +13 -0
  2. {entropy_data-0.3.1 → entropy_data-0.3.3}/PKG-INFO +1 -1
  3. {entropy_data-0.3.1 → entropy_data-0.3.3}/pyproject.toml +1 -1
  4. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/cli.py +2 -0
  5. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/client.py +53 -3
  6. entropy_data-0.3.3/src/entropy_data/commands/connection.py +190 -0
  7. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/datacontracts.py +9 -0
  8. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/dataproducts.py +9 -0
  9. entropy_data-0.3.3/src/entropy_data/commands/gitconnections.py +183 -0
  10. entropy_data-0.3.3/src/entropy_data/commands/organization.py +51 -0
  11. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/config.py +10 -3
  12. entropy_data-0.3.3/tests/commands/test_connection.py +176 -0
  13. entropy_data-0.3.3/tests/commands/test_gitconnections.py +255 -0
  14. entropy_data-0.3.3/tests/commands/test_organization.py +85 -0
  15. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/test_config.py +23 -0
  16. entropy_data-0.3.1/src/entropy_data/commands/connection.py +0 -90
  17. entropy_data-0.3.1/tests/commands/test_connection.py +0 -71
  18. {entropy_data-0.3.1 → entropy_data-0.3.3}/.editorconfig +0 -0
  19. {entropy_data-0.3.1 → entropy_data-0.3.3}/.github/dependabot.yml +0 -0
  20. {entropy_data-0.3.1 → entropy_data-0.3.3}/.github/pull_request_template.md +0 -0
  21. {entropy_data-0.3.1 → entropy_data-0.3.3}/.github/workflows/ci.yaml +0 -0
  22. {entropy_data-0.3.1 → entropy_data-0.3.3}/.github/workflows/release.yaml +0 -0
  23. {entropy_data-0.3.1 → entropy_data-0.3.3}/.gitignore +0 -0
  24. {entropy_data-0.3.1 → entropy_data-0.3.3}/.pre-commit-config.yaml +0 -0
  25. {entropy_data-0.3.1 → entropy_data-0.3.3}/CLAUDE.md +0 -0
  26. {entropy_data-0.3.1 → entropy_data-0.3.3}/Dockerfile +0 -0
  27. {entropy_data-0.3.1 → entropy_data-0.3.3}/LICENSE +0 -0
  28. {entropy_data-0.3.1 → entropy_data-0.3.3}/README.md +0 -0
  29. {entropy_data-0.3.1 → entropy_data-0.3.3}/release +0 -0
  30. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/__init__.py +0 -0
  31. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/__main__.py +0 -0
  32. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/__init__.py +0 -0
  33. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/access.py +0 -0
  34. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/api_keys.py +0 -0
  35. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/assets.py +0 -0
  36. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/certifications.py +0 -0
  37. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/costs.py +0 -0
  38. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/definitions.py +0 -0
  39. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/events.py +0 -0
  40. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/example_data.py +0 -0
  41. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/import_export.py +0 -0
  42. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/lineage.py +0 -0
  43. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/search.py +0 -0
  44. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/settings.py +0 -0
  45. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/sourcesystems.py +0 -0
  46. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/tags.py +0 -0
  47. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/teams.py +0 -0
  48. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/test_results.py +0 -0
  49. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/commands/usage.py +0 -0
  50. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/output.py +0 -0
  51. {entropy_data-0.3.1 → entropy_data-0.3.3}/src/entropy_data/util.py +0 -0
  52. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/__init__.py +0 -0
  53. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/__init__.py +0 -0
  54. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_api_keys.py +0 -0
  55. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_assets.py +0 -0
  56. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_costs.py +0 -0
  57. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_lineage.py +0 -0
  58. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_settings.py +0 -0
  59. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_tags.py +0 -0
  60. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_teams.py +0 -0
  61. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/commands/test_usage.py +0 -0
  62. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/conftest.py +0 -0
  63. {entropy_data-0.3.1 → entropy_data-0.3.3}/tests/test_client.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [0.3.3]
6
+
7
+ - 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.
8
+ - 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.
9
+ - `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.
10
+ - `connection list` surfaces the stored vanity URL.
11
+
12
+ ## [0.3.2]
13
+
14
+ - Add `git-connection` subcommands to `dataproducts` and `datacontracts`
15
+
3
16
  ## [0.3.1]
4
17
 
5
18
  - Support Python 3.11 (lowered minimum from 3.12)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entropy-data
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: CLI for Entropy Data
5
5
  Project-URL: Homepage, https://entropy-data.com
6
6
  Project-URL: Documentation, https://docs.entropy-data.com
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "entropy-data"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "CLI for Entropy Data"
5
5
  requires-python = ">=3.11"
6
6
  license = "MIT"
@@ -103,6 +103,7 @@ from entropy_data.commands.events import events_app # noqa: E402
103
103
  from entropy_data.commands.example_data import example_data_app # noqa: E402
104
104
  from entropy_data.commands.import_export import import_app # noqa: E402
105
105
  from entropy_data.commands.lineage import lineage_app # noqa: E402
106
+ from entropy_data.commands.organization import organization_app # noqa: E402
106
107
  from entropy_data.commands.search import search_app # noqa: E402
107
108
  from entropy_data.commands.settings import settings_app # noqa: E402
108
109
  from entropy_data.commands.sourcesystems import sourcesystems_app # noqa: E402
@@ -125,6 +126,7 @@ app.add_typer(costs_app, name="costs", help="Manage costs.")
125
126
  app.add_typer(assets_app, name="assets", help="Manage data assets.")
126
127
  app.add_typer(tags_app, name="tags", help="Manage tags.")
127
128
  app.add_typer(api_keys_app, name="api-keys", help="Manage API keys.")
129
+ app.add_typer(organization_app, name="organization", help="Get organization details.")
128
130
  app.add_typer(settings_app, name="settings", help="Manage organization settings.")
129
131
  app.add_typer(events_app, name="events", help="Poll events.")
130
132
  app.add_typer(lineage_app, name="lineage", help="Manage lineage (OpenLineage events).")
@@ -121,12 +121,15 @@ class EntropyDataClient:
121
121
  _raise_for_status(response)
122
122
  return response.headers.get(RESPONSE_HEADER_LOCATION_HTML)
123
123
 
124
- def post_action_json(self, path: str, resource_id: str, action: str, params: dict | None = None,
125
- timeout: int = REQUEST_TIMEOUT) -> dict:
124
+ def post_action_json(
125
+ self, path: str, resource_id: str, action: str, params: dict | None = None, timeout: int = REQUEST_TIMEOUT
126
+ ) -> dict:
126
127
  """POST /api/{path}/{id}/{action} with query params. Returns response JSON."""
127
128
  _validate_resource_id(resource_id)
128
129
  response = self.session.post(
129
- f"{self.base_url}/api/{path}/{resource_id}/{action}", params=params, timeout=timeout,
130
+ f"{self.base_url}/api/{path}/{resource_id}/{action}",
131
+ params=params,
132
+ timeout=timeout,
130
133
  )
131
134
  _raise_for_status(response)
132
135
  return response.json()
@@ -155,6 +158,53 @@ class EntropyDataClient:
155
158
  _raise_for_status(response)
156
159
  return response.json()
157
160
 
161
+ def get_gitconnection(self, path: str, resource_id: str) -> dict:
162
+ """GET /api/{path}/{id}/gitconnection."""
163
+ _validate_resource_id(resource_id)
164
+ response = self.session.get(
165
+ f"{self.base_url}/api/{path}/{resource_id}/gitconnection",
166
+ timeout=REQUEST_TIMEOUT,
167
+ )
168
+ _raise_for_status(response)
169
+ return response.json()
170
+
171
+ def put_gitconnection(self, path: str, resource_id: str, body: dict) -> dict:
172
+ """PUT /api/{path}/{id}/gitconnection."""
173
+ _validate_resource_id(resource_id)
174
+ response = self.session.put(
175
+ f"{self.base_url}/api/{path}/{resource_id}/gitconnection",
176
+ json=body,
177
+ timeout=REQUEST_TIMEOUT,
178
+ )
179
+ _raise_for_status(response)
180
+ return response.json()
181
+
182
+ def delete_gitconnection(self, path: str, resource_id: str) -> None:
183
+ """DELETE /api/{path}/{id}/gitconnection."""
184
+ _validate_resource_id(resource_id)
185
+ response = self.session.delete(
186
+ f"{self.base_url}/api/{path}/{resource_id}/gitconnection",
187
+ timeout=REQUEST_TIMEOUT,
188
+ )
189
+ _raise_for_status(response)
190
+
191
+ def gitconnection_action(
192
+ self,
193
+ path: str,
194
+ resource_id: str,
195
+ action: str,
196
+ body: dict | None = None,
197
+ ) -> dict:
198
+ """POST /api/{path}/{id}/gitconnection/{action}. action ∈ {pull, push, push-pr}."""
199
+ _validate_resource_id(resource_id)
200
+ response = self.session.post(
201
+ f"{self.base_url}/api/{path}/{resource_id}/gitconnection/{action}",
202
+ json=body if body is not None else None,
203
+ timeout=REQUEST_TIMEOUT,
204
+ )
205
+ _raise_for_status(response)
206
+ return response.json()
207
+
158
208
  def search(self, query: str, **params) -> dict:
159
209
  """GET /api/search."""
160
210
  params["query"] = query
@@ -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)
@@ -113,3 +113,12 @@ def delete_datacontract(
113
113
  print_success(f"Data contract '{id}' deleted.")
114
114
  except Exception as e:
115
115
  handle_error(e)
116
+
117
+
118
+ from entropy_data.commands.gitconnections import make_gitconnection_app # noqa: E402
119
+
120
+ datacontracts_app.add_typer(
121
+ make_gitconnection_app(RESOURCE_PATH, "Data contract"),
122
+ name="gitconnection",
123
+ help="Manage the git connection.",
124
+ )
@@ -91,3 +91,12 @@ def delete_dataproduct(
91
91
  print_success(f"Data product '{id}' deleted.")
92
92
  except Exception as e:
93
93
  handle_error(e)
94
+
95
+
96
+ from entropy_data.commands.gitconnections import make_gitconnection_app # noqa: E402
97
+
98
+ dataproducts_app.add_typer(
99
+ make_gitconnection_app(RESOURCE_PATH, "Data product"),
100
+ name="gitconnection",
101
+ help="Manage the git connection.",
102
+ )
@@ -0,0 +1,183 @@
1
+ """Git connection subcommands. Used as a sub-typer of dataproducts and datacontracts."""
2
+
3
+ import json
4
+ from typing import Annotated, Optional
5
+
6
+ import typer
7
+
8
+ from entropy_data.output import OutputFormat, print_link, print_success
9
+
10
+ GIT_CONNECTION_TYPES = ("github", "gitlab", "bitbucket", "azuredevops")
11
+
12
+
13
+ def make_gitconnection_app(resource_path: str, resource_label: str) -> typer.Typer:
14
+ """Build a Typer app exposing /api/{resource_path}/{id}/gitconnection operations.
15
+
16
+ `resource_path` is the URL segment ("dataproducts" or "datacontracts").
17
+ `resource_label` is the human-readable name shown in success messages.
18
+ """
19
+
20
+ app = typer.Typer(no_args_is_help=True)
21
+
22
+ @app.command("get")
23
+ def get_(
24
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
25
+ output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
26
+ ) -> None:
27
+ """Get the git connection."""
28
+ from entropy_data.cli import get_client, get_output_format, handle_error
29
+
30
+ fmt = output or get_output_format()
31
+ try:
32
+ client = get_client()
33
+ data = client.get_gitconnection(resource_path, id)
34
+ if fmt == OutputFormat.json:
35
+ print(json.dumps(data, indent=2))
36
+ else:
37
+ print(json.dumps(data, indent=2))
38
+ except Exception as e:
39
+ handle_error(e)
40
+
41
+ @app.command("put")
42
+ def put_(
43
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
44
+ repository_url: Annotated[str, typer.Option("--repository-url", help="URL of the Git repository.")] = ...,
45
+ repository_path: Annotated[
46
+ str, typer.Option("--repository-path", help="Path to the YAML file in the repository.")
47
+ ] = ...,
48
+ repository_branch: Annotated[
49
+ Optional[str],
50
+ typer.Option("--repository-branch", help="Branch to use. Defaults to 'main'."),
51
+ ] = None,
52
+ git_connection_type: Annotated[
53
+ Optional[str],
54
+ typer.Option(
55
+ "--git-connection-type",
56
+ help=f"Git provider type. One of: {', '.join(GIT_CONNECTION_TYPES)}.",
57
+ ),
58
+ ] = None,
59
+ host: Annotated[
60
+ Optional[str],
61
+ typer.Option("--host", help="Host of a self-hosted git provider. Omit for SaaS."),
62
+ ] = None,
63
+ git_credential_external_id: Annotated[
64
+ Optional[str],
65
+ typer.Option(
66
+ "--git-credential-external-id",
67
+ help="External ID of a stored git credential to use.",
68
+ ),
69
+ ] = None,
70
+ ) -> None:
71
+ """Create or update the git connection."""
72
+ from entropy_data.cli import get_client, handle_error
73
+
74
+ if git_connection_type and git_connection_type not in GIT_CONNECTION_TYPES:
75
+ raise typer.BadParameter(
76
+ f"Must be one of: {', '.join(GIT_CONNECTION_TYPES)}",
77
+ param_hint="--git-connection-type",
78
+ )
79
+ if not git_connection_type and not git_credential_external_id:
80
+ raise typer.BadParameter(
81
+ "At least one of --git-connection-type or --git-credential-external-id must be provided.",
82
+ )
83
+
84
+ body: dict = {"repositoryUrl": repository_url, "repositoryPath": repository_path}
85
+ if repository_branch:
86
+ body["repositoryBranch"] = repository_branch
87
+ if git_connection_type:
88
+ body["gitConnectionType"] = git_connection_type
89
+ if host:
90
+ body["host"] = host
91
+ if git_credential_external_id:
92
+ body["gitCredentialExternalId"] = git_credential_external_id
93
+
94
+ try:
95
+ client = get_client()
96
+ data = client.put_gitconnection(resource_path, id, body)
97
+ print_success(f"Git connection saved for {resource_label.lower()} '{id}'.")
98
+ print_link(data.get("webLink"))
99
+ except Exception as e:
100
+ handle_error(e)
101
+
102
+ @app.command("delete")
103
+ def delete_(
104
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
105
+ ) -> None:
106
+ """Delete the git connection."""
107
+ from entropy_data.cli import get_client, handle_error
108
+
109
+ try:
110
+ client = get_client()
111
+ client.delete_gitconnection(resource_path, id)
112
+ print_success(f"Git connection deleted for {resource_label.lower()} '{id}'.")
113
+ except Exception as e:
114
+ handle_error(e)
115
+
116
+ @app.command("pull")
117
+ def pull_(
118
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
119
+ ) -> None:
120
+ """Pull the file from Git into Entropy Data."""
121
+ from entropy_data.cli import get_client, handle_error
122
+
123
+ try:
124
+ client = get_client()
125
+ data = client.gitconnection_action(resource_path, id, "pull")
126
+ print_success(f"{resource_label} '{id}' pulled from Git.")
127
+ print_link(data.get("webLink"))
128
+ except Exception as e:
129
+ handle_error(e)
130
+
131
+ @app.command("push")
132
+ def push_(
133
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
134
+ commit_message: Annotated[
135
+ Optional[str], typer.Option("--commit-message", help="Custom commit message.")
136
+ ] = None,
137
+ ) -> None:
138
+ """Push the current Entropy Data file to Git."""
139
+ from entropy_data.cli import get_client, handle_error
140
+
141
+ body: dict | None = {"commitMessage": commit_message} if commit_message else None
142
+ try:
143
+ client = get_client()
144
+ data = client.gitconnection_action(resource_path, id, "push", body)
145
+ print_success(f"{resource_label} '{id}' pushed to Git.")
146
+ print_link(data.get("webLink"))
147
+ except Exception as e:
148
+ handle_error(e)
149
+
150
+ @app.command("push-pr")
151
+ def push_pr(
152
+ id: Annotated[str, typer.Argument(help=f"{resource_label} ID.")],
153
+ commit_message: Annotated[
154
+ Optional[str], typer.Option("--commit-message", help="Custom commit message.")
155
+ ] = None,
156
+ branch_name: Annotated[Optional[str], typer.Option("--branch-name", help="Name of the new branch.")] = None,
157
+ title: Annotated[Optional[str], typer.Option("--title", help="Pull request title.")] = None,
158
+ comment: Annotated[
159
+ Optional[str],
160
+ typer.Option("--comment", help="Pull request description / comment."),
161
+ ] = None,
162
+ ) -> None:
163
+ """Push the current Entropy Data file to Git as a pull/merge request."""
164
+ from entropy_data.cli import get_client, handle_error
165
+
166
+ body: dict = {}
167
+ if commit_message:
168
+ body["commitMessage"] = commit_message
169
+ if branch_name:
170
+ body["branchName"] = branch_name
171
+ if title:
172
+ body["title"] = title
173
+ if comment:
174
+ body["comment"] = comment
175
+ try:
176
+ client = get_client()
177
+ data = client.gitconnection_action(resource_path, id, "push-pr", body or None)
178
+ print_success(f"{resource_label} '{id}' pushed to Git as pull request.")
179
+ print_link(data.get("webLink"))
180
+ except Exception as e:
181
+ handle_error(e)
182
+
183
+ return app
@@ -0,0 +1,51 @@
1
+ """Organization 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.output import OutputFormat, console
10
+
11
+ organization_app = typer.Typer(no_args_is_help=True)
12
+
13
+
14
+ @organization_app.command("get")
15
+ def get_organization(
16
+ output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
17
+ ) -> None:
18
+ """Get settings of the organization the current API key is bound to."""
19
+ from entropy_data.cli import get_client, get_output_format, handle_error
20
+ from entropy_data.client import REQUEST_TIMEOUT, _raise_for_status
21
+
22
+ fmt = output or get_output_format()
23
+ try:
24
+ client = get_client()
25
+ response = client.session.get(
26
+ f"{client.base_url}/api/organization/settings",
27
+ timeout=REQUEST_TIMEOUT,
28
+ )
29
+ _raise_for_status(response)
30
+ data = response.json()
31
+ if fmt == OutputFormat.json:
32
+ console.print_json(json.dumps(data))
33
+ return
34
+
35
+ table = Table(show_header=False)
36
+ table.add_column("Field", style="cyan")
37
+ table.add_column("Value")
38
+ for key in ("vanityUrl", "host", "fullName", "logoUrl", "supportEmailAddress", "brand", "plan"):
39
+ value = data.get(key)
40
+ if value:
41
+ table.add_row(key, str(value))
42
+ sso = data.get("sso")
43
+ if sso:
44
+ table.add_row("sso.issuer", sso.get("issuer", ""))
45
+ if sso.get("tenant"):
46
+ table.add_row("sso.tenant", sso["tenant"])
47
+ if sso.get("autoJoin") is not None:
48
+ table.add_row("sso.autoJoin", str(sso["autoJoin"]))
49
+ console.print(table)
50
+ except Exception as e:
51
+ handle_error(e)
@@ -21,6 +21,7 @@ class ConfigurationError(Exception):
21
21
  class ConnectionConfig:
22
22
  api_key: str
23
23
  host: str = DEFAULT_HOST
24
+ vanity_url: str | None = None
24
25
 
25
26
 
26
27
  def load_config() -> dict:
@@ -47,6 +48,7 @@ def resolve_connection(
47
48
  """Resolve connection with precedence: CLI options > env vars > config file."""
48
49
  api_key = cli_api_key
49
50
  host = cli_host
51
+ vanity_url: str | None = None
50
52
 
51
53
  # Layer 2: environment variables
52
54
  if api_key is None:
@@ -68,6 +70,7 @@ def resolve_connection(
68
70
  api_key = conn.get("api_key")
69
71
  if host is None:
70
72
  host = conn.get("host")
73
+ vanity_url = conn.get("vanity_url")
71
74
 
72
75
  # Default host
73
76
  if host is None:
@@ -78,17 +81,20 @@ def resolve_connection(
78
81
  "No API key found. Set ENTROPY_DATA_API_KEY, use --api-key, or run: entropy-data connection add <name>"
79
82
  )
80
83
 
81
- return ConnectionConfig(api_key=api_key, host=host)
84
+ return ConnectionConfig(api_key=api_key, host=host, vanity_url=vanity_url)
82
85
 
83
86
 
84
- def add_connection(name: str, api_key: str, host: str = DEFAULT_HOST) -> None:
87
+ def add_connection(name: str, api_key: str, host: str = DEFAULT_HOST, vanity_url: str | None = None) -> None:
85
88
  """Add or update a named connection."""
86
89
  if not name or not name.strip():
87
90
  raise ConfigurationError("Connection name must not be empty.")
88
91
  config = load_config()
89
92
  if "connections" not in config:
90
93
  config["connections"] = {}
91
- config["connections"][name] = {"api_key": api_key, "host": host}
94
+ entry: dict = {"api_key": api_key, "host": host}
95
+ if vanity_url:
96
+ entry["vanity_url"] = vanity_url
97
+ config["connections"][name] = entry
92
98
  # Set as default if it's the first connection
93
99
  if "default_connection_name" not in config:
94
100
  config["default_connection_name"] = name
@@ -134,6 +140,7 @@ def list_connections() -> list[dict]:
134
140
  {
135
141
  "name": name,
136
142
  "host": conn.get("host", DEFAULT_HOST),
143
+ "vanity_url": conn.get("vanity_url"),
137
144
  "api_key": masked,
138
145
  "default": name == default_name,
139
146
  }