Response¶
The Response is an optional component of every Event. When an event declares a response field, the response carries the result of processing back to the event's origin.
How responses work¶
@response
class MyResponse(Response):
result: str
count: int
@event
class MyEvent(Event):
response: MyResponse
Key mechanics:
The
@eventdecorator detectsresponse: MyResponseand:Validates that
MyResponseis a subclass ofResponse.Makes the
responsefieldinit=False.Auto-instantiates it via
default_factory.
When a Manager handler modifies
event.response, the response is automatically sent when the handler returns.You never pass
response=...when constructing an event.
Response Status¶
Every response has a status — an internal attribute that tracks processing state.
Status values¶
Status |
Meaning |
Trigger |
|---|---|---|
|
Default — no processing has occurred |
Initial state before any handler runs |
|
Processing succeeded |
Automatically set when any response field is assigned a value, or via explicit |
|
Processing failed |
Set explicitly via |
Auto-status behavior¶
# In a Manager handler:
event.response.result = "done" # ← status auto-set to OK
# Explicit status:
event.response.set_status(ResponseStatus.FAILED)
event.response.set_status(ResponseStatus.OK)
Status vs HTTP status¶
Important: ResponseStatus.OK / ResponseStatus.FAILED do not control HTTP status codes. They are internal EDRI status indicators.
HTTP status is controlled by response directives (see below). By default, the HTTP status is 200. Directives can override it.
Internal Status |
Default HTTP Status |
When overridden |
|---|---|---|
|
200 |
Via directives (e.g., redirect → 302) |
|
200 |
Via directives (e.g., NotFoundResponseDirective → 404) |
Directives¶
Directives are instructions that modify how the API layer translates a response for the client — across any transport it supports (HTTP, WebSocket, and any API endpoints added in the future), not HTTP alone. They are attached to event.response using add_directive(directive, /, *, ignore_status=False). Adding a directive normally also marks the response as changed and, if it had no explicit status yet, sets its status to OK. Pass ignore_status=True to append the directive without that side effect — used when decorating a response that must keep its existing status (e.g. a FAILED error response).
Directive types (base — usable for both HTTP and WebSocket)¶
Directive Class |
HTTP Effect |
Typical Use Case |
|---|---|---|
|
500 Internal Server Error |
Unexpected errors |
|
401 Unauthorized |
Authentication failures |
These live in edri.dataclass.directive.base and extend ResponseDirective directly, so they work on both HTTP and WebSocket transports.
Directive types (HTTP)¶
Directive Class |
HTTP Effect |
Typical Use Case |
|---|---|---|
|
|
Session management, preferences |
|
403 Forbidden |
Authorization failures |
|
404 Not Found |
Resource not found |
|
409 Conflict |
Resource state conflicts |
|
400 Bad Request |
Invalid client input |
|
422 Unprocessable Content |
Semantically invalid request |
|
415 Unsupported Media Type |
Request payload in an unaccepted format |
|
304 Not Modified |
Conditional GET (ETag match) |
|
503 Service Unavailable |
Temporarily overloaded/down |
|
501 Not Implemented |
Requested functionality unsupported |
|
206 Partial Content |
Successful byte-range response |
|
416 Range Not Satisfiable |
Invalid or unsatisfiable Range |
|
201 Created (+ optional |
Resource created |
|
202 Accepted |
Accepted for async processing |
|
204 No Content |
Success with no response body |
|
301 Moved Permanently (+ |
Resource permanently moved |
|
302 Found (+ |
Temporary redirect (common default) |
|
303 See Other (+ |
Post/Redirect/Get |
|
307 Temporary Redirect (+ |
Temporary redirect, keeps method |
|
308 Permanent Redirect (+ |
Permanent redirect, keeps method |
|
410 Gone |
Resource permanently removed |
|
429 Too Many Requests (+ optional |
Rate limiting |
|
No status change (only adds header) |
Setting arbitrary response headers |
All HTTP directives live in edri.dataclass.directive.http.
Directive types (HTML)¶
Directive Class |
Effect |
Typical Use Case |
|---|---|---|
|
Browser redirect (302) |
Form submission → thank-you page |
RedirectResponseDirective lives in edri.dataclass.directive.html and is meant for HTML endpoints — it can take an API Event class and resolve it to the target URL (see below).
For REST/HTTP endpoints, use the per-status redirect directives from edri.dataclass.directive.http instead — MovedPermanentlyResponseDirective (301), FoundResponseDirective (302), SeeOtherResponseDirective (303), TemporaryRedirectResponseDirective (307), and PermanentRedirectResponseDirective (308). Each takes a location string and emits the matching 3xx status together with a Location header.
Using directives¶
Adding a directive¶
from edri.dataclass.directive.http import NotFoundResponseDirective
event.response.add_directive(NotFoundResponseDirective())
Adding a redirect (to a URL)¶
from edri.dataclass.directive.html import RedirectResponseDirective
event.response.set_status(ResponseStatus.OK)
event.response.add_directive(RedirectResponseDirective("/path/to/page"))
Adding a redirect (to an API Event)¶
event.response.add_directive(
RedirectResponseDirective(ContactThanks)
)
The framework resolves ContactThanks to the URL declared in its @api(url=...) decorator. For events with path placeholders:
event.response.add_directive(
RedirectResponseDirective(UserPage, user_id=42)
)
Access denied¶
from edri.dataclass.directive.http import AccessDeniedResponseDirective
event.response.set_status(ResponseStatus.FAILED)
event.response.add_directive(
AccessDeniedResponseDirective(message="Admin access required")
)
Internal server error¶
from edri.dataclass.directive.base import InternalServerErrorResponseDirective
event.response.set_status(ResponseStatus.FAILED)
event.response.add_directive(
InternalServerErrorResponseDirective(message="Database connection failed")
)
HTTP status code mapping¶
When the API layer processes a response, it determines the HTTP status code as follows:
Start with 200 (default).
If any directive overrides the status, that status is used.
If multiple directives set the status, last wins.
Directive |
HTTP Status |
|---|---|
None (default) |
200 OK |
|
302 Found (HTML only) |
|
404 Not Found |
|
403 Forbidden |
|
401 Unauthorized |
|
500 Internal Server Error |
|
400 Bad Request |
|
409 Conflict |
|
422 Unprocessable Content |
|
304 Not Modified |
|
503 Service Unavailable |
|
206 Partial Content |
|
416 Range Not Satisfiable |
|
No status change (only adds header) |
|
No status change (only adds cookie) |
Response lifecycle¶
flowchart TD
Recv["Event received by Manager"] --> Auto["Event.response<br/>auto-instantiated<br/>status = NONE"]
Auto --> Exec["Handler executes"]
Exec --> A1["field assignment → status auto-set to OK"]
Exec --> A2["set_status(FAILED) → status = FAILED"]
Exec --> A3["add_directive(...) → directive added"]
A1 --> Ret["Handler returns"]
A2 --> Ret
A3 --> Ret
Ret --> Mod{response modified?}
Mod -->|yes| Send["sent automatically through Router"]
Mod -->|no| Nothing["nothing sent (fire-and-forget)"]
Custom responses¶
Responses can be extended with custom fields:
@response
class PaginatedResponse(Response):
items: list[dict]
total_count: int
page: int
page_size: int
Responses can also use multiple inheritance:
@response
class TimestampedResponse(Response):
created_at: str
updated_at: str
@response
class AuditResponse(TimestampedResponse):
action: str
user_id: int
Gotchas & Notes¶
Responses are optional. If an event does not declare
response, no response is sent.Setting
event.responsein a Manager handler automatically sends it when the handler returns.In a Worker, you must call
self.event_send(event)explicitly — auto-send does not apply.ResponseStatus.FAILEDdoes not produce an HTTP error status by itself. Use directives for HTTP status control.Directive "last wins" applies per type — you can add a cookie directive and a redirect directive in the same response.
HTTP/HTML-specific directives are ignored on WebSocket responses. Base error directives such as
InternalServerErrorResponseDirectiveandUnauthorizedResponseDirectiveare handled by the WebSocket layer and produce an error-shaped response.Response fields whose name starts with
_are concealed: stripped from JSON (REST/WebSocket) output but still passed to HTML templates. Use this for server-side-only data. See Concealed fields (API output).