Module

_static

Static file serving with modern optimizations.

Designed for Bengal SSG output and Chirp static assets. Supports:

  • Protocol-owned zero-copy sendfile for supported HTTP/1 connections
  • ETag generation from mtime + size
  • 304 Not Modified responses
  • Range requests (Accept-Ranges, Content-Range, 206)
  • Precompressed file serving (.gz, .zst variants)
  • MIME type detection
  • Security: path traversal prevention, hidden file blocking

Example:

config = ServerConfig(static_files={"/static": "./public"})
# Requests to /static/* will be served from ./public/

Classes

StaticMount 7
Configuration for a static file mount point.

Configuration for a static file mount point.

Attributes

Name Type Description
url_path str
directory Path
cache_control str
precompressed bool
follow_symlinks bool
index_file str | None
extra_mime_types dict[str, str]
StaticFile 7
Resolved static file with metadata.

Resolved static file with metadata.

Attributes

Name Type Description
path Path
size int
mtime float
mime_type str
etag str
encoding str | None
cache_control str
StaticFiles 18
ASGI-compatible static file handler. Can be used as middleware or integrated into Worker. Example…

ASGI-compatible static file handler.

Can be used as middleware or integrated into Worker.

Example as middleware:

from pounce import StaticFiles

app = StaticFiles(
    app,
    mounts=[
        StaticMount("/static", Path("./public")),
        StaticMount("/assets", Path("./dist")),
    ]
)

Example in Worker (built-in): config = ServerConfig(static_files={"/static": "./public"})

Methods

Internal Methods 18
__init__ 2
Initialize static file handler.
def __init__(self, app: ASGIApp | None = None, *, mounts: list[StaticMount]) -> None
Parameters
Name Type Description
app

Optional ASGI app to call if path doesn't match (middleware mode)

Default:None
mounts

List of static mount configurations

_prepare_mounts 1 list[StaticMount]
Normalize and validate mount configurations.
def _prepare_mounts(self, mounts: list[StaticMount]) -> list[StaticMount]
Parameters
Name Type Description
mounts
Returns
list[StaticMount] Sorted list of mounts (longest url_path first for correct matching)
__call__ 3
Handle ASGI request. If path matches a static mount, serve the file. Otherwise…
async
async def __call__(self, scope: dict[str, Any], receive: Receive, send: Send) -> None

Handle ASGI request.

If path matches a static mount, serve the file. Otherwise, call the app.

Parameters
Name Type Description
scope
receive
send
_resolve_file 2 StaticFile | None
Resolve URL path to static file.
def _resolve_file(self, url_path: str, accept_encoding: bytes | None) -> StaticFile | None
Parameters
Name Type Description
url_path
accept_encoding
Returns
StaticFile | None StaticFile if found and valid, None otherwise
_find_precompressed 4 tuple[Path, str | None]
Find precompressed variant if available and client supports. Priority: zstd > …
def _find_precompressed(self, path: Path, mount: StaticMount, accept_encoding: bytes | None, original_stat: os.stat_result) -> tuple[Path, str | None]

Find precompressed variant if available and client supports.

Priority: zstd > gzip > identity

Parameters
Name Type Description
path
mount
accept_encoding
original_stat
Returns
tuple[Path, str | None] (file_path, encoding) where encoding is "gzip", "zstd", or None
_validate_precompressed 2 bool
Validate a precompressed variant against the same security checks as the origin…
def _validate_precompressed(self, path: Path, mount: StaticMount) -> bool

Validate a precompressed variant against the same security checks as the original.

Checks path traversal, hidden files, and symlinks.

Parameters
Name Type Description
path
mount
Returns
bool True if the precompressed path is safe to serve
_get_mime_type 2 str
Get MIME type for file. **Lookup order:** 1. Mount-specific extra_mime_types (…
def _get_mime_type(self, path: Path, mount: StaticMount | None = None) -> str

Get MIME type for file.

Lookup order:

  1. Mount-specific extra_mime_types (user overrides)
  2. stdlib mimetypes.guess_type()
  3. _MODERN_MIME_TYPES fallback for modern web extensions
  4. application/octet-stream
Parameters
Name Type Description
path
mount Default:None
Returns
str MIME type string
_generate_etag 3 str
Generate ETag from mtime, size, and encoding. Uses weak ETag (W/) because we u…
def _generate_etag(self, mtime: float, size: int, encoding: str | None = None) -> str

Generate ETag from mtime, size, and encoding.

Uses weak ETag (W/) because we use mtime, not content hash. Encoding is included so that compressed and uncompressed variants of the same file produce distinct ETags (RFC 7232).

Parameters
Name Type Description
mtime
size
encoding Default:None
Returns
str ETag header value (e.g., W/"5f3c-1a2b" or W/"5f3c-1a2b-gzip")
_check_not_modified 2 bool
Check if client has cached version (If-None-Match).
def _check_not_modified(self, headers: list[tuple[bytes, bytes]], file: StaticFile) -> bool
Parameters
Name Type Description
headers
file
Returns
bool True if client cache is valid (send 304), False otherwise
_parse_range_header 2 list[tuple[int, int]] | …
Parse Range header and return list of (start, end) byte ranges. Format: "bytes…
def _parse_range_header(self, range_header: str, file_size: int) -> list[tuple[int, int]] | None

Parse Range header and return list of (start, end) byte ranges.

Format: "bytes=0-499" or "bytes=500-999" or "bytes=-500"

Parameters
Name Type Description
range_header
file_size
Returns
list[tuple[int, int]] | None List of (start, end) tuples (inclusive), or None if invalid
_get_header 2 bytes | None
Get header value by name (case-insensitive).
def _get_header(self, headers: list[tuple[bytes, bytes]], name: bytes) -> bytes | None
Parameters
Name Type Description
headers
name
Returns
bytes | None Header value as bytes, or None if not found
_send_304 2
Send 304 Not Modified response.
async
async def _send_304(self, file: StaticFile, send: Send) -> None
Parameters
Name Type Description
file
send
_send_206 4
Send 206 Partial Content response (RFC 7233). Single range: Content-Range head…
async
async def _send_206(self, file: StaticFile, ranges: list[tuple[int, int]], send: Send, *, sendfile_enabled: bool = False) -> None

Send 206 Partial Content response (RFC 7233).

Single range: Content-Range header with the range body. Multiple ranges: multipart/byteranges body with MIME boundary.

Parameters
Name Type Description
file
ranges
send
sendfile_enabled Default:False
_send_206_single 4
Send a single-range 206 response.
async
async def _send_206_single(self, file: StaticFile, range_pair: tuple[int, int], send: Send, *, sendfile_enabled: bool = False) -> None
Parameters
Name Type Description
file
range_pair
send
sendfile_enabled Default:False
_send_206_multipart 3
Send a multipart/byteranges 206 response (RFC 7233 §4.1). Each part has its ow…
async
async def _send_206_multipart(self, file: StaticFile, ranges: list[tuple[int, int]], send: Send) -> None

Send a multipart/byteranges 206 response (RFC 7233 §4.1).

Each part has its own Content-Type and Content-Range headers, separated by a MIME boundary. Sendfile is not used here because the part headers must be interleaved with file data.

Parameters
Name Type Description
file
ranges
send
_send_file 4
Send full file response (200 OK).
async
async def _send_file(self, file: StaticFile, method: str, send: Send, *, sendfile_enabled: bool = False) -> None
Parameters
Name Type Description
file
method
send
sendfile_enabled Default:False
_send_file_body 5
Send file body, using protocol-owned zero-copy sendfile when available. When t…
async
async def _send_file_body(self, path: Path, offset: int, count: int, send: Send, *, sendfile_enabled: bool = False) -> None

Send file body, using protocol-owned zero-copy sendfile when available.

When the ASGI scope advertisespounce.sendfile, emit a Pounce extension message describing the file range. The bridge and active protocol own framing, byte accounting, and socket writes.

Falls back to chunked reads through ASGI send otherwise.

Parameters
Name Type Description
path
offset
count
send
sendfile_enabled Default:False
_send_file_range 5
Send file range (for 206 responses).
async
async def _send_file_range(self, path: Path, start: int, count: int, send: Send, *, sendfile_enabled: bool = False) -> None
Parameters
Name Type Description
path
start
count
send
sendfile_enabled Default:False

Functions

create_static_handler 5 StaticFiles
Create StaticFiles handler from simple dict config.
def create_static_handler(mounts: dict[str, str], cache_control: str = 'public, max-age=3600', precompressed: bool = True, follow_symlinks: bool = False, index_file: str | None = 'index.html') -> StaticFiles
Parameters
Name Type Description
mounts dict[str, str]

Dict of {url_path: directory} mappings

cache_control str

Cache-Control header value

Default:'public, max-age=3600'
precompressed bool

Serve .gz/.zst if available

Default:True
follow_symlinks bool

Allow serving symlinked files

Default:False
index_file str | None

Filename to serve for directories

Default:'index.html'
Returns
StaticFiles