Exceptions Reference¶
All exceptions are importable from django_bolt.exceptions.
from django_bolt.exceptions import (
HTTPException,
RequestValidationError,
ResponseValidationError,
BadRequest,
NotFound,
# ... etc
)
HTTP Exceptions¶
HTTPException¶
Base class for all HTTP error responses.
HTTPException(
status_code: int = 500,
detail: str = "",
headers: dict[str, str] = None,
extra: dict | list = None,
)
| Parameter | Type | Description |
|---|---|---|
status_code |
int |
HTTP status code |
detail |
str |
Error message (defaults to HTTP phrase) |
headers |
dict |
Headers to include in response |
extra |
dict \| list |
Additional data in response |
Response format:
Client Errors (4xx)¶
| Exception | Status | Description |
|---|---|---|
BadRequest |
400 | Invalid request syntax or parameters |
Unauthorized |
401 | Authentication required or failed |
Forbidden |
403 | Insufficient permissions |
NotFound |
404 | Resource not found |
MethodNotAllowed |
405 | HTTP method not supported |
NotAcceptable |
406 | Cannot produce requested format |
Conflict |
409 | Request conflicts with current state |
Gone |
410 | Resource permanently deleted |
UnprocessableEntity |
422 | Semantic validation error |
TooManyRequests |
429 | Rate limit exceeded |
Usage:
from django_bolt.exceptions import NotFound, BadRequest
@api.get("/users/{user_id}")
async def get_user(user_id: int):
if user_id < 1:
raise BadRequest(detail="Invalid user ID")
user = await User.objects.filter(id=user_id).afirst()
if not user:
raise NotFound(detail="User not found")
return {"id": user.id}
Server Errors (5xx)¶
| Exception | Status | Description |
|---|---|---|
InternalServerError |
500 | Unexpected server error |
NotImplemented |
501 | Feature not implemented |
BadGateway |
502 | Invalid upstream response |
ServiceUnavailable |
503 | Server temporarily unavailable |
GatewayTimeout |
504 | Upstream server timeout |
Validation Errors¶
RequestValidationError¶
Raised when request data fails validation. Returns 422 Unprocessable Entity.
Multi-error collection: Django-Bolt collects all @field_validator and @model_validator errors before raising, so users see all issues at once:
# Access all errors
try:
serializer = MySerializer(email="bad", password="short")
except RequestValidationError as e:
all_errors = e.errors() # Returns list of error dicts
print(str(e)) # "body.email: Invalid email; body.password: Too short"
Response format:
{
"detail": [
{
"loc": ["body", "email"],
"msg": "Invalid email",
"type": "value_error"
},
{
"loc": ["body", "password"],
"msg": "Password too short",
"type": "value_error"
}
]
}
ResponseValidationError¶
Raised when handler return value fails validation. Returns 500 Internal Server Error. With DEBUG=False, the response body does not show the validation errors. Bolt writes them to the server log.
FileResponse errors¶
FileResponse raises these errors when it gets the path of the file. File raises ResponseFileNotFound when it reads the file. Only these errors give a 404 or a 403, and the 404 and 403 responses do not show the path. A FileNotFoundError or a PermissionError of other code gives a 500. To answer 404 or 403 from handler code, raise NotFound or Forbidden.
| Exception | Subclass of | Status | When raised |
|---|---|---|---|
ResponseFileNotFound |
FileNotFoundError |
404 | The path names no regular file: it does not exist, it is a directory, or it is not valid |
ResponseFileNotAllowed |
PermissionError |
403 | The path is outside BOLT_ALLOWED_FILE_PATHS, also when it names no file or the server cannot read it |
Validation Layers¶
Django-Bolt's Serializer has two validation layers:
| Layer | Source | Raw msgspec | With model_validate() |
|---|---|---|---|
| Meta constraints | Meta(min_length, pattern, ge, le, ...) |
Fail-fast | Collects all errors |
| Custom validators | @field_validator, @model_validator |
N/A | Collects all errors |
Multi-error collection¶
When using model_validate() or model_validate_json(), all errors are collected before raising RequestValidationError:
# Both Meta constraints AND custom validators collect errors
try:
UserSerializer.model_validate({
"name": "X", # Meta: min_length=2 violated
"email": "bad", # Meta: pattern violated
"age": 200 # Meta: le=150 violated
})
except RequestValidationError as e:
print(len(e.errors())) # 3 errors, not just 1
This approach is inspired by Litestar - when validation fails, each field is validated individually to collect all errors.
When to expect which exception¶
| How you validate | Exception | Multi-error? |
|---|---|---|
Serializer.model_validate(dict) |
RequestValidationError |
Yes |
Serializer.model_validate_json(json) |
RequestValidationError |
Yes |
Serializer(field=value) (direct) |
RequestValidationError |
Yes (custom only) |
msgspec.convert(data, Serializer) |
msgspec.ValidationError |
No (fail-fast) |
msgspec.json.decode(json, type=Serializer) |
msgspec.ValidationError |
No (fail-fast) |
Note: Direct instantiation (Serializer(field=value)) runs custom validators but bypasses Meta constraints - this is msgspec's design for performance.
Validation Error Types¶
When validation fails, Django-Bolt returns structured errors with these types:
| Type | Description | Example Message |
|---|---|---|
missing_field |
Required field not provided, at any depth | Object missing required field 'price' |
validation_error |
Type mismatch or Meta constraint violation | Expected 'int', got 'str' |
json_invalid |
Malformed JSON | Invalid JSON at line 2, column 5 |
value_error |
Custom @field_validator or @model_validator failure |
Invalid email format |
Error Location (loc)¶
The loc field indicates where the error occurred:
| Location | Format | Example |
|---|---|---|
| Request body | ["body", "field"] |
["body", "email"] |
| Nested field | ["body", "user", "name"] |
["body", "address", "city"] |
| Array item | ["body", "0", "field"] |
["body", "0", "id"] |
| Query param | ["query", "param"] |
["query", "page"] |
| Path param | ["path", "param"] |
["path", "user_id"] |
| Header | ["header", "name"] |
["header", "authorization"] |
The loc of a missing_field error ends with the name of the missing field, also in a nested struct or in an array item. The msg names the field and has no path. The index of an array item in loc is a string, such as "0".
Example Validation Errors¶
Missing required field:
{
"detail": [
{
"loc": ["body", "price"],
"msg": "Object missing required field `price`",
"type": "missing_field"
}
]
}
Missing field in a nested struct:
For User(name: str, address: Address) and Address(city: str, zip: str), the body {"name": "Ada", "address": {"city": "Paris"}} gives:
{
"detail": [
{
"loc": ["body", "address", "zip"],
"msg": "Object missing required field `zip`",
"type": "missing_field"
}
]
}
For a list of Address items, {"items": [{"city": "Paris"}]} gives "loc": ["body", "items", "0", "zip"].
Type mismatch:
{
"detail": [
{
"loc": ["body", "age"],
"msg": "Expected `int`, got `str`",
"type": "validation_error"
}
]
}
Invalid JSON:
{
"detail": [
{
"loc": ["body", 2, 15],
"msg": "Invalid JSON at line 2, column 15: unexpected character",
"type": "json_invalid"
}
]
}
Multiple errors:
{
"detail": [
{
"loc": ["body", "email"],
"msg": "Expected `str`, got `int`",
"type": "validation_error"
},
{
"loc": ["body", "age"],
"msg": "Object missing required field `age`",
"type": "missing_field"
}
]
}
Adding Custom Headers¶
Adding Extra Data¶
raise BadRequest(
detail="Validation failed",
extra={
"field": "email",
"value": "invalid@",
"suggestion": "Enter a valid email address"
}
)
Response:
{
"detail": "Validation failed",
"extra": {
"field": "email",
"value": "invalid@",
"suggestion": "Enter a valid email address"
}
}
Status Code Summary¶
| Code | Exception | When Raised |
|---|---|---|
| 400 | BadRequest |
Explicit raise only |
| 401 | Unauthorized |
Authentication failed |
| 403 | Forbidden |
Guard/permission denied |
| 404 | NotFound |
Resource not found, Django's Http404, ResponseFileNotFound from FileResponse |
| 405 | MethodNotAllowed |
Wrong HTTP method |
| 406 | NotAcceptable |
Cannot produce requested format |
| 409 | Conflict |
State conflict |
| 410 | Gone |
Resource deleted |
| 422 | UnprocessableEntity |
Validation errors, missing required params (automatic) |
| 429 | TooManyRequests |
Rate limit exceeded |
| 500 | InternalServerError |
Unhandled exceptions, response validation |
| 501 | NotImplemented |
Feature not implemented |
| 502 | BadGateway |
Upstream error |
| 503 | ServiceUnavailable |
Server unavailable |
| 504 | GatewayTimeout |
Upstream timeout |