SQLAlchemy je najpopularniji ORM (Object Relational Mapper) u Pythonu. Omogućava ti da radiš sa bazom kroz Python objekte, umesto da pišeš SQL ručno.
Engine– konekcija ka bazi (npr. SQLite, PostgreSQL, MySQL).Session– "radna sesija" koja drži transakcije i objekte dok ne pozovešcommit().Model– Python klasa koja predstavlja tabelu u bazi.Query– način da izvlačiš podatke iz baze.
pip install sqlalchemyfrom sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker
# --- Engine (konekcija sa bazom) ---
engine = create_engine("sqlite:///example.db", echo=True)
# --- Bazna klasa za modele ---
Base = declarative_base()
# --- Model (tabela) ---
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
def __repr__(self):
return f"User(id={self.id}, name='{self.name}', age={self.age})"
# --- Kreiraj tabele ---
Base.metadata.create_all(engine)Ovo ti kreira SQLite bazu example.db sa tabelom users.
Session = sessionmaker(bind=engine)
session = Session()Novi korisnik:
new_user = User(name="Pera", age=30)
session.add(new_user) # ubaci u transakciju
session.commit() # potvrdi u baziSvi korisnici:
users = session.query(User).all()
print(users)Filtriranje:
pera = session.query(User).filter_by(name="Pera").first()
print(pera)pera.age = 31
session.commit()session.delete(pera)
session.commit()- Definišeš
model(klasu) <=> tabela. - Napraviš
sessionda radiš sa bazom. - Dodaješ/čitaš/menjaš/brišeš objekte kao obične Python objekte.
Jedan korisnik ima više postova.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
# --- Konekcija na SQLite bazu ---
engine = create_engine("sqlite:///blog.db", echo=True)
Base = declarative_base()
# --- Session ---
Session = sessionmaker(bind=engine)
session = Session()class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
# Relacija 1:N (User → Post)
posts = relationship("Post", back_populates="author")
def __repr__(self):
return f"User(id={self.id}, name='{self.name}')"
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String)
content = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
# Veza ka User modelu
author = relationship("User", back_populates="posts")
def __repr__(self):
return f"Post(id={self.id}, title='{self.title}')"
# Kreiranje tabela
Base.metadata.create_all(engine)Novi korisnik:
pera = User(name="Pera")Dodaj u 1: n relaciju:
pera.posts = [
Post(title="Prvi post", content="Ovo je moj prvi post."),
Post(title="Drugi post", content="Ovo je moj drugi post."),
]
session.add(pera)
session.commit()Nađi korisnika u 1:n relaciji:
pera = session.query(User).filter_by(name="Pera").first()
print(pera)
print("Postovi:", pera.posts) # lista povezanih postova
# Nađi post i vidi autora
post = session.query(Post).first()
print(post)
print("Autor:", post.author)Kada pokreneš ovaj kod:
- Kreiraće se tabele users i posts.
- Pera će imati 2 posta.
- Možeš iz User da ideš ka Post i obrnuto.
User ↔ Group (korisnik može biti u više grupa, grupa može imati više korisnika).
from sqlalchemy import create_engine, Column, Integer, String, Table, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
engine = create_engine("sqlite:///nm.db", echo=True)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()Za N:M relacije u SQLAlchemy se koristi pomoćna tabela:
user_group = Table(
'user_group',
Base.metadata,
Column('user_id', Integer, ForeignKey('users.id')),
Column('group_id', Integer, ForeignKey('groups.id'))
)class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
# Relacija N:M sa Group
groups = relationship( "Group", secondary=user_group,
back_populates="users"
)
def __repr__(self):
return f"User(id={self.id}, name='{self.name}')"
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True)
name = Column(String)
# Relacija N:M sa User
users = relationship("User", secondary=user_group,
back_populates="groups")
def __repr__(self):
return f"Group(id={self.id}, name='{self.name}')"
# Kreiranje tabela
Base.metadata.create_all(engine)# Kreiraj korisnike
pera = User(name="Pera")
mika = User(name="Mika")
# Kreiraj grupe
admin = Group(name="Admin")
user = Group(name="User")
# Dodaj korisnike u grupe
pera.groups = [admin, user] # Pera je u obe grupe
mika.groups = [user] # Mika je samo u User grupi
session.add_all([pera, mika, admin, user])
session.commit()Nađi korisnika i njegove grupe u n:m relaciji:
pera = session.query(User).filter_by(name="Pera").first()
print(pera)
print("Grupe:", pera.groups)Nađi grupu i njene korisnike u n:m relaciji:
user_group_obj = session.query(Group).filter_by(name="User").first()
print(user_group_obj)
print("Korisnici:", user_group_obj.users)- SQLAlchemy automatski vodi evidenciju kroz pomoćnu tabelu
user_group. - Možeš da ideš sa korisnika na grupe i obrnuto, sve Python objektima.
- Ovo je standardni način za N:M relacije u SQLAlchemy.
declarative_base() je jedan od najčešćih načina da se grade modeli u SQLAlchemy, ali nije jedini. Evo kako to izgleda:
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)Ovde:
Baseje metaklasa – svaki model koji je nasleđuje se registruje uBase.metadata. Kasnije kad pozovešBase.metadata.create_all(engine), SQLAlchemy zna koje tabele da napravi.
Ovo je "stari" stil, ali i dalje super popularan i radi u SQLAlchemy 2.x.
Od verzije 2.0 postoji elegantniji način:
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)Prednost:
- Tipovi su jasniji (bolja podrška za IDE i mypy).
- Više u duhu modernog Pythona.
- Ako praviš veće projekte i koristiš SQLAlchemy 2.x, ovo bih preporučio.
| Stari stil (declarative_base()) | Novi stil (DeclarativeBase) |
|---|---|
| Funkcija koja vraća Base klasu | Klasa koju sam definišeš |
| Široko korišćen, gomila tutorijala | Moderniji, preporučen za nove projekte |
| Dobro radi sa starim kodom | Bolje radi sa type-hinting alatima |
- Ako učiš i praviš prvi projekat, slobodno koristi
declarative_base()jer ima više primera i resursa. - Ako krećeš od nule i koristiš SQLAlchemy 2.x, idi odmah na DeclarativeBase – čistije je i dugoročno bolje.
Hajde da uradimo isti model User–Post (1:N) i User–Group (N:M) u oba stila, pa da jasno vidiš razliku.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
engine = create_engine("sqlite:///oldstyle.db", echo=True)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
# N:M pomoćna tabela
user_group = Table(
"user_group",
Base.metadata,
Column("user_id", Integer, ForeignKey("users.id")),
Column("group_id", Integer, ForeignKey("groups.id"))
)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
posts = relationship("Post", back_populates="author")
groups = relationship("Group", secondary=user_group,
back_populates="users")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", back_populates="posts")
class Group(Base):
__tablename__ = "groups"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship("User", secondary=user_group, back_populates="groups")
Base.metadata.create_all(engine)Ovde sve ide preko klase Base = declarative_base().
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker, Mapped, mapped_column
engine = create_engine("sqlite:///newstyle.db", echo=True)
Session = sessionmaker(bind=engine)
session = Session()
# Nova bazna klasa
class Base(DeclarativeBase):
pass
# N:M pomoćna tabela
user_group = Table(
"user_group",
Base.metadata,
Column("user_id", ForeignKey("users.id"), primary_key=True),
Column("group_id", ForeignKey("groups.id"), primary_key=True)
)
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String)
posts: Mapped[list["Post"]] = relationship("Post", back_populates="author")
groups: Mapped[list["Group"]] = relationship("Group", secondary=user_group,
back_populates="users")
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
author: Mapped["User"] = relationship("User", back_populates="posts")
class Group(Base):
__tablename__ = "groups"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String)
users: Mapped[list["User"]] = relationship("User", secondary=user_group,
back_populates="groups")
Base.metadata.create_all(engine)Ovde koristiš:
DeclarativeBaseumestodeclarative_base().Mapped[]+mapped_column()za tipove (super korisno sa IDE/mypy).
Isto ponašanje, ali moderniji i čitljiviji API.
- Stari stil → kraći kod, ali bez type-hintova.
- Novi stil → eksplicitno kažeš tipove (Mapped[int], Mapped[str]) i dobićeš autocomplete + type-checking u editoru.
from sqlalchemy import create_engine, String, ForeignKey, Table
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, sessionmaker
# Engine & Session
engine = create_engine("sqlite:///example.db", echo=True)
Session = sessionmaker(bind=engine)
# Base klasa
class Base(DeclarativeBase):
pass
# N:M pomoćna tabela
user_group = Table(
"user_group",
Base.metadata,
mapped_column("user_id", ForeignKey("users.id"), primary_key=True),
mapped_column("group_id", ForeignKey("groups.id"), primary_key=True)
)
# Modeli
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String)
posts: Mapped[list["Post"]] = relationship(back_populates="author")
groups: Mapped[list["Group"]] = relationship(secondary=user_group, back_populates="users")
def __repr__(self):
return f"User(id={self.id}, name={self.name})"
class Post(Base):
__tablename__ = "posts"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str] = mapped_column(String)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
author: Mapped["User"] = relationship(back_populates="posts")
def __repr__(self):
return f"Post(id={self.id}, title={self.title})"
class Group(Base):
__tablename__ = "groups"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String)
users: Mapped[list["User"]] = relationship(secondary=user_group, back_populates="groups")
def __repr__(self):
return f"Group(id={self.id}, name={self.name})"
# Kreiranje šema
Base.metadata.create_all(engine)
# Sesija
session = Session()
# Insert podataka
pera = User(name="Pera")
mika = User(name="Mika")
admin = Group(name="Admin")
general = Group(name="General")
pera.posts = [
Post(title="Pera prvi post"),
Post(title="Pera drugi post")
]
mika.posts = [
Post(title="Mika jedini post")
]
pera.groups = [admin, general]
mika.groups = [general]
session.add_all([pera, mika])
session.commit()# Upit za sve korisnike
print("\n--- Users ---")
for u in session.query(User).all():
print(u, "Posts:", u.posts, "Groups:", u.groups)
# Nađi jednog korisnika
print("\n--- Find Pera ---")
pera = session.query(User).filter_by(name="Pera").first()
print(pera, pera.posts, pera.groups)
# Svi postovi
print("\n--- Posts ---")
for p in session.query(Post).all():
print(p, "Author:", p.author)Šta ćeš dobiti?
User → Post(1:N) iUser → Group(N:M) relacije rade kroz Python objekte.- Kad nadješ korisnika (Peru), automatski dobijaš njegove i
postsigroups. - Sve se ponaša kao ORM, nema ručnog pisanja SQL-a.
- User ima
posts(lista postova). - Post ima
author(jedan korisnik).
Ako ne povežeš te dve strane, ORM ne zna da su one deo iste veze – tretira ih odvojeno.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
posts = relationship("Post") # nema veze sa 'author'
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User") # nema veze sa 'posts'Ovdje User.posts i Post.author nisu povezani.
Kad dodaš post u user.posts, SQLAlchemy ne zna da automatski postavi post.author. Moraš oba polja sam da održavaš.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
posts = relationship("Post", back_populates="author")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String)
user_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", back_populates="posts")Sada su povezani.
- Kad ubaciš post u
user.posts, ORM automatski postavipost.author = user. - Kad postaviš
post.author = user, ORM automatski ubaci post uuser. posts.
To je "dvosmerna sinhronizacija".
pera = User(name="Pera")
post1 = Post(title="Prvi post")
pera.posts.append(post1)
print(post1.author) # automatski -> User(name="Pera")Bez back_populates, ovo ne bi radilo – post1.author bi ostao None dok ručno ne postaviš.
back_populates- kaže SQLAlchemy-u da su ova dva polja dve strane iste veze. Bez toga ORM ne može da ih automatski sinhronizuje.