Debugging¶
Veloce has two development conveniences for unhandled errors: a rich HTML
traceback page served when debug is on, and
a PROPAGATE_EXCEPTIONS switch that re-raises exceptions out of dispatch so a
test sees the real traceback instead of a 500. Both ship with Veloce — there
is nothing to install.
Enabling debug mode¶
Pass debug=True to the constructor, or set app.debug (it is bound to
app.config["DEBUG"]).
from veloce import Veloce
app = Veloce(debug=True)
@app.get("/boom")
async def boom():
raise ValueError("something went wrong")
if __name__ == "__main__":
app.run(port=8000)
Visit http://127.0.0.1:8000/boom in a browser and Veloce renders the
exception type, message, and every traceback frame with a window of source
around the failing line. Chained exceptions (raise ... from), __notes__
(PEP 678), and exception groups (PEP 654) are all shown.
Note
app.run() is the built-in development server. It is for local use only;
run under a hardened ASGI server (uvicorn module:app) in production. See
Deployment.
HTML for browsers, plain text for tools¶
The response format follows the request's Accept header. A browser sending
Accept: text/html gets the styled HTML page; curl, CLI tools, and
programmatic clients (no Accept, */*, or a text/plain preference) get the
plain-text traceback instead.
Never enable debug in production
The traceback page exposes source code, file paths, and local framework
internals. Keep debug=False for anything reachable beyond localhost.
Binding the dev server to a non-local host with debug=True logs a warning
for exactly this reason.
The debug page is read-only¶
Veloce's traceback page is a navigable HTML view, not an interactive in-browser console. It contains no form, no input, no JavaScript that posts back, and no endpoint that evaluates user-supplied code. Live frame evaluation is intentionally unimplemented because it turns a debug page into a remote-code-execution surface.
Note
Every value interpolated into the page — file paths, source lines, the exception message — is HTML-escaped, so exception content cannot inject markup.
Surfacing exceptions in tests¶
By default an unhandled exception becomes a 500 response, which is correct
for production but hides the real error from a test. Set PROPAGATE_EXCEPTIONS
to re-raise it out of dispatch so the assertion sees the original traceback.
from veloce import TestClient, Veloce
app = Veloce()
app.config["PROPAGATE_EXCEPTIONS"] = True
@app.get("/boom")
async def boom():
raise ValueError("something went wrong")
client = TestClient(app)
try:
client.get("/boom")
assert False, "expected the exception to propagate"
except ValueError as exc:
assert str(exc) == "something went wrong"
Without the flag, the same call returns a response instead of raising:
from veloce import TestClient, Veloce
app = Veloce()
@app.get("/boom")
async def boom():
raise ValueError("something went wrong")
client = TestClient(app)
resp = client.get("/boom")
assert resp.status_code == 500
Propagation resolution¶
PROPAGATE_EXCEPTIONS is a tri-state. When unset (None), it falls back to
the combination of DEBUG and TESTING: exceptions propagate only when both
are enabled. An explicit value always wins.
PROPAGATE_EXCEPTIONS |
DEBUG |
TESTING |
Exceptions propagate |
|---|---|---|---|
True |
(any) | (any) | Yes. |
False |
(any) | (any) | No — becomes a 500. |
None (default) |
True |
True |
Yes. |
None (default) |
otherwise | otherwise | No — becomes a 500. |
So enabling both DEBUG and TESTING is the implicit way to get propagation
without naming the key:
from veloce import Veloce
app = Veloce(debug=True)
app.config["TESTING"] = True # DEBUG + TESTING -> exceptions propagate
Note
Registered exception handlers run before propagation is considered. A
handler that returns a response for the raised type wins; propagation only
applies to exceptions that reach the unhandled 500 path. See
Error handling.
Next steps¶
- Convert raised exceptions into responses — see Error handling.
- Write tests against the in-memory client — see Testing.
- Run the app for local development — see Deployment.
- Full signatures are in the API reference.