Request Data¶
In every application there will be times where sending a payload to the server will be needed.
Esmerald is prepared to handle those with ease and that is thanks to Pydantic.
There are two ways of doing this, using the data or using the payload.
Warning¶
You can only declare data
or payload
in the handler but not both or an ImproperlyConfigured
exception is raised.
The data
field¶
When sending a payload to the backend to be validated, the handler needs to have a data
field declared. Without it,
it will not be possible to process the information and/or will not be recognised.
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
@post("/create")
async def create_user(data: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
Fundamentally the data
field is what holds the information about the sent payload data into the server.
The data can also be simple types such as list
, dict
, str
. It does not necessarily mean you need to always use
pydantic models.
The payload
field¶
Fundamentally is an alternative to data
but does exactly the same. If you are more familiar with
the concept of payload
then this is for you.
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
@post("/create")
async def create_user(payload: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
Nested models¶
You can also do nested models for the data
or payload
to be processed.
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class Address(BaseModel):
zip_code: str
country: str
street: str
region: str
class User(BaseModel):
name: str
email: EmailStr
address: Address
@post("/create")
async def create_user(data: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class Address(BaseModel):
zip_code: str
country: str
street: str
region: str
class User(BaseModel):
name: str
email: EmailStr
address: Address
@post("/create")
async def create_user(payload: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
The data expected to be sent to be validated is all required and expected with the following format:
{
"name": "John",
"email": "john.doe@example.com",
"address": {
"zip_code": "90210",
"country": "United States",
"street": "Orange county street",
"region": "California"
}
}
You can nest as many models as you wish to nest as long as it is send in the right format.
Mandatory fields¶
There are many ways to process and validate a field and also the option to make it non mandatory.
That can be achieved by using the typing Optional
to make it not mandatory.
from typing import Optional
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class Address(BaseModel):
zip_code: str
country: str
street: Optional[str]
region: Optional[str]
class User(BaseModel):
name: str
email: EmailStr
address: Optional[Address]
@post("/create")
async def create_user(data: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
from typing import Optional
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class Address(BaseModel):
zip_code: str
country: str
street: Optional[str]
region: Optional[str]
class User(BaseModel):
name: str
email: EmailStr
address: Optional[Address]
@post("/create")
async def create_user(payload: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
The address
is not mandatory to be send in the payload and therefore it can be done like this:
{
"name": "John",
"email": "john.doe@example.com"
}
But you can also send the address and inside without the mandatory fields:
{
"name": "John",
"email": "john.doe@example.com",
"address": {
"zip_code": "90210",
"country": "United States"
}
}
Field validation¶
What about the field validation? What if you need to validate some of the data being sent to the backend?
Since Esmerald uses pydantic, you can take advantage of it.
from typing import List
from pydantic import BaseModel, EmailStr, Field
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str = Field(min_length=3)
email: EmailStr
hobbies: List[str] = Field(min_items=3)
age: int = Field(ge=18)
@post("/create")
async def create_user(data: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
from typing import List
from pydantic import BaseModel, EmailStr, Field
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str = Field(min_length=3)
email: EmailStr
hobbies: List[str] = Field(min_items=3)
age: int = Field(ge=18)
@post("/create")
async def create_user(payload: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
Since pydantic runs the validations internally, you will have the errors thrown if something is missing.
The expected payload would be:
{
"name": "John",
"email": "john.doe@example.com",
"hobbies": [
"running",
"swimming",
"Netflix bing watching"
],
"age": 18
}
Custom field validation¶
You don't necessarily need to use the pydantic default validation for your fields. You can always apply one of your own.
from typing import List
from pydantic import BaseModel, EmailStr, Field, validator
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
hobbies: List[str] = Field(min_items=3)
age: int
@validator("age", always=True)
def validate_age(cls, value: int) -> int:
"""
Validates the age of a user.
"""
if value < 18:
raise ValueError("The age must be at least 18.")
return value
@validator("name")
def validate_name(cls, value: str) -> str:
"""
Validates the name of a user.
"""
if len(value) < 3:
raise ValueError("The name must be at least 3 characters.")
return value
@post("/create")
async def create_user(data: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
from typing import List
from pydantic import BaseModel, EmailStr, Field, validator
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
hobbies: List[str] = Field(min_items=3)
age: int
@validator("age", always=True)
def validate_age(cls, value: int) -> int:
"""
Validates the age of a user.
"""
if value < 18:
raise ValueError("The age must be at least 18.")
return value
@validator("name")
def validate_name(cls, value: str) -> str:
"""
Validates the name of a user.
"""
if len(value) < 3:
raise ValueError("The name must be at least 3 characters.")
return value
@post("/create")
async def create_user(payload: User) -> None:
"""
Creates a user in the system and does not return anything.
Default status_code: 201
"""
app = Esmerald(routes=[Gateway(handler=create_user)])
Summary¶
- To process a payload it must have a
data
or apayload
field declared in the handler. data
orpayload
can be any type, including pydantic models.- Validations can be achieved by:
- Using the
Field
from pydantic and automatic delegate the validations to it. - Using custom validations.
- Using the
- To make a field non-mandatory you must use the
Optional
.