withcache 0.13.2__tar.gz → 0.13.3__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.
- {withcache-0.13.2 → withcache-0.13.3}/PKG-INFO +1 -1
- {withcache-0.13.2 → withcache-0.13.3}/shim/build.zig.zon +1 -1
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/__init__.py +1 -1
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_api.py +22 -2
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_catalog_api.py +57 -0
- {withcache-0.13.2 → withcache-0.13.3}/.gitignore +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/LICENSE +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/README.md +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/deploy/Containerfile +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/deploy/compose.local-build.yml +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/deploy/compose.yml +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/deploy/envvars.example +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/hatch_build.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/pyproject.toml +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/shim/build.zig +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/shim/shim.zig +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_app.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_events_log.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_settings_store.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_shim.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_table_state.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/_layout.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/_table_macros.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/catalog.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/dashboard.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/events.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/login.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/misses.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/_templates/ui/settings.html +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/client.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/curlwithcache.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/oras.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/server.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/static/bootstrap-icons.min.css +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/static/bootstrap.min.css +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/static/fonts/bootstrap-icons.woff +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/static/fonts/bootstrap-icons.woff2 +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/static/htmx.min.js +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/src/withcache/wgetwithcache.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_differential.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_admin_forms.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_blob.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_events.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_scaffold.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_settings_persistence.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_ui_pages.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_fastapi_uvicorn_smoke.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_oras.py +0 -0
- {withcache-0.13.2 → withcache-0.13.3}/tests/test_withcache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withcache
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.3
|
|
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>
|
|
@@ -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.13.
|
|
5
|
+
.version = "0.13.3",
|
|
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.13.
|
|
25
|
+
__version__ = "0.13.3"
|
|
26
26
|
|
|
27
27
|
__all__ = ["__version__", "blob_url", "cache_base", "is_cached", "oras", "serve_url"]
|
|
@@ -234,9 +234,29 @@ def register_api_routes(app: FastAPI) -> None:
|
|
|
234
234
|
entries_out: list[dict[str, Any]] = []
|
|
235
235
|
for e in cs.entries:
|
|
236
236
|
fetch_url = e.get("resolved_src") or e.get("src") or ""
|
|
237
|
-
if not fetch_url
|
|
237
|
+
if not fetch_url:
|
|
238
238
|
continue
|
|
239
|
-
|
|
239
|
+
blob = store.get_blob(fetch_url)
|
|
240
|
+
if blob is None:
|
|
241
|
+
continue
|
|
242
|
+
out = dict(e)
|
|
243
|
+
# Enrich with the downloaded blob's authoritative sha256 +
|
|
244
|
+
# size. The catalog TOML may or may not carry these (nosi's
|
|
245
|
+
# gen_catalog omits them at emit time because the blob
|
|
246
|
+
# digest isn't known until the artifact is packed); the
|
|
247
|
+
# blob store computed both during the actual download so
|
|
248
|
+
# the /catalog response is the right place to surface
|
|
249
|
+
# them. Bty consumes ``sha256`` to gate machine binds --
|
|
250
|
+
# without this enrichment a downloaded-but-unhashed entry
|
|
251
|
+
# can never satisfy bty's ``^[0-9a-f]{64}$`` ref check
|
|
252
|
+
# and the whole ramboot flow deadlocks.
|
|
253
|
+
blob_sha = blob["sha256"]
|
|
254
|
+
if isinstance(blob_sha, str) and blob_sha and not out.get("sha256"):
|
|
255
|
+
out["sha256"] = blob_sha
|
|
256
|
+
blob_size = blob["size"]
|
|
257
|
+
if isinstance(blob_size, int) and blob_size > 0 and not out.get("size_bytes"):
|
|
258
|
+
out["size_bytes"] = blob_size
|
|
259
|
+
entries_out.append(out)
|
|
240
260
|
return JSONResponse(
|
|
241
261
|
{
|
|
242
262
|
"url": cs.url,
|
|
@@ -122,6 +122,63 @@ class ListCatalogTests(_CatalogApiBase):
|
|
|
122
122
|
self.assertEqual(entries[0]["sha256"], "a" * 64)
|
|
123
123
|
self.assertEqual(entries[0]["description"], "downloaded")
|
|
124
124
|
|
|
125
|
+
def test_list_enriches_sha256_and_size_from_blob_store(self) -> None:
|
|
126
|
+
"""A catalog TOML entry whose bytes are downloaded but that
|
|
127
|
+
omits ``sha256`` / ``size_bytes`` (nosi's gen_catalog does
|
|
128
|
+
exactly this: the OCI blob digest and content-length aren't
|
|
129
|
+
knowable at TOML-emit time) must still surface those two
|
|
130
|
+
fields in ``GET /catalog`` -- withcache computed them during
|
|
131
|
+
the download and stashed them in the blob row.
|
|
132
|
+
|
|
133
|
+
Bty's ``bty_image_ref`` regex is ``^[0-9a-f]{64}$``; without
|
|
134
|
+
this enrichment a downloaded-but-not-hashed entry can never
|
|
135
|
+
satisfy the check and the ramboot machine bind 422s. Pin the
|
|
136
|
+
wire so a future regression fails the test suite instead of
|
|
137
|
+
the deploy.
|
|
138
|
+
"""
|
|
139
|
+
self.catalog.entries = [
|
|
140
|
+
{
|
|
141
|
+
"name": "no-sha-in-toml",
|
|
142
|
+
"src": "https://example/toml-quiet.img.gz",
|
|
143
|
+
"format": "img.gz",
|
|
144
|
+
"resolved_src": "https://example/toml-quiet.img.gz",
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
self._seed_store_row("https://example/toml-quiet.img.gz", "b" * 64)
|
|
148
|
+
|
|
149
|
+
r = self.client.get("/catalog")
|
|
150
|
+
self.assertEqual(r.status_code, 200)
|
|
151
|
+
entries = r.json()["entries"]
|
|
152
|
+
self.assertEqual(len(entries), 1)
|
|
153
|
+
self.assertEqual(entries[0]["sha256"], "b" * 64)
|
|
154
|
+
# ``_seed_store_row`` writes 1024-byte blob rows.
|
|
155
|
+
self.assertEqual(entries[0]["size_bytes"], 1024)
|
|
156
|
+
|
|
157
|
+
def test_list_does_not_overwrite_toml_provided_sha256(self) -> None:
|
|
158
|
+
"""When the catalog TOML already carries ``sha256`` (e.g. a
|
|
159
|
+
future gen_catalog that emits it), the enrichment path must
|
|
160
|
+
not clobber the operator-declared value with the blob's
|
|
161
|
+
computed one. The TOML value wins. Same for ``size_bytes``.
|
|
162
|
+
"""
|
|
163
|
+
toml_sha = "c" * 64
|
|
164
|
+
blob_sha = "d" * 64
|
|
165
|
+
self.catalog.entries = [
|
|
166
|
+
{
|
|
167
|
+
"name": "declared-sha",
|
|
168
|
+
"src": "https://example/declared.img.gz",
|
|
169
|
+
"format": "img.gz",
|
|
170
|
+
"sha256": toml_sha,
|
|
171
|
+
"size_bytes": 42,
|
|
172
|
+
"resolved_src": "https://example/declared.img.gz",
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
self._seed_store_row("https://example/declared.img.gz", blob_sha)
|
|
176
|
+
|
|
177
|
+
r = self.client.get("/catalog")
|
|
178
|
+
entries = r.json()["entries"]
|
|
179
|
+
self.assertEqual(entries[0]["sha256"], toml_sha)
|
|
180
|
+
self.assertEqual(entries[0]["size_bytes"], 42)
|
|
181
|
+
|
|
125
182
|
def _seed_store_row(self, url: str, sha256: str) -> None:
|
|
126
183
|
"""Insert a blobs row for ``url`` so ``store.get_blob`` returns
|
|
127
184
|
it; skips the actual file write because ``list_catalog`` only
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|