Python Notes
Introduction to ORM (Object-Relational Mapping) with SQLAlchemy - models, sessions, queries, relationships, migrations with Alembic, and best practices with Hindi explanations.
Introduction
ORM (Object-Relational Mapping) aapko database tables ko Python classes ki tarah use karne deta hai. SQL likhne ki jagah Python objects use karo — ORM automatically SQL generate karta hai.
pip install sqlalchemy
pip install alembic # migrations ke liye2. Session — Database Operations
from sqlalchemy.orm import sessionmaker
# Session factory create karo
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
# Context manager for session management
from contextlib import contextmanager
@contextmanager
def get_session():
"""Session create karo, use karo, close karo"""
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
# Basic usage
with get_session() as session:
print("Session created and ready")3. CREATE — Adding Records
4. READ — Querying Records
📝 Hindi Explanation
SQLAlchemy query syntax Python-like hai —.filter()WHERE clause,.order_by()ORDER BY,.limit()LIMIT.func.count(),func.avg()SQL aggregate functions hain..first()ek record,.all()saare records return karta hai.
5. UPDATE — Modifying Records
# Update single record
with get_session() as session:
student = session.get(Student, 1)
if student:
student.marks = 98.0
student.grade = "A+"
print(f"Updated: {student.name} — marks: {student.marks}")
# Auto-commit on context exit
# Bulk update with query
with get_session() as session:
# Update multiple records at once
updated_count = session.query(Student).filter(
Student.marks >= 90
).update({"grade": "A+"}, synchronize_session=False)
print(f"Updated {updated_count} students to A+")
# Update with ORM operations
with get_session() as session:
# Add bonus marks to all B grade students
b_students = session.query(Student).filter(Student.grade == "B").all()
for student in b_students:
student.marks += 2.0 # 2 marks bonus
print(f"Gave bonus to {len(b_students)} students")6. DELETE — Removing Records
# Delete single record
with get_session() as session:
student = session.get(Student, 1)
if student:
session.delete(student)
print(f"Deleted: {student.name}")
# Bulk delete with filter
with get_session() as session:
deleted_count = session.query(Student).filter(
Student.is_active == False
).delete(synchronize_session=False)
print(f"Deleted {deleted_count} inactive students")
# Soft delete pattern
with get_session() as session:
session.query(Student).filter(Student.id == 2).update(
{"is_active": False}
)
print("Soft deleted student 2")7. Relationships — JOINs with ORM
📝 Hindi Explanation
N+1 Problem: Jab aap 10 students fetch karo, phir har student ke courses ke liye alag query karo — 11 queries hoti hain (1 + 10)!joinedload()yaselectinload()se yeh ek ya do queries mein solve ho jaata hai. Always use eager loading jab related data ki zaroorat ho.
8. Migrations with Alembic
# Alembic setup
# alembic init alembic
# Edit alembic/env.py to set target_metadata = Base.metadata
# alembic/versions/001_initial.py (auto-generated)
"""Initial migration
Revision ID: 001
"""
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'students',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('email', sa.String(100), unique=True),
sa.Column('marks', sa.Float()),
sa.PrimaryKeyConstraint('id')
)
def downgrade():
op.drop_table('students')
# Adding a column
"""Add phone to students
Revision ID: 002
"""
def upgrade():
op.add_column('students', sa.Column('phone', sa.String(15), nullable=True))
def downgrade():
op.drop_column('students', 'phone')
# Run migrations:
# alembic upgrade head # Latest version pe upgrade
# alembic downgrade -1 # One version back
# alembic current # Current version check
# alembic history # History dekho
# alembic revision --autogenerate -m "Add phone column" # Auto-generate9. Advanced SQLAlchemy Patterns
from sqlalchemy import event
from sqlalchemy.orm import validates
class Student(Base):
__tablename__ = "students_v2"
id = Column(Integer, primary_key=True)
name = Column(String(100))
email = Column(String(100))
marks = Column(Float)
grade = Column(String(2))
@validates('email')
def validate_email(self, key, email):
"""Email validation at ORM level"""
if '@' not in email:
raise ValueError(f"Invalid email: {email}")
return email.lower()
@validates('marks')
def validate_marks(self, key, marks):
"""Marks auto-assign grade"""
if marks is not None and not 0 <= marks <= 100:
raise ValueError("Marks must be between 0 and 100")
return marks
# SQLAlchemy events
@event.listens_for(Student, 'before_insert')
def set_grade(mapper, connection, target):
"""Grade automatically set karo insert se pehle"""
if target.marks is not None:
if target.marks >= 90: target.grade = "A"
elif target.marks >= 75: target.grade = "B"
elif target.marks >= 60: target.grade = "C"
else: target.grade = "D"10. Best Practices
| Feature | Raw SQL | SQLAlchemy ORM |
|---|---|---|
| Code style | SQL strings | Python objects |
| Type safety | Manual | Automatic |
| Relationships | Manual JOINs | .relationship() |
| Migrations | Manual | Alembic |
| Database switch | Rewrite SQL | Change engine URL |
| Performance | Slightly faster | Overhead, but manageable |
When to use ORM vs Raw SQL? - ORM: Complex domain models, team projects, frequent schema changes - Raw SQL: Performance-critical queries, complex analytics, reporting
# SQLAlchemy connection strings
sqlite:///mydb.db # SQLite
mysql+mysqlconnector://user:pass@host/db # MySQL
postgresql://user:pass@host:5432/db # PostgreSQL📤 Expected Outputs
Section 1 — Setup Output:
Database and tables created!
Section 2 — Session Output:
Session created and ready
Section 3 — CREATE Output:
Created student with ID: 1 Added 4 students Courses added!
Section 4 — READ Output:
Get by ID: <Student(id=1, name='Alice Johnson', marks=95.5)> Total students: 5 A grade students: ['Alice Johnson', 'Charlie Brown', 'Eve Wilson'] Top young students: ['Alice Johnson', 'Charlie Brown'] filter_by: <Student(id=1, name='Alice Johnson', marks=95.5)> Top 3 students: Alice Johnson: 95.5 Charlie Brown: 91.0 Eve Wilson: 88.0 Stats: total=5, avg=87.0, max=95.5 By grade: Grade A: 3 students, avg 91.5 Grade B: 2 students, avg 80.3
Section 5 — UPDATE Output:
Updated: Alice Johnson — marks: 98.0 Updated 3 students to A+ Gave bonus to 2 students
Section 6 — DELETE Output:
Deleted: Alice Johnson Deleted 0 inactive students Soft deleted student 2
Section 7 — Relationships Output:
Enrollments added! Student: Bob Smith Course: Python Programming, Score: 82.0 Bob Smith: 1 courses Charlie Brown: 0 courses Diana Prince: 1 courses Bob Smith -> Python Programming (score: 82.0) Charlie Brown -> Web Development (score: 91.0) Diana Prince -> Python Programming (score: 95.0)
⚠️ Common Mistakes
❌ Mistake 1: Session Close Nahi Karna
# ❌ WRONG — session leak hoga
session = SessionLocal()
student = session.query(Student).first()
# session close nahi kiya!
# ✅ CORRECT — context manager use karo
with get_session() as session:
student = session.query(Student).first()Problem: Open sessions database connections consume karte hain. Hameshawithstatement yatry/finallyuse karo.
❌ Mistake 2: Session Ke Bahar Lazy-Loaded Attributes Access Karna
# ❌ WRONG — DetachedInstanceError milega
with get_session() as session:
student = session.query(Student).first()
print(student.enrollments) # 💥 Session closed, lazy load fail!
# ✅ CORRECT — eager loading use karo
with get_session() as session:
student = session.query(Student).options(
selectinload(Student.enrollments)
).first()
print(student.enrollments) # ✅ Already loadedExplanation: Jab session close ho jaata hai, lazy-loaded relationships access nahi ho sakte. Pehle sejoinedload()yaselectinload()use karo.
❌ Mistake 3: N+1 Query Problem Ignore Karna
# ❌ WRONG — 101 queries for 100 students!
students = session.query(Student).all()
for s in students:
print(s.enrollments) # Har student ke liye alag query 😱
# ✅ CORRECT — Ek hi query mein sab load karo
students = session.query(Student).options(
selectinload(Student.enrollments)
).all()Impact: Performance 10x-100x slow ho sakti hai. Production mein hamesha eager loading use karo jab related data chahiye.
❌ Mistake 4: commit() Aur flush() Ka Confusion
# ❌ WRONG — flush() commit nahi karta
session.add(student)
session.flush() # Database mein write hua, par transaction commit nahi hua
# Agar exception aaye toh data rollback ho jayega
# ✅ CORRECT — commit() for permanent save
session.add(student)
session.commit() # Ab data permanent haiKey Difference:flush()SQL execute karta hai but transaction open rehta hai.commit()transaction permanently save karta hai.
❌ Mistake 5: String Concatenation in Queries (SQL Injection!)
# ❌ WRONG — SQL injection vulnerable
name = "Alice'; DROP TABLE students; --"
session.execute(f"SELECT * FROM students WHERE name = '{name}'")
# ✅ CORRECT — ORM use karo ya parameterized queries
session.query(Student).filter(Student.name == name).all()Security: ORM automatically parameterized queries generate karta hai. Raw strings kabhi mat concatenate karo!
❌ Mistake 6: Same Object Multiple Sessions Mein Use Karna
# ❌ WRONG — object ek session se doosre mein move nahi hota
with get_session() as session1:
student = session1.query(Student).first()
with get_session() as session2:
session2.add(student) # 💥 Error — already attached elsewhere
# ✅ CORRECT — merge() use karo ya fresh query karo
with get_session() as session2:
student = session2.merge(student) # Re-attach to new session❌ Mistake 7: Bulk Operations Mein Individual Inserts
Performance: Bulk operations 10x-50x faster hote hain individual inserts se.add_all()yabulk_insert_mappings()use karo.
✅ Key Takeaways
- 🔗 ORM (Object-Relational Mapping) database tables ko Python classes mein map karta hai — SQL likhne ki zaroorat nahi, Python objects se kaam karo
- 🏗️ DeclarativeBase se models define karo — har class = ek table, har Column = ek field
- 🔄 Session database operations ka gateway hai — hamesha context manager (
with) use karo for safe resource management - 📊 CRUD operations Python-like syntax mein —
.add(),.query(),.filter(),.delete()— no raw SQL needed - ⚡ N+1 Problem se bachne ke liye eager loading (
joinedload,selectinload) use karo — production mein critical hai - 🔗 Relationships (
one-to-many,many-to-many) define karorelationship()se — ORM automatically JOINs handle karta hai - 📦 Alembic se database migrations manage karo — schema changes track aur version control mein aate hain
- ✅ Validation ORM level pe karo
@validatesdecorator se — data integrity ensure hoti hai before database hit - 🛡️ SQL Injection safe — ORM automatically parameterized queries generate karta hai
- 🔀 Database switch easy hai — sirf connection string change karo (SQLite → PostgreSQL → MySQL)
❓ FAQ
Q1: ORM use karna chahiye ya Raw SQL?
Answer: Agar aapka project complex domain models ke saath hai, team mein kaam ho raha hai, ya frequent schema changes hote hain — ORM use karo. Agar purely analytics/reporting queries hain ya extreme performance chahiye — Raw SQL use karo. Most production apps mein ORM + occasional raw SQL ka combination best hai. 🎯
Q2: SQLAlchemy Core aur ORM mein kya difference hai?
Answer: SQLAlchemy Core ek low-level SQL toolkit hai — aap SQL expressions Python mein likhte ho but objects nahi milte. SQLAlchemy ORM Core ke upar built hai — yeh Python classes ko tables se map karta hai. Core faster hai for bulk operations, ORM developer-friendly hai for CRUD apps. Aap dono ek saath bhi use kar sakte ho! 🔀
Q3: flush() aur commit() mein kya fark hai?
Answer: flush() pending changes ko SQL mein convert karke database ko bhejta hai, lekin transaction open rehta hai — rollback ho sakta hai. commit() transaction ko permanently save karta hai — ab data safe hai. flush() useful hai jab aapko auto-generated ID chahiye commit se pehle (jaise foreign key set karna). 💾
Q4: N+1 Problem kya hai aur kaise solve kare?
Answer: Jab aap 100 students fetch karo aur phir har student ke enrollments access karo — 101 queries fire hoti hain (1 for students + 100 for enrollments). Solution: joinedload() (SQL JOIN mein sab aata hai) ya selectinload() (ek IN query se related data aata hai). Hamesha eager loading use karo jab related data display karna ho! ⚡
Q5: Alembic migrations kyun zaroori hain?
Answer: Production mein create_all() use nahi kar sakte — yeh existing data destroy kar sakta hai! Alembic schema changes ko versioned scripts mein track karta hai. Team members same migrations run karke sync rehte hain. Rollback bhi possible hai agar kuch galat ho. It's like Git for database schema! 📦
Q6: Kya SQLAlchemy async support karta hai?
Answer: Haan! SQLAlchemy 2.0+ mein create_async_engine aur AsyncSession available hai. FastAPI ke saath async ORM use kar sakte ho for high-concurrency apps. asyncpg (PostgreSQL) ya aiosqlite (SQLite) drivers use karo. Performance boost milta hai IO-heavy applications mein! 🚀
Q7: relationship() mein cascade="all, delete-orphan" kya karta hai?
Answer: Yeh define karta hai ki parent delete hone par children ka kya ho. all = insert/update/delete sab parent ke saath propagate hoga. delete-orphan = agar child ka parent remove ho jaye toh child bhi delete ho jayegi. Example: Student delete karo toh uske saare enrollments bhi auto-delete! 🗑️
Q8: SQLAlchemy mein connection pooling kaise kaam karta hai?
Answer: create_engine() by default connection pool maintain karta hai (default 5 connections, max 10). Har session pool se connection leta hai aur use ke baad return karta hai. Production mein pool_size, max_overflow, pool_timeout configure karo. Yeh database connections ka efficient reuse ensure karta hai! 🏊
🎯 Interview Questions
Q1: ORM kya hai aur iske advantages kya hain?
Answer: ORM (Object-Relational Mapping) ek programming technique hai jo database tables ko programming language ke objects mein map karta hai.
Advantages: (1) Database-agnostic code — engine URL change karo, code same rehta hai. (2) SQL injection automatically prevented. (3) Code readability better — Python objects use karo, raw SQL nahi. (4) Relationships easy to manage. (5) Migration tools (Alembic) available. (6) Validation at model level.
Disadvantages: (1) Complex queries mein performance overhead. (2) Learning curve. (3) Generated SQL sometimes suboptimal.
Q2: SQLAlchemy mein Session ka role explain karo.
Answer: Session ek Unit of Work pattern implement karta hai. Yeh:
- Objects track karta hai (new, modified, deleted)
- Changes batch karke database ko bhejta hai
- Transaction management handle karta hai (commit/rollback)
- Identity map maintain karta hai (same row = same object)
- First-level cache provide karta hai
Best practice: Short-lived sessions use karo, context manager ke saath. Long-lived sessions stale data aur memory leaks ka cause bante hain.
Q3: Lazy Loading vs Eager Loading explain karo. N+1 problem kya hai?
Answer: Lazy Loading (default): Related objects tab load hote hain jab aap access karo. Benefit: Memory save. Problem: N+1 queries.
Eager Loading: Related objects pehle se load ho jaate hain (JOIN ya IN query se). Types:
joinedload()— SQL JOIN use karta hai (fewer records ke liye best)selectinload()— Separate SELECT...IN query (many records ke liye best)subqueryload()— Subquery use karta hai
N+1 Problem: 1 query for parents + N queries for each parent's children. Solution: Eager loading!
Q4: SQLAlchemy mein relationship() aur ForeignKey ka difference kya hai?
Answer: ForeignKey database level constraint hai — yeh actual column define karta hai jo doosri table ki primary key reference karta hai. Yeh schema mein exist karta hai.
relationship() ORM level construct hai — yeh Python objects ke beech navigation enable karta hai. Database mein koi column nahi banta. back_populates se bidirectional access milta hai.
# ForeignKey — database column
student_id = Column(Integer, ForeignKey("students.id"))
# relationship — Python object access
student = relationship("Student", back_populates="enrollments")Q5: Alembic migrations kaise kaam karti hain?
Answer: Alembic database schema versioning tool hai (like Git for DB schema):
alembic init— setup directory structurealembic revision --autogenerate -m "message"— models compare karke migration script generate karoalembic upgrade head— latest version pe migrate karoalembic downgrade -1— ek step back jao
Har migration mein upgrade() aur downgrade() functions hote hain. Production mein create_all() kabhi use mat karo — hamesha Alembic migrations run karo!
Q6: session.flush() vs session.commit() vs session.expire_all() explain karo.
Answer:
flush(): Pending changes ko SQL mein convert karke execute karta hai, par transaction commit nahi karta. Auto-generated IDs milte hain. Rollback possible.commit(): Transaction permanently save karta hai. Internallyflush()call karta hai pehle. Ab rollback possible nahi.expire_all(): Session ke saare objects ko "stale" mark karta hai. Next access pe database se fresh data fetch hoga. Useful after external changes.
Q7: SQLAlchemy mein Many-to-Many relationship kaise implement karte hain?
Answer: Association table use karo:
# Association table
student_course = Table('student_course', Base.metadata,
Column('student_id', Integer, ForeignKey('students.id')),
Column('course_id', Integer, ForeignKey('courses.id'))
)
class Student(Base):
courses = relationship("Course", secondary=student_course, back_populates="students")
class Course(Base):
students = relationship("Student", secondary=student_course, back_populates="courses")Agar association table mein extra columns chahiye (like score, date), toh Association Object pattern use karo (jaise humne Enrollment model banaya).
Q8: SQLAlchemy mein connection pooling kaise configure karte hain?
Answer: create_engine() mein pool parameters set karo:
engine = create_engine(
"postgresql://user:pass@host/db",
pool_size=10, # Normal connections
max_overflow=20, # Extra connections when busy
pool_timeout=30, # Wait time for connection
pool_recycle=3600, # Recycle connections after 1 hour
pool_pre_ping=True, # Check connection health before use
)Production mein proper pool configuration critical hai — too few connections = bottleneck, too many = database overloaded.
Q9: ORM mein Soft Delete pattern kaise implement karo?
Answer: Column add karo is_deleted ya deleted_at, aur queries mein filter karo:
class Student(Base):
is_deleted = Column(Boolean, default=False)
deleted_at = Column(DateTime, nullable=True)
# Soft delete
student.is_deleted = True
student.deleted_at = datetime.utcnow()
# Query mein filter
active_students = session.query(Student).filter(Student.is_deleted == False).all()Advanced: SQLAlchemy mein @event.listens_for ya custom query_class se automatic filtering implement kar sakte ho.
Q10: SQLAlchemy 1.x vs 2.0 mein major differences kya hain?
Answer: SQLAlchemy 2.0 mein major changes:
- New query style:
session.execute(select(Student))instead ofsession.query(Student) - Type annotations support: Mapped columns with
Mapped[int] - DeclarativeBase class (instead of
declarative_base()function) - Async support built-in (
AsyncSession,create_async_engine) - Stricter typing — better IDE autocomplete
- 2.0 style is more explicit aur future-proof. Migration guide available hai official docs mein.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for ORM Introduction with SQLAlchemy.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Python Master Course topic.
Search Terms
python-master-course, python master course, python, master, course, databases, orm, introduction
Related Python Master Course Topics