Advanced Usage¶
Overview¶
For more advanced use cases, you can explicitly declare how each parameter's value is sourced from the request -- from the query parameters, path, body or headers -- as well as define additional validation rules. You import a class named after the request element that is expected to hold the value and assign it to the parameter's default.
from rest_typed import typed_api_view, Query, Path
@typed_api_view(["GET"])
def list_documents(year: date = Path(), title: str = Query(default=None)):
# ORM logic here...
In this example, year
is required and must come from the URL path and title
is an optional query parameter because the default
is set. This is similar to Django REST's serializer fields: passing a default implies that the filed is not required.
from rest_typed import typed_api_view, Header
@typed_api_view(["GET"])
def get_cache_header(cache: str = Header()):
# ORM logic here...
In this example, cache
is required and must come from the headers.
Additional Validation Rules¶
You can use the request element class (Query
, Path
, Body
, Header
) to set additional validation constraints. You'll find that these keywords are consistent with Django REST's serializer fields.
from rest_typed import typed_api_view, Query, Path
@typed_api_view(["GET"])
def search_restaurants(
year: date = Path(),
rating: int = Query(default=None, min_value=1, max_value=5)
):
# ORM logic here...
@typed_api_view(["GET"])
def get_document(id: str = Path(format="uuid")):
# ORM logic here...
@typed_api_view(["GET"])
def search_users(
email: str = Query(default=None, format="email"),
ip_address: str = Query(default=None, format="ip"),
):
# ORM logic here...
View a full list of supported types and additional validation rules.
Nested Body Fields¶
Similar to how source
is used in Django REST to control field mappings during serialization, you can use it to specify the exact path to the request data.
from pydantic import BaseModel
from rest_typed import typed_api_view, Query, Path
class Document(BaseModel):
title: str
body: str
"""
POST
{
"strict": false,
"data": {
"title": "A Dark and Stormy Night",
"body": "Once upon a time"
}
}
"""
@typed_api_view(["POST"])
def create_document(
strict_mode: bool = Body(source="strict"),
item: Document = Body(source="data")
):
# ORM logic here...
You can also use dot-notation to source data multiple levels deep in the JSON payload.
List Validation¶
For the basic case of list validation - validating types within a comma-delimited string - declare the type to get automatic validation/coercion:
from rest_typed import typed_api_view, Query
@typed_api_view(["GET"])
def search_movies(item_ids: List[int] = [])):
print(item_ids)
# GET /movies?items_ids=41,64,3
# [41, 64, 3]
But you can also specify min_length
and max_length
, as well as the delimiter
and specify additional rules for the child items -- think Django REST's ListField.
Import the generic Param
class and use it to set the rules for the child
elements:
from rest_typed import typed_api_view, Query, Param
@typed_api_view(["GET"])
def search_outcomes(
scores: List[int] = Query(delimiter="|", child=Param(min_value=0, max_value=100))
):
# ORM logic ...
@typed_api_view(["GET"])
def search_message(
recipients: List[str] = Query(min_length=1, max_length=10, child=Param(format="email"))
):
# ORM logic ...
Accessing the Request Object¶
You probably won't need to access the request
object directly, as this package will provide its relevant properties as view arguments. However, you can include it as a parameter annotated with its type and it will be injected:
from rest_framework.request import Request
from rest_typed import typed_api_view
@typed_api_view(["GET"])
def search_documens(request: Request, q: str = None):
# ORM logic ...
Interdependent Query Parameter Validation¶
Often, it's useful to validate a combination of query parameters - for instance, a start_date
shouldn't come after an end_date
. You can use complex schema object (Pydantic) for this scenario. In the example below, Query(source="*")
is instructing an instance of SearchParamsSchema
to be populated/validated using all of the query parameters together: request.query_params.dict()
.
from pydantic import BaseModel
from rest_typed import typed_api_view
class SearchParamsSchema(BaseModel):
start_date = fields.Date()
end_date = fields.Date()
@root_validator
def validate_dates(cls, values):
if values["start_date"] >= values["end_date"]:
raise ValueError("end_date must come after start_date")
@typed_api_view(["GET"])
def search_documens(search_params: SearchParamsSchema = Query(source="*")):
# ORM logic ...
(Simple) Access Control¶
You can apply some very basic access control by applying some validation rules to a view parameter sourced from the CurrentUser
request element class. In the example below, a ValidationError
will be raised if the request.user
is not a member of either super_users
or admins
.
from my_pydantic_schemas import BookingSchema
from rest_typed import typed_api_view, CurrentUser
@typed_api_view(["POST"])
def create_booking(
booking: BookingSchema,
user: User = CurrentUser(member_of_any=["super_users", "admins"])
):
# Do something with the request.user
Read more about the Current User
request element class.