Koble til og bruke databaser fra Python-kode.
Databasar i Python med SQLite
No skal vi kombinere Python-kunnskapane dine med databasekompetansen. SQLite er ein lettvekts database som kjem innebygd i Python – perfekt for å lage lokale applikasjonar, prototypar og mindre system.
I dette kapittelet lærer du:
- Kople Python til ein SQLite-database
- Utføre CRUD-operasjonar (Create, Read, Update, Delete)
- Bruke parameteriserte spørjingar for å unngå SQL injection
- Handtere feil og transaksjonar
- Byggje praktiske databaseapplikasjonar
Kople til database med sqlite3
Python har innebygd støtte for SQLite gjennom sqlite3-modulen.
Grunnleggjande oppsett:
import sqlite3
# Koble til database (oppretter filen hvis den ikke finnes)
conn = sqlite3.connect('skole.db')
# Opprett en cursor for å utføre SQL-kommandoer
cursor = conn.cursor()
# Utfør SQL-kommandoer her...
# Lukk forbindelsen når du er ferdig
conn.close()In-memory database (for testing):
# Database som bare eksisterer i RAM
conn = sqlite3.connect(':memory:')Context manager (anbefalt):
import sqlite3
# Automatisk lukking av forbindelse
with sqlite3.connect('skole.db') as conn:
cursor = conn.cursor()
# Gjør databaseoperasjoner her
# conn lukkes automatisk når blokken er ferdigOpprette tabellar
import sqlite3
conn = sqlite3.connect('skole.db')
cursor = conn.cursor()
# Opprett tabell
cursor.execute('''
CREATE TABLE IF NOT EXISTS Elev (
elevID INTEGER PRIMARY KEY AUTOINCREMENT,
navn TEXT NOT NULL,
klasse TEXT,
epost TEXT UNIQUE
)
''')
# Lagre endringer
conn.commit()
conn.close()Viktige punkt:
- IF NOT EXISTS hindrar feil dersom tabellen allereie finst
- AUTOINCREMENT genererer automatisk aukande ID-ar
- commit() må kallast for å lagre endringar
- Flerlinja SQL i triple quotes (''') for lesbarheit
CRUD-operasjonar
Create (INSERT):
# FARLIG – ikke gjør dette! (SQL injection-risiko)
navn = "Ole Olsen"
cursor.execute(f"INSERT INTO Elev (navn, klasse) VALUES ('{navn}', '3A')")
# RIKTIG – bruk parameteriserte spørringer:
cursor.execute(
"INSERT INTO Elev (navn, klasse, epost) VALUES (?, ?, ?)",
("Ole Olsen", "3A", "ole@example.com")
)
conn.commit()
# Hent ID-en til den nye raden:
ny_id = cursor.lastrowid
print(f"Ny elev opprettet med ID: {ny_id}")Read (SELECT):
# Hent én rad
cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (1,))
elev = cursor.fetchone()
print(elev) # Tuple: (1, 'Ole Olsen', '3A', 'ole@example.com')
# Hent alle rader
cursor.execute("SELECT navn, klasse FROM Elev ORDER BY navn")
alle_elever = cursor.fetchall()
for elev in alle_elever:
print(f"{elev[0]} - {elev[1]}")
# Hent rad for rad (for store resultater)
cursor.execute("SELECT * FROM Elev")
for rad in cursor:
print(rad)Update (UPDATE):
# Oppdater én elev
cursor.execute(
"UPDATE Elev SET klasse = ? WHERE elevID = ?",
("3B", 1)
)
conn.commit()
print(f"Endret {cursor.rowcount} rad(er)")Delete (DELETE):
# Slett én elev
cursor.execute("DELETE FROM Elev WHERE elevID = ?", (1,))
conn.commit()
print(f"Slettet {cursor.rowcount} rad(er)")SQL injection og tryggleik
SQL injection er ein av dei farlegaste tryggleikssårbarheitene i webapplikasjonar.
Farleg kode (ALDRI gjer dette):
# Brukerinput
bruker_input = input("Skriv navn: ")
# FARLIG: String formatting
query = f"SELECT * FROM Elev WHERE navn = '{bruker_input}'"
cursor.execute(query)Kva kan gå gale?
Dersom brukaren skriv:
Ole' OR '1'='1Blir spørjinga:
SELECT * FROM Elev WHERE navn = 'Ole' OR '1'='1'Dette returnerer ALLE elevar fordi '1'='1' alltid er sant!
Verre: brukaren kan skrive:
'; DROP TABLE Elev; --Dette kan slette heile tabellen!
RETT måte (parameteriserte spørjingar):
# Trygt: Python escaper verdien automatisk
bruker_input = input("Skriv navn: ")
cursor.execute(
"SELECT * FROM Elev WHERE navn = ?",
(bruker_input,)
)Viktig: Bruk ALLTID ? for verdiar, aldri string formatting (f"") eller konkatenering (+)!
Eksempel: Komplett CRUD-applikasjon
import sqlite3
class ElevDatabase:
def __init__(self, db_fil='skole.db'):
self.conn = sqlite3.connect(db_fil)
self.cursor = self.conn.cursor()
self._opprett_tabell()
def _opprett_tabell(self):
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS Elev (
elevID INTEGER PRIMARY KEY AUTOINCREMENT,
navn TEXT NOT NULL,
klasse TEXT,
epost TEXT UNIQUE
)
''')
self.conn.commit()
def legg_til_elev(self, navn, klasse, epost):
"""Create - legg til ny elev"""
try:
self.cursor.execute(
"INSERT INTO Elev (navn, klasse, epost) VALUES (?, ?, ?)",
(navn, klasse, epost)
)
self.conn.commit()
return self.cursor.lastrowid
except sqlite3.IntegrityError:
return None # Epost finnes allerede
def hent_alle_elever(self):
"""Read - hent alle elever"""
self.cursor.execute("SELECT * FROM Elev ORDER BY navn")
return self.cursor.fetchall()
def hent_elev(self, elevID):
"""Read - hent én elev"""
self.cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (elevID,))
return self.cursor.fetchone()
def oppdater_elev(self, elevID, navn=None, klasse=None, epost=None):
"""Update - oppdater elev"""
if navn:
self.cursor.execute(
"UPDATE Elev SET navn = ? WHERE elevID = ?",
(navn, elevID)
)
if klasse:
self.cursor.execute(
"UPDATE Elev SET klasse = ? WHERE elevID = ?",
(klasse, elevID)
)
if epost:
self.cursor.execute(
"UPDATE Elev SET epost = ? WHERE elevID = ?",
(epost, elevID)
)
self.conn.commit()
return self.cursor.rowcount > 0
def slett_elev(self, elevID):
"""Delete - slett elev"""
self.cursor.execute("DELETE FROM Elev WHERE elevID = ?", (elevID,))
self.conn.commit()
return self.cursor.rowcount > 0
def lukk(self):
self.conn.close()
# Bruk av klassen
if __name__ == "__main__":
db = ElevDatabase()
# Legg til elever
id1 = db.legg_til_elev("Ole Olsen", "3A", "ole@example.com")
id2 = db.legg_til_elev("Kari Hansen", "3B", "kari@example.com")
# Hent alle
print("Alle elever:")
for elev in db.hent_alle_elever():
print(elev)
# Oppdater
db.oppdater_elev(id1, klasse="3C")
# Hent én
print("\nEn elev:")
print(db.hent_elev(id1))
# Slett
db.slett_elev(id2)
db.lukk()Row factory – få resultat som dictionaries
Som standard returnerer SQLite resultat som tuples. Vi kan endre dette:
import sqlite3
conn = sqlite3.connect('skole.db')
# Få resultater som dictionaries
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM Elev WHERE elevID = ?", (1,))
elev = cursor.fetchone()
# Nå kan vi bruke kolonnenavn:
print(elev['navn'])
print(elev['klasse'])
# Eller konvertere til dict:
elev_dict = dict(elev)
print(elev_dict)
conn.close()Dette gjer koden meir leseleg og mindre feilutsett.
Feilhandtering og transaksjonar
Try-except for database-feil:
import sqlite3
try:
conn = sqlite3.connect('skole.db')
cursor = conn.cursor()
cursor.execute(
"INSERT INTO Elev (navn, epost) VALUES (?, ?)",
("Ole", "ole@example.com")
)
conn.commit()
except sqlite3.IntegrityError as e:
print(f"Integritetsfeil (f.eks. duplikat epost): {e}")
except sqlite3.OperationalError as e:
print(f"Operasjonsfeil (f.eks. tabellen finnes ikke): {e}")
except sqlite3.Error as e:
print(f"Database-feil: {e}")
finally:
conn.close()Transaksjonar:
Ein transaksjon er ein sekvens av operasjonar som anten blir utførte heilt eller ikkje i det heile.
try:
conn = sqlite3.connect('skole.db')
cursor = conn.cursor()
# Start transaksjon (implisitt)
cursor.execute("INSERT INTO Elev (navn) VALUES (?)", ("Ole",))
cursor.execute("INSERT INTO Karakter (elevID, fag, karakter) VALUES (?, ?, ?)",
(cursor.lastrowid, "Matte", 5))
# Hvis alt går bra:
conn.commit()
except sqlite3.Error as e:
# Hvis noe går galt, angre alle endringer:
conn.rollback()
print(f"Feil: {e}")
finally:
conn.close()Cursor: Objekt som utfører SQL-kommandoar og hentar resultat.
commit(): Lagrar endringar til databasen.
rollback(): Angrar endringar sidan siste commit.
fetchone(): Hentar éi rad frå resultatet.
fetchall(): Hentar alle rader frå resultatet.
Parameterisert spørjing: SQL-spørjing med ? for verdiar, hindrar SQL injection.
SQL injection: Tryggleikssårbarheit der vondsinna SQL-kode blir injisert via brukarinput.
Transaksjon: Sekvens av operasjonar som blir utførte som ei atomisk eining.
Kvifor er parameteriserte spørjingar viktige?
Kva gjer conn.commit()?
Skriv ein Python-funksjon søk_bøker(søkeord) som:
- Koplar til ein database 'bibliotek.db'
- Søkjer etter bøker der tittelen inneheld søkeordet (case-insensitive)
- Returnerer ei liste med tuplar (tittel, forfattar, år)
- Brukar parameteriserte spørjingar
Gitt tabell:
Bok (bokID, tittel, forfatter, utgivelsesår)Forklar kvifor denne koden er farleg:
navn = input("Skriv navn: ")
cursor.execute(f"DELETE FROM Elev WHERE navn = '{navn}'")
conn.commit()Gi eit eksempel på kva ein vondsinna brukar kunne skrive, og skriv ein trygg versjon av koden.
Lag ein klasse ProduktDatabase som handterer ein produktdatabase:
Produkt (produktID, navn, pris, antall)Klassen skal ha metodar for:
- __init__(db_fil): Kople til og opprett tabell
- legg_til_produkt(navn, pris, antall): Legg til nytt produkt
- hent_alle_produkter(): Hent alle produkt
- oppdater_antall(produktID, endring): Endre tal (kan vere negativt for sal)
- finn_lave_lagre(grense=10): Finn produkt med tal under grense
Bruk row_factory for dictionary-resultat.
// --- Samleoppgaver ---
Lag eit komplett biblioteksystem med følgjande funksjonalitet:
Tabellar:
Bok (ISBN PRIMARY KEY, tittel, forfatter, utgivelsesår, antall_eksemplarer)
Medlem (medlemsID PRIMARY KEY, navn, epost UNIQUE, registrert_dato)
Utlån (utlånID PRIMARY KEY, ISBN, medlemsID, utlånsdato, innleveringsfrist, innlevert_dato)Oppgåver:
a) Lag ein BibliotekDatabase-klasse som handterer alle tabellane
b) Implementer metodar for:
- Registrere ny bok
- Registrere nytt medlem
- Låne ut ei bok (sjekk at ho er tilgjengeleg)
- Levere inn ei bok
- Finne alle aktive utlån for eit medlem
- Finne medlemmer med forfalne lån (innleveringsfrist passert, ikkje innlevert)
- Finn mest populære bøker (flest utlån totalt)
c) Lag eit enkelt tekstbasert menygrensesnitt for å teste systemet
d) Bruk transaksjonar der det er nødvendig
Oppsummering
I dette kapittelet har du lært:
- sqlite3: Python sin modul for SQLite-databasar.
- Cursor og connection: utfører SQL og lagrar endringar.
- CRUD-operasjonar: INSERT, SELECT, UPDATE og DELETE.
- SQL injection: bruk parameteriserte spørjingar for tryggleik.
- Feilhandtering og transaksjonar: trygg databasebruk.
Nøkkelbegrep
| Begrep | Forklaring |
|---|---|
| sqlite3 | Python sin innebygde SQLite-modul |
| SQL injection | Angrep via uvalidert input i SQL |
| Parameterisert spørjing | Trygg måte å sende verdiar til SQL på |
Oppgaver
Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.