Static files¶
A fast hunter still needs markings: CSS, JavaScript, images, fonts, favicons. VelociPy’s StaticFiles handler serves those assets directly from disk without dragging the main application into the work. It guards against directory traversal, caches filesystem metadata, and answers conditional requests with 304 Not Modified when the client already has the right file.
Use app.mount() to pin the handler under a URL prefix, then let the framework handle the rest.
Serving assets¶
Create a static/ directory and put your assets inside it:
Mount StaticFiles at /static:
from velocipy import VelociPy
from velocipy.routing.static import StaticFiles
app = VelociPy()
app.mount("/static", StaticFiles(directory="static"))
Now GET /static/style.css returns static/style.css with the correct content-type and a strong ETag.
Tip
StaticFiles can also be imported from velocipy directly: from velocipy import StaticFiles.
HTML mode for single-page and static sites¶
When html=True, a request for a trailing-slash path maps to index.html inside the matching directory. This is useful for static sites, documentation builds, or single-page applications that ship a prebuilt index.html.
from velocipy import VelociPy
from velocipy.routing.static import StaticFiles
app = VelociPy()
# Static assets at /static/<path>
app.mount("/static", StaticFiles(directory="static"))
# index.html fallback for directory requests at /
app.mount("/", StaticFiles(directory="static", html=True))
With this setup, GET / returns static/index.html if it exists, and GET /about/ returns static/about/index.html.
Warning
Without html=True, trailing slashes are stripped and the path must name a file directly. Directory requests return 404 Not Found.
How it works under the hood¶
StaticFiles keeps three claws sharp:
- Path resolution happens off the event loop with
asyncio.to_thread, so a slow disk does not block the pack. - Directory-traversal guard resolves the target path and ensures it stays inside the configured root.
- Metadata cache stores the resolved path, computed
ETag, and guessed media type after the first request, so repeat hunts are cheap.
If the client sends If-None-Match matching the cached ETag, VelociPy returns 304 Not Modified without re-reading the file.
Multiple mounts and root fallback¶
You can mount several directories under different prefixes. A common pattern is /static for build assets plus / for an HTML entrypoint.
from pathlib import Path
from velocipy import VelociPy
from velocipy.routing.static import StaticFiles
app = VelociPy()
app.mount("/static", StaticFiles(directory="static"))
app.mount("/uploads", StaticFiles(directory="uploads"))
app.mount("/", StaticFiles(directory="public", html=True))
Because the router evaluates routes in order, put narrower prefixes such as /static before broader ones such as /.
Programmatic directory creation¶
If you generate the static directory at runtime, make sure it exists before constructing StaticFiles. The handler raises RuntimeError if the directory is missing.
from pathlib import Path
from velocipy import VelociPy
from velocipy.routing.static import StaticFiles
app = VelociPy()
static_dir = Path(__file__).parent / "static"
static_dir.mkdir(exist_ok=True)
app.mount("/static", StaticFiles(directory=static_dir))
At a glance¶
| Gear | What it does |
|---|---|
StaticFiles(directory) |
Serves files from a local directory with traversal protection. |
StaticFiles(directory, html=True) |
Serves index.html for trailing-slash directory requests. |
app.mount("/static", ...) |
Pins the handler under a URL prefix. |
ETag + If-None-Match |
Conditional responses save bandwidth with 304 Not Modified. |
| Metadata cache | Filesystem work is done once per URL, then reused. |
See examples/static_files.py for a runnable demo.