Ачааллж байна...
Энэ хичээлд бид Түвшин 1-д сурсан бүх зүйлээ нэг бодит программд нэгтгэнэ. Бид Монголын хотуудын API бүтээнэ — хотуудын жагсаалт өгдөг, тодорхой нэг хотын дэлгэрэнгүйг өгдөг, хайлт хийдэг, шүүлт хийдэг, байхгүй зүйл дээр зөв 404 буцаадаг API.
Энэ бол уншиж, ойлгож, дагаж бичих хичээл. Гэрийн даалгавар биш. Бид мөр мөрөөр хамтдаа бичих бөгөөд алхам бүрийн дараа "одоо ажиллуулаад ийм зүйл харах ёстой" гэсэн шалгах цэг байна. Хэрэв ямар нэг алхам дээр таны харж байгаа зүйл миний бичсэнээс өөр байвал тэр алхам дээрээ зогсоод шалгаарай — цаашаа явахаас өмнө.
Бид нэг файлаар ажиллана: main.py. Олон файлт бүтцийг Түвшин 3-т сурна.
Эхлээд юу бүтээхээ тодорхой болгоё. Энэ бол бодит төсөл эхлүүлэхийн өмнө хийдэг ажил — код бичихээс өмнө юу хийхээ мэдэх.
Method | Зам | Юу хийх вэ |
|---|---|---|
GET |
| API-ийн тухай товч мэдээлэл |
GET |
| Бүх хотын жагсаалт (хайлт, шүүлт, хязгаарлалттай) |
GET |
| Хамгийн олон хүн амтай хот |
GET |
| Тодорхой нэг хотын дэлгэрэнгүй |
GET |
| Ерөнхий статистик |
Таван endpoint. Тэдгээрийн дараалал чухал гэдгийг та Бүлэг 2-ын сүүлийн хичээлээс мэднэ: /cities/largest нь /cities/{city_id}-аас дээр байх ёстой. Тэгэхгүй бол largest гэдэг текстийг тоо болгож хувиргах гэж оролдоод 422 гарна.
Бид энэ дарааллыг эхнээсээ зөв барина.
main.py файлаа дараах кодоор бүхэлд нь солино:
python
# main.py
from fastapi import FastAPI, HTTPException
app = FastAPI(
title="Монголын хотуудын API",
description="Монгол улсын хот, суурин газруудын мэдээлэл",
version="1.0.0",
)
cities = [
{
"id": 1,
"name": "Улаанбаатар",
"province": "Улаанбаатар",
"population": 1600000,
"founded": 1639,
"is_capital": True,
},
{
"id": 2,
"name": "Эрдэнэт",
"province": "Орхон",
"population": 98000,
"founded": 1974,
"is_capital": False,
},
{
"id": 3,
"name": "Дархан",
"province": "Дархан-Уул",
"population": 75000,
"founded": 1961,
"is_capital": False,
},
{
"id": 4,
"name": "Чойбалсан",
"province": "Дорнод",
"population": 38000,
"founded": 1930,
"is_capital": False,
},
{
"id": 5,
"name": "Мөрөн",
"province": "Хөвсгөл",
"population": 39000,
"founded": 1933,
"is_capital": False,
},
{
"id": 6,
"name": "Ховд",
"province": "Ховд",
"population": 31000,
"founded": 1685,
"is_capital": False,
},
]
@app.get("/")
def root():
return {
"name": "Монголын хотуудын API",
"version": "1.0.0",
"total_cities": len(cities),
}Хадгална.
Юу бичсэн бэ
FastAPI(...) дотор гурван параметр өглөө. title нь /docs хуудасны гарчиг. description нь түүний доорх тайлбар. version нь хувилбарын дугаар. Эдгээр нь бүгд заавал биш, гэхдээ баримт бичгийг мэргэжлийн болгоно.
cities бол dictionary-үүдийн list. Талбар бүрийн түлхүүр латин үсгээр байгааг анзаараарай — name, province, population. Утга нь монголоор. Энэ бол Бүлэг 3-ын Хичээл 1-д тогтоосон дүрэм.
root() функц нь len(cities) ашиглан хотын тоог тоолж байна — тоог гараар бичсэнгүй. Хэрэв та ирээдүйд хот нэмбэл энэ тоо өөрөө зөв байх болно. Энэ бол жижиг зүйл боловч сайн зуршил: тоолж болох зүйлийг гараар бүү бич.
Шалгах цэг
Терминалаа шалгана — reload болсон уу?
http://127.0.0.1:8000/Хариу:
json
{"name":"Монголын хотуудын API","version":"1.0.0","total_cities":6}Дараа нь /docs хуудсаа нээнэ. Дээд талд Монголын хотуудын API 1.0.0 гэсэн гарчиг, доор нь тайлбар харагдана. Endpoint-ийн жагсаалтад ганц мөр байна.
Хэрэв энэ хоёулаа зөв харагдаж байвал үргэлжлүүлээрэй.
Хамгийн энгийн хувилбараар эхэлье. Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.get("/cities")
def get_cities():
return citiesХадгална.
Шалгах цэг
http://127.0.0.1:8000/citiesХариу:
json
[{"id":1,"name":"Улаанбаатар","province":"Улаанбаатар","population":1600000,"founded":1639,"is_capital":true},{"id":2,"name":"Эрдэнэт","province":"Орхон","population":98000,"founded":1974,"is_capital":false}, ... ]Зургаан хот бүгд ирнэ. Python-ы True нь JSON-ы true болсныг анзаараарай.
Ажиллаж байна. Гэхдээ энэ endpoint бол хэтэрхий энгийн — жинхэнэ API-д хэрэглэгч хайх, шүүх, хязгаарлах боломжтой байх ёстой. Дараагийн алхамд түүнийг нэмнэ.
get_cities функцийг дараах кодоор солино:
python
# main.py (get_cities-ыг солино)
@app.get("/cities")
def get_cities(
search: str = "",
province: str = "",
min_population: int = 0,
limit: int = 10,
):
result = cities
if search:
result = [c for c in result if search.lower() in c["name"].lower()]
if province:
result = [c for c in result if c["province"].lower() == province.lower()]
if min_population > 0:
result = [c for c in result if c["population"] >= min_population]
return result[:limit]Хадгална.
Кодын задаргаа
Дөрвөн query parameter бүгд default утгатай тул бүгд заавал биш. Хэрэглэгч юу ч өгөхгүй бол бүх хот (эхний 10) ирнэ.
result = cities — эхлээд бүх хотоор эхэлж байна. Дараа нь шүүлт бүр result-ыг нарийсгана. Энэ загварыг дараалсан шүүлт гэж нэрлэж болно: шүүлт бүр өмнөхийнхөө үр дүн дээр ажиллана.
if search: — хоосон текст нь Python-д худал утгатай тул хэрэглэгч хайлт өгөөгүй бол энэ блок алгасагдана. Курс 1-ээс танил зарчим.
Гурван list comprehension байна — Курс 2-ын Бүлэг 2. Тус бүр нь тодорхой нөхцөлд тохирох хотуудыг л үлдээж байна.
.lower() хоёр талд хэрэглэсэн нь том жижиг үсгийн ялгааг арилгаж байна: "улаанбаатар" гэж хайсан ч "Улаанбаатар" олдоно.
search нь дотор нь агуулагдах эсэхийг шалгаж байна (in), харин province нь яг тэнцүү эсэхийг шалгаж байна (==). Энэ ялгаа зориудаар: хайлт нь уян хатан байх ёстой, аймгийн шүүлт нь тодорхой байх ёстой.
result[:limit] — Курс 2-ын slicing. Эцэст нь хязгаарлалт хэрэгжинэ.
Шалгах цэг 1: параметргүй
http://127.0.0.1:8000/citiesЗургаан хот бүгд ирнэ (6 < 10 тул хязгаарлалт нөлөөлөхгүй).
Шалгах цэг 2: хайлт
http://127.0.0.1:8000/cities?search=дарХариу:
json
[{"id":3,"name":"Дархан","province":"Дархан-Уул","population":75000,"founded":1961,"is_capital":false}]"дар" гэсэн үсэг зөвхөн "Дархан" дотор байна.
Шалгах цэг 3: аймгаар шүүх
http://127.0.0.1:8000/cities?province=ОрхонХариу:
json
[{"id":2,"name":"Эрдэнэт","province":"Орхон","population":98000,"founded":1974,"is_capital":false}]Шалгах цэг 4: хүн амаар шүүх
http://127.0.0.1:8000/cities?min_population=50000Хариу:
json
[{"id":1,"name":"Улаанбаатар", ... },{"id":2,"name":"Эрдэнэт", ... },{"id":3,"name":"Дархан", ... }]Гурван хот — 1,600,000 / 98,000 / 75,000 нь бүгд 50,000-аас их.
Шалгах цэг 5: хосолсон
http://127.0.0.1:8000/cities?min_population=30000&limit=2Хариу:
json
[{"id":1,"name":"Улаанбаатар", ... },{"id":2,"name":"Эрдэнэт", ... }]Зургаан хот бүгд 30,000-аас их боловч limit=2 тул зөвхөн хоёр нь ирлээ.
Хаягны бүтцийг ажиглаарай: ?min_population=30000 дараа нь &limit=2. Асуултын тэмдэг, дараа нь амперсанд.
Шалгах цэг 6: /docs
/docs дээр GET /cities мөрийг задална. Parameters хэсэгт дөрвөн мөр харагдана, тус бүр төрөл болон default утгатай:
search string (query) Default: ""
province string (query) Default: ""
min_population integer (query) Default: 0
limit integer (query) Default: 10Аль нь ч од (*) тэмдэггүй — бүгд заавал биш.
Try it out дарж, талбаруудыг дүүргэж туршаарай. Хоосон талбарууд хаягт орохгүй байхыг Request URL дээр ажиглаарай.
Одоо /cities/largest нэмнэ. Байрлал чухал — түүнийг /cities/{city_id}-ийн өмнө бичих ёстой. Гэхдээ бид {city_id}-г хараахан бичээгүй тул одоо бичихэд асуудалгүй.
Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.get("/cities/largest")
def get_largest_city():
return max(cities, key=lambda c: c["population"])Хадгална.
Кодын задаргаа
max() функц нь жагсаалтаас хамгийн их утгатай элементийг олно. Гэхдээ хот бол dictionary — "хамгийн их хот" гэж юу гэсэн үг вэ? key=lambda c: c["population"] нь тэр асуултад хариулж байна: хүн амын тоогоор харьцуул.
Курс 2-ын Бүлэг 3-ын lambda — нэргүй, нэг мөрийн функц.
Шалгах цэг
http://127.0.0.1:8000/cities/largestХариу:
json
{"id":1,"name":"Улаанбаатар","province":"Улаанбаатар","population":1600000,"founded":1639,"is_capital":true}Одоо хамгийн чухал endpoint. Файлын төгсгөлд нэмнэ — /cities/largest-ийн ДАРАА:
python
# main.py (нэмэлт)
@app.get("/cities/{city_id}")
def get_city(city_id: int):
for city in cities:
if city["id"] == city_id:
return city
raise HTTPException(
status_code=404,
detail=f"{city_id} дугаартай хот олдсонгүй",
)Хадгална.
Кодын задаргаа
city_id: int — path parameter, type hint-тэй. Хэрэглэгч abc илгээвэл FastAPI 422 буцаана, функц дуудагдахгүй.
for давталт хотыг хайна. Олдвол return city — функц шууд дуусна.
Олдохгүй бол давталт дуусаж, raise HTTPException(...) ажиллана. detail талбарт f-string ашиглан динамик мессеж үүсгэсэн — хэрэглэгч ямар дугаар хайснаа мессежээс харна.
Шалгах цэг 1: олдсон хот
http://127.0.0.1:8000/cities/3Хариу (статус 200):
json
{"id":3,"name":"Дархан","province":"Дархан-Уул","population":75000,"founded":1961,"is_capital":false}Шалгах цэг 2: олдоогүй хот
http://127.0.0.1:8000/cities/99Хариу (статус 404):
json
{"detail":"99 дугаартай хот олдсонгүй"}Status code 404 гэдгийг /docs дээр эсвэл F12 -> Network дээр баталгаажуулаарай. Энэ бол Бүлэг 3-ын гол ялалт: таны API одоо үнэн ярьж байна.
Шалгах цэг 3: буруу төрөл
http://127.0.0.1:8000/cities/abcХариу (статус 422):
json
{"detail":[{"type":"int_parsing","loc":["path","city_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]}Танил бүтэц. loc талбар ["path", "city_id"] — алдаа замд, city_id параметр дээр.
Шалгах цэг 4: дараалал зөв ажиллаж байна уу
Энэ бол хамгийн чухал шалгалт:
http://127.0.0.1:8000/cities/largestХариу:
json
{"id":1,"name":"Улаанбаатар", ... }Ажиллаж байна. Хэрэв та /cities/largest-ыг /cities/{city_id}-ийн доор бичсэн байсан бол энд 422 гарах байсан.
Туршиж үзэхийг хүсвэл: /cities/largest блокийг файлын хамгийн доор зөөж, хадгалаад, дахин хандаж үзээрэй. 422 гарна. Дараа нь буцааж дээш зөөнө. Энэ туршилт дүрмийг санахад тусална.
Сүүлийн endpoint. Файлын төгсгөлд нэмнэ:
python
# main.py (нэмэлт)
@app.get("/stats")
def get_stats():
populations = [c["population"] for c in cities]
return {
"total_cities": len(cities),
"total_population": sum(populations),
"largest_city": max(cities, key=lambda c: c["population"])["name"],
"smallest_city": min(cities, key=lambda c: c["population"])["name"],
"provinces": sorted({c["province"] for c in cities}),
}Хадгална.
Кодын задаргаа
populations — list comprehension ашиглан зөвхөн хүн амын тоог гаргаж авлаа.
sum(populations) — нийт хүн ам.
max(...)["name"] — хамгийн том хотыг оллоо, дараа нь түүний нэрийг гаргаж авлаа. Хоёр үйлдэл нэг мөрөнд.
sorted({c["province"] for c in cities}) — энд хоёр зүйл болж байна. Дотор нь set comprehension (буржгар хаалт) — Курс 2-ын Бүлэг 5. Энэ нь давхардсан аймгийн нэрсийг арилгана. Дараа нь sorted() түүнийг list болгож, цагаан толгойн дарааллаар эрэмбэлнэ.
sorted() заавал хэрэгтэй — учир нь Бүлэг 3-ын Хичээл 1-д сурсанчлан set нь JSON болж чаддаггүй. Хэрэв sorted() байхгүй бол 500 алдаа гарна.
Шалгах цэг
http://127.0.0.1:8000/statsХариу:
json
{"total_cities":6,"total_population":1881000,"largest_city":"Улаанбаатар","smallest_city":"Ховд","provinces":["Дархан-Уул","Дорнод","Орхон","Ховд","Хөвсгөл","Улаанбаатар"]}Зургаан аймаг, эрэмбэлэгдсэн, давхардалгүй.
Хэрэв та sorted()-ыг арилгаад зөвхөн {c["province"] for c in cities} үлдээвэл терминал дээр set тухай алдаа гарч, browser дээр 500 ирнэ. Хүсвэл туршаад үзээрэй — Бүлэг 3-ын Хичээл 1-д ярьсан хязгаарлалт бодитоор ажиллахыг харна.
Энэ бол таны бүрэн main.py файл:
python
# main.py
from fastapi import FastAPI, HTTPException
app = FastAPI(
title="Монголын хотуудын API",
description="Монгол улсын хот, суурин газруудын мэдээлэл",
version="1.0.0",
)
cities = [
{
"id": 1,
"name": "Улаанбаатар",
"province": "Улаанбаатар",
"population": 1600000,
"founded": 1639,
"is_capital": True,
},
{
"id": 2,
"name": "Эрдэнэт",
"province": "Орхон",
"population": 98000,
"founded": 1974,
"is_capital": False,
},
{
"id": 3,
"name": "Дархан",
"province": "Дархан-Уул",
"population": 75000,
"founded": 1961,
"is_capital": False,
},
{
"id": 4,
"name": "Чойбалсан",
"province": "Дорнод",
"population": 38000,
"founded": 1930,
"is_capital": False,
},
{
"id": 5,
"name": "Мөрөн",
"province": "Хөвсгөл",
"population": 39000,
"founded": 1933,
"is_capital": False,
},
{
"id": 6,
"name": "Ховд",
"province": "Ховд",
"population": 31000,
"founded": 1685,
"is_capital": False,
},
]
@app.get("/")
def root():
return {
"name": "Монголын хотуудын API",
"version": "1.0.0",
"total_cities": len(cities),
}
@app.get("/cities")
def get_cities(
search: str = "",
province: str = "",
min_population: int = 0,
limit: int = 10,
):
result = cities
if search:
result = [c for c in result if search.lower() in c["name"].lower()]
if province:
result = [c for c in result if c["province"].lower() == province.lower()]
if min_population > 0:
result = [c for c in result if c["population"] >= min_population]
return result[:limit]
@app.get("/cities/largest")
def get_largest_city():
return max(cities, key=lambda c: c["population"])
@app.get("/cities/{city_id}")
def get_city(city_id: int):
for city in cities:
if city["id"] == city_id:
return city
raise HTTPException(
status_code=404,
detail=f"{city_id} дугаартай хот олдсонгүй",
)
@app.get("/stats")
def get_stats():
populations = [c["population"] for c in cities]
return {
"total_cities": len(cities),
"total_population": sum(populations),
"largest_city": max(cities, key=lambda c: c["population"])["name"],
"smallest_city": min(cities, key=lambda c: c["population"])["name"],
"provinces": sorted({c["province"] for c in cities}),
}Жаран мөр орчим код. Таван endpoint. Бүрэн ажиллагаатай API.
/docs хуудсаа нээж, бүхэлд нь ажиглаарай.
Дээд талд таны API-ийн нэр, хувилбар, тайлбар.
Доор нь таван endpoint, тус бүр өөрийн замтай, товч тайлбартай (таны функцийн нэрнээс автоматаар үүссэн).
GET /cities дээр товшвол дөрвөн query parameter, тус бүр төрөл, default утгатай.
GET /cities/{city_id} дээр товшвол заавал байх path parameter (од тэмдэгтэй) болон Responses хэсэгт 200 ба 422.
Та энэ хуудсыг бичээгүй. Ганц мөр HTML, ганц мөр баримт бичиг бичээгүй. Энэ бүхэн таны Python кодоос автоматаар үүссэн.
Одоо Try it out ашиглан бүх endpoint-ийг ээлж дараалан туршаарай. Хайлт хийж үзээрэй. Байхгүй ID оруулж 404 харна уу. Буруу төрөл оруулж 422 харна уу.
Энэ бол таны API. Ажиллаж байна.
Энэ жижиг программд Түвшин 1-ийн бүх ойлголт ашиглагдсан.
app = FastAPI(...) — аппликейшн объект, тохиргоотой (Бүлэг 1).
@app.get(...) decorator — path operation бүртгэх (Бүлэг 2).
Path parameter {city_id} болон type hint-ээр автомат хувиргалт, validation (Бүлэг 2).
Query parameter — дөрвөн заавал бус параметр, default утгатай (Бүлэг 2).
Route ordering — /cities/largest нь /cities/{city_id}-ийн өмнө (Бүлэг 2).
JSON хариу — dict, list, үүрлэсэн бүтэц; түлхүүр латин, утга монгол; set-ийг list болгох (Бүлэг 3).
Status codes — 200 автоматаар, 404 зориудаар, 422 FastAPI-аас (Бүлэг 3).
HTTPException — raise, detail, динамик мессеж (Бүлэг 3).
Мөн Курс 1-2-оос: list comprehension, set comprehension, lambda, slicing, f-string, max/min/sum/sorted, dictionary-үүдийн list.
Эдгээр нь заавал биш. Дараагийн хичээл тэднийг хийсэн гэж таамаглахгүй. Гэхдээ сонирхвол оролдож үзээрэй.
Нэгдүгээр өргөтгөл: эрэмбэлэх параметр. /cities endpoint-д sort_by: str = "" гэсэн query parameter нэмж, population эсвэл founded гэсэн утгаар эрэмбэлж болно. Санамж: sorted(result, key=lambda c: c[sort_by]). Гэхдээ хэрэглэгч байхгүй талбарын нэр өгвөл KeyError -> 500 гарна — түүнийг HTTPException(400, ...) болгож барих нь илүү зөв.
Хоёрдугаар өргөтгөл: нийслэл endpoint. /cities/capital гэсэн endpoint нэмж, is_capital талбар нь True байгаа хотыг буцаана. Түүнийг хаана байрлуулах ёстойг та мэднэ.
Гуравдугаар өргөтгөл: аймгуудын endpoint. /provinces гэсэн endpoint нэмж, аймаг бүрийн нэр болон тэнд байгаа хотын тоог буцаана. Санамж: {"name": "Орхон", "city_count": 1} хэлбэртэй dictionary-үүдийн list.
Таны API ажиллаж байна, гэхдээ түүнд хоёр том дутагдал бий. Тэднийг нэрлэх нь чухал, учир нь Түвшин 2 бүхэлдээ тэдгээрийг шийдэхэд зориулагдана.
Эхний дутагдал: зөвхөн УНШИЖ чадна. Таны бүх endpoint нь GET. Хэрэглэгч шинэ хот нэмж чадахгүй, байгаа хотыг засаж чадахгүй, устгаж чадахгүй. Энэ бол зөвхөн хагас API юм.
Яагаад нэмж чадахгүй вэ? Учир нь шинэ хот нэмэхийн тулд хэрэглэгч бидэнд өгөгдөл илгээх ёстой — нэр, аймаг, хүн ам, он. Тэр өгөгдөл хаягт багтахгүй (хэтэрхий том, хэтэрхий бүтэцтэй). Түүнд request body гэсэн өөр механизм хэрэгтэй.
Хоёр дахь дутагдал: ирж буй өгөгдлийг шалгах арга байхгүй. Хэрэглэгч хот нэмэхийг оролдвол тэр population талбарт "маш их" гэсэн текст бичвэл яах вэ? founded талбарт -500 гэж бичвэл? name талбарыг огт бичихгүй бол?
Одоогийн мэдлэгээр бид эдгээрийг гараар шалгах ёстой болно — олон арван if мөр, олон арван raise. Уйтгартай, алдаатай, давтагдмал.
Түвшин 2 хоёуланг нь шийднэ. Тэнд бид Pydantic сурна: класс бичээд, талбар бүрийн төрлийг зарлаад, дараа нь FastAPI ирж буй бүх өгөгдлийг автоматаар шалгаж, буруу бол дэлгэрэнгүй 422 буцаана. Ганц ч if бичихгүйгээр.
Курс 2-ын Бүлэг 9-д та dataclass сурсан: type hint-тэй талбарууд, default утга, boilerplate байхгүй. Тэр үед би "энэ гурвал — dataclass + hints + defaults — бол яг Pydantic-ийн хэлбэр" гэж хэлсэн. Одоо тэр амлалт биелэх цаг иржээ.
Түвшин 1-ийн бүх ойлголтыг нэг бодит API-д нэгтгэлээ: таван endpoint, хайлт, шүүлт, 404, статистик.
Endpoint бичихээсээ өмнө юу бүтээхээ тодорхойлох нь код бичихийн эхний алхам.
Тусгай зам (/cities/largest) хувьсагчтай замын (/cities/{city_id}) өмнө байрлана.
Дараалсан шүүлтийн загвар: result эхлээд бүх өгөгдөл, дараа нь шүүлт бүр түүнийг нарийсгана.
set нь JSON болж чаддаггүй — sorted() эсвэл list() ашиглана.
Таны API одоо үнэн ярьж байна: 200 амжилтад, 404 олдоогүйд, 422 буруу төрөлд.
Хязгаар: зөвхөн уншиж чадна, ирж буй өгөгдлийг шалгах арга байхгүй.
Түвшин 1 дууслаа. Та FastAPI-ийн суурийг бүрэн эзэмшлээ.
Түвшин 2-т бид API-ийн нөгөө хагасыг нээнэ: өгөгдөл хүлээж авах. Бүлэг 4 бүхэлдээ Pydantic-д зориулагдана — таны API-ийн хаалган дээр зогсох хатуу шаардлагатай маягт. Дараа нь Бүлэг 5-д POST, PUT, DELETE сурч, Бүлэг 6-д хариугаа хянаж, эцэст нь Тэмдэглэлийн API v1 бүтээнэ — бүрэн CRUD-тай, жинхэнэ API.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.