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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: deepsieve-cli
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: DeepSieve CLI — run cited deep research from your terminal
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "deepsieve-cli"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "DeepSieve CLI — run cited deep research from your terminal"
5
5
  requires-python = ">=3.11"
6
6
  readme = "README.md"
@@ -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/data")
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("GET", f"/v1/data/{args.entity}", params=params)
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(_r):
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