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! 📝➡️💾
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:
SQLAlchemy to potężny ORM + narzędzie do pracy z bazami SQL.
1pip install sqlalchemy1from 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)| 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+) |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()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()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()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()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}")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")W tej lekcji nauczyłeś/aś się:
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! 📄🍃