Ачааллж байна...
Түвшин 2-ын төгсгөлд бид нэг дутагдлыг тэмдэглэсэн: таны бүх код нэг файлд, далан мөр болж хураагдсан. Тэмдэглэлийн API v1 хараахан жижиг тул энэ нь асуудал биш. Гэхдээ энэ API өсөж, хорин endpoint, арван model болбол нэг файл хэдэн зуун мөр болно. Тухайн үед нэг тодорхой endpoint олох нь хэцүү, зэрэгцэн ажиллах нь бэрх, алдаа гаргах нь амархан болно.
Бодит төсөл ингэж бүтдэггүй. Код нь олон файлд, ойлголтоор нь бүлэглэгдэн тархдаг. Түвшин 3 бүхэлдээ энэ тухай. Эхний алхам нь APIRouter — endpoint-үүдийг тусдаа файлд байрлуулах боломж олгодог хэрэгсэл.
Энэ хичээлд бид APIRouter-ыг сурч, кодоо хоёр файлд задална. Курс 2-ын Бүлэг 12-ын "олон файлтай төсөл" мэдлэг энд шууд ажиллана — зөвхөн одоо FastAPI-д тохирсон хэлбэрээр.
Курс 2-ын Бүлэг 12-т та өөрийн модуль үүсгэж, import хийж сурсан. Богино сануулга: нэг Python файл бол модуль. Нэг файлаас нөгөө файлын зүйлийг ашиглахын тулд import хийдэг.
python
# helpers.py файлд
def greet(name):
return f"Сайн байна уу, {name}"
# main.py файлд
from helpers import greet
print(greet("Дулгуун"))main.py нь helpers.py-аас greet функцийг авч байна. Энэ бол код олон файлд тархах үндсэн механизм. APIRouter нь яг үүн дээр тулгуурлана — зөвхөн функц биш, endpoint бүлгийг нэг файлаас нөгөө рүү зөөдөг.
Тэмдэглэлийн API нэмэгдэж, хоёр төрлийн нөөцтэй болсон гэж бодъё: тэмдэглэл (/notes) болон хэрэглэгч (/users). Нэг файлд бол ийм харагдана:
python
# main.py — бүх зүйл нэг файлд
app = FastAPI()
@app.get("/notes")
def get_notes(): ...
@app.post("/notes")
def create_note(): ...
@app.get("/notes/{note_id}")
def get_note(): ...
# ... тэмдэглэлийн дахин хэдэн endpoint ...
@app.get("/users")
def get_users(): ...
@app.post("/users")
def create_user(): ...
# ... хэрэглэгчийн дахин хэдэн endpoint ...Хэдхэн endpoint байхад зүгээр. Гэхдээ тэмдэглэлийн 8 endpoint, хэрэглэгчийн 8 endpoint, номын 8 endpoint болбол нэг файл 300 мөр болно. Тэмдэглэлийн код хэрэглэгчийн кодтой холилдож, тодорхой зүйл олоход хэцүү болно.
Байгалийн шийдэл: тэмдэглэлийн бүх endpoint нэг файлд, хэрэглэгчийн бүх endpoint өөр файлд. Тэр яг APIRouter-ийн зорилго юм.
Зоогийн газрын зүйрлэлээ ашиглая. Жижиг зоогийн газарт цэс нэг хуудас байж болно — бүх хоол нэг дор. Гэхдээ том зоогийн газрын цэс хэсэгт хуваагддаг: нэг хуудас зууш, нэг хуудас үндсэн хоол, нэг хуудас ундаа, нэг хуудас амттан.
Яагаад? Учир нь тэгвэл зочин хайж байгаа зүйлээ хурдан олдог. Ундаа хүсвэл ундааны хуудсыг нээнэ. Мөн гал тогооны талаас — зуушны тогооч зуушны хуудсыг л хариуцна, амттангийн тогооч амттангийн хуудсыг.
APIRouter бол цэсний нэг бүлэг хуудас юм. Тэмдэглэлийн router нь тэмдэглэлтэй холбоотой бүх endpoint-ыг агуулна. Хэрэглэгчийн router нь хэрэглэгчийнхийг. Дараа нь app нь эдгээр хуудсуудыг нэг цэс болгон нэгтгэнэ.
Одоо код руу орно. APIRouter нь app-тай маш төстэй ажилладаг — гэхдээ бие даасан app биш, харин app-д залгагдах хэсэг.
Эхлээд энгийн жишээ, нэг файлд харуулъя (дараа нь файл болгож задална). main.py:
python
# main.py
from fastapi import FastAPI, APIRouter
app = FastAPI(title="Тэмдэглэлийн API")
router = APIRouter()
@router.get("/notes")
def get_notes():
return [{"id": 1, "text": "Жишээ тэмдэглэл"}]
@router.post("/notes", status_code=201)
def create_note():
return {"message": "Тэмдэглэл үүслээ"}
app.include_router(router)Хадгална.
Кодын задаргаа
from fastapi import FastAPI, APIRouter — APIRouter-ыг import хийж байна.
router = APIRouter() — router объект үүсгэлээ. Энэ бол app-тай төстэй, гэхдээ бие даан ажиллаж чадахгүй — тэр зөвхөн endpoint цуглуулдаг.
@router.get("/notes") — endpoint-ыг app-д биш, router-т бүртгэж байна. @app.get биш @router.get. Энэ бол цорын ганц синтаксийн ялгаа. Endpoint-ийн бусад бүх зүйл (зам, функц, буцаах утга) яг адилхан.
app.include_router(router) — энэ бол гол мөр. "Router дээр бүртгэсэн бүх endpoint-ыг app-д нэмээд өг" гэж хэлж байна. Энэ мөргүйгээр router-ийн endpoint-ууд ажиллахгүй — тэд бүртгэгдсэн ч app-тай холбогдоогүй.
Туршина
http://127.0.0.1:8000/notesХариу:
json
[{"id":1,"text":"Жишээ тэмдэглэл"}]Ажиллаж байна. Хэрэглэгчийн зүгээс ямар ч ялгаа байхгүй — endpoint нь @app.get эсвэл @router.get дээр бүртгэгдсэн эсэхийг тэд мэдэхгүй. /docs дээр ч ялгаа харагдахгүй. APIRouter бол зөвхөн кодыг зохион байгуулах хэрэгсэл; API-ийн зан төлөвийг өөрчлөхгүй.
Дээрх жишээ нэг файлд байсан тул ашиг нь харагдахгүй. Одоо кодыг үнэхээр хоёр файлд задална.
Төслийн хавтастаа шинэ файл үүсгэнэ: notes.py (main.py-ийн хажууд, ижил түвшинд).
notes.py файлд бичнэ:
python
# notes.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter()
notes: list[dict] = []
next_id = 1
class NoteCreate(BaseModel):
text: str
done: bool = False
class NotePublic(BaseModel):
id: int
text: str
done: bool
@router.get("/notes", response_model=list[NotePublic])
def get_notes():
return notes
@router.post("/notes", status_code=201, response_model=NotePublic)
def create_note(note: NoteCreate):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_note
@router.get("/notes/{note_id}", response_model=NotePublic)
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Одоо main.py файлаа дараах кодоор бүхэлд нь солино:
python
# main.py
from fastapi import FastAPI
import notes
app = FastAPI(title="Тэмдэглэлийн API")
app.include_router(notes.router)Хадгална.
Кодын задаргаа
main.py одоо гурван мөр л байна. Гайхалтай ялгаа. Бүх endpoint, model, логик нь notes.py-д нүүсэн. main.py зөвхөн app үүсгэж, router-ыг холбож байна.
import notes — notes.py файлыг бүхэлд нь импортлож байна. Курс 2-ын Бүлэг 12-оос танил: файлын нэрийг (өргөтгөлгүйгээр) import хийдэг.
app.include_router(notes.router) — notes модулийн доторх router объектыг авч, app-д нэмж байна. notes.router гэдэг нь "notes.py доторх router" гэсэн үг — цэгээр модуль доторх зүйлд хандаж байна.
Туршина
Сервер reload болсныг шалгаад:
http://127.0.0.1:8000/docs/docs дээр гурван endpoint харагдана: GET /notes, POST /notes, GET /notes/{note_id}. Бүгд notes.py-аас ирсэн.
http://127.0.0.1:8000/notesХариу:
json
[]Ажиллаж байна. Код хоёр файлд тархсан ч API яг адилхан ажиллаж байна. Хэрэглэгч ялгааг мэдэхгүй; та зөвхөн кодоо цэгцэлсэн.
Одоо нэг зүйлийг анзаараарай: notes.py доторх бүх зам /notes-ээр эхэлж байна. /notes, /notes, /notes/{note_id}. Энэ давталт нь router бүрд гардаг.
APIRouter үүнийг цэвэрлэх арга өгдөг: prefix. Router үүсгэхдээ нийтлэг эхлэлийг нэг удаа зарлаж, дараа нь замуудаас хасна.
notes.py-г засна:
python
# notes.py (эхний хэсэг болон декораторуудыг өөрчилнө)
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter(prefix="/notes")
notes: list[dict] = []
next_id = 1
class NoteCreate(BaseModel):
text: str
done: bool = False
class NotePublic(BaseModel):
id: int
text: str
done: bool
@router.get("", response_model=list[NotePublic])
def get_notes():
return notes
@router.post("", status_code=201, response_model=NotePublic)
def create_note(note: NoteCreate):
global next_id
new_note = note.model_dump()
new_note["id"] = next_id
notes.append(new_note)
next_id += 1
return new_note
@router.get("/{note_id}", response_model=NotePublic)
def get_note(note_id: int):
for note in notes:
if note["id"] == note_id:
return note
raise HTTPException(status_code=404, detail="Тэмдэглэл олдсонгүй")Хадгална.
Кодын задаргаа
router = APIRouter(prefix="/notes") — router-т prefix зарлалаа. Одоо энэ router-ийн бүх endpoint-ийн зам автоматаар /notes-ээр эхэлнэ.
Тиймээс декораторуудаас /notes хасагдсан:
@router.get("/notes") -> @router.get("") — хоосон зам, prefix нь /notes болно.
@router.get("/notes/{note_id}") -> @router.get("/{note_id}") — prefix нь /notes/{note_id} болно.
Эцсийн зам нь prefix + декораторын зам. /notes + "" = /notes. /notes + /{note_id} = /notes/{note_id}. Үр дүн яг адилхан, гэхдээ давталт арилсан.
Хоосон зам ("") нь эхэндээ хачин харагддаг, гэхдээ prefix-тэй хослоход зөв утга гарна. "Энэ router-ийн үндэс зам" гэсэн үг.
Туршина
http://127.0.0.1:8000/notesЭцсийн зам яг адилхан — /notes. Хэрэглэгчийн зүгээс ямар ч өөрчлөлт байхгүй. Prefix бол зөвхөн кодыг цэвэрхэн болгох дотоод хэрэгсэл.
/docs дээр ч замууд ижилхэн харагдана: GET /notes, POST /notes, GET /notes/{note_id}.
Одоо APIRouter-ийн жинхэнэ ашгийг харъя. Хоёр дахь нөөц нэмье. Шинэ файл users.py үүсгэнэ:
python
# users.py
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/users")
users: list[dict] = []
next_id = 1
class UserCreate(BaseModel):
name: str
email: str
class UserPublic(BaseModel):
id: int
name: str
email: str
@router.get("", response_model=list[UserPublic])
def get_users():
return users
@router.post("", status_code=201, response_model=UserPublic)
def create_user(user: UserCreate):
global next_id
new_user = user.model_dump()
new_user["id"] = next_id
users.append(new_user)
next_id += 1
return new_userХадгална.
Одоо main.py-д хоёр router-ыг хоёуланг холбоно:
python
# main.py
from fastapi import FastAPI
import notes
import users
app = FastAPI(title="Тэмдэглэлийн API")
app.include_router(notes.router)
app.include_router(users.router)Хадгална.
Кодын задаргаа
import notes болон import users — хоёр модуль. app.include_router(...) хоёр удаа — router бүрд нэг. main.py одоо ч цэвэрхэн, зөвхөн залгах ажил хийж байна.
Тэмдэглэлийн бүх логик notes.py-д, хэрэглэгчийн бүх логик users.py-д, тус тусын жагсаалт, model, endpoint-тэйгээ. Хоёр нь бие биедээ огт нөлөөлдөггүй. Тэмдэглэл дээр ажиллах хүн notes.py-г л нээнэ; хэрэглэгч дээр ажиллах хүн users.py-г.
Туршина
/docs хуудсаа нээнэ. Одоо таван endpoint харагдана — гурав нь тэмдэглэлийн, хоёр нь хэрэглэгчийн. Бүгд нэг цэсэнд нэгдсэн, гэхдээ код нь хоёр файлд цэгцтэй тархсан.
http://127.0.0.1:8000/usersХариу:
json
[]Хэрэглэгчийн endpoint ажиллаж байна. Нэг POST хийж хэрэглэгч нэмээд, GET-ээр батлаарай. Тэмдэглэл болон хэрэглэгч бие даан ажилладаг.
include_router мартах
python
# main.py
from fastapi import FastAPI
import notes
app = FastAPI()
# app.include_router(notes.router) — мартсан!/notes руу хандвал:
Хариу (статус 404):
json
{"detail":"Not Found"}Router үүссэн, endpoint бүртгэгдсэн, гэхдээ app-тай холбогдоогүй. include_router бол router-ыг амьдруулдаг мөр. Мартвал endpoint-ууд байхгүй мэт харагдана.
router.router андуурах
python
app.include_router(notes) # буруу — notes бол модуль
app.include_router(notes.router) # зөв — router бол объектnotes бол файл (модуль). notes.router бол тэр файл доторх router объект. include_router-т объект хэрэгтэй, модуль биш. Андуурвал:
AttributeError: module 'notes' has no attribute ...prefix-т зураас давхардуулах
python
router = APIRouter(prefix="/notes")
@router.get("/notes") # prefix давхарлаа!
def get_notes(): ...Эцсийн зам нь /notes + /notes = /notes/notes болно. prefix зарласны дараа декоратороос prefix хэсгийг хасахаа мартвал давхардана. Засвар: @router.get("").
prefix-т зураасаар төгсгөх
python
router = APIRouter(prefix="/notes/") # төгсгөлийн зураас — бурууprefix зураасаар эхлэх ёстой, гэхдээ зураасаар төгсгөх ёсгүй. /notes/ + /{note_id} = /notes//{note_id} (давхар зураас). prefix-ийг /notes (төгсгөлийн зураасгүй) гэж бичнэ.
Шударга зөвлөгөө: маш жижиг API-д router заавал биш. Тэмдэглэлийн API v1 нэг файлд байсан бөгөөд тэр зүгээр байсан.
Router дараах үед хэрэгтэй болно:
Хэд хэдэн төрлийн нөөцтэй (тэмдэглэл, хэрэглэгч, ном) — тус бүрийг тусдаа файлд.
Нэг файл хэтэрхий урт болж, удирдахад бэрх болсон.
Хэд хэдэн хүн зэрэг ажилладаг — тус бүр өөр файл дээр.
Дүрэм: нэг нөөц = нэг router = нэг файл. Энэ бол бодит FastAPI төслүүдийн стандарт бүтэц.
Бид Тэмдэглэлийн API v2 (Бүлэг 9-ийн бүтээн байгуулалт)-ыг яг ийм бүтэцтэй барих бөгөөд capstone нь олон router-тай байх болно.
Хүсвэл гурав дахь router нэмж үзээрэй — жишээ нь tags.py, /tags prefix-тэй. main.py-д гурав дахь include_router нэмээд, /docs дээр гурван бүлэг endpoint харагдахыг ажиглаарай.
Сонирхвол include_router-ийг санаатайгаар мартаад, /notes руу хандаж 404 харна уу. Дараа нь буцааж нэмнэ. Router болон app-ийн холбоог гар дээрээ мэдрэх нь ойлголтыг бататгана.
APIRouter нь endpoint-үүдийг тусдаа файлд байрлуулах боломж олгодог — код цэгцтэй тархана.
router = APIRouter() — app-тай төстэй, гэхдээ бие даан ажиллахгүй.
Endpoint-ыг @router.get дээр бүртгэнэ (@app.get биш).
app.include_router(router) — router-ийн endpoint-ыг app-д нэмнэ. Энэ мөргүйгээр endpoint ажиллахгүй.
prefix (APIRouter(prefix="/notes")) — нийтлэг эхлэлийг нэг удаа зарлаж, давталтаас зайлсхийнэ. Декоратор дээр "" эсвэл /{id}.
Хэд хэдэн router-ыг include_router олон удаа дуудаж нэгтгэнэ.
Router бол зөвхөн кодыг зохион байгуулах хэрэгсэл — API-ийн зан төлөвийг өөрчлөхгүй, хэрэглэгч ялгааг мэдэхгүй.
Дүрэм: нэг нөөц = нэг router = нэг файл.
Одоо та кодоо олон router болгон задалж чадна. Гэхдээ бид файлуудаа зүгээр л хажуу хажууд нь тавьсан — main.py, notes.py, users.py бүгд нэг хавтаст. Жинхэнэ төсөл үүнээс илүү цэгцтэй байдаг: router-ууд нэг хавтаст, model-ууд өөр хавтаст, тохиргоо гурав дахь газар. Дараагийн хичээлд бид төслийн бодит бүтэц сурна — файлуудыг хавтсаар зохион байгуулж, том төсөл хэрхэн зохион байгуулагддгийг үзнэ. Курс 2-ын Бүлэг 12-ын package мэдлэг тэнд бүрэн ажиллах болно.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.