Skip to content

Commit b73dd9c

Browse files
committed
Fix typos and put sample link under contents
1 parent 4df0a7c commit b73dd9c

File tree

1 file changed

+13
-14
lines changed

1 file changed

+13
-14
lines changed

README.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ For the last 1.5 years in production,
55
we have been making good and bad decisions that impacted our developer experience dramatically.
66
Some of them are worth sharing.
77

8-
Project [sample](https://github.com/zhanymkanov/fastapi_production_template) built with this best-practices in mind.
9-
108
### Contents
119
1. [Project Structure. Consistent & predictable.](https://github.com/zhanymkanov/fastapi-best-practices#1-project-structure-consistent--predictable)
1210
2. [Excessively use Pydantic for data validation.](https://github.com/zhanymkanov/fastapi-best-practices#2-excessively-use-pydantic-for-data-validation)
@@ -33,6 +31,7 @@ Project [sample](https://github.com/zhanymkanov/fastapi_production_template) bui
3331
23. [If you must use sync SDK, then run it in a thread pool.](https://github.com/zhanymkanov/fastapi-best-practices#23-if-you-must-use-sync-sdk-then-run-it-in-a-thread-pool)
3432
24. [Use linters (black, isort, autoflake).](https://github.com/zhanymkanov/fastapi-best-practices#24-use-linters-black-isort-autoflake)
3533
25. [Bonus Section.](https://github.com/zhanymkanov/fastapi-best-practices#bonus-section)
34+
<p style="text-align: center;"> <i>Project <a href="https://github.com/zhanymkanov/fastapi_production_template">sample</a> built with these best-practices in mind. </i> </p>
3635

3736
### 1. Project Structure. Consistent & predictable
3837
There are many ways to structure the project, but the best structure is a structure that is consistent, straightforward, and has no surprises.
@@ -141,7 +140,7 @@ class UserBase(BaseModel):
141140

142141
```
143142
### 3. Use dependencies for data validation vs DB
144-
Pydantic can only validate the values of client input.
143+
Pydantic can only validate the values from client input.
145144
Use dependencies to validate data against database constraints like email already exists, user not found, etc.
146145
```python3
147146
# dependencies.py
@@ -214,7 +213,6 @@ async def valid_owned_post(
214213
# router.py
215214
@router.get("/users/{user_id}/posts/{post_id}", response_model=PostResponse)
216215
async def get_user_post(post: Mapping = Depends(valid_owned_post)):
217-
"""Get post that belong the user."""
218216
return post
219217

220218
```
@@ -328,12 +326,12 @@ async def get_user_profile_by_id(profile: Mapping = Depends(valid_profile_id)):
328326
async def get_user_profile_by_id(
329327
creator_profile: Mapping = Depends(valid_creator_id)
330328
):
331-
"""Get profile by id."""
329+
"""Get creator's profile by id."""
332330
return creator_profile
333331

334332
```
335333

336-
Use /me endpoints for users own resources (e.g. `GET /profiles/me`, `GET /users/me/posts`)
334+
Use /me endpoints for users resources (e.g. `GET /profiles/me`, `GET /users/me/posts`)
337335
1. No need to validate that user id exists - it's already checked via auth method
338336
2. No need to check whether the user id belongs to the requester
339337

@@ -367,8 +365,8 @@ def good_ping():
367365

368366
@router.get("/perfect-ping")
369367
async def perfect_ping():
370-
await asyncio.sleep(10) # non I/O blocking operation
371-
pong = await service.async_get_pong() # non I/O blocking db call
368+
await asyncio.sleep(10) # non-blocking I/O operation
369+
pong = await service.async_get_pong() # non-blocking I/O db call
372370

373371
return {"pong": pong}
374372

@@ -390,6 +388,7 @@ async def perfect_ping():
390388
3. While `good_ping` is being executed, event loop selects next tasks from the queue and works on them (e.g. accept new request, call db)
391389
- Independently of main thread (i.e. our FastAPI app),
392390
worker thread will be waiting for `time.sleep` to finish and then for `service.get_pong` to finish
391+
- Sync operation blocks only the side thread, not the main one.
393392
4. When `good_ping` finishes its work, server returns a response to the client
394393
3. `GET /perfect-ping`
395394
1. FastAPI server receives a request and starts handling it
@@ -399,7 +398,7 @@ async def perfect_ping():
399398
5. Event loop selects next tasks from the queue and works on them (e.g. accept new request, call db)
400399
6. When `service.async_get_pong` is done, server returns a response to the client
401400

402-
The second caveat is that operations that are non-blocking awaitables or are sent to thread pool must be I/O intensive tasks (e.g. open file, db call, external API call).
401+
The second caveat is that operations that are non-blocking awaitables or are sent to the thread pool must be I/O intensive tasks (e.g. open file, db call, external API call).
403402
- Awaiting CPU-intensive tasks (e.g. heavy calculations, data processing, video transcoding) is worthless since the CPU has to work to finish the tasks,
404403
while I/O operations are external and server does nothing while waiting for that operations to finish, thus it can go to the next tasks.
405404
- Running CPU-intensive tasks in other threads also isn't effective, because of [GIL](https://realpython.com/python-gil/).
@@ -596,7 +595,7 @@ async def test_create_post(client: TestClient):
596595
Unless you have sync db connections (excuse me?) or aren't planning to write integration tests.
597596
### 15. BackgroundTasks > asyncio.create_task
598597
BackgroundTasks can [effectively run](https://github.com/encode/starlette/blob/31164e346b9bd1ce17d968e1301c3bb2c23bb418/starlette/background.py#L25)
599-
both blocking and non-blocking I/O operations the same way it handles routes (`sync` functions are run in a threadpool, while `async` ones are awaited later)
598+
both blocking and non-blocking I/O operations the same way FastAPI handles blocking routes (`sync` tasks are run in a threadpool, while `async` tasks are awaited later)
600599
- Don't lie to the worker and don't mark blocking I/O operations as `async`
601600
- Don't use it for heavy CPU intensive tasks.
602601
```python
@@ -666,9 +665,9 @@ print(type(post.content))
666665
# Article is very inclusive and all fields are optional, allowing any dict to become valid
667666
```
668667
**Solutions:**
669-
1. Validate input has only valid fields
668+
1. Validate input has only allowed valid fields and raise error if unknowns are provided
670669
```python
671-
from pydantic import BaseModel, Extra, root_validator
670+
from pydantic import BaseModel, Extra
672671

673672
class Article(BaseModel):
674673
text: str | None
@@ -733,7 +732,7 @@ print(type(p.content))
733732
# OUTPUT: Article, because smart_union doesn't work for complex fields like classes
734733
```
735734

736-
**Fast Workaround:**
735+
3. Fast Workaround
737736

738737
Order field types properly: from the most strict ones to loose ones.
739738

@@ -950,7 +949,7 @@ async def root():
950949
[INFO] [2022-08-28 12:00:00.000030] called dict
951950
```
952951
### 23. If you must use sync SDK, then run it in a thread pool.
953-
If you must use an SDK to interact with external services, and it's not `async`,
952+
If you must use a library to interact with external services, and it's not `async`,
954953
then make the HTTP calls in an external worker thread.
955954

956955
For a simple example, we could use our well-known `run_in_threadpool` from starlette.

0 commit comments

Comments
 (0)