Reorder FastAPI serialization

This commit is contained in:
Yerassyl Zhanymkanov
2024-06-08 12:44:39 +02:00
parent 9728f53e3d
commit a4c9fc4c1f

107
README.md
View File

@@ -21,9 +21,9 @@ Some of them are worth sharing.
- [Prefer `async` dependencies](#prefer-async-dependencies) - [Prefer `async` dependencies](#prefer-async-dependencies)
- [Miscellaneous](#miscellaneous) - [Miscellaneous](#miscellaneous)
- [Follow the REST](#follow-the-rest) - [Follow the REST](#follow-the-rest)
- [ValueErrors might become Pydantic ValidationError](#valueerrors-might-become-pydantic-validationerror)
- [If you must use sync SDK, then run it in a thread pool.](#if-you-must-use-sync-sdk-then-run-it-in-a-thread-pool)
- [FastAPI response serialization](#fastapi-response-serialization) - [FastAPI response serialization](#fastapi-response-serialization)
- [If you must use sync SDK, then run it in a thread pool.](#if-you-must-use-sync-sdk-then-run-it-in-a-thread-pool)
- [ValueErrors might become Pydantic ValidationError](#valueerrors-might-become-pydantic-validationerror)
- [Docs](#docs) - [Docs](#docs)
- [Set DB keys naming conventions](#set-db-keys-naming-conventions) - [Set DB keys naming conventions](#set-db-keys-naming-conventions)
- [Migrations. Alembic](#migrations-alembic) - [Migrations. Alembic](#migrations-alembic)
@@ -507,6 +507,57 @@ async def get_user_profile_by_id(
return creator_profile return creator_profile
``` ```
### FastAPI response serialization
If you think you can return Pydantic object that matches your route's `response_model` to make some optimizations,
then it's wrong.
FastAPI firstly converts that pydantic object to dict with its `jsonable_encoder`, then validates
data with your `response_model`, and only then serializes your object to JSON.
```python
from fastapi import FastAPI
from pydantic import BaseModel, root_validator
app = FastAPI()
class ProfileResponse(BaseModel):
@model_validator(mode="after")
def debug_usage(self):
print("created pydantic model")
return self
@app.get("/", response_model=ProfileResponse)
async def root():
return ProfileResponse()
```
**Logs Output:**
```
[INFO] [2022-08-28 12:00:00.000000] created pydantic model
[INFO] [2022-08-28 12:00:00.000020] created pydantic model
```
### If you must use sync SDK, then run it in a thread pool.
If you must use a library to interact with external services, and it's not `async`,
then make the HTTP calls in an external worker thread.
We can use the well-known `run_in_threadpool` from starlette.
```python
from fastapi import FastAPI
from fastapi.concurrency import run_in_threadpool
from my_sync_library import SyncAPIClient
app = FastAPI()
@app.get("/")
async def call_my_sync_library():
my_data = await service.get_my_data()
client = SyncAPIClient()
await run_in_threadpool(client.make_request, data=my_data)
```
### ValueErrors might become Pydantic ValidationError ### ValueErrors might become Pydantic ValidationError
If you raise a `ValueError` in a Pydantic schema that is directly faced by the client, it will return a nice detailed response to users. If you raise a `ValueError` in a Pydantic schema that is directly faced by the client, it will return a nice detailed response to users.
@@ -546,58 +597,6 @@ async def get_creator_posts(profile_data: ProfileCreate):
<img src="images/value_error_response.png" width="400" height="auto"> <img src="images/value_error_response.png" width="400" height="auto">
### If you must use sync SDK, then run it in a thread pool.
If you must use a library to interact with external services, and it's not `async`,
then make the HTTP calls in an external worker thread.
We can use the well-known `run_in_threadpool` from starlette.
```python
from fastapi import FastAPI
from fastapi.concurrency import run_in_threadpool
from my_sync_library import SyncAPIClient
app = FastAPI()
@app.get("/")
async def call_my_sync_library():
my_data = await service.get_my_data()
client = SyncAPIClient()
await run_in_threadpool(client.make_request, data=my_data)
```
### FastAPI response serialization
If you think you can return Pydantic object that matches your route's `response_model` to make some optimizations,
then it's wrong.
FastAPI firstly converts that pydantic object to dict with its `jsonable_encoder`, then validates
data with your `response_model`, and only then serializes your object to JSON.
```python
from fastapi import FastAPI
from pydantic import BaseModel, root_validator
app = FastAPI()
class ProfileResponse(BaseModel):
@model_validator(mode="after")
def debug_usage(self):
print("created pydantic model")
return self
@app.get("/", response_model=ProfileResponse)
async def root():
return ProfileResponse()
```
**Logs Output:**
```
[INFO] [2022-08-28 12:00:00.000000] created pydantic model
[INFO] [2022-08-28 12:00:00.000020] created pydantic model
```
### Docs ### Docs
1. Unless your API is public, hide docs by default. Show it explicitly on the selected envs only. 1. Unless your API is public, hide docs by default. Show it explicitly on the selected envs only.
```python ```python