Classes
StaticMount
7
▼
Configuration for a static file mount point.
StaticMount
7
▼
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.
StaticFile
7
▼
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…
StaticFiles
18
▼
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.
__init__
2
▼
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.
_prepare_mounts
1
list[StaticMount]
▼
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
__call__
3
▼
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.
_resolve_file
2
StaticFile | None
▼
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 > …
_find_precompressed
4
tuple[Path, str | None]
▼
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…
_validate_precompressed
2
bool
▼
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 (…
_get_mime_type
2
str
▼
def _get_mime_type(self, path: Path, mount: StaticMount | None = None) -> str
Get MIME type for file.
Lookup order:
- Mount-specific extra_mime_types (user overrides)
- stdlib mimetypes.guess_type()
- _MODERN_MIME_TYPES fallback for modern web extensions
- 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…
_generate_etag
3
str
▼
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).
_check_not_modified
2
bool
▼
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…
_parse_range_header
2
list[tuple[int, int]] | …
▼
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).
_get_header
2
bytes | None
▼
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
_send_304
2
▼
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
_send_206
4
▼
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
_send_206_single
4
▼
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
_send_206_multipart
3
▼
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
_send_file
4
▼
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
_send_file_body
5
▼
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
_send_file_range
5
▼
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.
create_static_handler
5
StaticFiles
▼
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