withcache 0.6.1__tar.gz → 0.6.2__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.6.1 → withcache-0.6.2}/PKG-INFO +1 -1
- {withcache-0.6.1 → withcache-0.6.2}/shim/build.zig.zon +1 -1
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/__init__.py +1 -1
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/server.py +45 -13
- {withcache-0.6.1 → withcache-0.6.2}/tests/test_withcache.py +40 -0
- {withcache-0.6.1 → withcache-0.6.2}/.gitignore +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/LICENSE +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/README.md +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/deploy/Containerfile +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/deploy/compose.yml +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/hatch_build.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/pyproject.toml +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/shim/build.zig +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/shim/shim.zig +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/_shim.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/client.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/curlwithcache.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/oras.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/static/htmx.min.js +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/static/pico.min.css +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/src/withcache/wgetwithcache.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/tests/test_differential.py +0 -0
- {withcache-0.6.1 → withcache-0.6.2}/tests/test_oras.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withcache
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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.6.
|
|
5
|
+
.version = "0.6.2",
|
|
6
6
|
.fingerprint = 0xd7d96c5ed212ccaa,
|
|
7
7
|
.minimum_zig_version = "0.16.0",
|
|
8
8
|
.paths = .{
|
|
@@ -17,6 +17,6 @@ All modules are stdlib-only and self-contained.
|
|
|
17
17
|
from . import oras
|
|
18
18
|
from .client import blob_url, cache_base, is_cached, serve_url
|
|
19
19
|
|
|
20
|
-
__version__ = "0.6.
|
|
20
|
+
__version__ = "0.6.2"
|
|
21
21
|
|
|
22
22
|
__all__ = ["__version__", "blob_url", "cache_base", "is_cached", "oras", "serve_url"]
|
|
@@ -757,7 +757,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
757
757
|
if not self.is_authed():
|
|
758
758
|
self.send_text(401, "login required\n")
|
|
759
759
|
else:
|
|
760
|
-
|
|
760
|
+
# The browser sends the current URL hash (the active
|
|
761
|
+
# tab id) via the ``X-Active-Tab`` request header on
|
|
762
|
+
# every 1 Hz refresh. Server bakes the matching
|
|
763
|
+
# ``.active-tab`` class directly into the rendered
|
|
764
|
+
# HTML so the htmx innerHTML swap doesn't visibly
|
|
765
|
+
# blink while the post-swap JS would otherwise
|
|
766
|
+
# re-apply the class. See render_dash().
|
|
767
|
+
active = (self.headers.get("X-Active-Tab") or "").strip()
|
|
768
|
+
self.send_html(200, self.render_dash(active_tab=active))
|
|
761
769
|
elif parsed.path == "/":
|
|
762
770
|
if not self.is_authed():
|
|
763
771
|
self.redirect("/ui/login")
|
|
@@ -1093,11 +1101,17 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1093
1101
|
isn't wiped by the 1 Hz refresh. ``isCollapsed`` is true when
|
|
1094
1102
|
there's no selection or the caret is a zero-width point; once
|
|
1095
1103
|
the operator releases / clears the selection polling resumes
|
|
1096
|
-
on the next 1 s tick.
|
|
1104
|
+
on the next 1 s tick.
|
|
1105
|
+
``hx-headers`` sends the current URL hash (the active tab id)
|
|
1106
|
+
as ``X-Active-Tab`` on every refresh so the server can bake
|
|
1107
|
+
``.active-tab`` into the rendered HTML -- eliminating the
|
|
1108
|
+
visible flicker the post-swap JS-applied class would otherwise
|
|
1109
|
+
cause when the new innerHTML lands without the class. -->
|
|
1097
1110
|
<div id="dash" hx-get="/admin/dash"
|
|
1098
1111
|
hx-trigger="load, every 1s [document.getSelection().isCollapsed]"
|
|
1099
|
-
hx-swap="innerHTML"
|
|
1100
|
-
|
|
1112
|
+
hx-swap="innerHTML"
|
|
1113
|
+
hx-headers='js:{{"X-Active-Tab": (location.hash || "").replace(/^#/, "")}}'>
|
|
1114
|
+
{self.render_dash(active_tab=(self.headers.get("X-Active-Tab") or "").strip())}
|
|
1101
1115
|
</div>
|
|
1102
1116
|
|
|
1103
1117
|
<!-- Tab activation. Applies an ``active-tab`` class to the
|
|
@@ -1137,7 +1151,21 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1137
1151
|
</script>
|
|
1138
1152
|
</main></body></html>"""
|
|
1139
1153
|
|
|
1140
|
-
def render_dash(self) -> str:
|
|
1154
|
+
def render_dash(self, active_tab: str = "") -> str:
|
|
1155
|
+
# Tab activation is baked into the rendered HTML so the htmx
|
|
1156
|
+
# innerHTML swap doesn't strip `.active-tab` between the
|
|
1157
|
+
# swap and the post-settle JS re-apply. The client sends
|
|
1158
|
+
# the current hash via the ``X-Active-Tab`` header on each
|
|
1159
|
+
# 1 Hz refresh (and on every operator click via the same
|
|
1160
|
+
# script that watches hashchange). Unknown / empty value
|
|
1161
|
+
# defaults to the first tab.
|
|
1162
|
+
_TAB_IDS = ("tab-cached", "tab-streams", "tab-downloads", "tab-misses")
|
|
1163
|
+
if active_tab not in _TAB_IDS:
|
|
1164
|
+
active_tab = _TAB_IDS[0]
|
|
1165
|
+
|
|
1166
|
+
def _active(tab_id: str) -> str:
|
|
1167
|
+
return " active-tab" if tab_id == active_tab else ""
|
|
1168
|
+
|
|
1141
1169
|
nblobs, nmisses = self.store.counts()
|
|
1142
1170
|
jobs = self.mgr.list()
|
|
1143
1171
|
misses = self.store.list_misses()
|
|
@@ -1271,13 +1299,17 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1271
1299
|
<p><small>{nblobs} cached ({used}){full} · {nmisses} pending miss(es)</small></p>
|
|
1272
1300
|
{tab_style}
|
|
1273
1301
|
<nav class="tabs"><ul>
|
|
1274
|
-
<li><a href="#tab-cached"
|
|
1275
|
-
|
|
1276
|
-
<li><a href="#tab-
|
|
1277
|
-
|
|
1302
|
+
<li><a href="#tab-cached" class="{_active("tab-cached").lstrip()}"
|
|
1303
|
+
>Cached ({nblobs})</a></li>
|
|
1304
|
+
<li><a href="#tab-streams" class="{_active("tab-streams").lstrip()}"
|
|
1305
|
+
>Streams ({nstreams})</a></li>
|
|
1306
|
+
<li><a href="#tab-downloads" class="{_active("tab-downloads").lstrip()}"
|
|
1307
|
+
>Downloads ({njobs})</a></li>
|
|
1308
|
+
<li><a href="#tab-misses" class="{_active("tab-misses").lstrip()}"
|
|
1309
|
+
>Misses ({nmisses})</a></li>
|
|
1278
1310
|
</ul></nav>
|
|
1279
1311
|
|
|
1280
|
-
<section id="tab-cached" class="tab">
|
|
1312
|
+
<section id="tab-cached" class="tab{_active("tab-cached")}">
|
|
1281
1313
|
<figure><table class="striped">
|
|
1282
1314
|
<thead><tr>
|
|
1283
1315
|
<th>URL</th><th>Size</th><th class="num">Hits</th><th class="num">Misses</th>
|
|
@@ -1287,7 +1319,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1287
1319
|
</table></figure>
|
|
1288
1320
|
</section>
|
|
1289
1321
|
|
|
1290
|
-
<section id="tab-streams" class="tab">
|
|
1322
|
+
<section id="tab-streams" class="tab{_active("tab-streams")}">
|
|
1291
1323
|
<figure><table class="striped">
|
|
1292
1324
|
<thead><tr>
|
|
1293
1325
|
<th>URL</th><th>Client</th><th>Progress</th><th>Age</th>
|
|
@@ -1296,7 +1328,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1296
1328
|
</table></figure>
|
|
1297
1329
|
</section>
|
|
1298
1330
|
|
|
1299
|
-
<section id="tab-downloads" class="tab">
|
|
1331
|
+
<section id="tab-downloads" class="tab{_active("tab-downloads")}">
|
|
1300
1332
|
<div class="row">
|
|
1301
1333
|
<small>Auto-fetch workers feeding the cache.</small>
|
|
1302
1334
|
<form hx-post="/admin/clear" hx-target="#dash" hx-swap="innerHTML" style="margin:0">
|
|
@@ -1310,7 +1342,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1310
1342
|
</table></figure>
|
|
1311
1343
|
</section>
|
|
1312
1344
|
|
|
1313
|
-
<section id="tab-misses" class="tab">
|
|
1345
|
+
<section id="tab-misses" class="tab{_active("tab-misses")}">
|
|
1314
1346
|
<figure><table class="striped">
|
|
1315
1347
|
<thead><tr>
|
|
1316
1348
|
<th>URL</th><th class="num">Misses</th><th>Last seen</th><th>Action</th>
|
|
@@ -1023,5 +1023,45 @@ class TestOrasTagRevalidation(unittest.TestCase):
|
|
|
1023
1023
|
self.assertFalse(server._oras_tag_moved(self.TAG, self.HEX_A, resolve=_boom))
|
|
1024
1024
|
|
|
1025
1025
|
|
|
1026
|
+
class TestDashActiveTabFromHeader(unittest.TestCase):
|
|
1027
|
+
"""``GET /admin/dash`` bakes ``.active-tab`` into the rendered HTML
|
|
1028
|
+
based on the ``X-Active-Tab`` request header. The browser sends the
|
|
1029
|
+
current URL hash on every refresh so the htmx innerHTML swap doesn't
|
|
1030
|
+
visibly blink while a post-swap JS would otherwise re-apply the class.
|
|
1031
|
+
"""
|
|
1032
|
+
|
|
1033
|
+
def setUp(self):
|
|
1034
|
+
self.httpd, self.store = _start_withcache()
|
|
1035
|
+
self.base = f"http://127.0.0.1:{self.httpd.server_address[1]}"
|
|
1036
|
+
|
|
1037
|
+
def tearDown(self):
|
|
1038
|
+
self.httpd.shutdown()
|
|
1039
|
+
self.httpd.server_close()
|
|
1040
|
+
|
|
1041
|
+
def _dash(self, active_tab=None):
|
|
1042
|
+
req = urllib.request.Request(self.base + "/admin/dash")
|
|
1043
|
+
if active_tab is not None:
|
|
1044
|
+
req.add_header("X-Active-Tab", active_tab)
|
|
1045
|
+
return urllib.request.urlopen(req).read().decode("utf-8")
|
|
1046
|
+
|
|
1047
|
+
def test_default_tab_when_header_missing(self):
|
|
1048
|
+
body = self._dash()
|
|
1049
|
+
# tab-cached is the first tab and the no-header default
|
|
1050
|
+
self.assertIn('<section id="tab-cached" class="tab active-tab">', body)
|
|
1051
|
+
self.assertIn('<section id="tab-streams" class="tab">', body)
|
|
1052
|
+
|
|
1053
|
+
def test_header_picks_the_active_tab(self):
|
|
1054
|
+
body = self._dash("tab-misses")
|
|
1055
|
+
self.assertIn('<section id="tab-misses" class="tab active-tab">', body)
|
|
1056
|
+
self.assertIn('<section id="tab-cached" class="tab">', body)
|
|
1057
|
+
|
|
1058
|
+
def test_unknown_header_value_falls_back_to_first(self):
|
|
1059
|
+
"""A hand-crafted X-Active-Tab with a bogus value must not echo
|
|
1060
|
+
into the HTML; the renderer falls back to the first tab."""
|
|
1061
|
+
body = self._dash("tab-totally-not-real")
|
|
1062
|
+
self.assertIn('<section id="tab-cached" class="tab active-tab">', body)
|
|
1063
|
+
self.assertNotIn("tab-totally-not-real", body)
|
|
1064
|
+
|
|
1065
|
+
|
|
1026
1066
|
if __name__ == "__main__":
|
|
1027
1067
|
unittest.main(verbosity=2)
|
|
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
|