Module

_static

Static file serving with modern optimizations.

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

  • Zero-copy sendfile (os.sendfile on Linux, sendfile on macOS)
  • 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 6
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
StaticFile 6
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
StaticFiles 15
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 15
__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
_get_mime_type 1 str
Get MIME type for file.
def _get_mime_type(self, path: Path) -> str
Parameters
Name Type Description
path
Returns
str MIME type string (defaults to application/octet-stream)
_generate_etag 2 str
Generate ETag from mtime and size. Uses weak ETag (W/) because we use mtime, n…
def _generate_etag(self, mtime: float, size: int) -> str

Generate ETag from mtime and size.

Uses weak ETag (W/) because we use mtime, not content hash.

Parameters
Name Type Description
mtime
size
Returns
str ETag header value (e.g., W/"5f3c-1a2b")
_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 3
Send 206 Partial Content response. Currently supports single range only (multi…
async
async def _send_206(self, file: StaticFile, ranges: list[tuple[int, int]], send: Send) -> None

Send 206 Partial Content response.

Currently supports single range only (multipart ranges not implemented).

Parameters
Name Type Description
file
ranges
send
_send_file 3
Send full file response (200 OK).
async
async def _send_file(self, file: StaticFile, method: str, send: Send) -> None
Parameters
Name Type Description
file
method
send
_send_file_body 4
Send file body using chunked reads. TODO: Optimize with sendfile for zero-copy…
async
async def _send_file_body(self, path: Path, offset: int, count: int, send: Send) -> None

Send file body using chunked reads.

TODO: Optimize with sendfile for zero-copy transfer.

Parameters
Name Type Description
path
offset
count
send
_send_file_range 4
Send file range (for 206 responses).
async
async def _send_file_range(self, path: Path, start: int, count: int, send: Send) -> None
Parameters
Name Type Description
path
start
count
send

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