sovaria-cli 0.2.4__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.4 → sovaria_cli-0.2.5}/PKG-INFO +1 -1
  2. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/db.py +103 -14
  3. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_project_and_commands.py +46 -2
  4. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/pyproject.toml +1 -1
  5. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/.github/workflows/ci.yml +0 -0
  6. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/.github/workflows/publish.yml +0 -0
  7. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/.gitignore +0 -0
  8. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/.python-version +0 -0
  9. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/Makefile +0 -0
  10. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/README.md +0 -0
  11. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/__init__.py +0 -0
  12. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/__init__.py +0 -0
  13. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/__init__.py +0 -0
  14. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/admin.py +0 -0
  15. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/api.py +0 -0
  16. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/audit.py +0 -0
  17. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/auth_product.py +0 -0
  18. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/billing.py +0 -0
  19. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/chrono.py +0 -0
  20. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/context.py +0 -0
  21. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/data.py +0 -0
  22. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/deploy.py +0 -0
  23. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/governance.py +0 -0
  24. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/iam.py +0 -0
  25. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/init_cmd.py +0 -0
  26. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/keys.py +0 -0
  27. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/login.py +0 -0
  28. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/org.py +0 -0
  29. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/policy.py +0 -0
  30. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/project.py +0 -0
  31. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/realtime.py +0 -0
  32. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/scope.py +0 -0
  33. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/shell.py +0 -0
  34. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/sim.py +0 -0
  35. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/storage.py +0 -0
  36. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/commands/vector.py +0 -0
  37. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/main.py +0 -0
  38. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/__init__.py +0 -0
  39. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/api_client.py +0 -0
  40. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/auth_store.py +0 -0
  41. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/config.py +0 -0
  42. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/display.py +0 -0
  43. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/migrations.py +0 -0
  44. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/project_context.py +0 -0
  45. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/src/utils/scope_picker.py +0 -0
  46. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_auth_store_profiles.py +0 -0
  47. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_cli_security.py +0 -0
  48. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_cli_smoke_paths.py +0 -0
  49. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_config_profiles.py +0 -0
  50. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_main_startup.py +0 -0
  51. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/apps/cli/tests/test_scope_picker.py +0 -0
  52. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/pytest.ini +0 -0
  53. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/requirements-dev.txt +0 -0
  54. {sovaria_cli-0.2.4 → sovaria_cli-0.2.5}/requirements.txt +0 -0
  55. {sovaria_cli-0.2.4 → 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.4
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
@@ -28,23 +28,118 @@ _LIST_TABLES_SQL = (
28
28
  )
29
29
 
30
30
 
31
- def _fetch_live_tables(
31
+ def _begin_tx(
32
32
  *,
33
33
  profile: str | None,
34
34
  org_id: str | None,
35
35
  project_id: str | None,
36
36
  env_id: str | None,
37
- ) -> list[str]:
37
+ ) -> int:
38
38
  result = call_api(
39
39
  method="POST",
40
- path="/api/v1/sql/query",
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",
41
64
  profile=profile,
42
- body={"sql": _LIST_TABLES_SQL, "txid": 0},
65
+ body={"txid": txid},
43
66
  org_id=org_id,
44
67
  project_id=project_id,
45
68
  env_id=env_id,
46
69
  require_scope=True,
47
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
+ )
48
143
  rows = result.get("rows") if isinstance(result, dict) else None
49
144
  if not isinstance(rows, list):
50
145
  return []
@@ -154,15 +249,12 @@ def _execute_sql(
154
249
  env_id: str | None,
155
250
  ) -> None:
156
251
  try:
157
- result = call_api(
158
- method="POST",
159
- path="/api/v1/sql/query",
252
+ result = _run_sql_query(
253
+ sql,
160
254
  profile=profile,
161
- body={"sql": sql, "txid": 0},
162
255
  org_id=org_id,
163
256
  project_id=project_id,
164
257
  env_id=env_id,
165
- require_scope=True,
166
258
  )
167
259
  except CLIRequestError as exc:
168
260
  typer.echo(str(exc), err=True)
@@ -530,11 +622,8 @@ def migrations_push(
530
622
  continue
531
623
  typer.echo(f"Applying {path.name} …")
532
624
  try:
533
- call_api(
534
- method="POST",
535
- path="/api/v1/sql/query",
536
- body={"sql": sql, "txid": 0},
537
- require_scope=True,
625
+ _run_sql_query(
626
+ sql,
538
627
  **_scope_opts(profile, org_id, project_id, env_id),
539
628
  )
540
629
  except CLIRequestError as exc:
@@ -60,10 +60,20 @@ def test_db_tables_uses_information_schema(monkeypatch: pytest.MonkeyPatch) -> N
60
60
  from apps.cli.src.commands import db
61
61
 
62
62
  captured: dict[str, Any] = {}
63
+ calls: list[tuple[str, str]] = []
63
64
 
64
65
  def _fake_call_api(**kwargs: Any) -> dict[str, Any]:
65
- captured.update(kwargs)
66
- return {"rows": [["users"], ["orders"]]}
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 {}
67
77
 
68
78
  monkeypatch.setattr(db, "call_api", _fake_call_api)
69
79
  db.list_tables(profile=None, org_id=None, project_id=None, env_id=None, migrations=False)
@@ -71,6 +81,9 @@ def test_db_tables_uses_information_schema(monkeypatch: pytest.MonkeyPatch) -> N
71
81
  assert captured["method"] == "POST"
72
82
  assert captured["path"] == "/api/v1/sql/query"
73
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
74
87
 
75
88
 
76
89
  def test_sim_discover_path_smoke(monkeypatch: pytest.MonkeyPatch) -> None:
@@ -211,3 +224,34 @@ def test_storage_buckets_uses_call_api(monkeypatch: pytest.MonkeyPatch) -> None:
211
224
  assert captured["path"] == "/api/v1/store/buckets"
212
225
  assert captured["require_scope"] is True
213
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.4"
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