๐ (๐) ๐ฝ โฎ๏ธ ๐¶
Warning
๐ฅ ๐ โถ๏ธ, ๐ฐ ๐ (๐) ๐ฝ ๐ โ๏ธ ๐ธ๐ฒ ๐ ๐ฅ.
๐ญ ๐ ๐ถ ๐.
๐ฅ ๐ โถ๏ธ ๐ โช๏ธโก๏ธ ๐, ๐ ๐ฒ ๐ป ๐ โฎ๏ธ ๐ธ๐ฒ ๐ (๐ (๐) ๐ฝ), โ๏ธ ๐ ๐ ๐ ๐.
๐ฅ ๐ โช โ๏ธ ๐ ๐งข ๐ โ๏ธ ๐ ๐, ๐ ๐ช โ ๐ฅ โ โ๏ธ โซ๏ธ โฎ๏ธ FastAPI.
๐ 3๏ธโฃ.7๏ธโฃ โ โ
๐ ๐ ๐ช ๐ 3๏ธโฃ.7๏ธโฃ โ๏ธ ๐ ๐ โ๏ธ ๐ โฎ๏ธ FastAPI.
๐ ๐¶
๐ ๐ซ ๐ง ๐ ๐ ๏ธ, โ๏ธ โฎ๏ธ ๐ซ ๐คฏ.
๐ โ๏ธ ๐๏ธ ๐ ๐ ๐ฎ ๐ข & ๐ โ โซ๏ธ ๐ โ๏ธ.
๐ฅ ๐ ๐ ๏ธ ๐ธ โฎ๏ธ ๐ ๐ซ-๐ ๐ ๏ธ, & ๐ช ๐ท โฎ๏ธ ๐ ๐ฎ ๐ข, โซ๏ธ ๐ช ๐ ๐งฐ.
โ๏ธ ๐ฅ ๐ ๐ช ๐ ๐ข, ๐โ๐ฆบ ๐ ๐ 1๏ธโฃ ๐ ๐ฝ, ๐ท โฎ๏ธ ๐ ๐ ๏ธ (๐ FastAPI), โ๏ธ, ๐ ๐ ๐ช ๐ฎ ๐ โ ๐ ๐ ๐ ๐ข.
๐, โซ๏ธ ๐ช โซ๏ธ, & ๐ฅ ๐ ๐ ๐ โซ๏ธโ โซ๏ธโ ๐ ๐ โ๏ธ ๐ฎ ๐ช โ๏ธ ๐ โฎ๏ธ FastAPI.
๐ ๐ฑ¶
๐ฅ ๐ โ ๐ ๐ธ ๐ธ๐ฒ ๐ฐ (๐ (๐) ๐ฝ).
๐ ๐ ๐ค ๐.
, ๐ฅ ๐ ๐ฏ ๐ด ๐ ๐บ.
๐ ๐¶
โก๏ธ ๐ฌ ๐ โ๏ธ ๐ ๐ my_super_project
๐ ๐ ๐ง-๐ ๐ค sql_app
โฎ๏ธ ๐ ๐ ๐:
.
โโโ sql_app
โโโ __init__.py
โโโ crud.py
โโโ database.py
โโโ main.py
โโโ schemas.py
๐ ๐ ๐ ๐ ๐ฅ โ๏ธ ๐ธ๐ฒ ๐ฐ.
๐ โก๏ธ ๐ โซ๏ธโ ๐ ๐/๐น ๐จ.
โ ๐ ๐¶
โก๏ธ ๐ ๐ sql_app/database.py
.
๐ฉ ๐ ๐¶
โก๏ธ ๐ฅ โ ๐ ๐ ๐ ๐, โ ๐ ๐ฝ:
from contextvars import ContextVar
import peewee
DATABASE_NAME = "test.db"
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
db_state = ContextVar("db_state", default=db_state_default.copy())
class PeeweeConnectionState(peewee._ConnectionState):
def __init__(self, **kwargs):
super().__setattr__("_state", db_state)
super().__init__(**kwargs)
def __setattr__(self, name, value):
self._state.get()[name] = value
def __getattr__(self, name):
return self._state.get()[name]
db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False)
db._state = PeeweeConnectionState()
Tip
โ๏ธ ๐คฏ ๐ ๐ฅ ๐ ๐ โ๏ธ ๐ ๐ฝ, ๐ โณ, ๐ ๐ซ ๐ซ ๐ ๐ป. ๐ ๐ ๐ช โ๏ธ ๐ ๐ ๐ฝ ๐.
๐¶
โ:
check_same_thread=False
๐ 1๏ธโฃ ๐ธ๐ฒ ๐ฐ:
connect_args={"check_same_thread": False}
...โซ๏ธ ๐ช ๐ด SQLite
.
๐ก โน
โซ๏ธโ ๐ ๐ก โน ๐ (๐) ๐ฝ โ.
โ ๐ ๐-๐ PeeweeConnectionState
¶
๐ โ โฎ๏ธ ๐ & FastAPI ๐ ๐ โ๏ธ ๐ ๐ ๐ threading.local
, & โซ๏ธ ๐ซ โ๏ธ ๐ฏ ๐ ๐ โซ๏ธ โ๏ธ โก๏ธ ๐ ๐ต ๐/๐ ๐ (๐จ ๐ธ๐ฒ ๐ฐ).
& threading.local
๐ซ ๐ โฎ๏ธ ๐ ๐ โ ๐ ๐.
๐ก โน
threading.local
โ๏ธ โ๏ธ "๐ฑ" ๐ข ๐ โ๏ธ ๐ ๐ฒ ๐ ๐งต.
๐ โ ๐ ๐ ๏ธ ๐ โ๏ธ 1๏ธโฃ ๐ ๐งต ๐ ๐จ, ๐ โโ ๐, ๐ โโ ๐.
โ๏ธ ๐, ๐ ๐จ ๐ โ๏ธ ๐ฎ ๐ ๐ฝ ๐/๐, โ โ ๐ ๐ฅ .
โ๏ธ FastAPI, โ๏ธ ๐ ๐ โ, ๐ช ๐ต ๐
๐ 1๏ธโฃ ๐จ ๐ ๐ ๐งต. & ๐ ๐ฐ, ๐ ๐จ, โซ๏ธ ๐ช ๐ ๐ ๐ ๐ ๐งต (๐งต), โ๏ธ ๐ ๐ฅ ๐ โ๏ธ async def
โ๏ธ ๐ def
. ๐ โซ๏ธโ ๐ค ๐ ๐ญ ๐ FastAPI.
โ๏ธ ๐ 3๏ธโฃ.7๏ธโฃ & ๐ ๐ ๐ ๐ง ๐ threading.local
, ๐ ๐ช โ๏ธ ๐ฅ ๐โ threading.local
๐ โ๏ธ, โ๏ธ ๐ โฎ๏ธ ๐ ๐ โ.
๐ฅ ๐ โ๏ธ ๐. โซ๏ธ ๐ค contextvars
.
๐ฅ ๐ ๐ ๐ ๐ ๐ ๐ โ๏ธ threading.local
& โ ๐ซ โฎ๏ธ contextvars
, โฎ๏ธ ๐ โน.
๐ 5๏ธโฃ๐ ๐ ๐ ๐ (& โซ๏ธ ๐ค), ๐ ๐ซ ๐ค ๐ช ๐ ๐ค โ โซ๏ธ ๐ท โ๏ธ โซ๏ธ.
๐ฅ ๐ โ PeeweeConnectionState
:
from contextvars import ContextVar
import peewee
DATABASE_NAME = "test.db"
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
db_state = ContextVar("db_state", default=db_state_default.copy())
class PeeweeConnectionState(peewee._ConnectionState):
def __init__(self, **kwargs):
super().__setattr__("_state", db_state)
super().__init__(**kwargs)
def __setattr__(self, name, value):
self._state.get()[name] = value
def __getattr__(self, name):
return self._state.get()[name]
db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False)
db._state = PeeweeConnectionState()
๐ ๐ ๐ โช๏ธโก๏ธ ๐ ๐ ๐ โ๏ธ ๐.
โซ๏ธ โ๏ธ ๐ โ โ ๐ โ๏ธ contextvars
โฉ๏ธ threading.local
.
contextvars
๐ท ๐ ๐ ๐ threading.local
. โ๏ธ ๐ ๐ ๐ ๐ ๐ค ๐ ๐ ๐ ๐ท โฎ๏ธ threading.local
.
, ๐ฅ ๐ช โ ๐ฑ โ โซ๏ธ ๐ท ๐ฅ โซ๏ธ โ๏ธ threading.local
. __init__
, __setattr__
, & __getattr__
๐ ๏ธ ๐ โ ๐ฑ ๐ โ๏ธ ๐ ๐ต ๐ค ๐ โซ๏ธ ๐ ๐ โฎ๏ธ FastAPI.
Tip
๐ ๐ โ ๐ ๐ญ โ ๐โ โ๏ธ โฎ๏ธ FastAPI. ๐ซ ๐ฒ ๐ โ๏ธ ๐ช ๐ ๐ โ โ๏ธ, ๐ โ, โ๏ธ.
โ๏ธ โซ๏ธ ๐ซ ๐ค ๐ ๐ ๐-๐๏ธ. ๐ ๐ โ๏ธ ๐ def
๐ข & ๐ซ async def
.
โ๏ธ ๐ PeeweeConnectionState
๐¶
๐, ๐ ._state
๐ ๐ข ๐ ๐ฝ db
๐ โ๏ธ ๐ PeeweeConnectionState
:
from contextvars import ContextVar
import peewee
DATABASE_NAME = "test.db"
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
db_state = ContextVar("db_state", default=db_state_default.copy())
class PeeweeConnectionState(peewee._ConnectionState):
def __init__(self, **kwargs):
super().__setattr__("_state", db_state)
super().__init__(**kwargs)
def __setattr__(self, name, value):
self._state.get()[name] = value
def __getattr__(self, name):
return self._state.get()[name]
db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False)
db._state = PeeweeConnectionState()
Tip
โ ๐ญ ๐ ๐ db._state
โฎ๏ธ ๐ db
.
Tip
๐ ๐ ๐ ๐ ๐ ๐ ๐ฝ, ๐ PostgresqlDatabase
, MySQLDatabase
, โ๏ธ.
โ ๐ฝ ๐ท¶
โก๏ธ ๐ ๐ ๐ sql_app/models.py
.
โ ๐ ๐ท ๐ ๐ฝ¶
๐ โ ๐ ๐ท (๐) User
& Item
.
๐ ๐ ๐ ๐ ๐ฅ ๐ โฉ ๐ ๐ฐ & โน ๐ท โ๏ธ ๐ ๐ฝ ๐ธ๐ฒ ๐ฐ.
Tip
๐ โ๏ธ โ "๐ท" ๐ ๐ ๐ & ๐ ๐ ๐ โฎ๏ธ ๐ฝ.
โ๏ธ Pydantic โ๏ธ โ "๐ท" ๐ ๐ณ ๐, ๐ฝ ๐ฌ, ๐ ๏ธ, & ๐งพ ๐ & ๐.
๐ db
โช๏ธโก๏ธ database
(๐ database.py
โช๏ธโก๏ธ ๐) & โ๏ธ โซ๏ธ ๐ฅ.
import peewee
from .database import db
class User(peewee.Model):
email = peewee.CharField(unique=True, index=True)
hashed_password = peewee.CharField()
is_active = peewee.BooleanField(default=True)
class Meta:
database = db
class Item(peewee.Model):
title = peewee.CharField(index=True)
description = peewee.CharField(index=True)
owner = peewee.ForeignKeyField(User, backref="items")
class Meta:
database = db
Tip
๐ โ ๐ ๐ฑ ๐ข.
โซ๏ธ ๐ ๐ ๐ฎ id
๐ข ๐ข ๐ ๐.
โซ๏ธ ๐ โ ๐ ๐ โ๏ธ ๐ ๐ ๐.
Item
, โซ๏ธ ๐ โ ๐ข owner_id
โฎ๏ธ ๐ข ๐ User
. โ๏ธ ๐ฅ ๐ซ ๐ฃ โซ๏ธ ๐.
โ Pydantic ๐ท¶
๐ โก๏ธ โ
๐ sql_app/schemas.py
.
Tip
โ ๐จ ๐ ๐ ๐ท & Pydantic ๐ท, ๐ฅ ๐ โ๏ธ ๐ models.py
โฎ๏ธ ๐ ๐ท, & ๐ schemas.py
โฎ๏ธ Pydantic ๐ท.
๐ซ Pydantic ๐ท ๐ฌ ๐ โ๏ธ ๐ "๐" (โ ๐ ๐ ).
๐ ๐ โน ๐ฅ โ ๐จ โช โ๏ธ ๐ฏโโ๏ธ.
โ Pydantic ๐ท / ๐¶
โ ๐ ๐ Pydantic ๐ท ๐ธ๐ฒ ๐ฐ:
from typing import Any, List, Union
import peewee
from pydantic import BaseModel
from pydantic.utils import GetterDict
class PeeweeGetterDict(GetterDict):
def get(self, key: Any, default: Any = None):
res = getattr(self._obj, key, default)
if isinstance(res, peewee.ModelSelect):
return list(res)
return res
class ItemBase(BaseModel):
title: str
description: Union[str, None] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
Tip
๐ฅ ๐ฅ ๐ ๐ท โฎ๏ธ id
.
๐ฅ ๐ซ ๐ฏ โ id
๐ข ๐ ๐ท, โ๏ธ ๐ ๐ฎ 1๏ธโฃ ๐.
๐ฅ โ ๐ฑ owner_id
๐ข Item
.
โ PeeweeGetterDict
Pydantic ๐ท / ๐¶
๐โ ๐ ๐ ๐ ๐ ๐, ๐ some_user.items
, ๐ ๐ซ ๐ list
Item
.
โซ๏ธ ๐ ๐ ๐ ๐ ๐ ModelSelect
.
โซ๏ธ ๐ช โ list
๐ฎ ๐ฌ โฎ๏ธ list(some_user.items)
.
โ๏ธ ๐ โซ๏ธ ๐ซ list
. & โซ๏ธ ๐ซ โ ๐ ๐. โฉ๏ธ ๐, Pydantic ๐ซ ๐ญ ๐ข โ ๐ โซ๏ธ list
Pydantic ๐ท / ๐.
โ๏ธ โฎ๏ธ โฌ Pydantic โ ๐ ๐ ๐ ๐ ๐ โช๏ธโก๏ธ pydantic.utils.GetterDict
, ๐ ๐ ๏ธ โ๏ธ ๐โ โ๏ธ orm_mode = True
๐ ๐ฒ ๐ ๐ท ๐ข.
๐ฅ ๐ โ ๐ PeeweeGetterDict
๐ & โ๏ธ โซ๏ธ ๐ ๐ Pydantic ๐ท / ๐ ๐ โ๏ธ orm_mode
:
from typing import Any, List, Union
import peewee
from pydantic import BaseModel
from pydantic.utils import GetterDict
class PeeweeGetterDict(GetterDict):
def get(self, key: Any, default: Any = None):
res = getattr(self._obj, key, default)
if isinstance(res, peewee.ModelSelect):
return list(res)
return res
class ItemBase(BaseModel):
title: str
description: Union[str, None] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
๐ฅ ๐ฅ โ
๐ฅ ๐ข ๐ โ ๐ (โ
.items
some_user.items
) ๐ peewee.ModelSelect
.
& ๐ฅ ๐ ๐ผ, ๐จ list
โฎ๏ธ โซ๏ธ.
& โคด๏ธ ๐ฅ โ๏ธ โซ๏ธ Pydantic ๐ท / ๐ ๐ โ๏ธ orm_mode = True
, โฎ๏ธ ๐ณ ๐ข getter_dict = PeeweeGetterDict
.
Tip
๐ฅ ๐ด ๐ช โ 1๏ธโฃ PeeweeGetterDict
๐, & ๐ฅ ๐ช โ๏ธ โซ๏ธ ๐ Pydantic ๐ท / ๐.
๐ฉ ๐จ๐ป¶
๐ โก๏ธ ๐ ๐ sql_app/crud.py
.
โ ๐ ๐ฉ ๐จ๐ป¶
โ ๐ ๐ ๐ฉ ๐จ๐ป ๐ธ๐ฒ ๐ฐ, ๐ ๐ ๐ถ ๐:
from . import models, schemas
def get_user(user_id: int):
return models.User.filter(models.User.id == user_id).first()
def get_user_by_email(email: str):
return models.User.filter(models.User.email == email).first()
def get_users(skip: int = 0, limit: int = 100):
return list(models.User.select().offset(skip).limit(limit))
def create_user(user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db_user.save()
return db_user
def get_items(skip: int = 0, limit: int = 100):
return list(models.Item.select().offset(skip).limit(limit))
def create_user_item(item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db_item.save()
return db_item
๐ค ๐บ โฎ๏ธ ๐ ๐ธ๐ฒ ๐ฐ.
๐ฅ ๐ซ ๐ถโโ๏ธ db
๐ข ๐คญ. โฉ๏ธ ๐ฅ โ๏ธ ๐ท ๐. ๐ โฉ๏ธ db
๐ ๐ ๐, ๐ ๐ ๐ ๐ โ. ๐ โซ๏ธโ ๐ฅ โ๏ธ ๐ contextvars
โน ๐.
๐, ๐โ ๐ฌ ๐ ๐, ๐ get_users
, ๐ฅ ๐ ๐ค list
, ๐:
list(models.User.select())
๐ ๐ ๐ค ๐ ๐ฅ โ๏ธ โ ๐ PeeweeGetterDict
. โ๏ธ ๐ฌ ๐ณ ๐ โช list
โฉ๏ธ peewee.ModelSelect
response_model
โก ๐ ๏ธ โฎ๏ธ List[models.User]
(๐ ๐ฅ ๐ ๐ โช) ๐ ๐ท โ.
๐ FastAPI ๐ฑ¶
& ๐ ๐ sql_app/main.py
โก๏ธ ๐ ๏ธ & โ๏ธ ๐ ๐ ๐ ๐ฅ โ โญ.
โ ๐ฝ ๐¶
๐ถ ๐ ๐ โ ๐ฝ ๐:
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
โ ๐¶
โ ๐ ๐ ๐ ๐ ๐ฝ โถ๏ธ๏ธ โถ๏ธ ๐จ & ๐ โซ๏ธ ๐:
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
๐ฅ ๐ฅ โ๏ธ ๐ yield
โฉ๏ธ ๐ฅ ๐ค ๐ซ โ๏ธ ๐ฝ ๐ ๐.
โซ๏ธ ๐ ๐ฝ & โป ๐ ๐ฝ ๐ ๐ข ๐ ๐ฌ ๐ ๐จ (โ๏ธ contextvars
๐ฑ โช๏ธโก๏ธ ๐).
โฉ๏ธ ๐ฝ ๐ โ ๐ค/๐
พ ๐ง, ๐ ๐ โ โฎ๏ธ ๐ def
๐ข.
& โคด๏ธ, ๐ โก ๐ ๏ธ ๐ข ๐ ๐ช ๐ ๐ฝ ๐ฅ ๐ฎ โซ๏ธ ๐.
โ๏ธ ๐ฅ ๐ซ โ๏ธ ๐ฒ ๐ ๐ ๐ (โซ๏ธ ๐ค ๐ซ ๐ค ๐ ๐ฒ, โซ๏ธ โ๏ธ ๐ yield
). , ๐ฅ ๐ซ ๐ฎ โซ๏ธ โก ๐ ๏ธ ๐ข โ๏ธ โก ๐ ๏ธ ๐จโ๐จ dependencies
๐ข:
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
๐ ๐ข ๐ง-๐¶
๐ contextvars
๐ ๐ท, ๐ฅ ๐ช โ ๐ญ ๐ฅ โ๏ธ ๐ฌ ๐ฒ ContextVar
๐ ๐จ ๐ โ๏ธ ๐ฝ, & ๐ ๐ฒ ๐ โ๏ธ ๐ฝ ๐ต๐ธ (๐, ๐ต, โ๏ธ) ๐ ๐จ.
๐, ๐ฅ ๐ช โ โ1๏ธโฃ async
๐ reset_db_state()
๐ โ๏ธ ๐ง-๐ get_db()
. โซ๏ธ ๐ โ ๐ฒ ๐ ๐ข (โฎ๏ธ ๐ข dict
) ๐ ๐ โ๏ธ ๐ฝ ๐ต๐ธ ๐ ๐จ. & โคด๏ธ ๐ get_db()
๐ ๐ช โซ๏ธ ๐ฝ ๐ต๐ธ (๐, ๐ต, โ๏ธ).
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
โญ ๐จ, ๐ฅ ๐ โฒ ๐ ๐ ๐ข ๐ async
๐ reset_db_state()
& โคด๏ธ โ ๐ ๐ get_db()
๐, ๐ ๐ ๐จ ๐ โ๏ธ ๐ฎ ๐ ๐ฝ ๐ต๐ธ (๐, ๐ต, โ๏ธ).
Tip
FastAPI ๐ ๐ ๏ธ, 1๏ธโฃ ๐จ ๐ช โถ๏ธ โ ๐ ๏ธ, & โญ ๐, โ1๏ธโฃ ๐จ ๐ช ๐จ & โถ๏ธ ๐ญ ๐, & โซ๏ธ ๐ ๐ช ๐ ๏ธ ๐ ๐งต.
โ๏ธ ๐ ๐ข ๐ค ๐ซ ๐ โ,, ๐ ๐ฝ ๐ต๐ธ โ async
๐ reset_db_state()
๐ ๐ง ๐ฎ ๐ ๐ฝ ๐ ๐ ๐จ.
& ๐ ๐ฐ, ๐ ๐ ๏ธ ๐จ ๐ โ๏ธ ๐ฎ ๐ ๐ฝ ๐ต๐ธ ๐ ๐ ๐ฌ ๐ ๐จ.
๐ ๐ณ¶
๐ฅ ๐ โ๏ธ ๐ ๐ณ, โ ๐ฝ db.obj
.
, ๐ ๐ โฒ โซ๏ธ โฎ๏ธ:
async def reset_db_state():
database.db.obj._state._state.set(db_state_default.copy())
database.db.obj._state.reset()
โ ๐ FastAPI โก ๐ ๏ธ¶
๐, ๐, ๐ฅ ๐ฉ FastAPI โก ๐ ๏ธ ๐.
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
๐ def
๐ async def
¶
๐ โฎ๏ธ ๐ธ๐ฒ, ๐ฅ ๐ซ ๐จ ๐ณ ๐:
user = await models.User.select().first()
...โ๏ธ โฉ๏ธ ๐ฅ โ๏ธ:
user = models.User.select().first()
, ๐, ๐ฅ ๐ ๐ฃ โก ๐ ๏ธ ๐ข & ๐ ๐ต async def
, โฎ๏ธ ๐ def
,:
# Something goes here
def read_users(skip: int = 0, limit: int = 100):
# Something goes here
๐ฌ ๐ โฎ๏ธ ๐¶
๐ ๐ผ ๐ โ โก ๐ ๏ธ ๐ ๐ฌ ๐ ๐ญ ๐จ โฎ๏ธ time.sleep(sleep_time)
.
โซ๏ธ ๐ โ๏ธ ๐ฝ ๐ ๐ โถ๏ธ & ๐ โ ๐ฅ โญ ๐ ๐. & ๐ ๐ ๐จ ๐ โ ๐ ๐ฅ ๐.
๐ ๐ ๐ช โก๏ธ ๐ ๐ฏ ๐ ๐ ๐ฑ โฎ๏ธ ๐ & FastAPI ๐ญ โ โฎ๏ธ ๐ ๐ฉ ๐ ๐งต.
๐ฅ ๐ ๐ โ
โ ๐ ๐ ๐ ๐ ๐ฑ ๐ฅ โ๏ธ ๐ต ๐ ๏ธ, ๐ถ sql_app/database.py
๐ & ๐ค โธ:
# db._state = PeeweeConnectionState()
& ๐ sql_app/main.py
๐, ๐ค ๐ช async
๐ reset_db_state()
& โ โซ๏ธ โฎ๏ธ pass
:
async def reset_db_state():
# database.db._state._state.set(db_state_default.copy())
# database.db._state.reset()
pass
โคด๏ธ ๐ ๐ ๐ฑ โฎ๏ธ Uvicorn:
$ uvicorn sql_app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
๐ ๐ ๐ฅ http://127.0.0.1:8000/docs & โ ๐ฉโโคโ๐จ ๐ฉโ๐ป.
โคด๏ธ ๐ 1๏ธโฃ0๏ธโฃ ๐ http://127.0.0.1:8000/docs#/default/read_๐๐ฉโ๐ป_slowusers = ๐ ๐ฐ.
๐ถ โก ๐ ๏ธ "๐ค /slowusers/
" ๐ ๐. โ๏ธ "๐ โซ๏ธ ๐
" ๐ผ & ๐ ๏ธ ๐จ ๐ ๐, 1๏ธโฃ โถ๏ธ๏ธ โฎ๏ธ ๐.
๐ ๐ โ ๐ & โคด๏ธ ๐ซ ๐ ๐ฆ Internal Server Error
.
โซ๏ธโ ๐จ¶
๐ฅ ๐ ๐ โ ๐ ๐ฑ โ ๐ ๐ฝ & โ ๐ฅ โญ ๐ ๐ & ๐ช ๐ฝ ๐.
โคด๏ธ, ๐จ โญ ๐, ๐ ๐ฑ ๐ โ ๐ ๐ฅ ๐, & ๐.
๐ โ ๐ โซ๏ธ ๐ ๐ ๐ ๐ ๐ ๐' ๐จ โช ๐ โฎ๏ธ ๐.
โคด๏ธ 1๏ธโฃ ๐ ๐จ ๐ โ ๐ ๐ฅ ๐ ๐ ๐ ๐ฝ ๐, โ๏ธ 1๏ธโฃ ๐ โฎ๏ธ ๐จ ๐ ๐ ๐ ๐ฒ ๐ต ๐ ๐งต ๐ฅ ๐, โซ๏ธ ๐ โ๏ธ ๐ ๐ฝ ๐ ๐ โช ๐, & ๐ ๐ ๐ฎ โ & ๐ ๐ ๐ โซ๏ธ ๐ถ, & ๐จ ๐ โ๏ธ Internal Server Error
.
๐ ๐ ๐ฒ ๐จ ๐ ๐ 1๏ธโฃ ๐ ๐.
๐ฅ ๐ โ๏ธ ๐ ๐ฉโ๐ป ๐ฌ ๐ ๐ฑ โซ๏ธโ ๐ ๐ฐ, ๐ โซ๏ธโ ๐ช ๐จ.
& ๐ ๐ฑ โถ๏ธ ๐ต ๐ & ๐ ๐ฉโ๐ป ๐ ๐ฐ, โ ๐ฐ ๐ ๐จ ๐ช ๐ & ๐ โฒ โ.
๐ง ๐ โฎ๏ธ FastAPI¶
๐ ๐ถ ๐ ๐ sql_app/database.py
, & โ โธ:
db._state = PeeweeConnectionState()
& ๐ sql_app/main.py
๐, โ ๐ช async
๐ reset_db_state()
:
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
โ ๐ ๐โโ ๐ฑ & โถ๏ธ โซ๏ธ ๐.
๐ ๐ ๐ ๏ธ โฎ๏ธ 1๏ธโฃ0๏ธโฃ ๐. ๐ ๐ฐ ๐ ๐ซ ๐ โ & ๐ ๐ ๐ค ๐ ๐ ๐ต โ.
...๐ ๐ง โซ๏ธ โ
๐ ๐ ๐¶
๐ญ ๐ ๐ โ๏ธ ๐ ๐ my_super_project
(โ๏ธ ๐ ๐ ๐) ๐ ๐ ๐ง-๐ ๐ค sql_app
.
sql_app
๐ โ๏ธ ๐ ๐:
-
sql_app/__init__.py
: ๐ ๐. -
sql_app/database.py
:
from contextvars import ContextVar
import peewee
DATABASE_NAME = "test.db"
db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None}
db_state = ContextVar("db_state", default=db_state_default.copy())
class PeeweeConnectionState(peewee._ConnectionState):
def __init__(self, **kwargs):
super().__setattr__("_state", db_state)
super().__init__(**kwargs)
def __setattr__(self, name, value):
self._state.get()[name] = value
def __getattr__(self, name):
return self._state.get()[name]
db = peewee.SqliteDatabase(DATABASE_NAME, check_same_thread=False)
db._state = PeeweeConnectionState()
sql_app/models.py
:
import peewee
from .database import db
class User(peewee.Model):
email = peewee.CharField(unique=True, index=True)
hashed_password = peewee.CharField()
is_active = peewee.BooleanField(default=True)
class Meta:
database = db
class Item(peewee.Model):
title = peewee.CharField(index=True)
description = peewee.CharField(index=True)
owner = peewee.ForeignKeyField(User, backref="items")
class Meta:
database = db
sql_app/schemas.py
:
from typing import Any, List, Union
import peewee
from pydantic import BaseModel
from pydantic.utils import GetterDict
class PeeweeGetterDict(GetterDict):
def get(self, key: Any, default: Any = None):
res = getattr(self._obj, key, default)
if isinstance(res, peewee.ModelSelect):
return list(res)
return res
class ItemBase(BaseModel):
title: str
description: Union[str, None] = None
class ItemCreate(ItemBase):
pass
class Item(ItemBase):
id: int
owner_id: int
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
is_active: bool
items: List[Item] = []
class Config:
orm_mode = True
getter_dict = PeeweeGetterDict
sql_app/crud.py
:
from . import models, schemas
def get_user(user_id: int):
return models.User.filter(models.User.id == user_id).first()
def get_user_by_email(email: str):
return models.User.filter(models.User.email == email).first()
def get_users(skip: int = 0, limit: int = 100):
return list(models.User.select().offset(skip).limit(limit))
def create_user(user: schemas.UserCreate):
fake_hashed_password = user.password + "notreallyhashed"
db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
db_user.save()
return db_user
def get_items(skip: int = 0, limit: int = 100):
return list(models.Item.select().offset(skip).limit(limit))
def create_user_item(item: schemas.ItemCreate, user_id: int):
db_item = models.Item(**item.dict(), owner_id=user_id)
db_item.save()
return db_item
sql_app/main.py
:
import time
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from . import crud, database, models, schemas
from .database import db_state_default
database.db.connect()
database.db.create_tables([models.User, models.Item])
database.db.close()
app = FastAPI()
sleep_time = 10
async def reset_db_state():
database.db._state._state.set(db_state_default.copy())
database.db._state.reset()
def get_db(db_state=Depends(reset_db_state)):
try:
database.db.connect()
yield
finally:
if not database.db.is_closed():
database.db.close()
@app.post("/users/", response_model=schemas.User, dependencies=[Depends(get_db)])
def create_user(user: schemas.UserCreate):
db_user = crud.get_user_by_email(email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(user=user)
@app.get("/users/", response_model=List[schemas.User], dependencies=[Depends(get_db)])
def read_users(skip: int = 0, limit: int = 100):
users = crud.get_users(skip=skip, limit=limit)
return users
@app.get(
"/users/{user_id}", response_model=schemas.User, dependencies=[Depends(get_db)]
)
def read_user(user_id: int):
db_user = crud.get_user(user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@app.post(
"/users/{user_id}/items/",
response_model=schemas.Item,
dependencies=[Depends(get_db)],
)
def create_item_for_user(user_id: int, item: schemas.ItemCreate):
return crud.create_user_item(item=item, user_id=user_id)
@app.get("/items/", response_model=List[schemas.Item], dependencies=[Depends(get_db)])
def read_items(skip: int = 0, limit: int = 100):
items = crud.get_items(skip=skip, limit=limit)
return items
@app.get(
"/slowusers/", response_model=List[schemas.User], dependencies=[Depends(get_db)]
)
def read_slow_users(skip: int = 0, limit: int = 100):
global sleep_time
sleep_time = max(0, sleep_time - 1)
time.sleep(sleep_time) # Fake long processing request
users = crud.get_users(skip=skip, limit=limit)
return users
๐ก โน¶
Warning
๐ ๐ถ ๐ก โน ๐ ๐ ๐ฒ ๐ซ ๐ช.
โ ¶
๐ โ๏ธ threading.local
๐ข ๐ช โซ๏ธ ๐ฝ "๐ต๐ธ" ๐ฝ (๐, ๐ต, โ๏ธ).
threading.local
โ ๐ฒ ๐ โฎ๏ธ ๐งต, โ๏ธ ๐ ๐ ๏ธ ๐ ๐ ๐ ๐ (โ
๐ ๐จ) ๐ ๐งต, & ๐ฒ ๐ซ โ.
๐ ๐ ๐, ๐ ๐ ๏ธ ๐ช ๐ ๐ ๐ ๐งต (โ๏ธ asyncio.run_in_executor
), โ๏ธ ๐ ๐ ๐จ.
๐ โ ๐, โฎ๏ธ ๐ โฎ๏ธ ๐ ๏ธ, ๐ ๐ ๐ช โ๏ธ ๐ threading.local
๐ข & ๐ ๐ ๐ค ๐ ๐ & ๐ฝ (๐ ๐ซ ๐ซ๐ ๐ซ), & ๐ ๐ฐ, ๐ฅ ๐ซ ๐ ๏ธ ๐ ๐ค/๐
พ-๐ง ๐ ๐งต (โฎ๏ธ ๐ def
๐ข FastAPI, โก ๐ ๏ธ & ๐), ๐ ๐ ๐ ๐ซ โ๏ธ ๐ ๐ฝ ๐ต๐ธ ๐ข, โช โซ๏ธ ๐ ๐ ๐จ & โซ๏ธ ๐ ๐ช ๐ค ๐ ๐ ๐ฝ ๐ต๐ธ.
๐ ๐ข¶
๐ 3๏ธโฃ.7๏ธโฃ โ๏ธ contextvars
๐ ๐ช โ ๐ง๐ฟ ๐ข ๐ถ ๐ threading.local
, โ๏ธ ๐ ๐ซ ๐ โ.
๐ค ๐ ๐ โ๏ธ ๐คฏ.
ContextVar
โ๏ธ โ ๐ ๐น, ๐:
some_var = ContextVar("some_var", default="default value")
โ ๐ฒ โ๏ธ โฎ๏ธ "๐" (โ โฎ๏ธ ๐จ) โ๏ธ:
some_var.set("new value")
๐ค ๐ฒ ๐ ๐ ๐ (โ ๐ ๐ ๐ โฎ๏ธ ๐จ) โ๏ธ:
some_var.get()
โ ๐ ๐ข async
๐ reset_db_state()
¶
๐ฅ ๐ ๐ ๐ โ ๐ฒ โฎ๏ธ some_var.set("updated in function")
(โ
๐ async
๐), ๐ ๐ โซ๏ธ & ๐ ๐ ๐ถ โฎ๏ธ (โ
๐ ๐ async
๐ข ๐ค โฎ๏ธ await
) ๐ ๐ ๐ ๐ ๐ฒ.
, ๐ ๐ผ, ๐ฅ ๐ฅ โ ๐ ๐ต๐ธ ๐ข (โฎ๏ธ ๐ข dict
) async
๐, ๐ ๐ ๐ ๐ ๐ ๐ฑ ๐ ๐ ๐ ๐ฒ & ๐ ๐ช โป โซ๏ธ ๐ ๐จ.
& ๐ ๐ข ๐ โ ๐ โญ ๐จ, ๐ฅ ๐ซ ๐ ๏ธ.
โ ๐ฝ ๐ต๐ธ ๐ get_db()
¶
get_db()
๐ def
๐ข, FastAPI ๐ โ โซ๏ธ ๐ ๐งต, โฎ๏ธ ๐ "๐", ๐งโ๐คโ๐ง ๐ ๐ฒ ๐ ๐ข ( dict
โฎ๏ธ โฒ ๐ฝ ๐ต๐ธ). โคด๏ธ โซ๏ธ ๐ช ๐ฎ ๐ฝ ๐ต๐ธ ๐ dict
, ๐ ๐, โ๏ธ.
โ๏ธ ๐ฅ ๐ฒ ๐ ๐ข (๐ข dict
) โ ๐ ๐ def
๐ข, โซ๏ธ ๐ โ ๐ ๐ฒ ๐ ๐ ๐ง ๐ด ๐ ๐งต ๐งต, & ๐ ๐ (๐ โก ๐ ๏ธ ๐ข) ๐ซ๐ โ๏ธ ๐ โซ๏ธ. get_db()
๐ฅ ๐ช ๐ด โ ๐ฒ dict
, โ๏ธ ๐ซ ๐ dict
โซ๏ธ.
, ๐ฅ ๐ช โ๏ธ async
๐ reset_db_state()
โ dict
๐ ๐ข. ๐ ๐, ๐ ๐ โ๏ธ ๐ ๐ dict
๐ฝ ๐ต๐ธ ๐ ๐จ.
๐ & ๐ ๐ get_db()
¶
โคด๏ธ โญ โ ๐, โซ๏ธโ ๐ซ ๐ & ๐ ๐ฝ async
๐ โซ๏ธ, โฉ๏ธ get_db()
โ
async
๐ โ๏ธ async
๐ ๐ข ๐ก ๐ ๐จ, โ๏ธ ๐ & ๐ช ๐ฝ ๐ โ ๐ง, โซ๏ธ ๐ช ๐ ๐ญ ๐ฅ โซ๏ธ ๐ค.
๐ฅ ๐ช ๐ def
๐ get_db()
.