withcache 0.5.0__tar.gz → 0.5.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.5.0 → withcache-0.5.2}/PKG-INFO +1 -1
- {withcache-0.5.0 → withcache-0.5.2}/shim/build.zig.zon +1 -1
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/__init__.py +1 -1
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/server.py +87 -33
- {withcache-0.5.0 → withcache-0.5.2}/.gitignore +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/LICENSE +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/README.md +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/deploy/Containerfile +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/deploy/compose.yml +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/hatch_build.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/pyproject.toml +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/shim/build.zig +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/shim/shim.zig +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/_shim.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/client.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/curlwithcache.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/static/htmx.min.js +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/static/pico.min.css +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/src/withcache/wgetwithcache.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/tests/test_differential.py +0 -0
- {withcache-0.5.0 → withcache-0.5.2}/tests/test_withcache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withcache
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.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.5.
|
|
5
|
+
.version = "0.5.2",
|
|
6
6
|
.fingerprint = 0xd7d96c5ed212ccaa,
|
|
7
7
|
.minimum_zig_version = "0.16.0",
|
|
8
8
|
.paths = .{
|
|
@@ -982,7 +982,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
982
982
|
<body><main class="container">
|
|
983
983
|
<nav>
|
|
984
984
|
<ul><li>
|
|
985
|
-
<strong>withcache</strong>
|
|
985
|
+
<strong>withcache</strong>
|
|
986
986
|
<small class="mono">v{html.escape(__version__)}</small>
|
|
987
987
|
</li></ul>
|
|
988
988
|
<ul>
|
|
@@ -996,7 +996,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
996
996
|
hx-indicator="#spin" hx-on::after-request="this.reset()">
|
|
997
997
|
<fieldset role="group">
|
|
998
998
|
<input type="url" name="url" placeholder="https://origin/path/artifact.tar.gz" required>
|
|
999
|
-
|
|
999
|
+
<!-- white-space: nowrap so the label "Fetch & store" stays on
|
|
1000
|
+
one line; default flex-grow inside fieldset[role=group]
|
|
1001
|
+
shrinks the button to content width, which on a narrow
|
|
1002
|
+
viewport wrapped the ampersand to a second line. -->
|
|
1003
|
+
<button type="submit" style="white-space: nowrap;">Fetch & store</button>
|
|
1000
1004
|
</fieldset>
|
|
1001
1005
|
</form>
|
|
1002
1006
|
|
|
@@ -1011,6 +1015,42 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1011
1015
|
hx-swap="innerHTML">
|
|
1012
1016
|
{self.render_dash()}
|
|
1013
1017
|
</div>
|
|
1018
|
+
|
|
1019
|
+
<!-- Tab activation. Applies an ``active-tab`` class to the
|
|
1020
|
+
``section.tab`` whose id matches the URL hash (defaulting to
|
|
1021
|
+
the first section when no hash is set) and to the
|
|
1022
|
+
corresponding ``nav.tabs a``. Runs on initial load, on every
|
|
1023
|
+
click into a tab link (so the operator gets immediate
|
|
1024
|
+
feedback before the next htmx tick), and on every
|
|
1025
|
+
``htmx:afterSettle`` so the class survives the 1 Hz
|
|
1026
|
+
innerHTML replacement of ``#dash``. Without this the
|
|
1027
|
+
previous ``:target``-based CSS would snap the operator back
|
|
1028
|
+
to the first tab within a second of any click. -->
|
|
1029
|
+
<script>
|
|
1030
|
+
(function () {{
|
|
1031
|
+
function applyActiveTab() {{
|
|
1032
|
+
var hash = (window.location.hash || '').replace(/^#/, '');
|
|
1033
|
+
var sections = document.querySelectorAll('#dash section.tab');
|
|
1034
|
+
if (!sections.length) return;
|
|
1035
|
+
var ids = Array.prototype.map.call(sections, function (s) {{ return s.id; }});
|
|
1036
|
+
if (ids.indexOf(hash) === -1) hash = ids[0];
|
|
1037
|
+
sections.forEach(function (s) {{
|
|
1038
|
+
s.classList.toggle('active-tab', s.id === hash);
|
|
1039
|
+
}});
|
|
1040
|
+
document.querySelectorAll('#dash nav.tabs a').forEach(function (a) {{
|
|
1041
|
+
var target = (a.getAttribute('href') || '').replace(/^#/, '');
|
|
1042
|
+
a.classList.toggle('active-tab', target === hash);
|
|
1043
|
+
}});
|
|
1044
|
+
}}
|
|
1045
|
+
window.addEventListener('hashchange', applyActiveTab);
|
|
1046
|
+
document.body.addEventListener('htmx:afterSettle', applyActiveTab);
|
|
1047
|
+
document.addEventListener('click', function (ev) {{
|
|
1048
|
+
var a = ev.target.closest && ev.target.closest('#dash nav.tabs a');
|
|
1049
|
+
if (a) setTimeout(applyActiveTab, 0);
|
|
1050
|
+
}});
|
|
1051
|
+
applyActiveTab();
|
|
1052
|
+
}})();
|
|
1053
|
+
</script>
|
|
1014
1054
|
</main></body></html>"""
|
|
1015
1055
|
|
|
1016
1056
|
def render_dash(self) -> str:
|
|
@@ -1024,13 +1064,20 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1024
1064
|
used += f" / {human_size(self.store.max_bytes)}"
|
|
1025
1065
|
full = "" if self.store.has_capacity() else " · <strong>cache full</strong>"
|
|
1026
1066
|
|
|
1027
|
-
# Tabs are
|
|
1028
|
-
# section
|
|
1029
|
-
#
|
|
1030
|
-
#
|
|
1031
|
-
#
|
|
1032
|
-
#
|
|
1033
|
-
#
|
|
1067
|
+
# Tabs are driven by an ``active-tab`` class applied to one
|
|
1068
|
+
# ``section.tab`` (and matching ``nav.tabs a``). A tiny script
|
|
1069
|
+
# at the bottom of the dash watches the URL hash, the htmx
|
|
1070
|
+
# post-swap event, and click events on the tab links so the
|
|
1071
|
+
# class survives every 1 Hz innerHTML replacement.
|
|
1072
|
+
#
|
|
1073
|
+
# An earlier pure-CSS attempt used ``:target`` + ``:has()``.
|
|
1074
|
+
# That works on a static page, but when htmx swaps the
|
|
1075
|
+
# ``#dash`` innerHTML each second the freshly-inserted
|
|
1076
|
+
# ``section.tab`` elements do not always get re-matched by
|
|
1077
|
+
# ``:target`` (the browser keeps the URL hash but the
|
|
1078
|
+
# newly-inserted node is not the one ``:target`` resolved to
|
|
1079
|
+
# at hash-change time). The visible symptom was the tab
|
|
1080
|
+
# snapping back to Streams within a second of every click.
|
|
1034
1081
|
tab_style = """
|
|
1035
1082
|
<style>
|
|
1036
1083
|
nav.tabs { margin: 1rem 0 .25rem; border-bottom: 1px solid var(--pico-muted-border-color); }
|
|
@@ -1042,22 +1089,13 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1042
1089
|
margin-bottom: -1px; font-size: .9rem;
|
|
1043
1090
|
}
|
|
1044
1091
|
nav.tabs a:hover { color: var(--pico-color); }
|
|
1045
|
-
|
|
1046
|
-
section.tab:target { display: block; }
|
|
1047
|
-
body:not(:has(section.tab:target)) section.tab#tab-streams { display: block; }
|
|
1048
|
-
body:has(#tab-streams:target) nav.tabs a[href="#tab-streams"],
|
|
1049
|
-
body:has(#tab-downloads:target) nav.tabs a[href="#tab-downloads"],
|
|
1050
|
-
body:has(#tab-misses:target) nav.tabs a[href="#tab-misses"],
|
|
1051
|
-
body:has(#tab-cached:target) nav.tabs a[href="#tab-cached"] {
|
|
1052
|
-
color: var(--pico-color);
|
|
1053
|
-
border-bottom-color: var(--pico-primary, #0172ad);
|
|
1054
|
-
font-weight: 600;
|
|
1055
|
-
}
|
|
1056
|
-
body:not(:has(section.tab:target)) nav.tabs a[href="#tab-streams"] {
|
|
1092
|
+
nav.tabs a.active-tab {
|
|
1057
1093
|
color: var(--pico-color);
|
|
1058
1094
|
border-bottom-color: var(--pico-primary, #0172ad);
|
|
1059
1095
|
font-weight: 600;
|
|
1060
1096
|
}
|
|
1097
|
+
section.tab { display: none; padding-top: .75rem; }
|
|
1098
|
+
section.tab.active-tab { display: block; }
|
|
1061
1099
|
</style>
|
|
1062
1100
|
"""
|
|
1063
1101
|
|
|
@@ -1102,10 +1140,26 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1102
1140
|
or '<tr><td colspan="4"><em>No misses recorded.</em></td></tr>'
|
|
1103
1141
|
)
|
|
1104
1142
|
|
|
1143
|
+
# Build the per-row /b/ serve URL once; the cell wraps the
|
|
1144
|
+
# origin string in a link that GETs the cached bytes when
|
|
1145
|
+
# the operator clicks. Same path-encoded form the shim
|
|
1146
|
+
# generates so curl / wget / a browser save all end up
|
|
1147
|
+
# writing the correct output filename.
|
|
1148
|
+
def _serve_url_for(row: sqlite3.Row) -> str:
|
|
1149
|
+
origin = row["url"]
|
|
1150
|
+
token = base64.urlsafe_b64encode(origin.encode("utf-8")).decode("ascii").rstrip("=")
|
|
1151
|
+
# last path segment of the origin, fallback "download" so a
|
|
1152
|
+
# URL ending in / still serves with a usable filename.
|
|
1153
|
+
name = urllib.parse.urlsplit(origin).path.rsplit("/", 1)[-1] or "download"
|
|
1154
|
+
return f"/b/{token}/{urllib.parse.quote(name)}"
|
|
1155
|
+
|
|
1105
1156
|
blob_rows = (
|
|
1106
1157
|
"".join(
|
|
1107
1158
|
f"""<tr>
|
|
1108
|
-
<td class="url">
|
|
1159
|
+
<td class="url">
|
|
1160
|
+
<a href="{_serve_url_for(b)}"
|
|
1161
|
+
title="Download the cached object">{html.escape(b["url"])}</a>
|
|
1162
|
+
</td>
|
|
1109
1163
|
<td>{human_size(b["size"])}</td>
|
|
1110
1164
|
<td class="num">{b["hits"]}</td>
|
|
1111
1165
|
<td class="num">{b["misses"]}</td>
|
|
@@ -1133,12 +1187,22 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1133
1187
|
<p><small>{nblobs} cached ({used}){full} · {nmisses} pending miss(es)</small></p>
|
|
1134
1188
|
{tab_style}
|
|
1135
1189
|
<nav class="tabs"><ul>
|
|
1190
|
+
<li><a href="#tab-cached">Cached ({nblobs})</a></li>
|
|
1136
1191
|
<li><a href="#tab-streams">Streams ({nstreams})</a></li>
|
|
1137
1192
|
<li><a href="#tab-downloads">Downloads ({njobs})</a></li>
|
|
1138
1193
|
<li><a href="#tab-misses">Misses ({nmisses})</a></li>
|
|
1139
|
-
<li><a href="#tab-cached">Cached ({nblobs})</a></li>
|
|
1140
1194
|
</ul></nav>
|
|
1141
1195
|
|
|
1196
|
+
<section id="tab-cached" class="tab">
|
|
1197
|
+
<figure><table class="striped">
|
|
1198
|
+
<thead><tr>
|
|
1199
|
+
<th>URL</th><th>Size</th><th class="num">Hits</th><th class="num">Misses</th>
|
|
1200
|
+
<th>SHA-256</th><th>Fetched</th><th>Action</th>
|
|
1201
|
+
</tr></thead>
|
|
1202
|
+
<tbody>{blob_rows}</tbody>
|
|
1203
|
+
</table></figure>
|
|
1204
|
+
</section>
|
|
1205
|
+
|
|
1142
1206
|
<section id="tab-streams" class="tab">
|
|
1143
1207
|
<figure><table class="striped">
|
|
1144
1208
|
<thead><tr>
|
|
@@ -1169,16 +1233,6 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1169
1233
|
</tr></thead>
|
|
1170
1234
|
<tbody>{miss_rows}</tbody>
|
|
1171
1235
|
</table></figure>
|
|
1172
|
-
</section>
|
|
1173
|
-
|
|
1174
|
-
<section id="tab-cached" class="tab">
|
|
1175
|
-
<figure><table class="striped">
|
|
1176
|
-
<thead><tr>
|
|
1177
|
-
<th>URL</th><th>Size</th><th class="num">Hits</th><th class="num">Misses</th>
|
|
1178
|
-
<th>SHA-256</th><th>Fetched</th><th>Action</th>
|
|
1179
|
-
</tr></thead>
|
|
1180
|
-
<tbody>{blob_rows}</tbody>
|
|
1181
|
-
</table></figure>
|
|
1182
1236
|
</section>"""
|
|
1183
1237
|
|
|
1184
1238
|
def _stream_progress_cell(self, s: Stream) -> str:
|
|
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
|