Learn / Frameworks / Flask / Databases with SQLAlchemy

Flask · Lesson 3 of 4

Databases with SQLAlchemy

Models, queries and migrations with Flask-SQLAlchemy.

  • Intermediate
  • 16 min read
  • 3 objectives

Before this lessonLesson 2: Templates and Forms

What you will learn

  • Define models
  • Query and commit
  • Understand migrations

Most apps need a database. The usual choice with Flask is Flask-SQLAlchemy, which wraps SQLAlchemy, Python's leading ORM, so you work with classes instead of raw SQL.

pip install flask flask-sqlalchemy flask-migrate

Setup and models

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime, timezone

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    name = db.Column(db.String(80), nullable=False)
    posts = db.relationship("Post", backref="author", lazy=True)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    body = db.Column(db.Text)
    created = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)

with app.app_context():
    db.create_all()

Create, read, update, delete

with app.app_context():
    ada = User(email="ada@example.com", name="Ada")
    db.session.add(ada)
    db.session.commit()

    db.session.add(Post(title="Hello", body="First", user_id=ada.id))
    db.session.commit()

    User.query.all()
    User.query.filter_by(email="ada@example.com").first()
    db.session.get(User, 1)
    Post.query.filter(Post.title.contains("Hel")).order_by(Post.created.desc()).limit(10).all()

    ada.name = "Ada Lovelace"       # update by changing attributes
    db.session.commit()

    db.session.delete(ada)
    db.session.commit()

Changes are staged in the session and only written when you call commit(). If something fails, call db.session.rollback().

Using it in routes

@app.route("/posts")
def posts():
    posts = Post.query.order_by(Post.created.desc()).all()
    return jsonify([{"id": p.id, "title": p.title, "author": p.author.name} for p in posts])

@app.route("/posts/<int:post_id>")
def post_detail(post_id):
    post = db.get_or_404(Post, post_id)
    return jsonify(id=post.id, title=post.title)

Migrations

When models change, create_all will not alter existing tables. Flask-Migrate (Alembic) records schema changes as versioned scripts.

flask db init
flask db migrate -m "add posts"
flask db upgrade
# Write your solution here
Up next · Lesson 4Blueprints, Testing and DeploymentStructure larger apps, test them and run with gunicorn.