Django#
Basic#
import json
from dataclasses import dataclass
from typing import Annotated, Optional
from django.http import HttpRequest, HttpResponse, JsonResponse
from koda_validate import *
from koda_validate.serialization import to_serializable_errs
@dataclass
class ContactForm:
name: str
message: str
# Annotated `Validator`s are used if defined -- instead
# of Koda Validate's default for the type)
email: Annotated[str, StringValidator(EmailPredicate())]
subject: Optional[str] = None
def contact(request: HttpRequest) -> HttpResponse:
if request.method != "POST":
return HttpResponse("HTTP method not allowed", status=405)
try:
posted_json = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"_root_": "expected json"}, status=400)
else:
result = DataclassValidator(ContactForm)(posted_json)
match result:
case Valid(contact_form):
print(contact_form)
return JsonResponse({"success": True})
case Invalid() as inv:
return JsonResponse(to_serializable_errs(inv), status=400, safe=False)
Fuller Example (with Async)#
import asyncio
import json
from typing import Annotated, Optional, TypedDict
from django.http import HttpRequest, HttpResponse, JsonResponse
from koda_validate import *
from koda_validate.serialization import SerializableErr, to_serializable_errs
class Captcha(TypedDict):
seed: Annotated[str, StringValidator(ExactLength(16))]
response: Annotated[str, StringValidator(MaxLength(16))]
async def validate_captcha(captcha: Captcha) -> Optional[ErrType]:
"""
after we validate that the seed and response both conform to the types/shapes we want,
we need to check our database to make sure the response is correct
"""
await asyncio.sleep(0.01) # pretend to ask db
if captcha["seed"] != captcha["response"][::-1]:
return SerializableErr({"response": "bad captcha response"})
else:
return None
class ContactForm(TypedDict):
email: Annotated[str, StringValidator(EmailPredicate())]
message: Annotated[str, StringValidator(MaxLength(500), MinLength(10))]
# we only need to explicitly define the TypedDictValidator here because we want
# to include additional validation in validate_captcha
captcha: Annotated[
Captcha, TypedDictValidator(Captcha, validate_object_async=validate_captcha)
]
contact_validator = TypedDictValidator(ContactForm)
async def contact_async(request: HttpRequest) -> HttpResponse:
if request.method != "POST":
return HttpResponse("HTTP method not allowed", status=405)
try:
posted_json = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"__container__": "expected json"}, status=400)
else:
result = await TypedDictValidator(ContactForm).validate_async(posted_json)
match result:
case Valid(contact_form):
print(contact_form)
return JsonResponse({"success": True})
case Invalid() as inv:
return JsonResponse(to_serializable_errs(inv), status=400, safe=False)
# if you want a JSON Schema from a ``Validator``, there's `to_json_schema()`
# schema = to_json_schema(contact_validator)
# hook_into_some_api_definition(schema)