Koble til og bruke databaser fra Python-kode.
Databaser i Python med SQLite
Nå skal vi kombinere Python-kunnskapene dine med databasekompetansen. SQLite er en lettvekts database som kommer innebygd i Python – perfekt for å lage lokale applikasjoner, prototyper og mindre systemer.
I dette kapittelet lærer du:
- Koble Python til en SQLite-database
- Utføre CRUD-operasjoner (Create, Read, Update, Delete)
- Bruke parameteriserte spørringer for å unngå SQL injection
- Håndtere feil og transaksjoner
- Bygge praktiske databaseapplikasjoner
Koble til database med sqlite3
Python har innebygd støtte for SQLite gjennom sqlite3-modulen.
Grunnleggende 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 tabeller
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 punkter:
- IF NOT EXISTS forhindrer feil hvis tabellen allerede finnes
- AUTOINCREMENT genererer automatisk økende ID-er
- commit() må kalles for å lagre endringer
- Flerlinjet SQL i triple quotes (''') for lesbarhet
CRUD-operasjoner
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 sikkerhet
SQL injection er en av de farligste sikkerhetssårbarhetene i webapplikasjoner.
Farlig kode (ALDRI gjør dette):
# Brukerinput
bruker_input = input("Skriv navn: ")
# FARLIG: String formatting
query = f"SELECT * FROM Elev WHERE navn = '{bruker_input}'"
cursor.execute(query)Hva kan gå galt?
Hvis brukeren skriver:
Ole' OR '1'='1Blir spørringen:
SELECT * FROM Elev WHERE navn = 'Ole' OR '1'='1'Dette returnerer ALLE elever fordi '1'='1' alltid er sant!
Verre: brukeren kan skrive:
'; DROP TABLE Elev; --Dette kan slette hele tabellen!
RIKTIG måte (parameteriserte spørringer):
# Trygt: Python escaper verdien automatisk
bruker_input = input("Skriv navn: ")
cursor.execute(
"SELECT * FROM Elev WHERE navn = ?",
(bruker_input,)
)Viktig: Bruk ALLTID ? for verdier, 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å resultater som dictionaries
Som standard returnerer SQLite resultater 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 gjør koden mer leselig og mindre feilutsatt.
Feilhåndtering og transaksjoner
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()Transaksjoner:
En transaksjon er en sekvens av operasjoner som enten utføres helt eller ikke i det hele tatt.
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-kommandoer og henter resultater.
commit(): Lagrer endringer til databasen.
rollback(): Angrer endringer siden siste commit.
fetchone(): Henter én rad fra resultatet.
fetchall(): Henter alle rader fra resultatet.
Parameterisert spørring: SQL-spørring med ? for verdier, forhindrer SQL injection.
SQL injection: Sikkerhetssårbarhet der ondsinnet SQL-kode injiseres via brukerinput.
Transaksjon: Sekvens av operasjoner som utføres som en atomisk enhet.
Hvorfor er parameteriserte spørringer viktige?
Hva gjør conn.commit()?
Skriv en Python-funksjon søk_bøker(søkeord) som:
- Kobler til en database 'bibliotek.db'
- Søker etter bøker der tittelen inneholder søkeordet (case-insensitive)
- Returnerer en liste med tupler (tittel, forfatter, år)
- Bruker parameteriserte spørringer
Gitt tabell:
Bok (bokID, tittel, forfatter, utgivelsesår)Forklar hvorfor denne koden er farlig:
navn = input("Skriv navn: ")
cursor.execute(f"DELETE FROM Elev WHERE navn = '{navn}'")
conn.commit()Gi et eksempel på hva en ondsinnet bruker kunne skrive, og skriv en trygg versjon av koden.
Lag en klasse ProduktDatabase som håndterer en produktdatabase:
Produkt (produktID, navn, pris, antall)Klassen skal ha metoder for:
- __init__(db_fil): Koble til og opprett tabell
- legg_til_produkt(navn, pris, antall): Legg til nytt produkt
- hent_alle_produkter(): Hent alle produkter
- oppdater_antall(produktID, endring): Endre antall (kan være negativt for salg)
- finn_lave_lagre(grense=10): Finn produkter med antall under grense
Bruk row_factory for dictionary-resultater.
// --- Samleoppgaver ---
Lag et komplett biblioteksystem med følgende funksjonalitet:
Tabeller:
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)Oppgaver:
a) Lag en BibliotekDatabase-klasse som håndterer alle tabellene
b) Implementer metoder for:
- Registrere ny bok
- Registrere nytt medlem
- Låne ut en bok (sjekk at den er tilgjengelig)
- Levere inn en bok
- Finne alle aktive utlån for et medlem
- Finne medlemmer med forfalte lån (innleveringsfrist passert, ikke innlevert)
- Finn mest populære bøker (flest utlån totalt)
c) Lag et enkelt tekstbasert menygrensesnitt for å teste systemet
d) Bruk transaksjoner der det er nødvendig
Oppsummering
I dette kapittelet har du lært:
- sqlite3: Pythons modul for SQLite-databaser.
- Cursor og connection: utfører SQL og lagrer endringer.
- CRUD-operasjoner: INSERT, SELECT, UPDATE og DELETE.
- SQL injection: bruk parameteriserte spørringer for sikkerhet.
- Feilhaandtering og transaksjoner: trygg databasebruk.
Noekkelbegreper
| Begrep | Forklaring |
|---|---|
| sqlite3 | Pythons innebygde SQLite-modul |
| SQL injection | Angrep via uvalidert input i SQL |
| Parameterisert spørring | Trygg måte å sende verdier 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.