Prerequisites
- Basic understanding of programming concepts π
- Python installation (3.8+) π
- VS Code or preferred IDE π»
What you'll learn
- Understand the concept fundamentals π―
- Apply the concept in real projects ποΈ
- Debug common issues π
- Write clean, Pythonic code β¨
π Flask Database: SQLAlchemy
Hey there, Flask developer! π Ever wanted to add a database to your Flask app without writing tons of SQL? Today weβre diving into SQLAlchemy - the magical tool that makes databases feel as easy as working with Python objects! πͺ
π― Introduction
Imagine building a blog, online store, or social media app - they all need to store data somewhere, right? Thatβs where SQLAlchemy comes in! Itβs like having a friendly translator between your Python code and the database. Instead of writing complex SQL queries, youβll work with Python objects that feel natural and intuitive! π¨
In this tutorial, youβll learn:
- How to set up SQLAlchemy with Flask π οΈ
- Create database models like a pro ποΈ
- Query data without writing SQL π
- Handle relationships between tables π€
- Best practices for production apps π
Ready to level up your Flask apps? Letβs go! πͺ
π Understanding SQLAlchemy
What is SQLAlchemy? π€
SQLAlchemy is an Object-Relational Mapping (ORM) library. Think of it as a bridge between your Python code and the database. Instead of writing:
SELECT * FROM users WHERE age > 18;
Youβll write:
users = User.query.filter(User.age > 18).all() # π― Much more Pythonic!
Why Use SQLAlchemy? π
- No More SQL Injection π‘οΈ - SQLAlchemy protects you automatically
- Database Independence π - Switch from SQLite to PostgreSQL easily
- Python Objects π - Work with familiar Python classes
- Automatic Migrations π¦ - Update your database schema with ease
π§ Basic Syntax and Usage
Letβs set up SQLAlchemy in your Flask app!
Installation π¦
pip install flask-sqlalchemy # π Get the Flask integration
Basic Setup π οΈ
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
# ποΈ Create your Flask app
app = Flask(__name__)
# π§ Configure the database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///myapp.db' # π SQLite for development
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # π Silence the deprecation warning
# π Initialize SQLAlchemy
db = SQLAlchemy(app)
Creating Your First Model ποΈ
class User(db.Model):
"""π§ A user in our application"""
id = db.Column(db.Integer, primary_key=True) # π Unique identifier
username = db.Column(db.String(80), unique=True, nullable=False) # π Must be unique
email = db.Column(db.String(120), unique=True, nullable=False) # π§ Contact info
created_at = db.Column(db.DateTime, default=datetime.utcnow) # β° Timestamp
def __repr__(self):
return f'<User {self.username}>' # π Friendly representation
Creating Tables π¨
# In your Python shell or app initialization
with app.app_context():
db.create_all() # πͺ Creates all tables defined by your models!
π‘ Practical Examples
Example 1: Blog Application π
Letβs build a simple blog with posts and comments!
from datetime import datetime
class Post(db.Model):
"""π A blog post"""
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# π Relationships
author = db.relationship('User', backref='posts')
comments = db.relationship('Comment', backref='post', lazy='dynamic')
class Comment(db.Model):
"""π¬ A comment on a post"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# π Relationships
author = db.relationship('User', backref='comments')
Using the Models π
# π§ Create a new user
new_user = User(username='python_lover', email='[email protected]')
db.session.add(new_user)
db.session.commit() # πΎ Save to database!
# π Create a blog post
post = Post(
title='Why I Love Python! π',
content='Python is amazing because...',
author=new_user # π Automatic relationship!
)
db.session.add(post)
db.session.commit()
# π¬ Add a comment
comment = Comment(
content='Great post! π',
post=post,
author=new_user
)
db.session.add(comment)
db.session.commit()
Example 2: E-commerce Product Catalog ποΈ
class Category(db.Model):
"""π·οΈ Product category"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
products = db.relationship('Product', backref='category', lazy='dynamic')
class Product(db.Model):
"""π¦ A product in our store"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, default=0)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
def is_available(self):
"""π Check if product is in stock"""
return self.stock > 0
# π Using the models
electronics = Category(name='Electronics')
db.session.add(electronics)
laptop = Product(
name='Super Laptop 3000 π»',
price=999.99,
stock=10,
category=electronics
)
db.session.add(laptop)
db.session.commit()
# π Query products
available_products = Product.query.filter(Product.stock > 0).all()
electronics_products = electronics.products.all() # π― All products in category!
Example 3: Social Media App π₯
# π€ Many-to-many relationship for followers
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model):
"""π€ Social media user"""
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
bio = db.Column(db.String(200))
# π Self-referential many-to-many
followed = db.relationship(
'User', secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
def follow(self, user):
"""β Follow another user"""
if not self.is_following(user):
self.followed.append(user)
def unfollow(self, user):
"""β Unfollow a user"""
if self.is_following(user):
self.followed.remove(user)
def is_following(self, user):
"""π Check if following a user"""
return self.followed.filter(
followers.c.followed_id == user.id).count() > 0
π Advanced Concepts
Query Power-ups! πͺ
# π Complex queries
from sqlalchemy import and_, or_, desc
# Find recent posts by specific authors
recent_posts = Post.query.filter(
and_(
Post.created_at > datetime(2024, 1, 1),
or_(
Post.author_id == 1,
Post.author_id == 2
)
)
).order_by(desc(Post.created_at)).limit(10).all()
# π Aggregation
from sqlalchemy import func
# Count posts per user
post_counts = db.session.query(
User.username,
func.count(Post.id).label('post_count')
).join(Post).group_by(User.id).all()
# π― Pagination
page = 1
per_page = 20
posts = Post.query.paginate(page=page, per_page=per_page, error_out=False)
Database Migrations with Flask-Migrate π
# Installation
# pip install flask-migrate
from flask_migrate import Migrate
migrate = Migrate(app, db)
# Commands:
# flask db init # π Initialize migrations
# flask db migrate -m "Add user table" # π Create migration
# flask db upgrade # β¬οΈ Apply migrations
Performance Optimization ποΈ
# β Bad: N+1 query problem
posts = Post.query.all()
for post in posts:
print(post.author.username) # π± Queries database each time!
# β
Good: Eager loading
posts = Post.query.options(db.joinedload(Post.author)).all()
for post in posts:
print(post.author.username) # π Already loaded!
# π― Lazy loading strategies
class Post(db.Model):
# ... other fields ...
comments = db.relationship('Comment', lazy='select') # Default
# lazy='joined' # π Load with parent
# lazy='subquery' # π Load in separate query
# lazy='dynamic' # π Returns query object
β οΈ Common Pitfalls and Solutions
Pitfall 1: Forgetting to Commit π
# β Wrong
user = User(username='newuser')
db.session.add(user)
# Oops! Data not saved! π±
# β
Right
user = User(username='newuser')
db.session.add(user)
db.session.commit() # πΎ Now it's saved!
Pitfall 2: Circular Imports π
# β Wrong: models.py imports app, app imports models
# app.py
from models import User # π± Circular import!
# β
Right: Use application factory pattern
# app.py
def create_app():
app = Flask(__name__)
db.init_app(app)
return app
# models.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # π― No app needed here!
Pitfall 3: Not Handling Exceptions π‘οΈ
# β Wrong
user = User(username='duplicate')
db.session.add(user)
db.session.commit() # π₯ Might fail if username exists!
# β
Right
try:
user = User(username='duplicate')
db.session.add(user)
db.session.commit()
except IntegrityError:
db.session.rollback() # π Undo changes
flash('Username already exists!', 'error')
π οΈ Best Practices
1. Use Context Managers π―
# β
Automatic cleanup
with app.app_context():
users = User.query.all()
# Database operations here
2. Index Important Fields π
class User(db.Model):
username = db.Column(db.String(80), unique=True, nullable=False, index=True) # ποΈ Faster lookups!
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
3. Use Validators π‘οΈ
from sqlalchemy.orm import validates
class User(db.Model):
email = db.Column(db.String(120))
@validates('email')
def validate_email(self, key, email):
"""π§ Ensure valid email format"""
if '@' not in email:
raise ValueError("Invalid email!")
return email
4. Separate Concerns π
# models/user.py
class User(db.Model):
# User model definition
pass
# models/post.py
class Post(db.Model):
# Post model definition
pass
# models/__init__.py
from .user import User
from .post import Post
π§ͺ Hands-On Exercise
Ready to practice? Letβs build a task management system! π
Your Mission: Create a Flask app with SQLAlchemy that manages tasks and projects.
Requirements:
- Create a
Project
model with name and description - Create a
Task
model with title, status, and due date - Tasks belong to projects (one-to-many relationship)
- Add methods to mark tasks as complete
- Create routes to list and create projects/tasks
Try it yourself first! π€
π‘ Click here for the solution
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# π Models
class Project(db.Model):
"""π A project containing tasks"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# π Relationship
tasks = db.relationship('Task', backref='project', lazy='dynamic')
def completion_percentage(self):
"""π Calculate project completion"""
total = self.tasks.count()
if total == 0:
return 0
completed = self.tasks.filter_by(status='completed').count()
return int((completed / total) * 100)
class Task(db.Model):
"""β
A task within a project"""
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
status = db.Column(db.String(20), default='pending') # pending, in_progress, completed
due_date = db.Column(db.Date)
project_id = db.Column(db.Integer, db.ForeignKey('project.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def mark_complete(self):
"""β
Mark task as completed"""
self.status = 'completed'
db.session.commit()
def is_overdue(self):
"""β° Check if task is overdue"""
if self.due_date and self.status != 'completed':
return datetime.now().date() > self.due_date
return False
# π£οΈ Routes
@app.route('/')
def index():
"""π Home page with all projects"""
projects = Project.query.all()
return render_template('index.html', projects=projects)
@app.route('/project/new', methods=['GET', 'POST'])
def new_project():
"""β Create new project"""
if request.method == 'POST':
project = Project(
name=request.form['name'],
description=request.form['description']
)
db.session.add(project)
db.session.commit()
return redirect(url_for('index'))
return render_template('new_project.html')
@app.route('/project/<int:id>')
def view_project(id):
"""π View project details"""
project = Project.query.get_or_404(id)
return render_template('project.html', project=project)
@app.route('/task/new/<int:project_id>', methods=['GET', 'POST'])
def new_task(project_id):
"""β Add task to project"""
project = Project.query.get_or_404(project_id)
if request.method == 'POST':
task = Task(
title=request.form['title'],
due_date=datetime.strptime(request.form['due_date'], '%Y-%m-%d').date() if request.form['due_date'] else None,
project=project
)
db.session.add(task)
db.session.commit()
return redirect(url_for('view_project', id=project_id))
return render_template('new_task.html', project=project)
@app.route('/task/complete/<int:id>')
def complete_task(id):
"""β
Mark task as complete"""
task = Task.query.get_or_404(id)
task.mark_complete()
return redirect(url_for('view_project', id=task.project_id))
if __name__ == '__main__':
with app.app_context():
db.create_all() # ποΈ Create tables
app.run(debug=True) # π Start the app!
π Key Takeaways
Youβve just mastered Flask-SQLAlchemy! Hereβs what you learned:
- SQLAlchemy = Python Objects π - No more SQL strings!
- Models Define Tables π - Python classes become database tables
- Relationships are Easy π - Connect data with simple attributes
- Queries are Pythonic π - Filter, sort, and paginate with methods
- Migrations Keep You Safe π‘οΈ - Update schemas without data loss
Remember:
- Always commit your changes! πΎ
- Use relationships to avoid duplicate data π
- Handle exceptions gracefully π‘οΈ
- Index fields you search frequently π
π€ Next Steps
Congratulations, database wizard! π§ββοΈ Youβve unlocked the power of SQLAlchemy! Hereβs what to explore next:
- Flask-Migrate π - Master database migrations
- Advanced Queries π - Learn joins, subqueries, and CTEs
- Performance Tuning ποΈ - Optimize for production
- Multiple Databases ποΈ - Connect to PostgreSQL, MySQL, etc.
Keep building amazing Flask apps with databases! The data world is yours to conquer! π
Happy coding! π