Skip to main content

ASGI static file server — like Whitenoise, but for ASGI

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.

The compress CLI needs Brotli:

$ uv add 'whitesnout[compress]'

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)
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 100 Max entries in the LRU stat cache
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
  • Powerful cachingETag, Last-Modified, Cache-Control headers; 304 Not Modified responses for conditional requests
  • Immutable cache — detects hashed filenames (e.g. app.abc12345.js) and 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 dependency bloat
  • MIME types — content-type detection for 30+ file extensions, with automatic charset for text types
  • 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 transparently; pure Python fallback when unavailable
  • Multi-platform wheels — pre-built for Linux (x86_64, arm64), macOS (x86_64, arm64), and Windows (amd64)

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
└── py.typed

whitesnout._rs           # Compiled Rust extension (PyO3)
├── LRUCache             # Rust implementation, auto fallback to Python

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 — 500 requests (10 concurrent) against uvicorn with a mix of static files (265 KB across 34 items) and a JSON API endpoint.

v0.2.0

Server RPS P50 (ms) P99 (ms) RAM (MB)
whitesnout 966 5.8 119.2 31.8
whitenoise 925 5.9 80.0 31.5

Platform: Linux x86_64 · Python: 3.14.3 · uvicorn: 0.47.0

Running yourself

$ uv run python benchmarks/benchmark.py

ROADMAP

v0.3.0 ─── Range Requests + Security headers + Accept-Encoding (current)
v0.2.0 ─── Ruff + Ty + type safety
v0.1.0 ─── Published
   │
   ├─ v0.2.0  Ruff + Ty + type safety
   ├─ v0.3.0  Range Requests + Security headers + Accept-Encoding quality values
   ├─ v0.4.0  Rust Phase 2 (utils, file_handler) + LRU cache O(1)
   ├─ v0.5.0  CORS + Logging + Cache invalidation
   └─ v1.0.0  Env vars + Async file IO + Benchmarks

v0.2.0 — Ruff + Ty

  • Add ruff (lint + format) and ty (type checker) for code validation
  • Fix all lint/type errors; pass both in CI

v0.3.0 — Range Requests & Security

  • Range Requests: Parse Range: header, respond with 206 Partial Content + Content-Range. Required for video, audio, and PDF seeking
  • Security headers: X-Content-Type-Options: nosniff, X-Frame-Options: DENY by default
  • Accept-Encoding quality values: Parse Accept-Encoding: gzip, br;q=0.1 correctly instead of naive substring matching
  • Respect brotli/gzip flags: find_compressed must skip .br when brotli=False

v0.4.0 — Rust Phase 2 + LRU O(1)

  • Port utils.py (MIME types) → src/utils.rs
  • Port file_handler.py (find_compressed, is_hashed_file) → src/file_handler.rs
  • Replace Vec-based Rust LRU with a LinkedHashMap for O(1) operations
  • Add Rust unit tests (#[cfg(test)])
  • Expand MIME type table from 30 → 100+

v0.5.0 — CORS, Logging & Cache invalidation

  • CORS opt-in: Config cors=True → add Access-Control-Allow-Origin: *
  • Logging: Basic request logging (method, path, status, bytes, duration)
  • Cache invalidation: Programmatic invalidate() method to purge the LRU cache

v1.0.0 — Production readiness

  • Environment variables: WHITESNOUT_DIRECTORY, WHITESNOUT_CACHE_MAX_AGE, etc.
  • Async file IO: Migrate iter_chunks to anyio or aiofiles to avoid blocking the event loop
  • Benchmarks: Compare against Whitenoise and raw ASGI serving

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-0.3.0.tar.gz (58.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-0.3.0-cp310-abi3-win_amd64.whl (143.9 kB view details)

Uploaded CPython 3.10+Windows x86-64

whitesnout-0.3.0-cp310-abi3-musllinux_1_2_x86_64.whl (350.4 kB view details)

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

whitesnout-0.3.0-cp310-abi3-musllinux_1_2_aarch64.whl (337.9 kB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ ARM64

whitesnout-0.3.0-cp310-abi3-manylinux_2_28_x86_64.whl (277.5 kB view details)

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

whitesnout-0.3.0-cp310-abi3-manylinux_2_28_aarch64.whl (271.6 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.28+ ARM64

whitesnout-0.3.0-cp310-abi3-macosx_11_0_arm64.whl (242.4 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

whitesnout-0.3.0-cp310-abi3-macosx_10_12_x86_64.whl (246.2 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file whitesnout-0.3.0.tar.gz.

File metadata

  • Download URL: whitesnout-0.3.0.tar.gz
  • Upload date:
  • Size: 58.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for whitesnout-0.3.0.tar.gz
Algorithm Hash digest
SHA256 67259e5a67e89e9d521b41c7b40d20d5e8e00b00ee0fed5b0009f258d3ff5826
MD5 133969b6a9afefe49cc18dd26dac0c9d
BLAKE2b-256 b87a924eb3e69ac7067e6a452b94d89a32bba5d9863293cfe7d748e53375f01c

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: whitesnout-0.3.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 143.9 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-0.3.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 0ff01928665a7c1117232fefc1c9c70f6cd0a2a25a7dc6fd7b8e719e83a5a745
MD5 b5e807a3e23cf63ef8056d31a42e608f
BLAKE2b-256 8660a369530459b0e67a0652d5458b1795366d121c35e81435c4438277b308d9

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 70a7a0f95d04da6d2691526868a37b7baeff25c3667bf6f073f87e3322c97210
MD5 211b55a0b327b59b69f6f8476184d12c
BLAKE2b-256 60c1d011a4688b16fe7502e8c6352114679d269f19ae679586c979e8d80a8a92

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 a2d60ee1ded2465d13c453d96edff384112b4616dfaf7be165f11c7742478c22
MD5 f994d66e6c9abf3b3f6cdf75fcb91c5b
BLAKE2b-256 b611ad78252f3312a18a0ed9eb90bf8f7ca3e8d047c8f35ec22ef7e08d083ade

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ad74500b534fb4eb24e0167efc21c40df021aa49bb5bbf79ff25526f7f176015
MD5 36b0d96693a5155092e32624aca9cbd5
BLAKE2b-256 67395fcb9dc166ff19d7e4786080c58eb12b30ed8c48d4af5b9ebfb3e1f1ccc9

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 58d66847042d31cce864ca473bb91f52074788e1b0e32f4bceec9c83ef55d79f
MD5 f21a9e7926163919531882ac78d788c3
BLAKE2b-256 b93faa1566537db2ad0eed610471b66129542a06152fa49ccad6a1a56b6f3176

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 27deec1c17e9e61888e0eccaa802fe3528ba493c14c25a67b9f235de04021ad8
MD5 ff39c3ed377beabf796b216930376b90
BLAKE2b-256 fb87174141309ec0ada1cded9c864741e8420892f5091126991dd380cb017df1

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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-0.3.0-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for whitesnout-0.3.0-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 f9149c662e5a5767f3b96ac396b4e15075e5d1b7acbc6ccaedda89431869d374
MD5 73996bbdc1a31c4e5a8ab021c3ad3566
BLAKE2b-256 d2fdcd2ecd701b64d863f4ffcda552bf243ff0b544ec2009a1769ee15a1ce8d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for whitesnout-0.3.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