Flask-style helpers¶
Veloce ships a set of Flask-style helpers for the common cases:
request-scoped proxies (g, current_app,
request, session), response shortcuts
(jsonify, make_response,
redirect, send_file), and control-flow
helpers (abort, flash). All import from the
top-level veloce package.
A first example¶
from veloce import Veloce, abort, g, jsonify
app = Veloce()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id < 1:
abort(404)
g.user_id = user_id # stash on the request-scoped namespace
return jsonify(id=g.user_id, name="alice")
abort(404) raises the typed NotFound exception, g carries
per-request state, and jsonify builds a JSON response. The rest of this
page covers each helper.
Request-scoped proxies¶
Four singletons resolve to per-request state on every access. They are
backed by contextvars, so they are correct under async concurrency —
each in-flight request sees its own value with no thread-local hacks.
g¶
g is a request-scoped namespace for stashing data that several functions
in one request need to share — a database handle, the authenticated user,
a request-scoped cache. It is cleared between requests.
from veloce import Request, Veloce, g
app = Veloce()
@app.get("/profile")
async def profile(request: Request):
g.request_id = request.headers.get("x-request-id", "none")
return {"request_id": g.request_id}
Reading an attribute that was never set raises AttributeError. Use the
dict-style accessors to avoid that:
from veloce import Veloce, g
app = Veloce()
@app.get("/safe")
async def safe():
value = g.get("missing", "default") # no AttributeError
g.setdefault("counter", 0)
popped = g.pop("counter", None)
return {"value": value, "popped": popped, "has_value": "value" in g}
g supports g.get(name, default=None), g.pop(name, *args),
g.setdefault(name, default=None), and name in g.
current_app¶
current_app proxies to the app handling the current request. Use it to
reach app.config, app.state, or app.extensions from code that does
not otherwise have the app:
from veloce import Veloce, current_app
app = Veloce()
app.config["FEATURE_FLAG"] = True
@app.get("/flag")
async def flag():
return {"enabled": current_app.config.get("FEATURE_FLAG")}
Accessing current_app outside an active request raises RuntimeError.
request¶
request proxies to the Request
being handled. The usual way to reach the request is to annotate a
handler parameter as Request (see
Requests & Responses); the request proxy is
for helper code spawned during the request that cannot easily receive it
as an argument.
from veloce import Veloce, request
app = Veloce()
def client_ua() -> str:
return request.headers.get("user-agent", "unknown")
@app.get("/ua")
async def ua():
return {"user_agent": client_ua()}
session¶
session proxies to the current request's session. It requires
SessionMiddleware (or ServerSessionMiddleware) — see the
Sessions guide:
from veloce import SessionMiddleware, Veloce, session
app = Veloce()
app.add_middleware(SessionMiddleware, secret_key="change-me-in-production")
@app.get("/visits")
async def visits():
session["visits"] = session.get("visits", 0) + 1
return {"visits": session["visits"]}
The proxy supports item access (session["k"]), in, iteration,
len(), and the dict methods of the underlying
Session.
abort¶
abort raises an
HTTPException by status code. For
known codes it raises the matching typed subclass (NotFound for 404,
Forbidden for 403) so error handlers registered against that subclass match:
from veloce import Veloce, abort
app = Veloce()
@app.get("/restricted")
async def restricted():
abort(403, "You may not view this")
The signature is abort(status_code, detail="", headers=None). When
detail is omitted, the standard reason phrase for the status code is
used. See Error handling for custom handlers.
jsonify¶
jsonify builds a
JSONResponse. Pass keyword arguments
to build an object, or a single positional value (dict, list, or any
JSON-serialisable value):
from veloce import Veloce, jsonify
app = Veloce()
@app.get("/kw")
async def kw():
return jsonify(name="alice", age=30) # -> {"name": "alice", "age": 30}
@app.get("/positional")
async def positional():
return jsonify([1, 2, 3]) # -> [1, 2, 3]
Passing both positional and keyword arguments raises TypeError. When
called inside a request, jsonify honours two app.config flags:
JSON_SORT_KEYS (default True) sorts dict keys, and
JSONIFY_PRETTYPRINT_REGULAR (default False) indents the output.
redirect¶
redirect builds a redirect response. The
default status code is 302:
from veloce import Veloce, redirect
app = Veloce()
@app.get("/old")
async def old():
return redirect("/new")
@app.get("/permanent")
async def permanent():
return redirect("/new", code=301)
The signature is redirect(location, code=302, headers=None). See RFC
9110 §15.4 for the
redirect status codes — 303 forces a GET, 307 and 308 preserve the
method.
make_response¶
make_response coerces a body, status
code, and headers into a Response, picking
the response type from the body. A str becomes an HTML response, bytes an
octet-stream, and a dict or list a JSON response:
from veloce import Veloce, make_response
app = Veloce()
@app.get("/custom")
async def custom():
resp = make_response("<h1>Hello</h1>", 200)
resp.headers["X-Custom"] = "1"
return resp
@app.get("/created")
async def created():
return make_response({"id": 1}, 201)
The signature is make_response(body=b"", status_code=200, headers=None,
content_type=None). A Pydantic model (anything with model_dump) is
serialised to JSON.
send_file¶
send_file serves a file from a filesystem
path and returns a FileResponse with
conditional-GET headers (Last-Modified, ETag) already set:
from veloce import Veloce, send_file
app = Veloce()
@app.get("/report")
async def report():
return send_file("reports/q1.pdf")
@app.get("/download")
async def download():
return send_file(
"reports/q1.pdf",
as_attachment=True,
download_name="quarter-1.pdf",
max_age=3600,
)
The signature is send_file(path_or_file, mimetype=None,
as_attachment=False, download_name=None, last_modified=None, etag=True,
max_age=None). Set as_attachment=True to add a
Content-Disposition: attachment header, etag=False to suppress the
auto-generated ETag, and max_age= to add a Cache-Control header.
Do not pass user input straight to send_file
send_file does not sanitise the path against directory traversal.
To serve a file whose name comes from the request, use
send_from_directory (or send_from_directory_async), which is
traversal-safe and returns 403 on any escape attempt. See the
OWASP path-traversal
reference.
send_file blocks inside async handlers
send_file calls the blocking FileResponse
constructor, which reads file metadata synchronously and emits a
DeprecationWarning when invoked from an async def handler on a running
event loop. Inside async routes use the async-safe alternatives instead:
async_send_file (the async counterpart of send_file, same arguments),
await FileResponse.from_path(...) for a lower-level single file, or
await send_from_directory_async(...) for traversal-safe directory serving.
async_send_file mirrors send_file argument-for-argument but reads the file
in an executor so it never blocks the event loop:
from veloce import Veloce, async_send_file
app = Veloce()
@app.get("/download")
async def download():
return await async_send_file(
"reports/q1.pdf",
as_attachment=True,
download_name="quarter-1.pdf",
max_age=3600,
)
flash¶
flash queues a one-time message for the next
request — the classic POST/redirect/GET pattern. Because the message has
to survive a redirect, the queue is carried in the session, so flash
requires SessionMiddleware (or ServerSessionMiddleware):
from veloce import (
SessionMiddleware,
Veloce,
flash,
get_flashed_messages,
redirect,
)
app = Veloce()
app.add_middleware(SessionMiddleware, secret_key="change-me-in-production")
@app.post("/items")
async def create_item():
flash("Item created successfully")
return redirect("/items")
@app.get("/items")
async def list_items():
messages = get_flashed_messages()
return {"flashes": messages}
The signature is flash(message, category="message"). Read the queue
with get_flashed_messages(with_categories=False, category_filter=None),
which drains it — each message is shown once. Pass
with_categories=True to receive (category, message) pairs:
from veloce import SessionMiddleware, Veloce, flash, get_flashed_messages
app = Veloce()
app.add_middleware(SessionMiddleware, secret_key="change-me-in-production")
@app.get("/notify")
async def notify():
flash("Saved", "success")
flash("Check your input", "error")
return {"messages": get_flashed_messages(with_categories=True)}
Calling flash without session middleware installed raises
RuntimeError. Calling get_flashed_messages outside a request returns
an empty list, so it is safe to call during a template render.
Next steps¶
- Sessions — the session backends
sessionandflashbuild on. - Requests & Responses — the
Requestobject and the response classesjsonify,make_response, andredirectreturn. - Templates —
get_flashed_messagesis designed to be called from a template. - The API reference lists every helper with full signatures.