Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

SQLAlchemy - ORM dla Python

Witaj, @name! Darwin tutaj z fascynującą lekcją o ORM.

W poprzedniej lekcji poznałeś/aś surowe SQL -

cursor.execute("SELECT * FROM species")
. Działa świetnie, ale... pisanie SQL jako stringi jest niewygodne, podatne na błędy i nie korzysta z pythonowych klas!

Teraz poznasz ORM (Object-Relational Mapping) - sposób na pracę z bazą danych używając pythonowych klas zamiast SQL! 🐍🔗

Analogia Safari: Surowe SQL to jak wypełnianie formularzy papierowych ręcznie. SQLAlchemy ORM to cyfrowy system - obiekty Python automatycznie mapowane na tabele bazy danych! 📝➡️💾

Czym jest ORM?

ORM (Object-Relational Mapping) to technika mapowania obiektów programistycznych na tabele w bazie relacyjnej.

Zamiast:

1cursor.execute("INSERT INTO species (name, population) VALUES (?, ?)", ("Lew", 120))

Piszesz:

1lion = Species(name="Lew", population=120)
2session.add(lion)
3session.commit()

Zalety ORM:

  • ✅ Pythonowe klasy zamiast SQL stringów
  • ✅ Type hints i autocomplete
  • ✅ Mniej podatne na SQL injection
  • ✅ Łatwiejsze relacje między tabelami
  • ✅ Migracje i ewolucja schematu
  • ✅ Niezależność od bazy (PostgreSQL, MySQL, SQLite...)

SQLAlchemy - najpopularniejszy ORM w Pythonie

SQLAlchemy to potężny ORM + narzędzie do pracy z bazami SQL.

Instalacja

1pip install sqlalchemy

Podstawy SQLAlchemy - Modele

1from sqlalchemy import create_engine, Column, Integer, String, Boolean
2from sqlalchemy.orm import declarative_base, sessionmaker
3
4# 1. Utwórz Base - klasa bazowa dla modeli
5Base = declarative_base()
6
7# 2. Zdefiniuj model - klasa Python = tabela SQL
8class Species(Base):
9    __tablename__ = 'species'  # Nazwa tabeli
10
11    # Kolumny
12    id = Column(Integer, primary_key=True, autoincrement=True)
13    scientific_name = Column(String, nullable=False, unique=True)
14    common_name = Column(String, nullable=False)
15    population = Column(Integer, default=0)
16    habitat = Column(String)
17    endangered = Column(Boolean, default=False)
18
19    def __repr__(self):
20        return f"<Species('{self.common_name}', pop={self.population})>"
21
22
23# 3. Połącz się z bazą danych
24engine = create_engine('sqlite:///safari_orm.db', echo=True)  # echo=True wyświetla SQL
25
26# 4. Utwórz tabele
27Base.metadata.create_all(engine)
28
29# 5. Utwórz sesję (zarządza operacjami na bazie)
30Session = sessionmaker(bind=engine)
31session = Session()

Uwaga:

Base.metadata.create_all()
tworzy tabele tylko jeśli nie istnieją (jak
IF NOT EXISTS
)

Typy kolumn

| SQLAlchemy | SQL | Python | |------------|-----|--------| |

Integer
| INTEGER |
int
| |
String
| VARCHAR |
str
| |
Text
| TEXT |
str
(dłuższy) | |
Float
| REAL |
float
| |
Boolean
| BOOLEAN |
bool
| |
DateTime
| TIMESTAMP |
datetime
| |
Date
| DATE |
date
| |
JSON
| JSON |
dict
/
list
(SQLite 3.9+) |

CRUD z SQLAlchemy

CREATE - dodawanie rekordów

1from sqlalchemy.orm import Session
2
3# Utwórz obiekt
4lion = Species(
5    scientific_name="Panthera leo",
6    common_name="Lew",
7    population=120,
8    habitat="sawanna",
9    endangered=True
10)
11
12# Dodaj do sesji
13session.add(lion)
14
15# Zatwierdź
16session.commit()
17
18print(f"Dodano lwa ID: {lion.id}")  # ID automatycznie przypisane!
19
20# Dodaj wiele naraz
21species_list = [
22    Species(scientific_name="Loxodonta africana", common_name="Słoń", population=450, endangered=True),
23    Species(scientific_name="Gorilla gorilla", common_name="Goryl", population=230, endangered=True),
24]
25
26session.add_all(species_list)
27session.commit()

READ - pobieranie danych

1# Wszystkie gatunki
2all_species = session.query(Species).all()
3for species in all_species:
4    print(species.common_name, species.population)
5
6# Pierwszy wynik
7first = session.query(Species).first()
8
9# Pobierz po ID
10lion = session.query(Species).get(1)  # ID = 1
11# lub (SQLAlchemy 2.0+)
12lion = session.get(Species, 1)
13
14# Filtrowanie - WHERE
15endangered = session.query(Species).filter(Species.endangered == True).all()
16savanna = session.query(Species).filter(Species.habitat == "sawanna").all()
17
18# Wiele warunków (AND)
19results = session.query(Species).filter(
20    Species.endangered == True,
21    Species.population > 100
22).all()
23
24# OR
25from sqlalchemy import or_
26results = session.query(Species).filter(
27    or_(Species.habitat == "sawanna", Species.habitat == "las")
28).all()
29
30# LIKE
31results = session.query(Species).filter(Species.scientific_name.like("Panthera%")).all()
32
33# ORDER BY
34sorted_species = session.query(Species).order_by(Species.population.desc()).all()
35
36# LIMIT i OFFSET
37top_5 = session.query(Species).limit(5).all()
38page_2 = session.query(Species).limit(10).offset(10).all()
39
40# COUNT
41count = session.query(Species).count()
42endangered_count = session.query(Species).filter(Species.endangered == True).count()

UPDATE - aktualizacja

1# Metoda 1: Pobierz obiekt, zmień, commit
2lion = session.query(Species).filter(Species.common_name == "Lew").first()
3lion.population = 125
4session.commit()
5
6# Metoda 2: Bulk update
7session.query(Species).filter(Species.habitat == "sawanna").update({
8    "endangered": True
9})
10session.commit()

DELETE - usuwanie

1# Metoda 1: Pobierz obiekt, usuń
2species_to_delete = session.query(Species).get(10)
3session.delete(species_to_delete)
4session.commit()
5
6# Metoda 2: Bulk delete
7session.query(Species).filter(Species.population == 0).delete()
8session.commit()

Relacje - One-to-Many

1from sqlalchemy import ForeignKey
2from sqlalchemy.orm import relationship
3
4class Species(Base):
5    __tablename__ = 'species'
6
7    id = Column(Integer, primary_key=True)
8    common_name = Column(String, nullable=False)
9    population = Column(Integer, default=0)
10
11    # Relacja: jeden gatunek -> wiele obserwacji
12    observations = relationship("Observation", back_populates="species", cascade="all, delete-orphan")
13
14
15class Observation(Base):
16    __tablename__ = 'observations'
17
18    id = Column(Integer, primary_key=True)
19    species_id = Column(Integer, ForeignKey('species.id'), nullable=False)
20    observation_date = Column(String, nullable=False)
21    location = Column(String, nullable=False)
22    count = Column(Integer, default=0)
23
24    # Relacja: wiele obserwacji -> jeden gatunek
25    species = relationship("Species", back_populates="observations")
26
27
28# Użycie
29lion = Species(common_name="Lew", population=120)
30
31# Dodaj obserwacje do lwa
32lion.observations.append(Observation(observation_date="2024-01-15", location="Serengeti", count=12))
33lion.observations.append(Observation(observation_date="2024-01-20", location="Masai Mara", count=8))
34
35session.add(lion)
36session.commit()
37
38# Pobierz obserwacje
39lion = session.query(Species).filter(Species.common_name == "Lew").first()
40for obs in lion.observations:
41    print(f"{obs.observation_date}: {obs.count}x w {obs.location}")

Safari przykład - kompletny system ORM

1from sqlalchemy import create_engine, Column, Integer, String, Boolean, ForeignKey, DateTime
2from sqlalchemy.orm import declarative_base, sessionmaker, relationship
3from datetime import datetime
4from typing import List, Optional
5
6Base = declarative_base()
7
8
9class Species(Base):
10    """Model gatunku"""
11    __tablename__ = 'species'
12
13    id = Column(Integer, primary_key=True, autoincrement=True)
14    scientific_name = Column(String, nullable=False, unique=True)
15    common_name = Column(String, nullable=False)
16    population = Column(Integer, default=0)
17    habitat = Column(String)
18    endangered = Column(Boolean, default=False)
19    created_at = Column(DateTime, default=datetime.utcnow)
20    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
21
22    # Relacje
23    observations = relationship("Observation", back_populates="species", cascade="all, delete-orphan")
24
25    def __repr__(self):
26        return f"<Species('{self.common_name}', pop={self.population})>"
27
28
29class Observation(Base):
30    """Model obserwacji"""
31    __tablename__ = 'observations'
32
33    id = Column(Integer, primary_key=True, autoincrement=True)
34    species_id = Column(Integer, ForeignKey('species.id'), nullable=False)
35    observation_date = Column(String, nullable=False)
36    location = Column(String, nullable=False)
37    count = Column(Integer, default=0)
38    notes = Column(String)
39    created_at = Column(DateTime, default=datetime.utcnow)
40
41    # Relacje
42    species = relationship("Species", back_populates="observations")
43
44    def __repr__(self):
45        return f"<Observation({self.species.common_name if self.species else 'N/A'}, {self.count}x @ {self.location})>"
46
47
48class SafariORM:
49    """Zarządzanie bazą danych Safari przez ORM"""
50
51    def __init__(self, db_url: str = "sqlite:///safari_orm.db"):
52        self.engine = create_engine(db_url, echo=False)
53        Base.metadata.create_all(self.engine)
54        Session = sessionmaker(bind=self.engine)
55        self.session = Session()
56
57    # === SPECIES ===
58
59    def create_species(self, scientific_name: str, common_name: str,
60                      population: int = 0, habitat: str = "",
61                      endangered: bool = False) -> Species:
62        """Dodaj gatunek"""
63        species = Species(
64            scientific_name=scientific_name,
65            common_name=common_name,
66            population=population,
67            habitat=habitat,
68            endangered=endangered
69        )
70        self.session.add(species)
71        self.session.commit()
72        return species
73
74    def get_species(self, species_id: int) -> Optional[Species]:
75        """Pobierz gatunek"""
76        return self.session.get(Species, species_id)
77
78    def list_species(self, endangered: Optional[bool] = None,
79                    habitat: Optional[str] = None) -> List[Species]:
80        """Lista gatunków"""
81        query = self.session.query(Species)
82
83        if endangered is not None:
84            query = query.filter(Species.endangered == endangered)
85        if habitat:
86            query = query.filter(Species.habitat == habitat)
87
88        return query.order_by(Species.common_name).all()
89
90    def update_species(self, species_id: int, **kwargs) -> Optional[Species]:
91        """Zaktualizuj gatunek"""
92        species = self.session.get(Species, species_id)
93        if not species:
94            return None
95
96        for key, value in kwargs.items():
97            if hasattr(species, key):
98                setattr(species, key, value)
99
100        species.updated_at = datetime.utcnow()
101        self.session.commit()
102        return species
103
104    def delete_species(self, species_id: int) -> bool:
105        """Usuń gatunek"""
106        species = self.session.get(Species, species_id)
107        if not species:
108            return False
109
110        self.session.delete(species)
111        self.session.commit()
112        return True
113
114    # === OBSERVATIONS ===
115
116    def create_observation(self, species_id: int, observation_date: str,
117                          location: str, count: int, notes: str = "") -> Optional[Observation]:
118        """Dodaj obserwację"""
119        species = self.session.get(Species, species_id)
120        if not species:
121            return None
122
123        observation = Observation(
124            species_id=species_id,
125            observation_date=observation_date,
126            location=location,
127            count=count,
128            notes=notes
129        )
130        self.session.add(observation)
131        self.session.commit()
132        return observation
133
134    def get_observations_for_species(self, species_id: int) -> List[Observation]:
135        """Obserwacje gatunku"""
136        return self.session.query(Observation).filter(
137            Observation.species_id == species_id
138        ).order_by(Observation.observation_date.desc()).all()
139
140    def close(self):
141        """Zamknij sesję"""
142        self.session.close()
143
144
145# === DEMONSTRACJA ===
146
147print("=== SAFARI ORM SYSTEM ===\n")
148
149db = SafariORM("safari_orm_demo.db")
150
151# 1. Dodaj gatunki
152print("1. Dodawanie gatunków (ORM)...")
153lion = db.create_species("Panthera leo", "Lew", 120, "sawanna", True)
154elephant = db.create_species("Loxodonta africana", "Słoń", 450, "sawanna", True)
155gorilla = db.create_species("Gorilla gorilla", "Goryl", 230, "las", True)
156
157print(f"   Dodano: {lion}, {elephant}, {gorilla}")
158
159# 2. Pobierz gatunek
160print("\n2. Pobieranie gatunku...")
161retrieved_lion = db.get_species(lion.id)
162print(f"   {retrieved_lion.common_name}: {retrieved_lion.population} osobników")
163
164# 3. Lista zagrożonych
165print("\n3. Lista zagrożonych gatunków...")
166endangered = db.list_species(endangered=True)
167for species in endangered:
168    print(f"   - {species.common_name}: {species.population}")
169
170# 4. Aktualizacja
171print("\n4. Aktualizacja populacji...")
172db.update_species(lion.id, population=125)
173lion = db.get_species(lion.id)
174print(f"   Nowa populacja lwa: {lion.population}")
175
176# 5. Dodaj obserwacje
177print("\n5. Dodawanie obserwacji...")
178db.create_observation(lion.id, "2024-01-15", "Serengeti", 12, "Pride with cubs")
179db.create_observation(lion.id, "2024-01-20", "Masai Mara", 8, "Male coalition")
180
181# 6. Pobierz obserwacje
182print("\n6. Obserwacje lwa...")
183observations = db.get_observations_for_species(lion.id)
184for obs in observations:
185    print(f"   - {obs.observation_date}: {obs.count}x @ {obs.location}")
186
187# 7. Relacje
188print("\n7. Nawigacja przez relacje...")
189lion = db.get_species(lion.id)
190print(f"   Lew ma {len(lion.observations)} obserwacji:")
191for obs in lion.observations:
192    print(f"     {obs.location}: {obs.count}x")
193
194db.close()
195print("\n✓ Demonstracja zakończona")

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • ✅ Czym jest ORM i po co służy
  • ✅ SQLAlchemy: Base, modele, kolumny
  • ✅ CRUD z ORM: add, query, update, delete
  • ✅ Filtrowanie: filter(), order_by(), limit()
  • ✅ Relacje: One-to-Many, relationship, ForeignKey
  • ✅ Kompletny system Safari z ORM

Analogia Safari: ORM to cyfrowy system katalogowy - zamiast ręcznie wypełniać formularze SQL, używasz pythonowych obiektów które automatycznie mapują się na bazę! 🐍💾

W następnej (ostatniej!) lekcji tego modułu Darwin pokaże Ci NoSQL MongoDB - bazę dokumentową bez sztywnego schematu! 📄🍃

Przejdź do CodeWorlds