Ачааллж байна...
Өмнөх хичээлд бид Depends-ийн механизмыг сурсан. Жишээ нь энгийн байсан — тогтмол текст буцаах, хуудаслалтын dictionary буцаах. Одоо түүнийг бодит ажилд хэрэглэнэ.
Энэ хичээлд бид жинхэнэ, дахин ашиглагдах query шүүлтүүрийн dependency бүтээнэ: хуудаслалт, эрэмбэлэлт, хайлтыг нэг газар зохион байгуулж, олон endpoint дээр ашиглана. Мөн Pydantic model-ыг dependency болгон ашиглах илүү цэвэрхэн загварыг үзнэ.
Хамгийн чухал нь: энэ хичээлд тавьсан суурь дээр Бүлэг 12 (өгөгдлийн сангийн session) болон Бүлэг 13 (pagination) шууд баригдана. Тэдгээрийг ирэхэд та "аа, бид үүнийг мэднэ" гэж хэлэх болно.
main.py файлаа дараах кодоор солино. Энэ хичээлд бид нэг файлаар ажиллана:
python
# main.py
from fastapi import FastAPI, Depends, HTTPException
app = FastAPI(title="Тэмдэглэлийн API")
notes = [
{"id": 1, "text": "Сүү авах", "priority": "чухал", "done": False},
{"id": 2, "text": "Номоо буцаах", "priority": "энгийн", "done": True},
{"id": 3, "text": "Тайлан бичих", "priority": "яаралтай", "done": False},
{"id": 4, "text": "Гэрээ шалгах", "priority": "чухал", "done": True},
{"id": 5, "text": "Утас цэнэглэх", "priority": "энгийн", "done": False},
{"id": 6, "text": "Хурал бэлдэх", "priority": "яаралтай", "done": False},
{"id": 7, "text": "Мэйл шалгах", "priority": "энгийн", "done": True},
]Долоон тэмдэглэл, гурван priority, хоёр төлөв. Шүүлт, эрэмбэлэлт туршихад хангалттай.
Өмнөх хичээлийн загвараар эхэлье, гэхдээ илүү бүрэн:
python
# main.py (нэмэлт)
def list_params(
skip: int = 0,
limit: int = 10,
search: str = "",
sort_desc: bool = False,
):
if limit > 50:
limit = 50
if limit < 1:
limit = 1
if skip < 0:
skip = 0
return {
"skip": skip,
"limit": limit,
"search": search,
"sort_desc": sort_desc,
}Хадгална.
Кодын задаргаа
Дөрвөн параметр: skip (хэдийг алгасах), limit (хэдийг өгөх), search (хайлтын үг), sort_desc (буурахаар эрэмбэлэх эсэх). Бүгд default утгатай тул заавал биш.
Дараа нь гурван шалгалт. Эдгээр нь чухал бөгөөд dependency-ийн жинхэнэ үнэ цэнийг харуулж байна.
if limit > 50: limit = 50 — хэрэглэгч ?limit=999999 гэж илгээвэл яах вэ? Долоон тэмдэглэлтэй бол зүгээр, гэхдээ сая тэмдэглэлтэй өгөгдлийн сантай бол таны сервер сая мөр уншиж, санах ой дүүрч, унана. Дээд хязгаар нь хамгаалалт юм.
if limit < 1: limit = 1 — ?limit=0 эсвэл ?limit=-5 гэвэл утгагүй хариу гарна. Доод хязгаар түүнээс сэргийлнэ.
if skip < 0: skip = 0 — сөрөг skip нь Python-ы slicing-д хачирхалтай зан төлөв үүсгэдэг (жагсаалтын төгсгөлөөс тоолдог). Тэглэх нь илүү найдвартай.
Энэ гурван шалгалт бол яг dependency-ийн зорилго. Тэднийг endpoint бүрт бичих ёсгүй — нэг газар, бүх endpoint-д.
Одоо гурван endpoint бичье, гурвуулаа ижил dependency ашиглана:
python
# main.py (нэмэлт)
def apply_params(source: list[dict], params: dict) -> list[dict]:
result = source
if params["search"]:
term = params["search"].lower()
result = [n for n in result if term in n["text"].lower()]
result = sorted(result, key=lambda n: n["id"], reverse=params["sort_desc"])
start = params["skip"]
return result[start : start + params["limit"]]
@app.get("/notes")
def get_notes(params: dict = Depends(list_params)):
return apply_params(notes, params)
@app.get("/notes/done")
def get_done_notes(params: dict = Depends(list_params)):
source = [n for n in notes if n["done"]]
return apply_params(source, params)
@app.get("/notes/pending")
def get_pending_notes(params: dict = Depends(list_params)):
source = [n for n in notes if not n["done"]]
return apply_params(source, params)Хадгална.
Кодын задаргаа
apply_params бол ердийн туслах функц — dependency биш. Түүнийг FastAPI дуудахгүй; бид өөрсдөө дуудаж байна. Энэ ялгааг тодруулах нь чухал: Depends() дотор нэрлэгдсэн функцийг FastAPI дуудна; бусад функцийг та өөрөө дуудна.
apply_params нь жагсаалт болон параметрийг авч, шүүлт, эрэмбэлэлт, хуудаслалт хийж, үр дүн буцаана. Логик нь Түвшин 1-ээс танил: дараалсан шүүлт, sorted + lambda, slicing.
Гурван endpoint бүгд Depends(list_params) ашиглаж, дараа нь apply_params-ыг дуудна. Ялгаа нь зөвхөн эх сурвалж: бүх тэмдэглэл, дууссан, эсвэл дуусаагүй.
Энд ажлын хуваарилалтыг анзаараарай. list_params (dependency) нь параметрийг унших, шалгах. apply_params (туслах функц) нь логикийг гүйцэтгэх. Endpoint нь эх сурвалжийг сонгож, хоёрыг холбох. Гурван тодорхой үүрэг, тус бүр нэг газар.
Шалгах цэг 1: default
http://127.0.0.1:8000/notesХариу: долоон тэмдэглэл бүгд (limit 10 > 7).
Шалгах цэг 2: хуудаслалт
http://127.0.0.1:8000/notes?skip=2&limit=3Хариу:
json
[{"id":3,"text":"Тайлан бичих","priority":"яаралтай","done":false},{"id":4,"text":"Гэрээ шалгах","priority":"чухал","done":true},{"id":5,"text":"Утас цэнэглэх","priority":"энгийн","done":false}]Хоёрыг алгасаж, гурвыг авлаа — id 3, 4, 5.
Шалгах цэг 3: хайлт
http://127.0.0.1:8000/notes?search=шалгахХариу:
json
[{"id":4,"text":"Гэрээ шалгах","priority":"чухал","done":true},{"id":7,"text":"Мэйл шалгах","priority":"энгийн","done":true}]Хоёр тэмдэглэлд "шалгах" гэсэн үг байна.
Шалгах цэг 4: эрэмбэлэлт
http://127.0.0.1:8000/notes?sort_desc=true&limit=3Хариу:
json
[{"id":7,...},{"id":6,...},{"id":5,...}]Буурах дараалал — id 7, 6, 5.
Шалгах цэг 5: хамгаалалт ажиллаж байна
http://127.0.0.1:8000/notes?limit=999999Хариу: долоон тэмдэглэл (бүгд), алдаагүй.
Хэрэглэгч 999999 гэж хүссэн ч dependency түүнийг 50 болгож хязгаарласан. Долоон тэмдэглэлтэй бол ялгаа мэдэгдэхгүй, гэхдээ сая мөртэй өгөгдлийн санд энэ хамгаалалт таны серверийг аварна.
Мөн туршаад үзээрэй: ?limit=0 болон ?limit=-5 — хоёулаа нэг тэмдэглэл буцаана (limit 1 болж засагдсан).
Шалгах цэг 6: бусад endpoint
http://127.0.0.1:8000/notes/done?limit=2Хариу: дууссан тэмдэглэлээс хоёр.
http://127.0.0.1:8000/notes/pending?search=бэлдэхХариу: дуусаагүй тэмдэглэлээс "бэлдэх" агуулсан нь.
Ижил dependency гурван endpoint дээр ажиллаж байна. Хайлт, хуудаслалт, эрэмбэлэлт, хамгаалалт — бүгд нэг газар бичигдэж, гурван газар ашиглагдаж байна.
Одоо dependency-ийн жинхэнэ ашгийг харъя. Шинэ шаардлага гарлаа: priority-гээр шүүх боломж нэмэх.
Хэрэв dependency ашиглаагүй бол гурван endpoint дээр параметр нэмэх ёстой байсан. Одоо зөвхөн нэг газар:
python
# main.py (list_params болон apply_params-ыг солино)
def list_params(
skip: int = 0,
limit: int = 10,
search: str = "",
priority: str = "",
sort_desc: bool = False,
):
if limit > 50:
limit = 50
if limit < 1:
limit = 1
if skip < 0:
skip = 0
return {
"skip": skip,
"limit": limit,
"search": search,
"priority": priority,
"sort_desc": sort_desc,
}
def apply_params(source: list[dict], params: dict) -> list[dict]:
result = source
if params["search"]:
term = params["search"].lower()
result = [n for n in result if term in n["text"].lower()]
if params["priority"]:
result = [n for n in result if n["priority"] == params["priority"]]
result = sorted(result, key=lambda n: n["id"], reverse=params["sort_desc"])
start = params["skip"]
return result[start : start + params["limit"]]Хадгална.
Endpoint-үүдэд гар хүрээгүй. Гурвуулаа хэвээр:
python
@app.get("/notes")
def get_notes(params: dict = Depends(list_params)):
return apply_params(notes, params)Туршина
http://127.0.0.1:8000/notes?priority=чухалХариу:
json
[{"id":1,"text":"Сүү авах","priority":"чухал","done":false},{"id":4,"text":"Гэрээ шалгах","priority":"чухал","done":true}]Ажиллалаа. Одоо бусад endpoint дээр ч:
http://127.0.0.1:8000/notes/pending?priority=яаралтайХариу: дуусаагүй, яаралтай тэмдэглэлүүд.
Гурван endpoint шинэ чадвар авлаа, гэхдээ гурвууланд нь гар хүрээгүй. Энэ бол dependency-ийн гол давуу тал. Хэрэв арван endpoint байсан бол арван газар засах ёстой байсан — одоо ганц.
/docs дээр гурван endpoint бүгдэд нь priority параметр автоматаар гарч ирснийг ажиглаарай.
Одоог хүртэл dependency нь dictionary буцааж байсан. Тэгэхээр endpoint дотор params["skip"], params["limit"] гэж хандаж байна — дөрвөлжин хаалттай, түлхүүрийн нэр буруу бичих эрсдэлтэй, editor тусламж өгдөггүй.
Илүү сайн арга бий: Pydantic model-ыг dependency болгох. Тэгвэл цэгээр хандана, editor автоматаар гүйцээнэ, type hint ажиллана.
python
# main.py (list_params-ыг солино)
from pydantic import BaseModel
class ListParams(BaseModel):
skip: int = 0
limit: int = 10
search: str = ""
priority: str = ""
sort_desc: bool = False
def list_params(
skip: int = 0,
limit: int = 10,
search: str = "",
priority: str = "",
sort_desc: bool = False,
) -> ListParams:
if limit > 50:
limit = 50
if limit < 1:
limit = 1
if skip < 0:
skip = 0
return ListParams(
skip=skip,
limit=limit,
search=search,
priority=priority,
sort_desc=sort_desc,
)
def apply_params(source: list[dict], params: ListParams) -> list[dict]:
result = source
if params.search:
term = params.search.lower()
result = [n for n in result if term in n["text"].lower()]
if params.priority:
result = [n for n in result if n["priority"] == params.priority]
result = sorted(result, key=lambda n: n["id"], reverse=params.sort_desc)
return result[params.skip : params.skip + params.limit]
@app.get("/notes")
def get_notes(params: ListParams = Depends(list_params)):
return apply_params(notes, params)
@app.get("/notes/done")
def get_done_notes(params: ListParams = Depends(list_params)):
source = [n for n in notes if n["done"]]
return apply_params(source, params)
@app.get("/notes/pending")
def get_pending_notes(params: ListParams = Depends(list_params)):
source = [n for n in notes if not n["done"]]
return apply_params(source, params)Хадгална.
Кодын задаргаа
ListParams — Pydantic model, параметрүүдийн бүтцийг тодорхойлно. Бүлэг 4-6-т сурсан model-ыг одоо дотоод зорилгоор ашиглаж байна — оролт/гаралтын model биш, харин кодын доторх бүтэц.
list_params функц одоо ListParams объект буцаана, dictionary биш.
params: ListParams = Depends(list_params) — endpoint-ийн type hint нь одоо ListParams. Энэ нь зөвхөн уншихад тустай биш — editor тань одоо params. гэж бичихэд боломжит талбаруудыг санал болгоно.
apply_params дотор params["search"] биш params.search — цэгээр хандаж байна. Цэвэрхэн, аюулгүй. Хэрэв та params.serach гэж буруу бичвэл editor шууд улаанаар зурна; params["serach"] бол ажиллах үед л KeyError өгнө.
Туршина
Бүх өмнөх туршилтууд яг адилхан ажиллана:
http://127.0.0.1:8000/notes?priority=чухал&limit=1Хариу:
json
[{"id":1,"text":"Сүү авах","priority":"чухал","done":false}]Гадаад зан төлөв өөрчлөгдөөгүй — зөвхөн дотоод код цэвэрхэн боллоо. Энэ бол сайн засварын шинж: хэрэглэгч ялгааг мэдэхгүй, хөгжүүлэгч ажиллахад хялбар болно.
Одоо энэ хичээлийн хамгийн чухал зүйлийг тодорхой хэлье. Энд сурсан загвар нь хоёр ирээдүйн бүлэгт шууд ашиглагдана.
Бүлэг 13: Pagination
Бүлэг 13-ын "Pagination ба шүүлтүүр" хичээлд бид яг энэ skip/limit загварыг өгөгдлийн сангийн эсрэг ашиглана. Ялгаа нь: одоо бид Python жагсаалтыг slicing хийж байна (result[skip : skip + limit]); тэнд бид өгөгдлийн сангаас зөвхөн хэрэгтэй мөрийг асууна (.offset(skip).limit(limit)).
Ялгаа нь чухал. Одоо бид бүх долоон тэмдэглэлийг санах ойд авчраад, дараа нь гурвыг сонгож байна. Сая мөртэй өгөгдлийн санд энэ нь боломжгүй. Тэнд бид өгөгдлийн санд "зөвхөн 10 мөр өг" гэж хэлнэ, тэр 10 мөр л ирнэ.
Гэхдээ dependency-ийн бүтэц яг ижил хэвээр. Ижил list_params, ижил Depends, ижил параметрүүд. Зөвхөн apply_params-ын дотоод хэсэг өөрчлөгдөнө. Энэ бол сайн зохион байгуулалтын шинж: хэрэгжүүлэлт өөрчлөгдөхөд интерфейс хэвээр үлддэг.
Бүлэг 12: Өгөгдлийн сангийн session
Энэ нь илүү чухал. Бүлэг 12-т бид ийм dependency бичнэ:
python
def get_session():
with Session(engine) as session:
yield sessionДараа нь endpoint бүр ингэж бичигдэнэ:
python
@router.get("/notes")
def get_notes(session: Session = Depends(get_session)):
...Яг ижил загвар. Depends(функц), endpoint-ийн параметрт үр дүн ирнэ. Ялгаа нь буцаах зүйл: dictionary эсвэл model биш, харин өгөгдлийн сантай холбогдсон session.
Тэр үед dependency нь зөвхөн параметр цуглуулахаас илүү зүйл хийнэ: холболт нээх, ажил дуустал хүлээх, дараа нь холболтыг хаах. yield гэдэг үг (Курс 2-ын Бүлэг 13-ын generator) тэнд гарч ирнэ.
Тиймээс энэ хичээлд сурсан зүйл бол зөвхөн query параметрийн тухай биш. Энэ бол FastAPI-д хуваалцсан нөөцийг зохион байгуулах үндсэн загвар бөгөөд өгөгдлийн сан, аутентикаци, тохиргоо — бүгд түүн дээр баригдана.
apply_params-ыг Depends дотор нэрлэх
python
@app.get("/notes")
def get_notes(result = Depends(apply_params)): # буруу
return resultapply_params нь FastAPI-д дамжуулах ёсгүй функц — түүнд source гэсэн параметр байгаа бөгөөд FastAPI түүнийг хаягнаас олохыг оролдож, эвдэрнэ. apply_params бол ердийн туслах функц, dependency биш. Түүнийг та өөрөө дуудна.
Дүрэм: Depends() дотор зөвхөн хүсэлтээс өгөгдөл авдаг функц бичнэ. Дотоод логикийн функцийг шууд дууд.
Model-ыг шууд Depends-д өгөх
python
@app.get("/notes")
def get_notes(params: ListParams = Depends(ListParams)): # анхаар
...Энэ нь үнэндээ ажилладаг (Pydantic model-ыг dependency болгож болно), гэхдээ шалгалтын логик (limit > 50) байхгүй болно — model зөвхөн утга хүлээж авна, засварлахгүй. Хэрэв танд шалгалт хэрэгтэй бол функц (list_params) дундуур явуулах ёстой. Бид тэр загварыг ашиглаж байна.
Dependency-д хэт их зүйл хийлгэх
python
def list_params(skip: int = 0, limit: int = 10):
result = [n for n in notes if ...] # бүх логикийг энд хийх
return resultDependency нь параметр бэлдэх ёстой, бүх ажлыг хийх ёсгүй. Хэрэв dependency нь өгөгдлийг шүүж, эрэмбэлж, буцаадаг бол endpoint юу ч хийхгүй болно — тэгвэл яагаад endpoint байгаа юм бэ?
Тодорхой хуваарилалт: dependency нь бэлтгэнэ, endpoint нь ашиглана. Бэлтгэл станцын зүйрлэлийг санаарай — станц ногоо хэрчдэг, хоол хийдэггүй.
Хүсвэл done_only: bool | None = None гэсэн параметрийг list_params-д нэмээд, apply_params-д тохирох шүүлт нэмээрэй. Дараа нь гурван endpoint дээр автоматаар гарч ирснийг ажиглаарай. Бүлэг 6-ын bool | None болон is not None мэдлэг энд дахин ажиллана.
Сонирхвол limit-ийн дээд хязгаарыг 3 болгож өөрчлөөд, ?limit=100 илгээж үзээрэй. Гурав л ирнэ. Дараа нь буцааж 50 болгоно. Нэг газарт хийсэн өөрчлөлт бүх endpoint-д хэрхэн тархахыг гар дээрээ мэдрэх нь энэ хичээлийн гол санааг бататгана.
Query параметрийн dependency нь хуудаслалт, хайлт, шүүлт, эрэмбэлэлт-ийг нэг газар зохион байгуулна.
Dependency дотор хамгаалалтын шалгалт тавина (limit > 50, skip < 0) — endpoint бүрт биш, нэг газар.
Ажлын хуваарилалт: dependency бэлтгэнэ, туслах функц гүйцэтгэнэ, endpoint холбоно.
Шинэ параметр нэмэхэд зөвхөн dependency-г засна — бүх endpoint автоматаар шинэчлэгдэнэ.
Pydantic model-ыг dependency-ийн буцаах утга болгох нь илүү цэвэрхэн: params.skip (цэгээр), params["skip"] биш.
Depends() дотор зөвхөн хүсэлтээс өгөгдөл авдаг функц бичнэ; дотоод туслах функцийг шууд дууд.
Энэ загвар нь Бүлэг 13 (өгөгдлийн сангийн pagination) болон Бүлэг 12 (session dependency)-д шууд ашиглагдана.
Одоо та Depends-ыг бодит ажилд ашиглаж чадна. Гэхдээ бид түүнийг зөвхөн өгөгдөл бэлдэхэд ашиглаж ирсэн. Depends нь өөр нэг маш чухал зүйл хийж чадна: хүсэлтийг зогсоох.
Dependency нь HTTPException шидэж чадна — тэгвэл endpoint огт дуудагдахгүй. Энэ бол хамгаалалтын үндэс юм. Дараагийн хичээлд бид API key ашиглан бичих үйлдлүүдээ хамгаална: зөв түлхүүргүй хүн шинэ тэмдэглэл үүсгэж, засаж, устгаж чадахгүй болно. Тэр нь Бүлэг 8-ыг төгсгөж, таны API анх удаа жинхэнэ хамгаалалттай болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.