deepsieve-cli 0.1.1__tar.gz → 0.2.0__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.
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/PKG-INFO +1 -1
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/pyproject.toml +1 -1
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/src/deepsieve_cli/main.py +48 -2
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/tests/test_cli.py +62 -1
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/.gitignore +0 -0
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/README.md +0 -0
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/src/deepsieve_cli/__init__.py +0 -0
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/src/deepsieve_cli/__main__.py +0 -0
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/src/deepsieve_cli/client.py +0 -0
- {deepsieve_cli-0.1.1 → deepsieve_cli-0.2.0}/src/deepsieve_cli/config.py +0 -0
|
@@ -346,10 +346,49 @@ def cmd_members_list(args) -> int:
|
|
|
346
346
|
return EXIT_OK
|
|
347
347
|
|
|
348
348
|
|
|
349
|
+
def _blueprint_id(client, args) -> str | None:
|
|
350
|
+
"""The Blueprint these commands address: ``--blueprint``, else the active one.
|
|
351
|
+
|
|
352
|
+
`/v1/data` was removed (pre-GA breaking change): every dataset read now names
|
|
353
|
+
its Blueprint in the path, because a read against the wrong one returns a
|
|
354
|
+
well-formed dataset about the wrong domain and nothing in the response says
|
|
355
|
+
so. The CLI keeps its zero-argument form by resolving the active Blueprint
|
|
356
|
+
here — one extra request, and only when no id was given.
|
|
357
|
+
|
|
358
|
+
Returns None after printing the reason, so callers exit non-zero rather than
|
|
359
|
+
falling back to some other Blueprint.
|
|
360
|
+
"""
|
|
361
|
+
if getattr(args, "blueprint", None):
|
|
362
|
+
return args.blueprint
|
|
363
|
+
try:
|
|
364
|
+
body = client.request("GET", "/v1/blueprints")
|
|
365
|
+
except ApiError as exc:
|
|
366
|
+
err(str(exc))
|
|
367
|
+
return None
|
|
368
|
+
rows = body.get("data") or []
|
|
369
|
+
active = next((r for r in rows if r.get("is_active")), None)
|
|
370
|
+
if active is None:
|
|
371
|
+
# Say which of the two states this is. "No Blueprints at all" and "none
|
|
372
|
+
# of them is active" need different actions from the user, and an
|
|
373
|
+
# unqualified "not found" would send them looking for the wrong problem.
|
|
374
|
+
if not rows:
|
|
375
|
+
err("no Blueprints yet — design one in the app first.")
|
|
376
|
+
else:
|
|
377
|
+
err(
|
|
378
|
+
"no active Blueprint. Pass --blueprint <id>; "
|
|
379
|
+
"`deepsieve data catalog --blueprint <id>` lists ids via /v1/blueprints."
|
|
380
|
+
)
|
|
381
|
+
return None
|
|
382
|
+
return active.get("id")
|
|
383
|
+
|
|
384
|
+
|
|
349
385
|
def cmd_data_catalog(args) -> int:
|
|
350
386
|
client, _ = _require_auth(args)
|
|
387
|
+
bp_id = _blueprint_id(client, args)
|
|
388
|
+
if bp_id is None:
|
|
389
|
+
return EXIT_FAIL
|
|
351
390
|
try:
|
|
352
|
-
body = client.request("GET", "/v1/
|
|
391
|
+
body = client.request("GET", f"/v1/blueprints/{bp_id}")
|
|
353
392
|
except ApiError as exc:
|
|
354
393
|
err(str(exc))
|
|
355
394
|
return EXIT_FAIL
|
|
@@ -364,13 +403,18 @@ def cmd_data_catalog(args) -> int:
|
|
|
364
403
|
|
|
365
404
|
def cmd_data_get(args) -> int:
|
|
366
405
|
client, _ = _require_auth(args)
|
|
406
|
+
bp_id = _blueprint_id(client, args)
|
|
407
|
+
if bp_id is None:
|
|
408
|
+
return EXIT_FAIL
|
|
367
409
|
params: dict[str, Any] = {"limit": args.limit, "receipts": args.receipts}
|
|
368
410
|
if args.cursor:
|
|
369
411
|
params["cursor"] = args.cursor
|
|
370
412
|
if args.updated_since:
|
|
371
413
|
params["updated_since"] = args.updated_since
|
|
372
414
|
try:
|
|
373
|
-
body = client.request(
|
|
415
|
+
body = client.request(
|
|
416
|
+
"GET", f"/v1/blueprints/{bp_id}/entities/{args.entity}", params=params
|
|
417
|
+
)
|
|
374
418
|
except ApiError as exc:
|
|
375
419
|
err(str(exc))
|
|
376
420
|
return EXIT_FAIL
|
|
@@ -515,6 +559,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
515
559
|
data = sub.add_parser("data", help="your cited dataset", parents=[common]).add_subparsers(dest="sub", required=True)
|
|
516
560
|
|
|
517
561
|
dc = data.add_parser("catalog", help="entities and columns (never guess these)", parents=[common])
|
|
562
|
+
dc.add_argument("--blueprint", help="Blueprint id (default: the active one)")
|
|
518
563
|
dc.set_defaults(func=cmd_data_catalog)
|
|
519
564
|
|
|
520
565
|
dg = data.add_parser("get", help="rows for one entity", parents=[common])
|
|
@@ -523,6 +568,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
523
568
|
dg.add_argument("--cursor")
|
|
524
569
|
dg.add_argument("--updated-since")
|
|
525
570
|
dg.add_argument("--receipts", action="store_true", help="include per-cell citations")
|
|
571
|
+
dg.add_argument("--blueprint", help="Blueprint id (default: the active one)")
|
|
526
572
|
dg.set_defaults(func=cmd_data_get)
|
|
527
573
|
|
|
528
574
|
setup = sub.add_parser("setup", help="connect other tools", parents=[common]).add_subparsers(
|
|
@@ -230,10 +230,18 @@ def test_run_create_with_neither_sends_an_empty_broad_sweep(monkeypatch):
|
|
|
230
230
|
assert seen["body"]["dry_run"] is True
|
|
231
231
|
|
|
232
232
|
|
|
233
|
+
# A workspace with one Blueprint, active. `data` commands resolve through this
|
|
234
|
+
# listing since #1282 removed the ambient /v1/data — so a handler that does not
|
|
235
|
+
# serve it is a handler the data commands cannot get past.
|
|
236
|
+
_ONE_ACTIVE_BP = {"data": [{"id": "bp_1", "name": "Finance", "is_active": True}]}
|
|
237
|
+
|
|
238
|
+
|
|
233
239
|
def test_truncated_dataset_is_called_out(monkeypatch, capsys):
|
|
234
240
|
cfg.save_profile(cfg.Profile(name="default", origin="https://x.test", api_key="k"))
|
|
235
241
|
|
|
236
|
-
def handler(
|
|
242
|
+
def handler(r):
|
|
243
|
+
if r.url.path == "/v1/blueprints":
|
|
244
|
+
return httpx.Response(200, json=_ONE_ACTIVE_BP)
|
|
237
245
|
return httpx.Response(200, json={"data": [], "truncated": True, "preview_row_cap": 10})
|
|
238
246
|
|
|
239
247
|
monkeypatch.setattr(httpx, "Client", _mock(handler))
|
|
@@ -241,6 +249,59 @@ def test_truncated_dataset_is_called_out(monkeypatch, capsys):
|
|
|
241
249
|
assert "PREVIEW" in capsys.readouterr().err
|
|
242
250
|
|
|
243
251
|
|
|
252
|
+
def test_data_get_addresses_the_active_blueprint_by_id(monkeypatch):
|
|
253
|
+
"""The path must NAME a Blueprint. #1282 removed the ambient `/v1/data/{key}`,
|
|
254
|
+
so a CLI still calling it would 404 against any current deployment."""
|
|
255
|
+
cfg.save_profile(cfg.Profile(name="default", origin="https://x.test", api_key="k"))
|
|
256
|
+
seen = {}
|
|
257
|
+
|
|
258
|
+
def handler(r):
|
|
259
|
+
if r.url.path == "/v1/blueprints":
|
|
260
|
+
return httpx.Response(200, json=_ONE_ACTIVE_BP)
|
|
261
|
+
seen["path"] = r.url.path
|
|
262
|
+
return httpx.Response(200, json={"data": []})
|
|
263
|
+
|
|
264
|
+
monkeypatch.setattr(httpx, "Client", _mock(handler))
|
|
265
|
+
assert main(["data", "get", "companies"]) == 0
|
|
266
|
+
assert seen["path"] == "/v1/blueprints/bp_1/entities/companies"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_blueprint_flag_names_one_without_consulting_the_listing(monkeypatch):
|
|
270
|
+
"""`--blueprint` is the whole point of the flag: it must not be second-guessed
|
|
271
|
+
against the active one, and it must not cost a listing call."""
|
|
272
|
+
cfg.save_profile(cfg.Profile(name="default", origin="https://x.test", api_key="k"))
|
|
273
|
+
paths = []
|
|
274
|
+
|
|
275
|
+
def handler(r):
|
|
276
|
+
paths.append(r.url.path)
|
|
277
|
+
return httpx.Response(200, json={"data": []})
|
|
278
|
+
|
|
279
|
+
monkeypatch.setattr(httpx, "Client", _mock(handler))
|
|
280
|
+
assert main(["data", "get", "companies", "--blueprint", "bp_other"]) == 0
|
|
281
|
+
assert paths == ["/v1/blueprints/bp_other/entities/companies"]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_no_active_blueprint_fails_rather_than_picking_one(monkeypatch, capsys):
|
|
285
|
+
"""⚠️ The failure mode this helper exists to prevent.
|
|
286
|
+
|
|
287
|
+
With several Blueprints and none active, reading "whichever" returns a
|
|
288
|
+
well-formed dataset about the wrong domain — indistinguishable from the
|
|
289
|
+
right answer. So it exits NON-ZERO and names the flag, rather than falling
|
|
290
|
+
back to the first row.
|
|
291
|
+
"""
|
|
292
|
+
cfg.save_profile(cfg.Profile(name="default", origin="https://x.test", api_key="k"))
|
|
293
|
+
listed = {"data": [{"id": "a", "is_active": False}, {"id": "b", "is_active": False}]}
|
|
294
|
+
|
|
295
|
+
def handler(r):
|
|
296
|
+
if r.url.path == "/v1/blueprints":
|
|
297
|
+
return httpx.Response(200, json=listed)
|
|
298
|
+
raise AssertionError(f"must not read data without a Blueprint: {r.url.path}")
|
|
299
|
+
|
|
300
|
+
monkeypatch.setattr(httpx, "Client", _mock(handler))
|
|
301
|
+
assert main(["data", "get", "companies"]) != 0
|
|
302
|
+
assert "--blueprint" in capsys.readouterr().err
|
|
303
|
+
|
|
304
|
+
|
|
244
305
|
def test_setup_mcp_names_this_profiles_origin(monkeypatch, capsys):
|
|
245
306
|
cfg.save_profile(cfg.Profile(name="staging", origin="https://staging.test", api_key="k"))
|
|
246
307
|
assert main(["--profile", "staging", "setup", "mcp"]) == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|