Request & Response Models¶
Request¶
Request
dataclass
¶
Request(method: HTTPMethod, path: str, headers: Union[Dict[str, str], MultiValueHeaders], body: Optional[BinaryIO] = None, query_params: Optional[Dict[str, str]] = None, path_params: Optional[Dict[str, str]] = None, tls: bool = False, client_cert: Optional[Dict[str, Any]] = None)
Represents an HTTP request.
Supports the ASGI TLS extension for TLS/SSL connection information.
The body is a file-like stream of bytes that can be read by content parsers. This allows efficient handling of large request bodies without loading everything into memory.
Functions¶
__post_init__ ¶
Ensure headers is a MultiValueHeaders for case-insensitive header lookups.
get_accept_header ¶
Get the Accept header, defaulting to / if not present.
get_authorization_header ¶
Get the Authorization header.
get_if_match ¶
Get the If-Match header values as a list of ETags.
get_if_none_match ¶
Get the If-None-Match header values as a list of ETags.
get_if_modified_since ¶
Get the If-Modified-Since header as a datetime object.
get_if_unmodified_since ¶
Get the If-Unmodified-Since header as a datetime object.
Response¶
Response
dataclass
¶
Response(status_code: int, body: Optional[Union[str, bytes, BinaryIO, Path, dict, list]] = None, headers: Optional[Union[Dict[str, str], MultiValueHeaders]] = None, content_type: Optional[str] = None, request: Optional[Request] = None, available_content_types: Optional[list] = None, pre_calculated_headers: Optional[Union[Dict[str, str], MultiValueHeaders]] = None, etag: Optional[str] = None, last_modified: Optional[datetime] = None, range_start: Optional[int] = None, range_end: Optional[int] = None)
Represents an HTTP response.
The body can be: - str: Will be encoded to UTF-8 bytes - bytes: Used directly - BinaryIO: File-like object that will be streamed (useful for large files, S3 objects, etc.) - Path: Local filesystem path to a file (will be served efficiently) - dict/list: Will be JSON-encoded - None: Empty response body
For Path objects: - ASGI servers will use the path send extension for efficient file serving - Lambda will read the file and send as body - Content-Type is automatically detected from file extension - Content-Length is automatically set from file size
Range Request Support: - range_start/range_end: Set by framework when processing Range header - Adapters use these fields to send only requested byte range - ASGI adapter uses zero-copy extension when possible
Functions¶
set_etag ¶
Set the ETag header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
etag
|
str
|
The ETag value (without quotes) |
required |
weak
|
bool
|
Whether this is a weak ETag (prefixed with W/) |
False
|
set_last_modified ¶
Set the Last-Modified header.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
last_modified
|
datetime
|
The last modified datetime |
required |
is_range_response ¶
Check if this is a partial content (range) response.
Returns:
| Type | Description |
|---|---|
bool
|
True if range_start and range_end are set, indicating this is a 206 Partial Content response |
generate_etag_from_content ¶
Generate and set ETag based on response body content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weak
|
bool
|
Whether to generate a weak ETag |
False
|
Note
For streaming bodies (BinaryIO), this will read the entire stream to calculate the hash. The stream will be reset to the beginning after hashing.
HTTPMethod¶
HTTPMethod ¶
Bases: Enum
Enumeration of supported HTTP methods.
Overview¶
These are the core models for handling HTTP requests and responses in RestMachine.
Request Model¶
The Request object encapsulates an incoming HTTP request with:
- method - HTTP method (GET, POST, PUT, DELETE, etc.)
- path - Request path
- headers - HTTP headers (case-insensitive)
- query_params - Query string parameters
- path_params - Path parameters from route matching
- body - Request body (string or bytes)
Response Model¶
The Response object represents an HTTP response with:
- status_code - HTTP status code (200, 404, 500, etc.)
- body - Response body (string)
- headers - HTTP response headers
HTTPMethod Enum¶
Enum of supported HTTP methods: - GET - POST - PUT - DELETE - PATCH - HEAD - OPTIONS
Request Usage¶
Accessing Request Data¶
@app.get('/users/{user_id}')
def get_user(request: Request):
# Path parameters
user_id = request.path_params['user_id']
# Query parameters
page = request.query_params.get('page', '1')
# Headers
auth = request.headers.get('authorization')
# Body (for POST/PUT)
# body = request.body
return {"user_id": user_id, "page": page}
Creating Requests Manually¶
Useful for testing:
from restmachine import Request, HTTPMethod
request = Request(
method=HTTPMethod.GET,
path="/users/123",
headers={"authorization": "Bearer token"},
query_params={"page": "1"},
path_params={"user_id": "123"},
body=""
)
response = app.execute(request)
Response Usage¶
Returning Responses¶
Handlers can return responses in multiple ways:
1. Dictionary (auto-converted to JSON):
2. Tuple (body, status_code):
3. Tuple (body, status_code, headers):
@app.get('/file')
def download_file():
return "file contents", 200, {
"Content-Type": "text/plain",
"Content-Disposition": "attachment; filename=file.txt"
}
4. Explicit Response object:
from restmachine import Response
@app.get('/custom')
def custom_response():
return Response(
status_code=200,
body='{"custom": true}',
headers={"X-Custom-Header": "value"}
)
Response Status Codes¶
Use http.HTTPStatus for readable status codes:
from http import HTTPStatus
@app.post('/users')
def create_user(request):
return {"created": True}, HTTPStatus.CREATED
@app.get('/users/{user_id}')
def get_user(request):
user_id = request.path_params['user_id']
user = db.get(user_id)
if not user:
return {"error": "Not found"}, HTTPStatus.NOT_FOUND
return user, HTTPStatus.OK
Common status codes:
- 200 OK - Successful GET/PUT/PATCH
- 201 Created - Successful POST
- 204 No Content - Successful DELETE
- 400 Bad Request - Invalid request
- 401 Unauthorized - Authentication required
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource not found
- 500 Internal Server Error - Server error
HTTPMethod Usage¶
The HTTPMethod enum provides type-safe HTTP method constants:
from restmachine import HTTPMethod
# Used internally for route matching
@app.route(HTTPMethod.POST, '/users')
def create_user(request):
return {"created": True}
# Or use string (converted automatically)
@app.route('PATCH', '/users/{user_id}')
def patch_user(request):
return {"patched": True}
Headers¶
Request Headers¶
Headers are case-insensitive:
@app.get('/data')
def get_data(request: Request):
# All of these work:
auth = request.headers.get('Authorization')
auth = request.headers.get('authorization')
auth = request.headers.get('AUTHORIZATION')
content_type = request.headers.get('content-type')
return {"auth": auth, "type": content_type}
Response Headers¶
Set response headers in tuple return or Response object:
# Via tuple
@app.get('/data')
def get_data():
return {"data": ...}, 200, {
"Cache-Control": "max-age=3600",
"X-API-Version": "1.0"
}
# Via Response
from restmachine import Response
@app.get('/data')
def get_data():
return Response(
status_code=200,
body='{"data": "value"}',
headers={
"Cache-Control": "max-age=3600",
"X-API-Version": "1.0"
}
)
Query Parameters¶
Access query parameters from the request:
@app.get('/search')
def search(request: Request):
# GET /search?q=python&page=2
query = request.query_params.get('q')
page = int(request.query_params.get('page', '1'))
results = search_db(query, page=page)
return {"results": results, "page": page}
Request Body¶
Access and parse request bodies:
@app.post('/users')
def create_user(request: Request):
import json
# Parse JSON body
data = json.loads(request.body)
name = data['name']
email = data['email']
user = db.create_user(name=name, email=email)
return {"created": user}, 201
For automatic validation, use @app.validates:
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
email: str
@app.validates
def validate_user(request: Request) -> UserCreate:
import json
return UserCreate.model_validate(json.loads(request.body))
@app.post('/users')
def create_user(validate_user: UserCreate):
# validate_user is already parsed and validated
return {"created": validate_user.model_dump()}, 201
See Also¶
- Application API - Main application class
- Basic Application Guide - Core concepts
- Validation Guide - Request validation
- Testing Guide - Testing with Request/Response