BaseResource
The class every resource inherits from. It is a Django View subclass with async dispatch, attribute-driven configuration and a long list of overridable hooks.
Mental model
A resource is a description of how a model maps to HTTP. You set attributes (what fields are exposed, what is filterable, who owns rows) and BaseResource turns that into endpoints.
model is not mandatory. For RPC-style endpoints (webhooks, OAuth callbacks, search, integrations) skip model and define routes with your own handlers. Those endpoints still benefit from auth, rate limit, security middleware, OpenAPI generation — they just don't get the default CRUD handlers.
Lifecycle
Each request runs through dispatch. The flow is:
- Resolve client IP (honors
TRUSTED_PROXIES) - Enforce rate limit and abuse blocking
- Authenticate (API key, then session cookie)
- Optionally validate
X-TokenifENFORCE_TOKEN - Optionally enforce
ALLOWED_ORIGINS(Origin with Referer fallback) - Switch tenant DB if the session has an account
- Resolve method (custom route via
routesor default GET/POST/PATCH/DELETE) - Try cache (when
cache = Trueand method is GET) - Run
pre_process, parse body, validate against schema if any - For list GET: build filters, paginate, order
- Run the handler
- Serialize response (apply
list_schema, rundehydrate, JSON-encode)
Per-instance state
Class attributes that default to None (lists, dicts) are normalized to per-instance objects in __init__. You can safely mutate self.list_fields, self.queryset_filter, etc., without leaking state across requests.
Overriding handlers
Replace any of get, post, patch, delete for full control, or override the inner pieces (get_objs, get_obj, create_obj, update_obj, delete_obj) to keep the surrounding plumbing.
class ReportResource(BaseResource):
model = Report
async def get_objs(self, request):
# custom list logic, still benefits from pagination + filters
return await super().get_objs(request)Throwing controlled errors
Raise HTTPException(status, detail) from anywhere. The ExceptionMiddleware turns it into a JSON error response.
from zeromcp.exception import HTTPException
if not user.is_admin:
raise HTTPException(403, 'Admins only')0-mcp by Stamatios Stamou Jr — github.com/ssjunior/0-mcp