Reorder FastAPI serialization
This commit is contained in:
107
README.md
107
README.md
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user