sovaria-cli 0.2.3__tar.gz → 0.2.5__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 (55) hide show
  1. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/PKG-INFO +1 -1
  2. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/__init__.py +1 -1
  3. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/db.py +162 -16
  4. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_main_startup.py +1 -1
  5. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_project_and_commands.py +61 -0
  6. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/pyproject.toml +1 -1
  7. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/.github/workflows/ci.yml +0 -0
  8. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/.github/workflows/publish.yml +0 -0
  9. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/.gitignore +0 -0
  10. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/.python-version +0 -0
  11. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/Makefile +0 -0
  12. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/README.md +0 -0
  13. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/__init__.py +0 -0
  14. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/__init__.py +0 -0
  15. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/admin.py +0 -0
  16. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/api.py +0 -0
  17. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/audit.py +0 -0
  18. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/auth_product.py +0 -0
  19. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/billing.py +0 -0
  20. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/chrono.py +0 -0
  21. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/context.py +0 -0
  22. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/data.py +0 -0
  23. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/deploy.py +0 -0
  24. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/governance.py +0 -0
  25. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/iam.py +0 -0
  26. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/init_cmd.py +0 -0
  27. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/keys.py +0 -0
  28. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/login.py +0 -0
  29. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/org.py +0 -0
  30. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/policy.py +0 -0
  31. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/project.py +0 -0
  32. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/realtime.py +0 -0
  33. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/scope.py +0 -0
  34. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/shell.py +0 -0
  35. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/sim.py +0 -0
  36. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/storage.py +0 -0
  37. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/commands/vector.py +0 -0
  38. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/main.py +0 -0
  39. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/__init__.py +0 -0
  40. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/api_client.py +0 -0
  41. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/auth_store.py +0 -0
  42. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/config.py +0 -0
  43. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/display.py +0 -0
  44. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/migrations.py +0 -0
  45. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/project_context.py +0 -0
  46. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/src/utils/scope_picker.py +0 -0
  47. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_auth_store_profiles.py +0 -0
  48. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_cli_security.py +0 -0
  49. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_cli_smoke_paths.py +0 -0
  50. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_config_profiles.py +0 -0
  51. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/apps/cli/tests/test_scope_picker.py +0 -0
  52. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/pytest.ini +0 -0
  53. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/requirements-dev.txt +0 -0
  54. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/requirements.txt +0 -0
  55. {sovaria_cli-0.2.3 → sovaria_cli-0.2.5}/scripts/cli_sdk_happy_path.sh +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sovaria-cli
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Sovaria BaaS command-line interface (sov)
5
5
  Project-URL: Homepage, https://sovaria.in
6
6
  Project-URL: Documentation, https://docs.sovaria.in/cli
@@ -1,3 +1,3 @@
1
1
  """Sovaria CLI — sov command."""
2
2
 
3
- __version__ = "0.2.3"
3
+ __version__ = "0.2.4"
@@ -18,6 +18,143 @@ from apps.cli.src.utils.display import print_json, print_sql_result
18
18
 
19
19
  app = typer.Typer(help="Database (SQL) management")
20
20
 
21
+ _INTERNAL_SQL_TABLES = ("sov_documents",)
22
+ _LIST_TABLES_SQL = (
23
+ "SELECT table_name FROM information_schema.tables "
24
+ "WHERE table_schema = current_schema() "
25
+ "AND table_type = 'BASE TABLE' "
26
+ f"AND table_name NOT IN ({', '.join(repr(name) for name in _INTERNAL_SQL_TABLES)}) "
27
+ "ORDER BY table_name;"
28
+ )
29
+
30
+
31
+ def _begin_tx(
32
+ *,
33
+ profile: str | None,
34
+ org_id: str | None,
35
+ project_id: str | None,
36
+ env_id: str | None,
37
+ ) -> int:
38
+ result = call_api(
39
+ method="POST",
40
+ path="/api/v1/data/rel/transaction/begin",
41
+ profile=profile,
42
+ org_id=org_id,
43
+ project_id=project_id,
44
+ env_id=env_id,
45
+ require_scope=True,
46
+ )
47
+ txid = result.get("txid") if isinstance(result, dict) else None
48
+ if not isinstance(txid, int) or txid < 1:
49
+ raise CLIRequestError(f"Invalid txid from transaction begin: {result!r}")
50
+ return txid
51
+
52
+
53
+ def _commit_tx(
54
+ txid: int,
55
+ *,
56
+ profile: str | None,
57
+ org_id: str | None,
58
+ project_id: str | None,
59
+ env_id: str | None,
60
+ ) -> None:
61
+ call_api(
62
+ method="POST",
63
+ path="/api/v1/data/rel/transaction/commit",
64
+ profile=profile,
65
+ body={"txid": txid},
66
+ org_id=org_id,
67
+ project_id=project_id,
68
+ env_id=env_id,
69
+ require_scope=True,
70
+ )
71
+
72
+
73
+ def _rollback_tx(
74
+ txid: int,
75
+ *,
76
+ profile: str | None,
77
+ org_id: str | None,
78
+ project_id: str | None,
79
+ env_id: str | None,
80
+ ) -> None:
81
+ try:
82
+ call_api(
83
+ method="POST",
84
+ path="/api/v1/data/rel/transaction/rollback",
85
+ profile=profile,
86
+ body={"txid": txid},
87
+ org_id=org_id,
88
+ project_id=project_id,
89
+ env_id=env_id,
90
+ require_scope=True,
91
+ )
92
+ except CLIRequestError:
93
+ pass
94
+
95
+
96
+ def _run_sql_query(
97
+ sql: str,
98
+ *,
99
+ profile: str | None,
100
+ org_id: str | None,
101
+ project_id: str | None,
102
+ env_id: str | None,
103
+ ) -> dict:
104
+ scope = {
105
+ "profile": profile,
106
+ "org_id": org_id,
107
+ "project_id": project_id,
108
+ "env_id": env_id,
109
+ }
110
+ txid = _begin_tx(**scope)
111
+ try:
112
+ result = call_api(
113
+ method="POST",
114
+ path="/api/v1/sql/query",
115
+ profile=profile,
116
+ body={"sql": sql, "txid": txid},
117
+ org_id=org_id,
118
+ project_id=project_id,
119
+ env_id=env_id,
120
+ require_scope=True,
121
+ )
122
+ _commit_tx(txid, **scope)
123
+ return result if isinstance(result, dict) else {"rows": result}
124
+ except Exception:
125
+ _rollback_tx(txid, **scope)
126
+ raise
127
+
128
+
129
+ def _fetch_live_tables(
130
+ *,
131
+ profile: str | None,
132
+ org_id: str | None,
133
+ project_id: str | None,
134
+ env_id: str | None,
135
+ ) -> list[str]:
136
+ result = _run_sql_query(
137
+ _LIST_TABLES_SQL,
138
+ profile=profile,
139
+ org_id=org_id,
140
+ project_id=project_id,
141
+ env_id=env_id,
142
+ )
143
+ rows = result.get("rows") if isinstance(result, dict) else None
144
+ if not isinstance(rows, list):
145
+ return []
146
+ tables: list[str] = []
147
+ for row in rows:
148
+ if isinstance(row, list) and row:
149
+ name = str(row[0]).strip()
150
+ elif isinstance(row, dict):
151
+ name = str(row.get("table_name") or row.get("TABLE_NAME") or "").strip()
152
+ else:
153
+ continue
154
+ if name and name not in _INTERNAL_SQL_TABLES:
155
+ tables.append(name)
156
+ return tables
157
+
21
158
 
22
159
  @app.command("tables")
23
160
  def list_tables(
@@ -25,22 +162,37 @@ def list_tables(
25
162
  project_id: str | None = typer.Option(None, "--project", help="Project ID"),
26
163
  env_id: str | None = typer.Option(None, "--env"),
27
164
  profile: str | None = typer.Option(None, "--profile"),
165
+ migrations: bool = typer.Option(
166
+ False,
167
+ "--migrations",
168
+ help="Show migration log only (legacy behaviour)",
169
+ ),
28
170
  ) -> None:
29
- """List relational tables / migration log for the linked project."""
171
+ """List live relational tables in the linked project database."""
30
172
  try:
31
- result = call_api(
32
- method="GET",
33
- path="/api/v1/data/rel/migrations",
173
+ if migrations:
174
+ result = call_api(
175
+ method="GET",
176
+ path="/api/v1/data/rel/migrations",
177
+ profile=profile,
178
+ org_id=org_id,
179
+ project_id=project_id,
180
+ env_id=env_id,
181
+ require_scope=True,
182
+ )
183
+ print_json(result)
184
+ return
185
+
186
+ tables = _fetch_live_tables(
34
187
  profile=profile,
35
188
  org_id=org_id,
36
189
  project_id=project_id,
37
190
  env_id=env_id,
38
- require_scope=True,
39
191
  )
192
+ print_json({"tables": tables, "total": len(tables), "source": "information_schema"})
40
193
  except CLIRequestError as exc:
41
194
  typer.echo(str(exc), err=True)
42
195
  raise typer.Exit(1) from exc
43
- print_json(result)
44
196
 
45
197
 
46
198
  @app.command("sql")
@@ -97,15 +249,12 @@ def _execute_sql(
97
249
  env_id: str | None,
98
250
  ) -> None:
99
251
  try:
100
- result = call_api(
101
- method="POST",
102
- path="/api/v1/sql/query",
252
+ result = _run_sql_query(
253
+ sql,
103
254
  profile=profile,
104
- body={"sql": sql, "txid": 0},
105
255
  org_id=org_id,
106
256
  project_id=project_id,
107
257
  env_id=env_id,
108
- require_scope=True,
109
258
  )
110
259
  except CLIRequestError as exc:
111
260
  typer.echo(str(exc), err=True)
@@ -473,11 +622,8 @@ def migrations_push(
473
622
  continue
474
623
  typer.echo(f"Applying {path.name} …")
475
624
  try:
476
- call_api(
477
- method="POST",
478
- path="/api/v1/sql/query",
479
- body={"sql": sql, "txid": 0},
480
- require_scope=True,
625
+ _run_sql_query(
626
+ sql,
481
627
  **_scope_opts(profile, org_id, project_id, env_id),
482
628
  )
483
629
  except CLIRequestError as exc:
@@ -26,4 +26,4 @@ def test_sov_console_script_help() -> None:
26
26
  )
27
27
 
28
28
  assert result.returncode == 0
29
- assert "sov version 0.2.3" in result.stdout
29
+ assert "sov version 0.2.4" in result.stdout
@@ -56,6 +56,36 @@ def test_resolve_scope_requires_linked_project(monkeypatch: pytest.MonkeyPatch)
56
56
  )
57
57
 
58
58
 
59
+ def test_db_tables_uses_information_schema(monkeypatch: pytest.MonkeyPatch) -> None:
60
+ from apps.cli.src.commands import db
61
+
62
+ captured: dict[str, Any] = {}
63
+ calls: list[tuple[str, str]] = []
64
+
65
+ def _fake_call_api(**kwargs: Any) -> dict[str, Any]:
66
+ method = str(kwargs.get("method", ""))
67
+ path = str(kwargs.get("path", ""))
68
+ calls.append((method, path))
69
+ if path.endswith("/transaction/begin"):
70
+ return {"txid": 1, "snapshot_txid": 1}
71
+ if path.endswith("/sql/query"):
72
+ captured.update(kwargs)
73
+ return {"rows": [["users"], ["orders"]]}
74
+ if path.endswith("/transaction/commit"):
75
+ return {"ok": True}
76
+ return {}
77
+
78
+ monkeypatch.setattr(db, "call_api", _fake_call_api)
79
+ db.list_tables(profile=None, org_id=None, project_id=None, env_id=None, migrations=False)
80
+
81
+ assert captured["method"] == "POST"
82
+ assert captured["path"] == "/api/v1/sql/query"
83
+ assert "information_schema.tables" in captured["body"]["sql"]
84
+ assert captured["body"]["txid"] == 1
85
+ assert ("POST", "/api/v1/data/rel/transaction/begin") in calls
86
+ assert ("POST", "/api/v1/data/rel/transaction/commit") in calls
87
+
88
+
59
89
  def test_sim_discover_path_smoke(monkeypatch: pytest.MonkeyPatch) -> None:
60
90
  from apps.cli.src.commands import sim
61
91
 
@@ -194,3 +224,34 @@ def test_storage_buckets_uses_call_api(monkeypatch: pytest.MonkeyPatch) -> None:
194
224
  assert captured["path"] == "/api/v1/store/buckets"
195
225
  assert captured["require_scope"] is True
196
226
  assert any("bucket-a" in line for line in output)
227
+
228
+
229
+ def test_fetch_live_tables_uses_transaction(monkeypatch: pytest.MonkeyPatch) -> None:
230
+ from apps.cli.src.commands import db
231
+
232
+ calls: list[tuple[str, str]] = []
233
+
234
+ def _fake_call_api(**kwargs: Any) -> dict[str, Any]:
235
+ method = str(kwargs.get("method", ""))
236
+ path = str(kwargs.get("path", ""))
237
+ calls.append((method, path))
238
+ if path.endswith("/transaction/begin"):
239
+ return {"txid": 7, "snapshot_txid": 7}
240
+ if path.endswith("/sql/query"):
241
+ return {"rows": [["users"], ["posts"]]}
242
+ if path.endswith("/transaction/commit"):
243
+ return {"ok": True}
244
+ return {}
245
+
246
+ monkeypatch.setattr(db, "call_api", _fake_call_api)
247
+ tables = db._fetch_live_tables(
248
+ profile=None,
249
+ org_id="org-1",
250
+ project_id="proj-1",
251
+ env_id=None,
252
+ )
253
+
254
+ assert tables == ["users", "posts"]
255
+ assert ("POST", "/api/v1/data/rel/transaction/begin") in calls
256
+ assert ("POST", "/api/v1/sql/query") in calls
257
+ assert ("POST", "/api/v1/data/rel/transaction/commit") in calls
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sovaria-cli"
7
- version = "0.2.3"
7
+ version = "0.2.5"
8
8
  description = "Sovaria BaaS command-line interface (sov)"
9
9
  readme = "apps/cli/README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes