Skip to content

Commit a4c9fc4

Browse files
committed
Reorder FastAPI serialization
1 parent 9728f53 commit a4c9fc4

File tree

1 file changed

+52
-53
lines changed

1 file changed

+52
-53
lines changed

README.md

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ Some of them are worth sharing.
2121
- [Prefer `async` dependencies](#prefer-async-dependencies)
2222
- [Miscellaneous](#miscellaneous)
2323
- [Follow the REST](#follow-the-rest)
24-
- [ValueErrors might become Pydantic ValidationError](#valueerrors-might-become-pydantic-validationerror)
25-
- [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)
2624
- [FastAPI response serialization](#fastapi-response-serialization)
25+
- [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)
26+
- [ValueErrors might become Pydantic ValidationError](#valueerrors-might-become-pydantic-validationerror)
2727
- [Docs](#docs)
2828
- [Set DB keys naming conventions](#set-db-keys-naming-conventions)
2929
- [Migrations. Alembic](#migrations-alembic)
@@ -507,44 +507,36 @@ async def get_user_profile_by_id(
507507
return creator_profile
508508

509509
```
510+
### FastAPI response serialization
511+
If you think you can return Pydantic object that matches your route's `response_model` to make some optimizations,
512+
then it's wrong.
510513

511-
### ValueErrors might become Pydantic ValidationError
512-
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.
514+
FastAPI firstly converts that pydantic object to dict with its `jsonable_encoder`, then validates
515+
data with your `response_model`, and only then serializes your object to JSON.
513516
```python
514-
# src.profiles.schemas
515-
from pydantic import BaseModel, field_validator
516-
517-
class ProfileCreate(BaseModel):
518-
username: str
519-
520-
@field_validator("password", mode="after")
521-
@classmethod
522-
def valid_password(cls, password: str) -> str:
523-
if not re.match(STRONG_PASSWORD_PATTERN, password):
524-
raise ValueError(
525-
"Password must contain at least "
526-
"one lower character, "
527-
"one upper character, "
528-
"digit or "
529-
"special symbol"
530-
)
517+
from fastapi import FastAPI
518+
from pydantic import BaseModel, root_validator
531519

532-
return password
520+
app = FastAPI()
533521

534522

535-
# src.profiles.routes
536-
from fastapi import APIRouter
523+
class ProfileResponse(BaseModel):
524+
@model_validator(mode="after")
525+
def debug_usage(self):
526+
print("created pydantic model")
537527

538-
router = APIRouter()
528+
return self
539529

540530

541-
@router.post("/profiles")
542-
async def get_creator_posts(profile_data: ProfileCreate):
543-
pass
531+
@app.get("/", response_model=ProfileResponse)
532+
async def root():
533+
return ProfileResponse()
534+
```
535+
**Logs Output:**
536+
```
537+
[INFO] [2022-08-28 12:00:00.000000] created pydantic model
538+
[INFO] [2022-08-28 12:00:00.000020] created pydantic model
544539
```
545-
**Response Example:**
546-
547-
<img src="images/value_error_response.png" width="400" height="auto">
548540

549541
### If you must use sync SDK, then run it in a thread pool.
550542
If you must use a library to interact with external services, and it's not `async`,
@@ -567,36 +559,43 @@ async def call_my_sync_library():
567559
await run_in_threadpool(client.make_request, data=my_data)
568560
```
569561

570-
### FastAPI response serialization
571-
If you think you can return Pydantic object that matches your route's `response_model` to make some optimizations,
572-
then it's wrong.
573-
574-
FastAPI firstly converts that pydantic object to dict with its `jsonable_encoder`, then validates
575-
data with your `response_model`, and only then serializes your object to JSON.
562+
### ValueErrors might become Pydantic ValidationError
563+
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.
576564
```python
577-
from fastapi import FastAPI
578-
from pydantic import BaseModel, root_validator
565+
# src.profiles.schemas
566+
from pydantic import BaseModel, field_validator
579567

580-
app = FastAPI()
568+
class ProfileCreate(BaseModel):
569+
username: str
570+
571+
@field_validator("password", mode="after")
572+
@classmethod
573+
def valid_password(cls, password: str) -> str:
574+
if not re.match(STRONG_PASSWORD_PATTERN, password):
575+
raise ValueError(
576+
"Password must contain at least "
577+
"one lower character, "
578+
"one upper character, "
579+
"digit or "
580+
"special symbol"
581+
)
581582

583+
return password
582584

583-
class ProfileResponse(BaseModel):
584-
@model_validator(mode="after")
585-
def debug_usage(self):
586-
print("created pydantic model")
587585

588-
return self
586+
# src.profiles.routes
587+
from fastapi import APIRouter
589588

589+
router = APIRouter()
590590

591-
@app.get("/", response_model=ProfileResponse)
592-
async def root():
593-
return ProfileResponse()
594-
```
595-
**Logs Output:**
596-
```
597-
[INFO] [2022-08-28 12:00:00.000000] created pydantic model
598-
[INFO] [2022-08-28 12:00:00.000020] created pydantic model
591+
592+
@router.post("/profiles")
593+
async def get_creator_posts(profile_data: ProfileCreate):
594+
pass
599595
```
596+
**Response Example:**
597+
598+
<img src="images/value_error_response.png" width="400" height="auto">
600599

601600
### Docs
602601
1. Unless your API is public, hide docs by default. Show it explicitly on the selected envs only.

0 commit comments

Comments
 (0)