Ачааллж байна...
Өмнөх хичээлд бид CRUD-ийн эхний хоёр үсгийг өгөгдлийн сангийн түвшинд эзэмшсэн: Create (add + commit) болон Read (select, get, where).
Энэ хичээлд үлдсэн хоёрыг нэмнэ: Update (засах) болон Delete (устгах). Тэдгээр нь өмнөхөөс арай өөр загвартай бөгөөд Update-д нэг сонирхолтой онцлог бий: засахдаа add дуудах шаардлагагүй. SQLModel объектын өөрчлөлтийг өөрөө ажигладаг.
Энэ хичээлийн дараа таны CRUD үг хэллэг өгөгдлийн сангийн түвшинд бүрэн болж, бид API-даа залгахад бэлэн болно.
db_test.py файлаа дараах кодоор солино. Хэрэв notes.db байгаа бол устгаад цэвэр эхэлье:
python
# db_test.py
from sqlmodel import SQLModel, Field, Session, create_engine, select
class Note(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
text: str
priority: str = "энгийн"
done: bool = False
engine = create_engine("sqlite:///notes.db", echo=True)
SQLModel.metadata.create_all(engine)
# Гурван тэмдэглэл нэмэх
with Session(engine) as session:
session.add(Note(text="Сүү авах", priority="чухал"))
session.add(Note(text="Номоо буцаах"))
session.add(Note(text="Тайлан бичих", priority="яаралтай"))
session.commit()
print("--- Бэлтгэл дууслаа ---")Ажиллуулна:
python db_test.pyГурван тэмдэглэл нэмэгдсэн. Одоо тэдгээрийг засаж, устгаж эхэлье.
Санамж: энэ файлыг дахин ажиллуулах бүрд гурван тэмдэглэл нэмэгдэнэ (өмнөхүүд нь үлдэнэ). Хэрэв цэвэр эхлэхийг хүсвэл notes.db-г устгаад дахин ажиллуулна.
Засах загвар нь дөрвөн алхамтай:
Объектыг олох.
Талбарыг нь өөрчлөх.
commit хийх.
(Хэрэгтэй бол) refresh хийх.
db_test.py-ийн төгсгөлд нэмнэ:
python
# db_test.py (нэмэлт)
print("--- Засах ---")
with Session(engine) as session:
note = session.get(Note, 1)
print(f"Өмнө: {note.text} — done={note.done}")
note.done = True
note.text = "Сүү ба талх авах"
session.commit()
session.refresh(note)
print(f"Дараа: {note.text} — done={note.done}")Ажиллуулна.
Гаралт (гол хэсэг):
--- Засах ---
... SELECT note.id, note.text, note.priority, note.done FROM note WHERE note.id = ?
... (1,)
Өмнө: Сүү авах — done=False
... UPDATE note SET text=?, done=? WHERE note.id = ?
... ('Сүү ба талх авах', 1, 1)
... COMMIT
... SELECT ... FROM note WHERE note.id = ?
Дараа: Сүү ба талх авах — done=TrueГол онцлог: add дуудаагүй
Кодыг сайтар хараарай. session.add(note) гэсэн мөр байхгүй.
Гэсэн ч UPDATE SQL ажиллаж, өөрчлөлт хадгалагдсан.
Яагаад? Учир нь session.get(...) нь объектыг session-д бүртгэдэг. Session тэр объектыг ажиглаж сууна. Хэрэв та түүний талбарыг өөрчилвөл session түүнийг анзаарч, commit хийхэд автоматаар UPDATE үүсгэнэ.
Энэ бол SQLModel-ийн (үнэндээ SQLAlchemy-ийн) хамгийн гоё чадваруудын нэг: өөрчлөлтийг мөрдөх (change tracking). Та ердийн Python объект дээр ердийн Python-оор ажиллана; ORM нь ард нь юу өөрчлөгдсөнийг тэмдэглэж, зөв SQL үүсгэнэ.
Session-аас аваагүй объектод (жишээ нь шинээр үүсгэсэн) add хэрэгтэй — тэр нь session-д бүртгэгдээгүй тул. Гэхдээ session-аас авсан объект аль хэдийн бүртгэгдсэн.
Дүрэм: шинэ объект -> add хэрэгтэй. Session-аас авсан объект -> add хэрэггүй.
(Хэрэв та зуршлаар session.add(note) гэж бичсэн ч алдаа гарахгүй — session тэр объектыг аль хэдийн мэдэж байгаа тул зүгээр л тоохгүй. Тиймээс энэ нь аюулгүй, зөвхөн илүүц.)
UPDATE SQL-ыг унших
sql
UPDATE note SET text=?, done=? WHERE note.id = ?Утгууд: ('Сүү ба талх авах', 1, 1).
Бүлэг 10-ын Хичээл 3-т сурсан бүтэц: UPDATE (аль хүснэгт), SET (юуг юу болгох), WHERE (аль мөрд).
Хамгийн чухал зүйл: WHERE note.id = ? байна. Санамсаргүй бүх мөрийг өөрчлөх аюул байхгүй — SQLModel үргэлж тодорхой мөрийг зорино.
Бүлэг 10-т ярьсан "WHERE-гүй UPDATE нь бүх мөрийг өөрчилдөг" гэсэн аюул энд байхгүй. Учир нь та объект дээр ажиллаж байна, хүснэгт дээр биш. Объект нь тодорхой нэг мөрийг төлөөлдөг.
Мөн анзаараарай: зөвхөн өөрчлөгдсөн багана SET-д орсон. text болон done өөрчлөгдсөн тул тэдгээр орсон; priority өөрчлөгдөөгүй тул ороогүй. SQLModel яг юу өөрчлөгдсөнийг мэдэж, зөвхөн түүнийг шинэчилж байна.
Байхгүй мөрийг засах гэвэл
python
# db_test.py (нэмэлт)
with Session(engine) as session:
note = session.get(Note, 999)
print(f"999-р тэмдэглэл: {note}")
if note is None:
print("Байхгүй тэмдэглэлийг засах боломжгүй")
else:
note.done = True
session.commit()Гаралт:
999-р тэмдэглэл: None
Байхгүй тэмдэглэлийг засах боломжгүйsession.get нь None буцаана. Хэрэв та шалгалгүйгээр note.done = True гэж бичвэл:
AttributeError: 'NoneType' object has no attribute 'done'Курс 1-ээс танил алдаа: None дээр цэгээр хандах гэж оролдсон.
Тиймээс засахаас өмнө үргэлж None эсэхийг шалгана. Бүлэг 12-т энэ шалгалт нь HTTPException(404) болно — таны API-ийн find_note загварын өгөгдлийн сангийн хувилбар.
Устгах загвар нь гурван алхамтай:
Объектыг олох.
session.delete(объект).
commit хийх.
db_test.py-ийн төгсгөлд нэмнэ:
python
# db_test.py (нэмэлт)
print("--- Устгах ---")
with Session(engine) as session:
note = session.get(Note, 2)
print(f"Устгах гэж байна: {note.text}")
session.delete(note)
session.commit()
print("Устгагдлаа")
# Батлах
with Session(engine) as session:
notes = session.exec(select(Note)).all()
print(f"Үлдсэн тэмдэглэл: {len(notes)}")
for n in notes:
print(" ", n.id, n.text)Ажиллуулна.
Гаралт (гол хэсэг):
--- Устгах ---
... SELECT ... FROM note WHERE note.id = ?
... (2,)
Устгах гэж байна: Номоо буцаах
... DELETE FROM note WHERE note.id = ?
... (2,)
... COMMIT
Устгагдлаа
... SELECT ... FROM note
Үлдсэн тэмдэглэл: 2
1 Сүү ба талх авах
3 Тайлан бичихХоёр дугаартай тэмдэглэл алга болсон.
DELETE SQL-ыг унших
sql
DELETE FROM note WHERE note.id = ?Дахин WHERE байна. Тодорхой мөр л устсан.
Бүлэг 10-т ярьсан "WHERE-гүй DELETE нь бүх мөрийг устгадаг" гэсэн аюул энд ч байхгүй — та объект устгаж байна, хүснэгтийг цэвэрлэж байгаа биш.
id дахин ашиглагдахгүй
Батлалтын гаралтыг анзаараарай:
1 Сүү ба талх авах
3 Тайлан бичихid 1 болон 3. 2 дахин ашиглагдаагүй — зүгээр л дутуу байна.
Хэрэв та шинэ тэмдэглэл нэмбэл түүний id нь 4 болно, 2 биш. Устгагдсан id-г өгөгдлийн сан дахин ашигладаггүй.
Энэ бол зөв зан төлөв. Яагаад? Учир нь id 2-той тэмдэглэлийг өөр газар лавласан байж болно — log-д, өөр хүснэгтэд, хэрэглэгчийн хадгалсан холбоос дээр. Хэрэв шинэ тэмдэглэлд ижил id өгвөл тэр лавлагаанууд буруу зүйл рүү заах болно.
id бол мөнхийн, давтагдашгүй таних тэмдэг. Нэг удаа өгөгдсөн бол хэзээ ч дахин ашиглагддаггүй.
Байхгүй мөрийг устгах гэвэл
python
with Session(engine) as session:
note = session.get(Note, 999)
if note is None:
print("Устгах зүйл байхгүй")
else:
session.delete(note)
session.commit()Ижил загвар: None эсэхийг шалгах. Хэрэв шалгалгүйгээр session.delete(None) гэвэл алдаа гарна.
Бүлэг 12-т энэ нь HTTPException(404) болно.
Нэг чухал зүйлийг тодруулъя. commit хийсний дараа устгал буцаагдахгүй. Мөр алга болно.
commit-ээс өмнө бол буцаах боломжтой:
python
with Session(engine) as session:
note = session.get(Note, 3)
session.delete(note)
session.rollback() # буцаах
session.commit()session.rollback() — "бүх баталгаажаагүй өөрчлөлтийг цуцал". Няравын ноорогийг урах.
Гэхдээ commit хийсний дараа rollback ажиллахгүй — өөрчлөлт аль хэдийн бичигдсэн.
Бодит системд энэ асуудлыг soft delete гэсэн аргаар шийддэг: мөрийг үнэхээр устгахгүй, харин deleted: bool = True гэсэн талбар тэмдэглэдэг. Дараа нь бүх асуултад where(Note.deleted == False) нэмдэг. Ингэснээр өгөгдөл хадгалагдаж, гэхдээ харагдахгүй болдог.
Бид энэ курсэд soft delete ашиглахгүй — жинхэнэ устгал нь илүү энгийн бөгөөд ойлголтод хангалттай. Гэхдээ бодит production системд энэ хувилбар байдгийг мэдэж байх нь зүйтэй.
Одоо танд өгөгдлийн сангийн түвшинд бүрэн CRUD байна. Дөрвүүлийг нэг дор харъя:
python
# CREATE
with Session(engine) as session:
note = Note(text="Шинэ тэмдэглэл")
session.add(note)
session.commit()
session.refresh(note)
# READ (бүгд)
with Session(engine) as session:
notes = session.exec(select(Note)).all()
# READ (нэг)
with Session(engine) as session:
note = session.get(Note, 1)
# UPDATE
with Session(engine) as session:
note = session.get(Note, 1)
note.done = True
session.commit()
# DELETE
with Session(engine) as session:
note = session.get(Note, 1)
session.delete(note)
session.commit()Хүснэгтээр:
CRUD | SQLModel | SQL | HTTP |
|---|---|---|---|
Create |
|
| POST |
Read (бүгд) |
|
| GET |
Read (нэг) |
|
| GET |
Update | талбар өөрчлөх + |
| PUT |
Delete |
|
| DELETE |
Гурван багана, ижил дөрвөн үйлдэл. Зүүн талын Python код нь дунд талын SQL болж хувирдаг; баруун талын HTTP method нь таны API-д тэдгээрийг илэрхийлдэг.
Бүлэг 12-т бид тэдгээрийг холбоно: HTTP хүсэлт ирж, endpoint дуудагдаж, SQLModel код ажиллаж, SQL үүсэж, өгөгдлийн сан өөрчлөгдөнө.
Бүх CRUD-ыг нэг дор харуулах бүрэн скрипт. notes.db-г устгаад, db_test.py-г солино:
python
# db_test.py
from sqlmodel import SQLModel, Field, Session, create_engine, select
class Note(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
text: str
priority: str = "энгийн"
done: bool = False
engine = create_engine("sqlite:///notes.db") # echo унтраасан
SQLModel.metadata.create_all(engine)
def create_note(text: str, priority: str = "энгийн") -> Note:
with Session(engine) as session:
note = Note(text=text, priority=priority)
session.add(note)
session.commit()
session.refresh(note)
return note
def get_all_notes() -> list[Note]:
with Session(engine) as session:
return list(session.exec(select(Note)).all())
def get_note(note_id: int) -> Note | None:
with Session(engine) as session:
return session.get(Note, note_id)
def update_note(note_id: int, text: str, done: bool) -> Note | None:
with Session(engine) as session:
note = session.get(Note, note_id)
if note is None:
return None
note.text = text
note.done = done
session.commit()
session.refresh(note)
return note
def delete_note(note_id: int) -> bool:
with Session(engine) as session:
note = session.get(Note, note_id)
if note is None:
return False
session.delete(note)
session.commit()
return True
# --- Туршилт ---
print("1. Үүсгэх")
a = create_note("Сүү авах", "чухал")
b = create_note("Номоо буцаах")
c = create_note("Тайлан бичих", "яаралтай")
print(f" Үүссэн id-ууд: {a.id}, {b.id}, {c.id}")
print("2. Бүгдийг унших")
for note in get_all_notes():
print(f" {note.id}: {note.text} ({note.priority}) done={note.done}")
print("3. Нэгийг унших")
note = get_note(1)
print(f" {note.text}")
print("4. Байхгүйг унших")
missing = get_note(999)
print(f" Үр дүн: {missing}")
print("5. Засах")
updated = update_note(1, "Сүү ба талх авах", True)
print(f" {updated.text} done={updated.done}")
print("6. Байхгүйг засах")
result = update_note(999, "юу ч", True)
print(f" Үр дүн: {result}")
print("7. Устгах")
ok = delete_note(2)
print(f" Амжилттай: {ok}")
print("8. Байхгүйг устгах")
ok = delete_note(999)
print(f" Амжилттай: {ok}")
print("9. Эцсийн байдал")
for note in get_all_notes():
print(f" {note.id}: {note.text}")Ажиллуулна.
Гаралт:
1. Үүсгэх
Үүссэн id-ууд: 1, 2, 3
2. Бүгдийг унших
1: Сүү авах (чухал) done=False
2: Номоо буцаах (энгийн) done=False
3: Тайлан бичих (яаралтай) done=False
3. Нэгийг унших
Сүү авах
4. Байхгүйг унших
Үр дүн: None
5. Засах
Сүү ба талх авах done=True
6. Байхгүйг засах
Үр дүн: None
7. Устгах
Амжилттай: True
8. Байхгүйг устгах
Амжилттай: False
9. Эцсийн байдал
1: Сүү ба талх авах
3: Тайлан бичихЭнэ бүтцийг анзаарах
Функцүүдийн хэлбэрийг сайтар хараарай. Тэдгээр нь таны API-ийн endpoint-той бараг ижил:
create_note — POST /notes шиг. Объект үүсгэж, хадгалж, буцаана.
get_all_notes — GET /notes шиг.
get_note — GET /notes/{id} шиг. Байхгүй бол None.
update_note — PUT /notes/{id} шиг. Байхгүй бол None.
delete_note — DELETE /notes/{id} шиг. Байхгүй бол False.
Бүлэг 12-т эдгээр функцийн агуулга нь шууд endpoint болж хувирна. Ялгаа нь: None буцаахын оронд HTTPException(404) шидэх; with Session(...) бичихийн оронд Depends(get_session) ашиглах.
Өөрөөр хэлбэл: та Бүлэг 12-ын кодыг аль хэдийн бичсэн, зөвхөн вэб бүрхүүлгүйгээр.
echo унтраасан
echo=True энэ жишээнд байхгүйг анзаараарай. Гаралт цэвэрхэн болсон — зөвхөн таны print-ууд.
SQL харах хэрэгтэй болвол echo=True буцааж нэмнэ. Хөгжүүлж байхад асуудал гарвал үүнийг түр асаах нь маш ашигтай debug арга юм.
commit мартах (дахин)
Update болон Delete-д ч ижил. note.done = True гэж бичээд commit мартвал — юу ч болохгүй. Session хаагдахад өөрчлөлт хаягдана.
Хамгийн түгээмэл алдаа, гурав дахь удаа дурдаж байна. Учир нь тэр үнэхээр түгээмэл.
None шалгахгүй
python
note = session.get(Note, 999)
note.done = True # AttributeErrorsession.get нь None буцаах боломжтой. Үргэлж шалга.
delete-д id өгөх
python
session.delete(2) # буруу — тоо
session.delete(note) # зөв — объектsession.delete нь объект хүлээж авдаг, id биш. Эхлээд get-ээр объектыг олж, дараа нь устгана.
Session хаагдсаны дараа объект ашиглах
python
def get_note(note_id: int):
with Session(engine) as session:
return session.get(Note, note_id)
note = get_note(1)
print(note.text) # ажиллаж болно, гэхдээ эрсдэлтэйSession хаагдсан ч энгийн талбарууд ихэвчлэн ажилладаг (аль хэдийн уншигдсан тул). Гэхдээ нарийн тохиолдолд асуудал гарч болно.
Бүлэг 12-т FastAPI-ийн dependency нь session-ыг хүсэлтийн турш нээлттэй байлгаж, энэ асуудлыг цэвэрхэн шийдэх болно.
Объектыг өөр session-д зөөх
python
with Session(engine) as session1:
note = session1.get(Note, 1)
with Session(engine) as session2:
note.done = True
session2.commit() # ажиллахгүйОбъект нь session1-д бүртгэгдсэн; session2 түүнийг мэдэхгүй. Өөрчлөлт хадгалагдахгүй.
Дүрэм: нэг session дотор ол, өөрчил, commit хий. Session хооронд объект зөөхгүй.
Хүсвэл дээрх бүтэн скриптийн функцүүдийг өөрчилж үзээрэй. Жишээ нь update_note-д priority параметр нэмэх, эсвэл delete_all_done() гэсэн функц бичих (бүх дууссан тэмдэглэлийг устгах — select(Note).where(Note.done == True) ашиглаад давталтаар устгана).
Сонирхвол echo=True буцааж асаагаад, UPDATE SQL-д зөвхөн өөрчлөгдсөн багана орж байгааг ажиглаарай. Хоёр талбар өөрчилвөл хоёул, нэг талбар өөрчилвөл нэг нь. SQLModel яг юуг мөрдөж байгааг харах нь гоё.
Update загвар: олох -> талбар өөрчлөх -> commit -> (refresh). add дуудах шаардлагагүй.
Session нь өөрөөс нь авсан объектуудыг ажиглаж, өөрчлөлтийг мөрддөг (change tracking).
Шинэ объектод add хэрэгтэй; session-аас авсан объектод хэрэггүй.
UPDATE SQL-д зөвхөн өөрчлөгдсөн багана орно, мөн WHERE id = ? үргэлж байна.
Delete загвар: олох -> session.delete(объект) -> commit. Объект өгнө, id биш.
Устгагдсан id дахин ашиглагдахгүй — id бол мөнхийн, давтагдашгүй таних тэмдэг.
session.get нь байхгүй үед None буцаана — засах/устгахаас өмнө үргэлж шалга.
commit-ээс өмнө session.rollback()-оор буцааж болно; дараа нь боломжгүй.
Бодит системд soft delete (deleted: bool талбар) гэсэн хувилбар байдаг — бид ашиглахгүй.
Нэг session дотор ол, өөрчил, commit хий — session хооронд объект зөөхгүй.
Бүлэг 11 дууслаа. Та одоо өгөгдлийн сангийн түвшинд бүрэн CRUD хийж чадна: model тодорхойлох, хүснэгт үүсгэх, мөр нэмэх, унших, шүүх, эрэмбэлэх, засах, устгах. Бүгдийг Python объектоор, ганц ч мөр SQL бичихгүйгээр.
Дараагийн бүлэгт бид хоёр ертөнцийг холбоно. Бүлэг 8-т сурсан Depends нь эцсийн, хамгийн чухал хэрэглээгээ олно: session-ыг dependency болгож өгөх. Тэнд yield гэдэг үг гарч ирнэ (Курс 2-ын generator) бөгөөд хүсэлт бүр өөрийн session-тэй болно. Дараа нь бид endpoint бүрийг өгөгдлийн сангийн эсрэг дахин бичиж, эцэст нь Тэмдэглэлийн API v3 бүтээнэ — тэнд серверээ унтраагаад дахин асаахад тэмдэглэлүүд газраасаа хөдлөхгүй байхыг та өөрийн нүдээр харна. Тэр мөч бол бүх сургалтын нугалаа юм.
Бүртгэлтэй болсноор энэ сургалтын бүх хичээлд хандах эрх авна.