10.5 Python ↔ PostgreSQL (psycopg2)
Stundas uzdevums: Savienot Python kodu ar PostgreSQL datubāzi un izpildīt drošus parametrizētus vaicājumus.
70 min plāns: Teorija un paraugs (~10 min) · 1. uzdevums (~15 min) - pieslēdzies datubāzei no Python · 2. uzdevums (~25 min) - ievadi un nolasi datus ar parametriem · 3. uzdevums (~20 min) - pārbaudi, kāpēc %s ir obligāts. Papildu uzdevumu sāc tikai tad, ja pārējie trīs ir gatavi.
Pirms sāc: aktivizē virtuālo vidi un izpildi pip install psycopg2-binary. Paroli neraksti kodā - to nekad nedrīkst nosūtīt uz GitHub.
Teorija: psycopg2 - Python ↔ PostgreSQL tilts
psycopg2 ir Python bibliotēka, kas savieno tavu kodu ar PostgreSQL datubāzi. Instalācija: pip install psycopg2-binary.
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="spele",
user="postgres",
password="tavs_parole"
)
cur = conn.cursor()
# DROŠA parametrizēta vaicājuma forma - %s ir TIKAI vietturis
cur.execute("SELECT * FROM speletaji WHERE punkti > %s", (100,))
for rinda in cur.fetchall():
print(rinda)
conn.commit() # apstiprina izmaiņas
cur.close()
conn.close()
⚠ DROŠĪBA: NEKAD nelieto string formatēšanu (f"...{x}...") SQL vaicājumos - tas ir SQL injekcijas uzbrukums!
Praktiskie uzdevumi
1. uzdevums -
Pieslēdzies datubāzei no Python
Beigās Python varēs lasīt tavu datubāzi.
- Izveido failu
db.pyun ierakstiimport psycopg2. - Izveido savienojumu ar
psycopg2.connect(host=..., database="spele", user=..., password=...). - Izveido kursoru ar
cur = conn.cursor(). - Izpildi
cur.execute("SELECT COUNT(*) FROM speletaji"). - Izdrukā rezultātu ar
cur.fetchone(). - Pievieno
.gitignorefailam rindu, kas neļauj nosūtīt paroles failu.
Gatavs, kad: Python izdrukā spēlētāju skaitu, un parole nav ierakstīta failā, kas nonāk GitHub.
2. uzdevums -
Ievadi un nolasi datus ar parametriem
Beigās tava programma pati pievienos rezultātus datubāzē.
- Uzraksti funkciju
def pievieno_speletaju(vards, punkti):. - Izpildi tajā
cur.execute("INSERT INTO speletaji (vards, punkti) VALUES (%s, %s)", (vards, punkti)). - Pievieno
conn.commit()pēc ievades. - Izsauc funkciju ar jaunu spēlētāju.
- Pārbaudi pgAdmin, vai rinda tiešām parādījās.
- Izņem
commit()un mēģini vēlreiz ar citu vārdu. - Pieraksti, kāpēc bez
commit()dati nesaglabājas.
Gatavs, kad: ar commit() rinda parādās datubāzē, bez tā - pazūd pēc programmas beigām.
3. uzdevums -
Pārbaudi, kāpēc %s ir obligāts
Beigās tu sapratīsi, kā slikti uzrakstīts vaicājums var izdzēst tabulu.
- Uzraksti apzināti nedrošu variantu ar
f"SELECT * FROM speletaji WHERE vards = '{vards}'". - Izsauc to ar parastu vārdu un pārbaudi, ka strādā.
- Izsauc to ar vārdu
' OR '1'='1. - Pieraksti, cik rindu atgriezās un kāpēc.
- Pārraksti vaicājumu ar
%svietturi. - Izsauc to ar to pašu ļauno vārdu vēlreiz.
- Pieraksti vienu secinājumu: kāpēc
%snav tas pats, kas f-string.
Gatavs, kad: ar f-string ļaunais vārds atgriež visas rindas, bet ar %s tas tiek meklēts kā parasts teksts.
Papildu uzdevums - Lieto with un RealDictCursor
Ja pamatdarbs ir gatavs, padari kodu drošāku un ērtāku.
- Pārraksti savienojumu ar
with psycopg2.connect(...) as conn:. - Pievieno
with conn.cursor(cursor_factory=RealDictCursor) as cur:. - Izpildi
SELECTun izdrukā vienu rindu. - Pieraksti, ar ko rezultāts tagad atšķiras no parasta kursora.
- Piekļūsti laukam pēc nosaukuma, nevis pēc numura.
Gatavs, kad: rindas dati pieejami pēc kolonnas nosaukuma, piemēram rinda["vards"].
Biežākās kļūdas
- SQL injekcija ar f-string: NEKAD
f"WHERE id = {user_id}"- vienmēr%s+ parametru tuple. - Aizmirsts commit(): Bez tā INSERT/UPDATE neparādīsies.
- Nav close(): Lieto
withbloku vaitry/finally- savienojumi jāatbrīvo.
Koda piemērs
import psycopg2
from psycopg2.extras import RealDictCursor
with psycopg2.connect(
host="localhost", database="spele",
user="postgres", password="parole"
) as conn:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
# DROŠA INSERT
cur.execute(
"INSERT INTO speletaji (vards) VALUES (%s) RETURNING id",
("Anna",)
)
jauns_id = cur.fetchone()["id"]
print(f"Pievienots ar id = {jauns_id}")
cur.execute("SELECT * FROM speletaji ORDER BY id DESC LIMIT 3")
for r in cur.fetchall():
print(r["vards"], r["punkti"])
Anna 0
Marta 200
Eva 145