Static assets and fingerprinting
Ship CSS, JavaScript, and images with content-hashed URLs so browsers can cache them across deploys. Use Assets for that tree. Use Files when the path on disk is the path in the URL (uploads, generated files). Both live on stario. stario.staticassets is obsolete.
This page is the recipe. For the HTTP contract, see Files and assets. For where files live in a larger app, see Structuring apps.
Assets at module level
Build Assets when the module loads. href() hashes that file and pins the digest. It does not keep file contents in memory.
The static directory must exist when assets.py is imported. Finish writing assets before the first href() or attach(). A missing path raises StarioError. A file that changes while hashing raises StarioRuntime. Restart after you replace an Assets file.
from pathlib import Path from stario import Assets # Single-file app: static/ sits next to main.pyASSETS = Assets(Path(__file__).resolve().parent / "static")STYLE_CSS = ASSETS.href("css/style.css")APP_JS = ASSETS.href("js/app.js")In a larger app, static/ sits next to app/, so app/assets.py walks one directory further:
PROJECT_ROOT = Path(__file__).resolve().parents[1]ASSETS = Assets(PROJECT_ROOT / "static")href(logical_path) returns the public URL (by default under /static/… with a fingerprint segment). Use those strings in views — no url_for and no App instance at import time.
Hidden files and dot-directories are skipped unless you pass include_hidden=True. Symlinks are skipped unless follow_symlinks=True.
attach in bootstrap
await attach(app) registers GET and HEAD, then loads the rest of the tree (hash, hold small files, precompress). Wrap it in a span step so the returned stats land on the startup trace:
from stario import App, Route, Span from app.assets import ASSETS async def bootstrap(app: App, span: Span): with span.step("static_assets") as s: s.attrs(await ASSETS.attach(app)) app.add(Route("GET /"), index) yieldAssets serves fingerprinted files with immutable cache headers. Small files are held in memory and precompressed (br, zstd, gzip) by default. Larger files stream from disk and support Range (uncompressed). Logical paths without a digest get 307 to the hashed URL.
register() and load() stay available when you must split the work. attach() is the bootstrap call. Order relative to other routes only matters for app.use on the same prefix.
Optional tuning: url_prefix= and content_types= on construction; precompress= and cache_control= on attach(). See Files and assets.
Files for a live tree
Files uses the live path. Default prefix is /data. GET is live after attach — a new file on disk is served. Files does not precompress unless you pass precompress=. Do not use Assets for visitor uploads.
from stario import Files UPLOADS = Files("./uploads", "/data") async def bootstrap(app: App, span: Span): await UPLOADS.attach(app) yieldWriting the bytes is still your handler. See User uploads and storage.
ASSETS.href in views
Resolve URLs before you build HTML. Pass plain strings into view functions:
from stario.markup import html as h from app.assets import APP_JS, STYLE_CSS def layout(*children): return h.HtmlDocument( {"lang": "en"}, h.Head( h.Link({"rel": "stylesheet", "href": STYLE_CSS}), h.Script({"type": "module", "src": APP_JS}), ), h.Body(*children), )In handlers, use the same module-level constants or call ASSETS.href("…") when the path depends on runtime data.
For links to application routes, use Route constants and .href() — see Structuring apps.
Tests
Assert fingerprinted URLs resolve through the running app:
from app.assets import ASSETS async def test_static_asset(client): url = ASSETS.href("css/style.css") assert url.startswith("/static/") r = await client.get(url) assert r.status_code == 200Use the same bootstrap as production so attach() runs in tests.
Related
Files and assets —
Assets,Files, obsoletestario.staticassetsStructuring apps —
app/assets.pyconventionRuntime — bootstrap and
attach()