API Helpers
fast_app.core.api provides validation, pagination, and filtering utilities to keep controllers clean. This module handles request/query parsing, Pydantic validation, async rule checks, and common pagination patterns.
Request validation
validate_request(schema, *, partial=False)
Parse and validate JSON request bodies. Returns the schema instance and stores the validated dictionary in quart.g.validated.
from fast_app.core.api import validate_request
from quart import g
async def store(data: LeadSchema):
# `data` is the schema instance, `data.validated` is the dict
lead = await Lead.create(data.validated)
return LeadResource(lead)
- Runs Pydantic field validation first.
- If the schema extends
fast_validation.Schema, callsawait schema.validate(partial=partial)to run async rules. - Raises
UnprocessableEntityException(422) on validation failure with sanitized error details.
validate_query(schema, *, partial=False)
Parse and validate query parameters. Returns the schema instance and stores the validated dictionary in g.validated_query.
from fast_app.core.api import validate_query
class LeadIndexFilter(Schema):
source: Literal["direct", "eshop", "pharmacy"] | None = None
rep_id: ObjectIdField | None = None
is_highlighted: bool | None = None
class Meta:
rules = [
Schema.Rule("$.rep_id", [ExistsValidatorRule(allow_null=True)]),
]
# Route.get("/lead", lead_controller.index)
async def index(filter: LeadIndexFilter):
# filter is validated automatically
return await list_paginated(Lead, LeadResource, filter=filter)
When you omit the model, ExistsValidatorRule infers it from the field name (e.g., rep_id → Rep).
The helper intelligently collects query parameters:
- Scalar fields:
?rep_id=abc123→{"rep_id": "abc123"} - List fields:
?tags=a&tags=bor?tags[]=a&tags[]=bor?tags=a,b,c→ all merged into a list - Type coercion happens during Pydantic validation
Pagination
FastApp includes three pagination helpers that return {meta, data} dictionaries ready for JSON serialization.
list_paginated(model, resource, *, filter=None, sort=None)
Standard pagination with count + find. Accepts query parameters page, per_page, sort_by, sort_direction.
from fast_app.core.api import list_paginated
async def index():
return await list_paginated(Lead, LeadResource)
Meta response includes total, current_page, per_page, last_page, skip, displaying.
search_paginated(model, resource, *, filter=None, sort=None)
Text search across model fields via model.search(). Requires query parameter search plus pagination params.
async def search():
return await search_paginated(Lead, LeadResource)
paginate(Model, Resource, *, filter=None, sort=None)
Convenience wrapper: checks for ?search=... and delegates to search_paginated or list_paginated accordingly.
async def index():
return await paginate(Lead, LeadResource)
Filtering
Schema-based filters
Pass a validated schema instance to filter to restrict results. The schema is converted to a dictionary (via model_dump(exclude_unset=True)) and merged with the base query.
class LeadIndexFilter(Schema):
source: Literal["direct", "eshop", "pharmacy", "distributor"] | None = None
county_id: ObjectIdField | None = None
is_highlighted: bool | None = None
class Meta:
rules = [
Schema.Rule("$.county_id", [ExistsValidatorRule(allow_null=True)]),
]
async def index(filter: LeadIndexFilter):
return await paginate(Lead, LeadResource, filter=filter)
Unset fields are excluded, so only provided filters apply.
get_mongo_filter_from_query(*, param_name="filter", allowed_fields=None, allowed_ops=None)
Parse a JSON (or base64-JSON) Mongo filter from the query string with strict operator and field allowlists.
from fast_app.core.api import get_mongo_filter_from_query
async def advanced_index():
extra_filter = get_mongo_filter_from_query(
allowed_fields=["name", "tags", "created_at"],
allowed_ops=["$eq", "$in", "$gte", "$lte"],
)
return await list_paginated(Item, ItemResource, filter=extra_filter)
Clients can send complex queries:
?filter={"name":{"$in":["A","B"]},"created_at":{"$gte":"2024-01-01"}}
or base64-encoded for URL safety. The helper validates operators/fields and raises UnprocessableEntityException if disallowed constructs appear.
Default allowed operators include $and, $or, $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex, $size, $all, $elemMatch.
Request helpers
get_client_ip()
Extract the client's IP address from X-Forwarded-For, X-Real-IP, or fallback to request.remote_addr. Useful for logging, rate limiting, or geolocation.
from fast_app.core.api import get_client_ip
async def track():
ip = get_client_ip()
await log_access(ip)
get_bearer_token()
Parse the Authorization: Bearer <token> header and return the token string, or None if absent.
from fast_app.core.api import get_bearer_token
async def validate_custom_auth():
token = get_bearer_token()
if token:
user = await verify_jwt(token)
Raises AppException if called outside a request context.
Tips
- Use
partial=Trueinvalidate_request/validate_queryfor PATCH endpoints to skip unset fields. - Combine schema filters with
get_mongo_filter_from_queryfor admin endpoints that need flexible querying. - Pagination helpers run
model.count()andmodel.find()concurrently viaasyncio.gatherfor better performance. - Schema-based filters leverage async rule validation, so you can enforce relational constraints (e.g.,
ExistsValidatorRule) before hitting the database.
With these helpers, controllers stay thin and focused on orchestration while validation and data fetching logic lives in reusable, testable utilities.