ASGI static file server — like Whitenoise, but native ASGI with a Rust hot path
Project description
whitesnout
WhiteSnout is an ASGI static file server for Python — like Whitenoise, but built for ASGI frameworks (FastAPI, Starlette, Django, etc.). It serves static files with minimal memory overhead, streaming content in chunks and leveraging pre-compressed assets. A Rust extension (PyO3) accelerates the hot path transparently.
Quick start
from whitesnout import WhiteSnout
# Standalone static file server
app = WhiteSnout(directory="./static")
Serve with any ASGI server:
$ uvicorn myapp:app
With FastAPI
from fastapi import FastAPI
from whitesnout import WhiteSnout
api = FastAPI()
@api.get("/api")
def read_root():
return {"hello": "world"}
app = WhiteSnout(api, directory="static")
With Django
# asgi.py
import os
from django.core.asgi import get_asgi_application
from whitesnout import WhiteSnout
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django_app = get_asgi_application()
application = WhiteSnout(django_app, directory="static")
Installation
$ uv add whitesnout
Or with pip:
$ pip install whitesnout
Requires Python ≥ 3.10.
No required runtime dependencies. Optional extras:
$ uv add 'whitesnout[compress]' # Brotli for the compress CLI
$ uv add 'whitesnout[streaming]' # aiofiles for non-blocking large-file streaming
What streaming does
Files ≤ sync_threshold (default 64 KB) are read in a single asyncio.to_thread call — no extra dependency needed. Covers ~95% of typical static assets (HTML, CSS, JS bundles, icons, fonts).
Files larger than sync_threshold are streamed in chunk_size (default 64 KB) pieces. Two backends are available:
- Without
streamingextra (default): chunks are read via blockingopen()inside the running event-loop task. Fine for low-concurrency workloads, but a slow disk read can stall other requests on the same worker. - With
streamingextra: chunks are read viaaiofiles, which dispatches each read to a thread pool. Other requests keep progressing while the slow read happens.
Install [streaming] when:
- Serving files routinely larger than 64 KB (large JS bundles, videos, downloads, datasets)
- Running on slow / network-mounted disks (NFS, SMB, EBS gp2)
- High concurrency: many simultaneous large-file downloads
Skip it when:
- Only serving small assets (typical SPA build output, icons, fonts) — fast path already covers everything
- Raising
sync_thresholdto fit your largest expected file (e.g.sync_threshold=2_000_000for ≤ 2 MB) - Memory-constrained environments —
aiofilesadds ~500 KB resident
Pre-compressing assets
Generate .gz and .br variants for all files in a directory:
$ python -m whitesnout compress static/
Compressed: 42 gzip, 42 brotli
This is a build-time step — at runtime WhiteSnout serves the pre-compressed files directly with zero CPU overhead.
Configuration
All options can be passed as keyword arguments to WhiteSnout:
| Option | Default | Description |
|---|---|---|
app |
None |
Inner ASGI app to fall through to when a file is not found |
directory |
"static" |
Root directory to serve files from |
index_file |
"index.html" |
File to serve for directory requests |
cache_max_age |
3600 |
max-age in Cache-Control for regular files |
immutable_max_age |
31536000 |
max-age for hashed files (1 year) |
immutable_pattern |
r"\.[a-f0-9]{8,}\." |
Regex to detect hashed filenames (e.g. styles.a1b2c3d4.css) |
chunk_size |
65536 |
Stream chunk size in bytes (64 KB) |
sync_threshold |
65536 |
Read files ≤ this size in a single thread call (skips async generator) |
charset |
"utf-8" |
Charset for text-based content types |
brotli |
True |
Look for .br pre-compressed variants |
gzip |
True |
Look for .gz pre-compressed variants |
max_cache_size |
64 |
Max entries in the native StatCache (stores size, mtime_ns tuples) |
cors |
False |
Shortcut: add Access-Control-Allow-Origin: * (legacy; prefer cors_allow_origins) |
cors_allow_origins |
None |
List of allowed origins (e.g. ["https://app.com"] or ["*"]); CORS preflight is handled accordingly; non-wildcard responses include Vary: Origin |
security_headers |
True |
Add X-Content-Type-Options: nosniff and X-Frame-Options: DENY |
hsts |
None |
Strict-Transport-Security header value (e.g. "max-age=31536000; includeSubDomains") |
csp |
None |
Content-Security-Policy header value |
referrer_policy |
None |
Referrer-Policy header value (e.g. "no-referrer") |
permissions_policy |
None |
Permissions-Policy header value |
mime_types |
None |
Dict of extension → MIME type overrides (e.g. {".epub": "application/epub+zip"}) |
skip_compress_extensions |
{".jpg", ".png", ".gif", ...} |
Extensions excluded from on-the-fly compression |
manifest_path |
None |
Path to a staticfiles.json / Webpack / Vite manifest; listed files are served as immutable |
autocompress |
False |
On-the-fly gzip/brotli compression with in-memory LRU cache (per-process) |
autocompress_max_size |
1_048_576 |
Skip on-the-fly compression for files larger than this (bytes) |
on_request |
None |
Callable (sync or async) invoked after every served request with a dict of metadata |
autorefresh |
False |
Clear path and stat caches on every request (dev mode); auto-enabled by the Django wrapper when settings.DEBUG is True |
path_resolver |
None |
Optional callable (path: str) -> Path | None called when the standard resolution misses; used by the Django integration to plug staticfiles.finders |
error_responses |
{404: b"Not Found", 405: b"Method Not Allowed", 416: b"Range Not Satisfiable"} |
Customize response bodies for error status codes; {} for empty bodies |
log_level |
"INFO" |
Logging level ("DEBUG", "INFO", "WARNING", etc.); None disables logging entirely |
app = WhiteSnout(
app=my_asgi_app,
directory="public",
cache_max_age=86400,
immutable_max_age=31536000,
chunk_size=131072,
)
Features
- ASGI-native — middleware or standalone, works with any ASGI framework
- Streaming — files are served in configurable chunks (64 KB by default), never loaded entirely into memory
- Zero-copy pre-compression — serves pre-existing
.gzand.brfiles with automaticAccept-Encodingnegotiation; brotli preferred over gzip - On-the-fly compression — opt-in
autocompress=Truecompresses uncompressed assets on first request and caches the result in memory - Powerful caching —
ETag,Last-Modified,Cache-Control,Vary: Accept-Encodingheaders; 304 Not Modified responses for conditional requests - Immutable cache — detects hashed filenames via regex or a Django/Webpack/Vite manifest, then applies
Cache-Control: public, immutable, max-age=31536000 - Index files —
index.htmlserved automatically for directory paths - Clean URLs —
/dirredirects to/dir/(301 Moved Permanently) - Path traversal protection — resolved paths are verified to stay within the root directory
- Low overhead — LRU cache for file stats reduces
stat()syscalls; no required runtime dependencies - MIME types — content-type detection for 100+ file extensions with
mime_types={...}overrides - Hardened security headers —
HSTS,CSP,Referrer-Policy,Permissions-Policycontrollable via config - CORS allowlist —
cors_allow_origins=[...]matches per-requestOriginand emitsVary: Origin - Observability hook —
on_request=callableis invoked after every served request with method/path/status/length/elapsed metadata - Compress CLI —
python -m whitesnout compress <directory>generates pre-compressed.gzand.brfiles as a build step - Rust extension —
whitesnout._rsspeeds up the LRU cache, stat cache, response building, header parsing, and date comparison transparently; pure Python fallback when unavailable - Native StatCache — stores
(size, mtime_ns)as a Rust struct instead of Pythonos.stat_result, reducing GC pressure and memory overhead - Django integration —
whitesnout.django.get_static_application()wires Django ASGI + STATIC_ROOT + manifest in one call - Configurable error bodies — customize 404/405/416 responses, or set
{}for empty bodies - Silencable logging — set
log_level=Noneto disable all logging output - Multiple directories — serve from additional directories and individual files via
add_directory()/add_files()with runtime registration and removal - Multi-platform wheels — pre-built for Linux (x86_64, arm64), macOS (x86_64, arm64), and Windows (amd64)
Error responses
By default, whitesnout returns 404 Not Found, 405 Method Not Allowed, and 416 Range Not Satisfiable with matching text bodies. Customize them via error_responses:
app = WhiteSnout(
directory="static",
error_responses={404: b"File not found"},
)
# Empty bodies for all errors
app = WhiteSnout(directory="static", error_responses={})
When an inner ASGI app is configured, 404 and 405 errors are delegated to it instead.
Logging
Request logging is enabled by default at INFO level. Control it via log_level:
# Default INFO logging (method, path, status, bytes, duration)
app = WhiteSnout(directory="static")
# Custom level
app = WhiteSnout(directory="static", log_level="WARNING")
# Completely silent
app = WhiteSnout(directory="static", log_level=None)
Multiple directories & extra files
Use add_directory() and add_files() to serve content from multiple locations:
from fastapi import FastAPI
from whitesnout import WhiteSnout
app = FastAPI()
@app.get("/api/health")
def health():
return {"status": "ok"}
ws = WhiteSnout(app, directory="frontend/dist")
# Extra directories
ws.add_directory("/media/uploads", "/mnt/storage/uploads")
ws.add_directory("/avatars", "/var/avatars")
# Individual files (take precedence over directories)
ws.add_files({
"/.well-known/security.txt": "security/security.txt",
"/favicon.ico": "branding/favicon.ico",
})
# Dynamic registrations at runtime
ws.remove_files("/favicon.ico")
ws.remove_directory("/avatars")
Resolution order: add_files() → add_directory() → main directory → inner ASGI app.
Hardened security headers
Enable strict transport security, content security policy, referrer policy, and permissions policy via constructor kwargs. They are appended to every static response:
app = WhiteSnout(
directory="static",
hsts="max-age=31536000; includeSubDomains; preload",
csp="default-src 'self'; img-src 'self' data:",
referrer_policy="strict-origin-when-cross-origin",
permissions_policy="geolocation=(), camera=()",
)
X-Content-Type-Options: nosniff and X-Frame-Options: DENY are still controlled by security_headers=True/False (on by default).
CORS allowlist
# Whitelist specific origins (preferred). Each match returns Vary: Origin.
app = WhiteSnout(
directory="static",
cors_allow_origins=["https://app.example.com", "https://admin.example.com"],
)
# Wildcard (legacy behavior; same as cors=True)
app = WhiteSnout(directory="static", cors_allow_origins=["*"])
OPTIONS preflights are answered with 204 when the request origin matches. Unmatched origins return 405 (or fall through to your inner app if configured).
Custom MIME types
Override or extend the built-in MIME table per instance:
app = WhiteSnout(
directory="static",
mime_types={
".epub": "application/epub+zip",
".webmanifest": "application/manifest+json",
},
)
The lookup runs after Rust derives the default content-type, so overrides win without sacrificing the hot path for everything else.
Manifest-based immutable caching
Whitesnout reads three manifest formats out of the box. Listed files get Cache-Control: public, immutable, max-age=31536000 regardless of the immutable_pattern regex.
app = WhiteSnout(
directory="static",
manifest_path="static/staticfiles.json", # Django, Webpack, or Vite
)
Supported shapes:
- Django
ManifestStaticFilesStorage—{"paths": {"app.css": "app.abc123.css"}} - Webpack —
{"app.js": "app.abc123.js"} - Vite —
{"src/main.ts": {"file": "assets/main.abc123.js"}}
This handles modern bundlers' hashed filenames that don't match the default regex (e.g. app-Abc123.js).
On-the-fly compression
Useful when running without a build-time compression step, or for dynamic directories where assets land at runtime:
app = WhiteSnout(
directory="static",
autocompress=True, # opt-in
autocompress_max_size=1_048_576, # skip files > 1 MB
skip_compress_extensions={
".jpg", ".png", ".webp", ".gz", ".br",
},
)
The first request triggers compression in a background thread and caches the result in a bounded in-memory LRU (per process, keyed by (path, mtime_ns, encoding)). Subsequent requests for the same file serve the cached bytes. Pre-compressed .gz / .br files still win when they exist on disk — autocompress is the fallback.
Brotli requires whitesnout[compress] installed; without it, only gzip is used.
Observability hook
def on_request(info: dict) -> None:
# info: method, path, status, length, elapsed_s, scope
metrics.timing("whitesnout.request_ms", info["elapsed_s"] * 1000)
metrics.incr(f"whitesnout.status.{info['status']}")
app = WhiteSnout(directory="static", on_request=on_request)
Async callables are awaited; exceptions raised by the hook are logged and swallowed so they never break a response. Use for OpenTelemetry spans, Prometheus counters, structured access logs, etc.
Django integration
Whitesnout is ASGI-native, so the Django integration only supports Django ASGI deployments (the
asgi.pypattern). For Django WSGI / classicrunserver, keep usingwhitenoise.
asgi.py wrapper
# asgi.py
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
django.setup()
from whitesnout.django import get_static_application
application = get_static_application()
get_static_application() reads:
STATIC_ROOT→directorySTATIC_URL→ mount prefix when not/static/STATICFILES_STORAGEorSTORAGES["staticfiles"]["BACKEND"]→ if a Manifest storage is configured, hooksstatic/staticfiles.jsonautomaticallyDEBUG→ if True andautorefreshis unset, enablesautorefresh=True
Pass overrides through to WhiteSnout:
application = get_static_application(
hsts="max-age=31536000; includeSubDomains",
cors_allow_origins=["https://app.example.com"],
)
Development without collectstatic
application = get_static_application(use_finders=True, autorefresh=True)
use_finders=True wires a path_resolver that calls django.contrib.staticfiles.finders.find() for every request that misses STATIC_ROOT. Combined with autorefresh=True, every request re-resolves and re-stats the file so edits show up immediately. Both options are typically gated by settings.DEBUG.
collectstatic with compression in one step
Replace Django's default storage with whitesnout's compressed manifest storage to emit .gz + .br siblings during manage.py collectstatic. No separate build step needed.
# settings.py (Django 4.2+)
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "whitesnout.storage.CompressedManifestStaticFilesStorage",
},
}
# Django < 4.2
STATICFILES_STORAGE = "whitesnout.storage.CompressedManifestStaticFilesStorage"
Two variants are shipped:
whitesnout.storage.CompressedStaticFilesStorage— no hashing, just gzip/brotli siblingswhitesnout.storage.CompressedManifestStaticFilesStorage— Django'sManifestStaticFilesStorage+ gzip/brotli (recommended for production)
Brotli output requires whitesnout[compress] installed in the build environment.
Examples
End-to-end deployments live in examples/:
fastapi-spa/— FastAPI + Vite SPA with manifest, SPA fallback, hardened headers, autocompressdjango-asgi/— Django ASGI withget_static_application()+CompressedManifestStaticFilesStorage+ dev mode (use_finders/autorefresh)starlette/— Starlette + multi-directory mount +add_filesoverrides +on_requestobservability hook
Each example is self-contained — cd in, install requirements, uvicorn to run.
Migration from Whitenoise
| Whitenoise option | Whitesnout equivalent |
|---|---|
WHITENOISE_ROOT / root= |
directory= |
WHITENOISE_USE_FINDERS |
Not applicable — point directory= at STATIC_ROOT after collectstatic |
WHITENOISE_MAX_AGE |
cache_max_age= |
WHITENOISE_IMMUTABLE_FILE_TEST |
immutable_pattern= + manifest_path= |
WHITENOISE_ALLOW_ALL_ORIGINS |
cors_allow_origins=["*"] (or cors=True) |
WHITENOISE_AUTOREFRESH |
autorefresh=True (auto-enabled by whitesnout.django.get_static_application() when settings.DEBUG is True) |
WHITENOISE_USE_FINDERS |
get_static_application(use_finders=True) |
CompressedManifestStaticFilesStorage |
whitesnout.storage.CompressedManifestStaticFilesStorage |
WHITENOISE_MIMETYPES |
mime_types={...} |
WHITENOISE_ADD_HEADERS_FUNCTION |
on_request=callable for observability; use mime_types + the security-header configs for static additions |
WHITENOISE_KEEP_ONLY_HASHED_FILES |
Handled by Django's STATICFILES_STORAGE; whitesnout reads the resulting manifest |
add_files() (whitenoise) |
add_files({...}) |
WhiteNoise(application, root=...) |
WhiteSnout(application, directory=...) |
ASGI-specific: whitenoise is WSGI-only and must be wrapped in WsgiToAsgi when used with ASGI servers, paying the adapter cost on every request. Whitesnout is ASGI-native.
Architecture
whitesnout/
├── main.py # ASGI middleware (always Python)
├── file_handler.py # Path resolution, compression negotiation
├── response.py # Header building, chunked streaming, 304
├── cache.py # LRU cache (Python → falls back to Rust)
├── config.py # Configuration dataclass
├── utils.py # MIME type table, helpers
├── cli.py # CLI entry point
├── compress.py # Compression logic
├── manifest.py # Django/Webpack/Vite manifest loader
├── autocompress.py # In-memory LRU + gzip/brotli on-the-fly
├── django.py # Django ASGI integration (get_static_application)
├── storage.py # Django staticfiles storage backend (collectstatic + compress)
├── _rs.pyi # Type stubs for the Rust extension
└── py.typed
examples/
├── fastapi-spa/ # FastAPI + Vite SPA
├── django-asgi/ # Django ASGI + collectstatic compress storage
└── starlette/ # Multi-directory + observability hook
whitesnout._rs # Compiled Rust extension (PyO3)
├── LRUCache # Generic LRU cache (O(1) via `lru` crate)
├── StatCache # Native stat cache (stores size, mtime_ns without PyObject)
├── response # compute_etag, format_last_modified, build_headers,
│ # check_304, parse_range, build_content_range, etc.
├── utils # guess_content_type (100+ MIME types)
└── file_handler # find_compressed, parse_accept_encoding, is_hashed_file
The core logic consists of pure functions designed for gradual migration to Rust. The ASGI integration layer (main.py) stays in Python forever — it is the thin touchpoint with the ASGI protocol.
Development
With Docker (recommended)
$ make build # Build Docker image + compile Rust + install deps
$ make test # Run test suite inside container
$ make shell # Open interactive shell in container
$ make release # Build release wheel
Requires Docker. The image is based on rust:slim-trixie with Python, uv, and maturin pre-installed.
Without Docker
$ uv sync --dev # Install Python deps + build Rust extension
$ uv run pytest -v # Run tests
$ maturin develop --uv # Rebuild Rust extension only
Requires Rust (via rustup) and maturin (cargo install maturin).
CHANGELOG
See CHANGELOG.md for the full release history.
Benchmark
Results measured with benchmarks/benchmark.py (median of 15 runs) — 500 requests (10 concurrent) against uvicorn with a mix of static files (265 KB across 34 items) and a JSON API endpoint.
- RPS — Requests per second (higher is better)
- P50 — Median latency in milliseconds (lower is better)
- P99 — 99th percentile latency in milliseconds (lower is better)
- RAM — Resident set size in megabytes (lower is better)
| Server | RPS | P50 (ms) | P99 (ms) | RAM (MB) |
|---|---|---|---|---|
| whitesnout | 845 | 6.0 | 82.2 | 33.3 |
| whitenoise | 778 | 6.0 | 86.1 | 31.7 |
Platform: Linux x86_64 (bare metal) · Python: 3.14.5 · uvicorn: 0.47.0 · WSGI bridge:
a2wsgiBoth servers run behind uvicorn for an apples-to-apples comparison. Whitenoise is WSGI-only, so it goes through
a2wsgi.WSGIMiddleware; whitesnout speaks ASGI natively. Variance is ±10% on busy machines.
nginx baseline (ceiling reference)
Both whitesnout and whitenoise are Python ASGI/WSGI servers — they cap out at roughly Python's request-handling rate. For honest context, run the nginx baseline (single-worker, sendfile-enabled) on the same machine:
$ uv run python benchmarks/benchmark_nginx.py
nginx will typically land in the 10–20k RPS range on the same workload. Use it as a "what's the hardware ceiling?" reference, not a peer comparison — whitesnout's value is being part of your Python application process, not replacing a CDN edge.
Running yourself
$ uv run python benchmarks/benchmark.py # whitesnout vs whitenoise
$ uv run python benchmarks/benchmark_nginx.py # nginx ceiling (requires nginx on PATH)
License
MIT — see LICENSE for the full text.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file whitesnout-2.0.0.tar.gz.
File metadata
- Download URL: whitesnout-2.0.0.tar.gz
- Upload date:
- Size: 114.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
675177465fc0d57ca35dba02965fbb1765b98401d0f7109512d21ecd526001b2
|
|
| MD5 |
4178dceca2fabe64ee9caf1c9b768b8d
|
|
| BLAKE2b-256 |
f0bc2a2e2bae83dbb2f50c7fb53d5c155c1515d307f7a369fcda25c533468d03
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0.tar.gz:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0.tar.gz -
Subject digest:
675177465fc0d57ca35dba02965fbb1765b98401d0f7109512d21ecd526001b2 - Sigstore transparency entry: 1592510980
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 865.2 kB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15eccd6502d551d0faf8c954250ac8ea1d32d3a35661565904197abb95887eeb
|
|
| MD5 |
f4b6390d7e012b3aa7eabc33eae4a4e7
|
|
| BLAKE2b-256 |
e0f366919743d3dcdb39bc2f4e20981aaf8edf7024538c34b8f7267db23e1d95
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-win_amd64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-win_amd64.whl -
Subject digest:
15eccd6502d551d0faf8c954250ac8ea1d32d3a35661565904197abb95887eeb - Sigstore transparency entry: 1592511266
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 1.2 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d6071333d4404dbe3d7cae57fbd67a989dec6c36788a6565286e291cf932718
|
|
| MD5 |
6496509ef5817cf8939a652ab6a4d703
|
|
| BLAKE2b-256 |
a1aad69b0ebd697c37b9ecaccfd7f6459569d1a36f57b76c66d7526e9102c984
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl -
Subject digest:
4d6071333d4404dbe3d7cae57fbd67a989dec6c36788a6565286e291cf932718 - Sigstore transparency entry: 1592511174
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.10+, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ad56604354e1309ac7ae9074e31e67268031cabaedf0ac28511e3361c1b00f5
|
|
| MD5 |
c7a8dee787e5aa73fd51cff069328e15
|
|
| BLAKE2b-256 |
4c519515f98405bdbb88d13015b41cc57bf1ade0d99a2b592fee67c81bbf029c
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl -
Subject digest:
2ad56604354e1309ac7ae9074e31e67268031cabaedf0ac28511e3361c1b00f5 - Sigstore transparency entry: 1592511220
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d73b7fffe7fa7a7c05f2f2e6e046a9db0370fc115e5eec70840e635a8f9b7c3b
|
|
| MD5 |
1ce53638aae9a94b9e43a4915be9f317
|
|
| BLAKE2b-256 |
11e99c6fe9fe45843b60f371d48d96261b8c714ba468fa530f030a25a7674300
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl -
Subject digest:
d73b7fffe7fa7a7c05f2f2e6e046a9db0370fc115e5eec70840e635a8f9b7c3b - Sigstore transparency entry: 1592511034
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.1 MB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
482c197564294a879904804da366396c6eb0c648bc3cb422d0854495ef9d0bd4
|
|
| MD5 |
a40e0829180096d2cd8c3715cb4b94db
|
|
| BLAKE2b-256 |
780c188444c0839eaf23fe2818a25aaf8ed4288cc7f348631edcdab607020058
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl -
Subject digest:
482c197564294a879904804da366396c6eb0c648bc3cb422d0854495ef9d0bd4 - Sigstore transparency entry: 1592511076
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 925.6 kB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39c54e877fbe60b346104bc773a4c997935e3f076574f5a6ce7db0a0abbfa9b1
|
|
| MD5 |
2bc3743aa31b776cdbf389a8ca73b432
|
|
| BLAKE2b-256 |
08ce2bc8bbb52a2b1a65e3d43667242895dab93fc9d3bd5e37d8baf5cbbeb50f
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
39c54e877fbe60b346104bc773a4c997935e3f076574f5a6ce7db0a0abbfa9b1 - Sigstore transparency entry: 1592511140
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 973.3 kB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
022cc123d24aa1e6affe0bb1739bc329dedbc2d5759ff09a6ac2b44ef5503f9d
|
|
| MD5 |
3284fd6413cf471d993a5cb54afef0cd
|
|
| BLAKE2b-256 |
87bb6addb29324ea282a42276eb4be5c45b5958bf280f732c60b474f222a6985
|
Provenance
The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
publish.yml on rroblf01/whitesnout
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
022cc123d24aa1e6affe0bb1739bc329dedbc2d5759ff09a6ac2b44ef5503f9d - Sigstore transparency entry: 1592511099
- Sigstore integration time:
-
Permalink:
rroblf01/whitesnout@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/rroblf01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fbc10cc6208b2ebb270a447af79b33899fab9ca -
Trigger Event:
push
-
Statement type: