withcache 0.12.0__tar.gz → 0.13.1__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 (49) hide show
  1. {withcache-0.12.0 → withcache-0.13.1}/PKG-INFO +10 -8
  2. {withcache-0.12.0 → withcache-0.13.1}/README.md +9 -7
  3. {withcache-0.12.0 → withcache-0.13.1}/shim/build.zig.zon +1 -1
  4. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/__init__.py +1 -1
  5. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_api.py +27 -4
  6. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_app.py +268 -62
  7. withcache-0.13.1/src/withcache/_events_log.py +277 -0
  8. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/_layout.html +3 -0
  9. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/dashboard.html +35 -17
  10. withcache-0.13.1/src/withcache/_templates/ui/events.html +125 -0
  11. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/client.py +22 -0
  12. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/server.py +90 -10
  13. withcache-0.13.1/tests/test_fastapi_events.py +205 -0
  14. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_ui_pages.py +4 -3
  15. {withcache-0.12.0 → withcache-0.13.1}/tests/test_withcache.py +66 -0
  16. {withcache-0.12.0 → withcache-0.13.1}/.gitignore +0 -0
  17. {withcache-0.12.0 → withcache-0.13.1}/LICENSE +0 -0
  18. {withcache-0.12.0 → withcache-0.13.1}/deploy/Containerfile +0 -0
  19. {withcache-0.12.0 → withcache-0.13.1}/deploy/compose.local-build.yml +0 -0
  20. {withcache-0.12.0 → withcache-0.13.1}/deploy/compose.yml +0 -0
  21. {withcache-0.12.0 → withcache-0.13.1}/deploy/envvars.example +0 -0
  22. {withcache-0.12.0 → withcache-0.13.1}/hatch_build.py +0 -0
  23. {withcache-0.12.0 → withcache-0.13.1}/pyproject.toml +0 -0
  24. {withcache-0.12.0 → withcache-0.13.1}/shim/build.zig +0 -0
  25. {withcache-0.12.0 → withcache-0.13.1}/shim/shim.zig +0 -0
  26. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_settings_store.py +0 -0
  27. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_shim.py +0 -0
  28. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_table_state.py +0 -0
  29. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/_table_macros.html +0 -0
  30. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/catalog.html +0 -0
  31. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/login.html +0 -0
  32. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/misses.html +0 -0
  33. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/_templates/ui/settings.html +0 -0
  34. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/curlwithcache.py +0 -0
  35. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/oras.py +0 -0
  36. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/static/bootstrap-icons.min.css +0 -0
  37. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/static/bootstrap.min.css +0 -0
  38. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/static/fonts/bootstrap-icons.woff +0 -0
  39. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/static/fonts/bootstrap-icons.woff2 +0 -0
  40. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/static/htmx.min.js +0 -0
  41. {withcache-0.12.0 → withcache-0.13.1}/src/withcache/wgetwithcache.py +0 -0
  42. {withcache-0.12.0 → withcache-0.13.1}/tests/test_differential.py +0 -0
  43. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_admin_forms.py +0 -0
  44. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_blob.py +0 -0
  45. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_catalog_api.py +0 -0
  46. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_scaffold.py +0 -0
  47. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_settings_persistence.py +0 -0
  48. {withcache-0.12.0 → withcache-0.13.1}/tests/test_fastapi_uvicorn_smoke.py +0 -0
  49. {withcache-0.12.0 → withcache-0.13.1}/tests/test_oras.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: withcache
3
- Version: 0.12.0
3
+ Version: 0.13.1
4
4
  Summary: Operator-curated, URL-keyed artifact cache for a small lab (CUDA/ROCm/DOCA/firmware)
5
5
  Project-URL: Homepage, https://github.com/safl/withcache
6
6
  Author-email: "Simon A. F. Lund" <safl@safl.dk>
@@ -68,14 +68,16 @@ disk.
68
68
 
69
69
  | Path | What it is |
70
70
  |-------------------------------|-------------------------------------------------------------|
71
- | `src/withcache/server.py` | The cache-host: blob store + miss table + **background download manager** + operator UI (Bootstrap 5 + Bootstrap Icons + HTMX) |
71
+ | `src/withcache/server.py` | The cache-host: blob store + miss table + **background download manager** + events log |
72
+ | `src/withcache/_app.py` | FastAPI app factory + operator UI wiring (Bootstrap 5 + Bootstrap Icons + HTMX) |
72
73
  | `src/withcache/_shim.py` | Shared shim core (find URL → probe → rewrite → exec) |
73
74
  | `src/withcache/curlwithcache.py` / `wgetwithcache.py` | The Python `curl` / `wget` shims |
74
75
  | `shim/shim.zig` | The native shim: one static binary, both tools via `argv[0]` |
75
76
  | `deploy/Containerfile`, `deploy/compose.yml` | Single Podman/Docker host deploy |
76
77
 
77
- The cache-host and the Python shims are **stdlib-only** (no third-party runtime
78
- deps); the native shim is a dependency-free static binary.
78
+ The cache-host runs on FastAPI + Jinja + Bootstrap (matching bty +
79
+ nbdmux for one trio-consistent chrome); the shims (Python and
80
+ native) are dependency-free so they drop into any environment.
79
81
 
80
82
  ## Install
81
83
 
@@ -245,11 +247,11 @@ Notes & limits (all degrade gracefully; worst case is "no caching, curl still wo
245
247
 
246
248
  `http://withcache-server:8081/` (Bootstrap 5 + Bootstrap Icons + HTMX, bundled
247
249
  offline; matches bty's chrome for a consistent trio) is a five-page dashboard:
248
- - **Cached** (landing): URL, size, **hits** (times served) and **misses** (times requested before it was cached), SHA-256, fetched-at, each with **Delete** to free space.
249
- - **Downloads**: live progress bars, `queued/running/completed/cancelled/failed`, **Cancel**, and **Clear finished**. Downloads run in a background worker pool, not in the request, so large pulls never block, modelled on [bty]'s job managers.
250
+ - **Dashboard** (landing): catalog + cache + activity summary, health checklist, and the last N audit events.
251
+ - **Catalog**: image catalog fetched from a nosi-style `catalog.toml` (URL configured on Settings > Catalog source). Add entries via the subnav's inline "Add ORAS" / "Add HTTPS" forms or "Fetch default" button, then **Download** each entry. Rows carry hits, size, download progress (live), and cached/failed pills.
250
252
  - **Misses**: URLs clients asked for that aren't downloaded yet. Each with **Fetch** (promotes to a catalog entry AND downloads it) and **Dismiss** (forget it).
251
- - **Catalog**: image catalog fetched from a nosi-style `catalog.toml` (URL configurable via `$WITHCACHE_CATALOG_URL` or the subnav Set&fetch input); pre-seed by URL via the "Add image from oras" or "Add HTTPS entry" forms, then **Download** each entry.
252
- - **Settings**: identity + storage paths + catalog source + logging (uvicorn level) + auth.
253
+ - **Events**: append-only audit log with a free-text filter and per-page pagination. Failure rows carry an ack button; the dashboard's Health tripwire flags unacknowledged failures.
254
+ - **Settings**: identity + storage paths + catalog source (editable) + logging (uvicorn level) + auth.
253
255
 
254
256
  ## Auth
255
257
 
@@ -45,14 +45,16 @@ disk.
45
45
 
46
46
  | Path | What it is |
47
47
  |-------------------------------|-------------------------------------------------------------|
48
- | `src/withcache/server.py` | The cache-host: blob store + miss table + **background download manager** + operator UI (Bootstrap 5 + Bootstrap Icons + HTMX) |
48
+ | `src/withcache/server.py` | The cache-host: blob store + miss table + **background download manager** + events log |
49
+ | `src/withcache/_app.py` | FastAPI app factory + operator UI wiring (Bootstrap 5 + Bootstrap Icons + HTMX) |
49
50
  | `src/withcache/_shim.py` | Shared shim core (find URL → probe → rewrite → exec) |
50
51
  | `src/withcache/curlwithcache.py` / `wgetwithcache.py` | The Python `curl` / `wget` shims |
51
52
  | `shim/shim.zig` | The native shim: one static binary, both tools via `argv[0]` |
52
53
  | `deploy/Containerfile`, `deploy/compose.yml` | Single Podman/Docker host deploy |
53
54
 
54
- The cache-host and the Python shims are **stdlib-only** (no third-party runtime
55
- deps); the native shim is a dependency-free static binary.
55
+ The cache-host runs on FastAPI + Jinja + Bootstrap (matching bty +
56
+ nbdmux for one trio-consistent chrome); the shims (Python and
57
+ native) are dependency-free so they drop into any environment.
56
58
 
57
59
  ## Install
58
60
 
@@ -222,11 +224,11 @@ Notes & limits (all degrade gracefully; worst case is "no caching, curl still wo
222
224
 
223
225
  `http://withcache-server:8081/` (Bootstrap 5 + Bootstrap Icons + HTMX, bundled
224
226
  offline; matches bty's chrome for a consistent trio) is a five-page dashboard:
225
- - **Cached** (landing): URL, size, **hits** (times served) and **misses** (times requested before it was cached), SHA-256, fetched-at, each with **Delete** to free space.
226
- - **Downloads**: live progress bars, `queued/running/completed/cancelled/failed`, **Cancel**, and **Clear finished**. Downloads run in a background worker pool, not in the request, so large pulls never block, modelled on [bty]'s job managers.
227
+ - **Dashboard** (landing): catalog + cache + activity summary, health checklist, and the last N audit events.
228
+ - **Catalog**: image catalog fetched from a nosi-style `catalog.toml` (URL configured on Settings > Catalog source). Add entries via the subnav's inline "Add ORAS" / "Add HTTPS" forms or "Fetch default" button, then **Download** each entry. Rows carry hits, size, download progress (live), and cached/failed pills.
227
229
  - **Misses**: URLs clients asked for that aren't downloaded yet. Each with **Fetch** (promotes to a catalog entry AND downloads it) and **Dismiss** (forget it).
228
- - **Catalog**: image catalog fetched from a nosi-style `catalog.toml` (URL configurable via `$WITHCACHE_CATALOG_URL` or the subnav Set&fetch input); pre-seed by URL via the "Add image from oras" or "Add HTTPS entry" forms, then **Download** each entry.
229
- - **Settings**: identity + storage paths + catalog source + logging (uvicorn level) + auth.
230
+ - **Events**: append-only audit log with a free-text filter and per-page pagination. Failure rows carry an ack button; the dashboard's Health tripwire flags unacknowledged failures.
231
+ - **Settings**: identity + storage paths + catalog source (editable) + logging (uvicorn level) + auth.
230
232
 
231
233
  ## Auth
232
234
 
@@ -2,7 +2,7 @@
2
2
  .name = .withcache_shim,
3
3
  // Zig requires a literal here; keep it in lockstep with the project's
4
4
  // single source (src/withcache/__init__.py) via `make bump` / `make version-check`.
5
- .version = "0.12.0",
5
+ .version = "0.13.1",
6
6
  .fingerprint = 0xd7d96c5ed212ccaa,
7
7
  .minimum_zig_version = "0.16.0",
8
8
  .paths = .{
@@ -22,6 +22,6 @@ inherit the framework floor.
22
22
  from . import oras
23
23
  from .client import blob_url, cache_base, is_cached, serve_url
24
24
 
25
- __version__ = "0.12.0"
25
+ __version__ = "0.13.1"
26
26
 
27
27
  __all__ = ["__version__", "blob_url", "cache_base", "is_cached", "oras", "serve_url"]
@@ -22,14 +22,17 @@ v0.11.0) promotes a missed URL to a catalog entry + enqueues the
22
22
  Download in one click.
23
23
 
24
24
  Streaming shape: FastAPI ``StreamingResponse`` with a generator
25
- that reads chunks from the on-disk blob at 64 KiB reads;
26
- :class:`StreamRegistry` ticks progress per chunk so the operator
27
- Downloads page shows in-flight transfers as they run.
25
+ that reads chunks from the on-disk blob at 64 KiB reads. The
26
+ Downloads-page real-time progress tracker was retired in v0.12.0
27
+ along with that page; per-entry download progress now folds into
28
+ the Catalog page's Downloaded column, populated from the
29
+ DownloadManager's job list directly.
28
30
  """
29
31
 
30
32
  from __future__ import annotations
31
33
 
32
34
  import base64
35
+ import contextlib
33
36
  import urllib.parse
34
37
  from collections.abc import Iterator
35
38
  from typing import Any
@@ -88,7 +91,27 @@ def _serve_blob(
88
91
  row = None
89
92
 
90
93
  if row is None:
91
- store.record_miss(url)
94
+ fresh = store.record_miss(url)
95
+ if fresh:
96
+ # First miss for this URL: emit an audit event so the
97
+ # operator sees it on /ui/events. Subsequent misses for
98
+ # the same URL bump the counter without re-emitting.
99
+ from . import _events_log
100
+
101
+ with contextlib.suppress(Exception):
102
+ with store.conn() as ev_conn:
103
+ _events_log.record(
104
+ ev_conn,
105
+ kind="blob.miss.recorded",
106
+ summary=f"First cache miss for {url}",
107
+ subject_kind="blob",
108
+ subject_id=url,
109
+ actor="client",
110
+ source_ip=_events_log.normalize_ip(
111
+ request.client.host if request.client else None
112
+ ),
113
+ )
114
+ ev_conn.commit()
92
115
  return PlainTextResponse(
93
116
  "cache miss: this URL hasn't been downloaded yet. "
94
117
  "Pick the matching catalog entry on /ui/catalog and hit Download.\n",