Getting Started¶
This page walks through the common use of the extension. See the rest of the documentation for more details about other features.
These docs cover how the extension works, not how to use SQLAlchemy. Read the SQLAlchemy docs, which include a comprehensive tutorial, to learn how to use SQLAlchemy.
Setup¶
Create an instance of SQLAlchemy
. Define the
SQLALCHEMY_ENGINES
config, a dict, with at least the "default"
key
with a connection string value. When setting up the Flask app, call the
extension’s init_app()
method.
from flask import Flask
from flask_sqlalchemy_lite import SQLAlchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config |= {
"SQLALCHEMY_ENGINES": {
"default": "sqlite:///default.sqlite",
},
}
app.config.from_prefixed_env()
db.init_app(app)
return app
When not using the app factory pattern, you can pass the app directly when
creating the instance, and it will call init_app
automatically.
app = Flask(__name__)
app.config |= {
"SQLALCHEMY_ENGINES": {
"default": "sqlite:///default.sqlite",
},
}
app.config.from_prefixed_env()
db = SQLAlchemy(app)
Models¶
The modern (SQLAlchemy 2) way to define models uses type annotations. Create a
base class first. Each model subclasses the base and defines at least a
__tablename__
and a primary key column.
from __future__ import annotations
from datetime import datetime
from datetime import UTC
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Model(DeclarativeBase):
pass
class User(Model):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
posts: Mapped[list[Post]] = relationship(back_populates="author")
class Post(Model):
__tablename__ = "post"
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
body: Mapped[str]
author_id: Mapped[int] = mapped_column(ForeignKey(User.id))
author: Mapped[User] = relationship(back_populates="posts")
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC))
There are other ways to define models, such as integrating with
dataclasses
, the legacy metaclass base, or setting up mappings manually.
This extension can be used with any method.
Creating Tables¶
Typically, you’ll want to use Alembic to generate and run migrations as you create and modify your tables. The Flask-Alembic extension provides integration between Flask, Flask-SQLAlchemy(-Lite), and Alembic. You can also use Alembic directly, but it will require a little more setup.
See Migrations with Alembic for instructions.
For basic uses, you can use the metadata.create_all()
method. You can call
this for multiple metadatas with different engines. This will create any tables
that do not exist. It will not update existing tables, such as adding new
columns. For that you need Alembic migrations.
Engines and session can only be accessed inside a Flask application context.
When not inside a request or CLI command, such as during setup, push a context
using a with
block.
with app.app_context():
Model.metadata.create_all(db.engine)
OtherModel.metadata.create_all(db.get_engine("other"))
Populating the Flask Shell¶
When using the flask shell
command to start an interactive interpreter,
any model classes that have been registered with any SQLAlchemy base class will
be made available. The SQLAlchemy
instance will be made available as
db
. And the sqlalchemy
namespace will be imported as sa
.
These three things make it easy to work with the database from the shell without needing any manual imports.
>>> for user in db.session.scalars(sa.select(User)):
... user.active = False
...
>>> db.session.commit()
Executing Queries¶
Queries are constructed and executed using standard SQLAlchemy. To add a model
instance to the session, use db.session.add(obj)
. To modify a row, modify the
model’s attributes. Then call db.session.commit()
to save the changes to the
database.
To query data from the database, use SQLAlchemy’s select()
constructor and
pass it to db.session.scalars()
when selecting a model, or .execute()
when
selecting a compound set of rows. There are also constructors for other
operations for less common use cases such as bulk inserts or updates.
from flask import request, abort, render_template
from sqlalchemy import select
@app.route("/users")
def user_list():
users = db.session.scalars(select(User).order_by(User.name)).all()
return render_template("users/list.html", users=users)
@app.route("/users/create")
def user_create():
name = request.form["name"]
if db.session.scalar(select(User).where(User.name == name)) is not None:
abort(400)
db.session.add(User(name=name))
db.session.commit()
return app.redirect(app.url_for("user_list"))
Application Context¶
Engines and sessions can only be accessed inside a Flask application context.
A context is active during each request, and during a CLI command. Therefore,
you can usually access db.session
without any extra work.
When not inside a request or CLI command, such as during setup or certain test
cases, push a context using a with
block.
with app.app_context():
# db.session and db.engine are accessible
...
Async¶
The extension also provides SQLAlchemy’s async engines and sessions. Prefix any
engine or session access with async_
to get the equivalent async objects. For
example, db.async_session
. You’ll want to
review SQLAlchemy’s async docs, as there are some more things to
be aware of than with sync usage.
In particular, SQLAlchemy warns that the async sessions it provides are not
safe to be used across concurrent tasks. For example, the same session should
not be passed to multiple tasks when using asyncio.gather
. Either use
db.get_async_session(name)
with a unique
name for each task, or use
db.async_sessionmaker
to manage session
lifetimes manually. The latter is what SQLAlchemy recommends.