generic-ml-cache-cli 0.2.0__tar.gz → 0.3.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.
Files changed (21) hide show
  1. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/PKG-INFO +2 -2
  2. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/pyproject.toml +2 -2
  3. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/src/generic_ml_cache_cli/cli.py +26 -1
  4. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_cli.py +45 -0
  5. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/.gitignore +0 -0
  6. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/LICENSE +0 -0
  7. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/NOTICE +0 -0
  8. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/README.md +0 -0
  9. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/src/generic_ml_cache_cli/__init__.py +0 -0
  10. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/src/generic_ml_cache_cli/__main__.py +0 -0
  11. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/src/generic_ml_cache_cli/config.py +0 -0
  12. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/conftest.py +0 -0
  13. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/fake_client.py +0 -0
  14. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_config.py +0 -0
  15. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_discover.py +0 -0
  16. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_effort.py +0 -0
  17. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_interrupt.py +0 -0
  18. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_models.py +0 -0
  19. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_passthrough.py +0 -0
  20. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_robustness.py +0 -0
  21. {generic_ml_cache_cli-0.2.0 → generic_ml_cache_cli-0.3.0}/tests/test_stdin_delivery.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: generic-ml-cache-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Terminal UI for generic-ml-cache: the gmlcache command. A thin inbound driver over generic-ml-cache-core -- reads config, provides the data source, maps commands onto the core library.
5
5
  Project-URL: Homepage, https://github.com/danielslobozian/generic-ml-cache
6
6
  Project-URL: Repository, https://github.com/danielslobozian/generic-ml-cache
@@ -24,7 +24,7 @@ Classifier: Programming Language :: Python :: 3.13
24
24
  Classifier: Topic :: Utilities
25
25
  Requires-Python: >=3.9
26
26
  Requires-Dist: argcomplete<4,>=3
27
- Requires-Dist: generic-ml-cache-core>=0.2.0
27
+ Requires-Dist: generic-ml-cache-core>=0.3.0
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: coverage>=7; extra == 'dev'
30
30
  Requires-Dist: pytest-cov; extra == 'dev'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "generic-ml-cache-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Terminal UI for generic-ml-cache: the gmlcache command. A thin inbound driver over generic-ml-cache-core -- reads config, provides the data source, maps commands onto the core library."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -25,7 +25,7 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.13",
26
26
  "Topic :: Utilities",
27
27
  ]
28
- dependencies = ["generic-ml-cache-core>=0.2.0", "argcomplete>=3,<4"]
28
+ dependencies = ["generic-ml-cache-core>=0.3.0", "argcomplete>=3,<4"]
29
29
 
30
30
  [project.urls]
31
31
  Homepage = "https://github.com/danielslobozian/generic-ml-cache"
@@ -172,6 +172,7 @@ def _cmd_run(args: argparse.Namespace) -> int:
172
172
  grants=list(getattr(args, "grant", None) or []),
173
173
  cache_mode=cache_mode,
174
174
  record_on_error=args.record_on_error,
175
+ tags=list(getattr(args, "tag", None) or []),
175
176
  )
176
177
 
177
178
  def executable_override(client: str):
@@ -550,11 +551,15 @@ def _cmd_list(args: argparse.Namespace) -> int:
550
551
  "kind": summary.kind,
551
552
  "key": summary.execution_key,
552
553
  "hits": hit_counts.get(summary.execution_key, 0),
554
+ "tags": wired.repository.tags_for(summary.execution_key),
553
555
  }
554
556
  for summary in wired.repository.current_execution_summaries()
555
557
  if (not args.client or summary.client == args.client)
556
558
  and (not args.model or summary.model == args.model)
557
559
  ]
560
+ wanted_tags = set(getattr(args, "tag", None) or [])
561
+ if wanted_tags:
562
+ entries = [entry for entry in entries if wanted_tags & set(entry["tags"])]
558
563
 
559
564
  if args.json:
560
565
  print(json.dumps({"executions": entries}, indent=2))
@@ -568,10 +573,13 @@ def _cmd_list(args: argparse.Namespace) -> int:
568
573
  for entry in sorted(entries, key=lambda item: (item["client"], item["model"], item["key"])):
569
574
  hits = entry["hits"]
570
575
  hits_text = _paint(str(hits), _GREEN) if hits else _paint(str(hits), _GREY)
571
- print(
576
+ line = (
572
577
  f" {entry['client']:<8} {entry['model']:<20} {entry['kind']:<18} "
573
578
  f"{_paint(entry['key'][:12], _GREY)} hits:{hits_text}"
574
579
  )
580
+ if entry["tags"]:
581
+ line += " tags:" + _paint(",".join(entry["tags"]), _TEAL)
582
+ print(line)
575
583
  return 0
576
584
 
577
585
 
@@ -709,6 +717,16 @@ def build_parser() -> argparse.ArgumentParser:
709
717
  choices=GRANT_CHOICES,
710
718
  help=_GRANT_HELP,
711
719
  )
720
+ run.add_argument(
721
+ "--tag",
722
+ action="append",
723
+ dest="tag",
724
+ metavar="TAG",
725
+ help=(
726
+ "label this execution with a tag for later grouping/queries (repeatable; "
727
+ "metadata only -- never part of the cache key). A relabel on a hit accumulates."
728
+ ),
729
+ )
712
730
  run.add_argument(
713
731
  "--json",
714
732
  action="store_true",
@@ -840,6 +858,13 @@ def build_parser() -> argparse.ArgumentParser:
840
858
  )
841
859
  listp.add_argument("--client", help="only executions recorded for this client")
842
860
  listp.add_argument("--model", help="only executions recorded for this model")
861
+ listp.add_argument(
862
+ "--tag",
863
+ action="append",
864
+ dest="tag",
865
+ metavar="TAG",
866
+ help="only executions carrying any of these tags (repeatable; match-any)",
867
+ )
843
868
  listp.add_argument("--json", action="store_true", help="emit machine-readable JSON")
844
869
  listp.set_defaults(func=_cmd_list)
845
870
 
@@ -324,3 +324,48 @@ def test_check_reports_hit_after_a_run(tmp_path, monkeypatch, capsys):
324
324
  out = capsys.readouterr().out
325
325
  assert rc == 0
326
326
  assert "status : hit" in out
327
+
328
+
329
+ def test_run_tag_stores_tags_through_the_cli(tmp_path):
330
+ import glob
331
+ import sqlite3
332
+
333
+ rc = run_cli(
334
+ [
335
+ "run",
336
+ "--client",
337
+ "fake",
338
+ "--model",
339
+ "m1",
340
+ "--effort",
341
+ "high",
342
+ "--prompt",
343
+ "STDOUT hi",
344
+ "--tag",
345
+ "ticket",
346
+ "--tag",
347
+ "id-scan",
348
+ ]
349
+ )
350
+ assert rc == 0
351
+ stores = glob.glob(str(tmp_path / "**" / "executions.sqlite3"), recursive=True)
352
+ assert stores, "no executions store was written"
353
+ connection = sqlite3.connect(stores[0])
354
+ stored = sorted(tag for (tag,) in connection.execute("SELECT tag FROM execution_tags"))
355
+ connection.close()
356
+ assert stored == ["id-scan", "ticket"]
357
+
358
+
359
+ def test_list_filters_by_tag_and_shows_tags(capsys):
360
+ import json
361
+
362
+ base = ["run", "--client", "fake", "--model", "m1", "--effort", "high"]
363
+ run_cli(base + ["--prompt", "STDOUT a", "--tag", "alpha"])
364
+ run_cli(base + ["--prompt", "STDOUT b", "--tag", "beta"])
365
+ capsys.readouterr()
366
+
367
+ rc = main(["list", "--tag", "alpha", "--json"])
368
+ assert rc == 0
369
+ listed = json.loads(capsys.readouterr().out)["executions"]
370
+ assert len(listed) == 1 # match-any filter keeps only the alpha-tagged entry
371
+ assert listed[0]["tags"] == ["alpha"]