inthub-cli 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ import getpass
2
+ import json as json_mod
3
+ from datetime import UTC, datetime
4
+ from enum import Enum
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from inthub import config
12
+ from inthub.client import InthubClient
13
+
14
+ app = typer.Typer(help="Manage connectors.", add_completion=False)
15
+ console = Console()
16
+
17
+
18
+ class OutputFormat(str, Enum):
19
+ table = "table"
20
+ json = "json"
21
+
22
+
23
+ def _client(debug: bool = False) -> InthubClient:
24
+ return InthubClient(config.require_token(), debug=debug)
25
+
26
+
27
+ @app.command("list")
28
+ def list_connectors(
29
+ tag: Annotated[
30
+ list[str] | None,
31
+ typer.Option("--tag", help="Filter by tag (repeatable, all must match)."),
32
+ ] = None,
33
+ output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
34
+ debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
35
+ ) -> None:
36
+ """List connectors, optionally filtered by tags."""
37
+ path = "/connectors"
38
+ if tag:
39
+ path += "?tags=" + ",".join(tag)
40
+
41
+ body = _client(debug).get(path).json()
42
+ connectors: list[dict[str, object]] = (
43
+ body.get("connectors", []) if isinstance(body, dict) else body
44
+ )
45
+
46
+ if output == OutputFormat.json:
47
+ print(json_mod.dumps(connectors))
48
+ return
49
+
50
+ if not connectors:
51
+ console.print("No connectors found.")
52
+ return
53
+
54
+ table = Table(show_header=True, header_style="bold", show_lines=True, expand=True)
55
+ table.add_column("ID", style="dim", overflow="fold")
56
+ table.add_column("NAME", overflow="fold")
57
+ table.add_column("STATE", no_wrap=True)
58
+ table.add_column("PLUGIN", overflow="fold")
59
+ table.add_column("KCC", overflow="fold")
60
+ table.add_column("ARGO HEALTH")
61
+ table.add_column("ARGO SYNC")
62
+ table.add_column("CREATED")
63
+ table.add_column("UPDATED")
64
+
65
+ for c in connectors:
66
+ state = str(c.get("state", ""))
67
+ state_cell = f"[green]{state}[/]" if state == "DEPLOYED" else f"[yellow]{state}[/]"
68
+
69
+ health = str(c.get("argoHealth", "") or "")
70
+ health_cell = (
71
+ f"[green]{health}[/]" if health == "Healthy"
72
+ else f"[red]{health}[/]" if health in ("Degraded", "Missing")
73
+ else health
74
+ )
75
+
76
+ sync = str(c.get("argoSync", "") or "")
77
+ sync_cell = (
78
+ f"[green]{sync}[/]" if sync == "Synced"
79
+ else f"[yellow]{sync}[/]" if sync == "OutOfSync"
80
+ else sync
81
+ )
82
+
83
+ table.add_row(
84
+ str(c.get("id", "")),
85
+ str(c.get("name", "")),
86
+ state_cell,
87
+ str(c.get("plugin", "")),
88
+ str(c.get("kafkaConnectCluster", "")),
89
+ health_cell,
90
+ sync_cell,
91
+ _fmt_date(str(c.get("createdAt", ""))),
92
+ _fmt_date(str(c.get("updatedAt", ""))),
93
+ )
94
+
95
+ console.print(table)
96
+
97
+
98
+ def _fmt_date(value: str) -> str:
99
+ if not value:
100
+ return ""
101
+ try:
102
+ dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
103
+ return dt.astimezone(UTC).strftime("%Y-%m-%d %H:%M")
104
+ except ValueError:
105
+ return value
106
+
107
+
108
+ @app.command("create")
109
+ def create_connector(
110
+ name: str = typer.Option(..., "--name", help="Connector name (lowercase, no spaces)."),
111
+ description: str = typer.Option("", "--description"),
112
+ kafka_connect_cluster: str = typer.Option(..., "--kafka-connect-cluster"),
113
+ plugin: str = typer.Option(..., "--plugin"),
114
+ configs: str = typer.Option(..., "--configs", help="Connector configs as a JSON string."),
115
+ tasks_max: int = typer.Option(1, "--tasks-max"),
116
+ output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
117
+ debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
118
+ ) -> None:
119
+ """Create a new connector."""
120
+ body = _client(debug).post("/connectors", json={
121
+ "name": name,
122
+ "description": description,
123
+ "kafkaConnectCluster": kafka_connect_cluster,
124
+ "plugin": plugin,
125
+ "configs": configs,
126
+ "tasksMax": tasks_max,
127
+ }).json()
128
+
129
+ if output == OutputFormat.json:
130
+ print(json_mod.dumps(body))
131
+ return
132
+
133
+ console.print(
134
+ f"[green]Created[/] [bold]{body['name']}[/] (id: {body['id']}, state: {body['state']})"
135
+ )
136
+
137
+
138
+ @app.command("clone")
139
+ def clone_connector(
140
+ id: str = typer.Argument(..., help="ID of the connector to clone."),
141
+ output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
142
+ debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
143
+ ) -> None:
144
+ """Clone an existing connector."""
145
+ body = _client(debug).post(f"/connectors/{id}/clone").json()
146
+
147
+ if output == OutputFormat.json:
148
+ print(json_mod.dumps(body))
149
+ return
150
+
151
+ console.print(f"[green]Cloned[/] → [bold]{body['name']}[/] (id: {body['id']})")
152
+
153
+
154
+ @app.command("delete")
155
+ def delete_connector(
156
+ id: str = typer.Argument(..., help="ID of the connector to delete."),
157
+ debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
158
+ ) -> None:
159
+ """Delete a connector."""
160
+ _client(debug).delete(f"/connectors/{id}")
161
+ console.print(f"Connector {id} deleted.")
162
+
163
+
164
+ @app.command("publish")
165
+ def publish_connector(
166
+ id: str = typer.Argument(..., help="ID of the connector to publish."),
167
+ branch: str = typer.Option("main", "--branch"),
168
+ commit_message: str = typer.Option(
169
+ "Publish connector via inthub CLI", "--commit-message"
170
+ ),
171
+ author: str = typer.Option(default_factory=getpass.getuser),
172
+ output: OutputFormat = typer.Option(OutputFormat.table, "--output", "-o"),
173
+ debug: bool = typer.Option(False, "--debug", help="Print HTTP request/response details."),
174
+ ) -> None:
175
+ """Publish (apply) a connector to git/ArgoCD."""
176
+ resp = _client(debug).post(f"/connectors/{id}/apply", json={
177
+ "branch": branch,
178
+ "commitMessage": commit_message,
179
+ "authorName": author,
180
+ })
181
+
182
+ if output == OutputFormat.json:
183
+ print(resp.text)
184
+ return
185
+
186
+ console.print(f"[green]Published[/] connector {id} → branch [bold]{branch}[/]")
inthub/config.py ADDED
@@ -0,0 +1,48 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ CONFIG_PATH = Path.home() / ".inthub" / "config.json"
7
+
8
+
9
+ def save(token: str, company_id: str, company_slug: str) -> None:
10
+ CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
11
+ CONFIG_PATH.write_text(
12
+ json.dumps(
13
+ {"token": token, "company_id": company_id, "company_slug": company_slug}, indent=2
14
+ )
15
+ )
16
+
17
+
18
+ def load() -> dict[str, str]:
19
+ if not CONFIG_PATH.exists():
20
+ return {}
21
+ try:
22
+ return json.loads(CONFIG_PATH.read_text()) # type: ignore[no-any-return]
23
+ except (json.JSONDecodeError, OSError):
24
+ return {}
25
+
26
+
27
+ def require_token() -> str:
28
+ """Return the stored token or exit with a clear message."""
29
+ token = os.environ.get("INTHUB_TOKEN") or load().get("token")
30
+ if not token:
31
+ from rich.console import Console
32
+ Console(stderr=True).print(
33
+ "[bold red]Not logged in.[/] Run [bold]inthub login[/] first."
34
+ )
35
+ sys.exit(1)
36
+ return token
37
+
38
+
39
+ def require_company_slug() -> str:
40
+ """Return the stored company slug or exit with a clear message."""
41
+ slug = os.environ.get("INTHUB_COMPANY_SLUG") or load().get("company_slug")
42
+ if not slug:
43
+ from rich.console import Console
44
+ Console(stderr=True).print(
45
+ "[bold red]Not logged in.[/] Run [bold]inthub login[/] first."
46
+ )
47
+ sys.exit(1)
48
+ return slug
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.3
2
+ Name: inthub-cli
3
+ Version: 0.1.4
4
+ Summary: inthub CLI — manage your inthub.io resources from the terminal
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: google-cloud-secret-manager==2.21.0
7
+ Requires-Dist: httpx==0.27.2
8
+ Requires-Dist: pyodbc==5.2.0
9
+ Requires-Dist: python-ulid==2.7.0
10
+ Requires-Dist: rich==13.9.4
11
+ Requires-Dist: snowflake-connector-python==3.12.0
12
+ Requires-Dist: typer==0.15.4
13
+ Provides-Extra: dev
14
+ Requires-Dist: mypy==1.11.2; extra == 'dev'
15
+ Requires-Dist: pytest-mock==3.14.0; extra == 'dev'
16
+ Requires-Dist: pytest==8.3.3; extra == 'dev'
17
+ Requires-Dist: ruff==0.6.9; extra == 'dev'
@@ -0,0 +1,24 @@
1
+ inthub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ inthub/__main__.py,sha256=nQtjBZyq-GNUnbPcMDw4aUfHliff6uy8eTvqdI0fqfc,67
3
+ inthub/cli.py,sha256=n_5mKY59l64K3oMYrwp2Bx8GZvzN57EJpNqc_0afWUc,716
4
+ inthub/client.py,sha256=pJSWaz_Pe7B_aFd-yn1E2ycdNV-FgDfnRyFv0hKt8Y4,2927
5
+ inthub/config.py,sha256=mPCIs5P-m5bHOG0aSh6K6G7SIr46uGG6Cr8Fmkj_atc,1413
6
+ inthub/bulk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ inthub/bulk/csv_state.py,sha256=akJi_w5UG0L_vLI-MYzuI_-aDlGlmnOOUIVqLaK5sGc,3206
8
+ inthub/bulk/ddl_converter.py,sha256=LW22keUqTnlUeprs58YaTP7pwGFy9-P_oQ-pb0ghx-M,2590
9
+ inthub/bulk/gcp_secrets.py,sha256=EU4QFKRn-NbM1Yi4IpXt5B87QgoJV76S-YvSuX3DBN8,839
10
+ inthub/bulk/snowflake_client.py,sha256=q5-c7cT-jypqvtSWpaT54hzdh7NqWCJ4CftvIPBSPPk,1724
11
+ inthub/bulk/sqlserver.py,sha256=kka5gpoPX5LoH_mq0C5p3zfaEkzWSPyReTMYkDQXpfU,1435
12
+ inthub/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ inthub/commands/auth.py,sha256=7BZFAxu45DeYFWNU0nkRYzbr12FJW5sVr8sgY2ebyUA,1765
14
+ inthub/commands/connectors.py,sha256=ig479BiUmldBQd8sNM4xtdcLyWHye6v0TAQKVVr5MAo,6134
15
+ inthub/commands/bulk/__init__.py,sha256=t66st9_IA_Z0a5gMsrwno6czwcSuci2M7vEtU2HBR1U,459
16
+ inthub/commands/bulk/manage.py,sha256=Pq_ONwmM-7UxOg30Un3h9VZcyuVOufQZ3iNiXuhu3oY,2938
17
+ inthub/commands/bulk/progress.py,sha256=zdcXujx-Kv7mXaXyuWm9OchVkZMbnOx6yKzc7kNRWQM,1314
18
+ inthub/commands/bulk/src_sqlserver_sink_snowflake/__init__.py,sha256=MNZXDi08CyXZg-fanmjSiv3_M8XKKl34__MuVr-bKcY,4357
19
+ inthub/commands/bulk/src_sqlserver_sink_snowflake/connectors.py,sha256=nEEFP7UVAhYxZab5FCLAjm4o3GJ_CdMYlgYK8fsCdTQ,4548
20
+ inthub/commands/bulk/src_sqlserver_sink_snowflake/orchestrator.py,sha256=nl_gyv3KM4xtnD3fDnK4htsyv4jXbXFT05-37Ics35o,12821
21
+ inthub_cli-0.1.4.dist-info/METADATA,sha256=7K1hE3_GCFII7fFLz5m4tCYpoc8-WyWabNlcM9NWF74,605
22
+ inthub_cli-0.1.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
23
+ inthub_cli-0.1.4.dist-info/entry_points.txt,sha256=V6U8-d1J5_N3GP-1k1LHTjdZvnZoykJBRCItrWvEiPg,43
24
+ inthub_cli-0.1.4.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ inthub = inthub.cli:main