Complex Request Data in Esmerald: Advanced Guide¶
Esmerald allows for highly flexible handling of request data. Since release 3.4+, Esmerald supports multiple payloads, letting you structure data more clearly. This feature makes your request data more organized and manageable, especially when dealing with multiple entities.
📦 Sending Multiple Payloads¶
Esmerald allows you to declare multiple payloads in a handler, enabling better organization of complex data. You can use the data
or payload
field to achieve this, with each one serving the same purpose.
Example: Using data
or payload
¶
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
hobbies: list[str] # Pydantic model handles type validation
@post("/create")
async def create_user(data: User) -> None:
pass
app = Esmerald(routes=[Gateway(handler=create_user)])
Request Payload (for data
or payload
):
{
"name": "John",
"email": "john.doe@example.com",
"hobbies": ["running", "swimming"]
}
🏠 Splitting Data by Responsibility¶
Sometimes, you might want to send data that represents different entities (like a User
and an Address
). Esmerald allows you to handle such cases by splitting the payload into separate parts.
Example: User and Address¶
from pydantic import BaseModel, EmailStr
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
class Address(BaseModel):
street_name: str
post_code: str
@post("/create")
async def create_user(user: User, address: Address) -> None:
pass
app = Esmerald(routes=[Gateway(handler=create_user)])
Request Payload:
{
"user": {
"name": "John",
"email": "john.doe@example.com"
},
"address": {
"street_name": "123 Queens Park",
"post_code": "90241"
}
}
In this case, Esmerald automatically maps the user
and address
parts of the request to their respective models.
🧩 Optional Fields in Payloads¶
You can also mark fields as optional, which makes them not required for validation.
Example: Optional Address¶
from pydantic import BaseModel, EmailStr
from typing import Union
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
class Address(BaseModel):
street_name: str
post_code: str
@post("/create")
async def create_user(user: User, address: Union[Address, None] = None) -> None:
pass
app = Esmerald(routes=[Gateway(handler=create_user)])
Request Payload (Optional Address):
- With Address:
{
"user": {
"name": "John",
"email": "john.doe@example.com"
},
"address": {
"street_name": "123 Queens Park",
"post_code": "90241"
}
}
- Without Address:
{
"user": {
"name": "John",
"email": "john.doe@example.com"
}
}
In this example, the address
is optional. If it's not provided, Esmerald will still process the user
data.
🔄 Using Different Encoders¶
Esmerald supports multiple encoders, such as Pydantic and Msgspec. This allows you to mix and match encoders based on your data needs.
Example: Using Pydantic and Msgspec¶
from pydantic import BaseModel, EmailStr
from msgspec import Struct
from typing import Union
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
class Address(Struct):
street_name: str
post_code: str
@post("/create")
async def create_user(user: User, address: Union[Address, None] = None) -> None:
pass
app = Esmerald(routes=[Gateway(handler=create_user)])
Request Payload:
{
"user": {
"name": "John",
"email": "john.doe@example.com"
},
"address": {
"street_name": "123 Queens Park",
"post_code": "90241"
}
}
Esmerald automatically handles the encoder types (Pydantic
for User
and Msgspec
for Address
), so you don't have to manually process the encoding and decoding of your data.
⚠️ Important Note on Complex Bodies¶
Once you add an extra body (like address
), you must declare it explicitly in your handler.
Example of Declaring Complex Request Data:
from pydantic import BaseModel, EmailStr
from typing import Union
from esmerald import Esmerald, Gateway, post
class User(BaseModel):
name: str
email: EmailStr
class Address(BaseModel):
street_name: str
post_code: str
@post("/create")
async def create_user(data: User, address: Union[Address, None] = None) -> None:
...
app = Esmerald(routes=[Gateway(handler=create_user)])
Request Payload (Explicit Declaration):
{
"data": {
"name": "John",
"email": "john.doe@example.com"
},
"address": {
"street_name": "123 Queens Park",
"post_code": "90241"
}
}
Esmerald requires the explicit declaration of each part of the payload when working with complex bodies.
📌 Conclusion¶
With Esmerald, you can easily handle complex request data, split data into multiple parts, and use advanced techniques like optional fields and different encoders. Whether you're handling a single object or complex nested data, Esmerald's flexibility ensures you can build scalable, well-structured APIs with ease.