Skip to main content

ASGI static file server — like Whitenoise, but native ASGI with a Rust hot path

Project description

whitesnout

tests PyPI Python versions codecov License: MIT

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 streaming extra (default): chunks are read via blocking open() 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 streaming extra: chunks are read via aiofiles, 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_threshold to fit your largest expected file (e.g. sync_threshold=2_000_000 for ≤ 2 MB)
  • Memory-constrained environments — aiofiles adds ~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 .gz and .br files with automatic Accept-Encoding negotiation; brotli preferred over gzip
  • On-the-fly compression — opt-in autocompress=True compresses uncompressed assets on first request and caches the result in memory
  • Powerful cachingETag, Last-Modified, Cache-Control, Vary: Accept-Encoding headers; 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 filesindex.html served automatically for directory paths
  • Clean URLs/dir redirects 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 headersHSTS, CSP, Referrer-Policy, Permissions-Policy controllable via config
  • CORS allowlistcors_allow_origins=[...] matches per-request Origin and emits Vary: Origin
  • Observability hookon_request=callable is invoked after every served request with method/path/status/length/elapsed metadata
  • Compress CLIpython -m whitesnout compress <directory> generates pre-compressed .gz and .br files as a build step
  • Rust extensionwhitesnout._rs speeds 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 Python os.stat_result, reducing GC pressure and memory overhead
  • Django integrationwhitesnout.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=None to 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.py pattern). For Django WSGI / classic runserver, keep using whitenoise.

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_ROOTdirectory
  • STATIC_URL → mount prefix when not /static/
  • STATICFILES_STORAGE or STORAGES["staticfiles"]["BACKEND"] → if a Manifest storage is configured, hooks static/staticfiles.json automatically
  • DEBUG → if True and autorefresh is unset, enables autorefresh=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 siblings
  • whitesnout.storage.CompressedManifestStaticFilesStorage — Django's ManifestStaticFilesStorage + 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, autocompress
  • django-asgi/ — Django ASGI with get_static_application() + CompressedManifestStaticFilesStorage + dev mode (use_finders / autorefresh)
  • starlette/ — Starlette + multi-directory mount + add_files overrides + on_request observability 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: a2wsgi

Both 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

whitesnout-2.0.0.tar.gz (114.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

whitesnout-2.0.0-cp310-abi3-win_amd64.whl (865.2 kB view details)

Uploaded CPython 3.10+Windows x86-64

whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl (1.2 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl (1.1 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl (1.1 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ x86-64

whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl (1.1 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl (925.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl (973.3 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

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

Hashes for whitesnout-2.0.0.tar.gz
Algorithm Hash digest
SHA256 675177465fc0d57ca35dba02965fbb1765b98401d0f7109512d21ecd526001b2
MD5 4178dceca2fabe64ee9caf1c9b768b8d
BLAKE2b-256 f0bc2a2e2bae83dbb2f50c7fb53d5c155c1515d307f7a369fcda25c533468d03

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-2.0.0.tar.gz:

Publisher: publish.yml on rroblf01/whitesnout

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for whitesnout-2.0.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 15eccd6502d551d0faf8c954250ac8ea1d32d3a35661565904197abb95887eeb
MD5 f4b6390d7e012b3aa7eabc33eae4a4e7
BLAKE2b-256 e0f366919743d3dcdb39bc2f4e20981aaf8edf7024538c34b8f7267db23e1d95

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-2.0.0-cp310-abi3-win_amd64.whl:

Publisher: publish.yml on rroblf01/whitesnout

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 4d6071333d4404dbe3d7cae57fbd67a989dec6c36788a6565286e291cf932718
MD5 6496509ef5817cf8939a652ab6a4d703
BLAKE2b-256 a1aad69b0ebd697c37b9ecaccfd7f6459569d1a36f57b76c66d7526e9102c984

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 2ad56604354e1309ac7ae9074e31e67268031cabaedf0ac28511e3361c1b00f5
MD5 c7a8dee787e5aa73fd51cff069328e15
BLAKE2b-256 4c519515f98405bdbb88d13015b41cc57bf1ade0d99a2b592fee67c81bbf029c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d73b7fffe7fa7a7c05f2f2e6e046a9db0370fc115e5eec70840e635a8f9b7c3b
MD5 1ce53638aae9a94b9e43a4915be9f317
BLAKE2b-256 11e99c6fe9fe45843b60f371d48d96261b8c714ba468fa530f030a25a7674300

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 482c197564294a879904804da366396c6eb0c648bc3cb422d0854495ef9d0bd4
MD5 a40e0829180096d2cd8c3715cb4b94db
BLAKE2b-256 780c188444c0839eaf23fe2818a25aaf8ed4288cc7f348631edcdab607020058

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 39c54e877fbe60b346104bc773a4c997935e3f076574f5a6ce7db0a0abbfa9b1
MD5 2bc3743aa31b776cdbf389a8ca73b432
BLAKE2b-256 08ce2bc8bbb52a2b1a65e3d43667242895dab93fc9d3bd5e37d8baf5cbbeb50f

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-2.0.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 022cc123d24aa1e6affe0bb1739bc329dedbc2d5759ff09a6ac2b44ef5503f9d
MD5 3284fd6413cf471d993a5cb54afef0cd
BLAKE2b-256 87bb6addb29324ea282a42276eb4be5c45b5958bf280f732c60b474f222a6985

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page