Validation

One of the most commonly implemented features of a web application is user-input validation. For obvious reasons, this is not only a security issue, but also just plain good practice. You want to make sure your data conforms to expectations, and throw a 400 response when it does not.

Implementation#

Validation with Dataclasses#

With the introduction of Data Classes, Python made it super simple to create objects that meet a defined schema. However, the standard library only supports type checking validation, not runtime validation. Sanic Extensions adds the ability to do runtime validations on incoming requests using dataclasses out of the box. If you also have either pydantic or attrs installed, you can alternatively use one of those libraries.

First, define a model.

@dataclass
class SearchParams:
    q: str

Then, attach it to your route

from sanic_ext import validate

@app.route("/search")
@validate(query=SearchParams)
async def handler(request, query: SearchParams):
    return json(asdict(query))

You should now have validation on the incoming request.

$ curl localhost:8000/search                                       
⚠️ 400 β€” Bad Request
====================
Invalid request body: SearchParams. Error: missing a required argument: 'q'
$ curl localhost:8000/search\?q=python                             
{"q":"python"}

Validation with Pydantic#

You can use Pydantic models also.

First, define a model.

class Person(BaseModel):
    name: str
    age: int

Then, attach it to your route

from sanic_ext import validate

@app.post("/person")
@validate(json=Person)
async def handler(request, body: Person):
    return json(body.dict())

You should now have validation on the incoming request.

$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST  
{"name":"Alice","age":21}

Validation with Attrs#

You can use Attrs also.

First, define a model.

@attrs.define
class Person:
    name: str
    age: int

Then, attach it to your route

from sanic_ext import validate

@app.post("/person")
@validate(json=Person)
async def handler(request, body: Person):
    return json(attrs.asdict(body))

You should now have validation on the incoming request.

$ curl localhost:8000/person -d '{"name": "Alice", "age": 21}' -X POST  
{"name":"Alice","age":21}

What can be validated?#

The validate decorator can be used to validate incoming user data from three places: JSON body data (request.json), form body data (request.form), and query parameters (request.args).

As you might expect, you can attach your model using the keyword arguments of the decorator.

@validate(
    json=ModelA,
    query=ModelB,
    form=ModelC,
)