FLASK-SQLALCHEMY(1)
π¦ NAME π¦
flask-sqlalchemy β it's the manual page name bestie, and the thing itself is Flask-SQLAlchemy 3.1.1 [image] π
So. Flask-SQLAlchemy is an extension for Flask (https://flask.palletsprojects.com/) that adds support for SQLAlchemy (https://www.sqlalchemy.org/) to your application. It simplifies using SQLAlchemy with Flask by setting up common objects and patterns for using those objects, like a session tied to each web request, models, and engines. Iconic behavior honestly β¨
Flask-SQLAlchemy does not change how SQLAlchemy works or is used β no cap, it just wraps it. See the SQLAlchemy documentation (https://docs.sqlalchemy.org/) to learn how to work with the ORM in depth. The docs here only cover setting up the extension, not how to use SQLAlchemy. π«Ά
π USER GUIDE π
πΈ Quick Start πΈ
Flask-SQLAlchemy simplifies using SQLAlchemy by automatically handling creating, using, and cleaning up the SQLAlchemy objects you'd normally work with. It adds a few useful features, but it still works like SQLAlchemy. Lowkey the whole vibe π
This page walks you through the basic use of Flask-SQLAlchemy. For full capabilities and customization, see the rest of these docs, including the API docs for the SQLAlchemy object (#flask_sqlalchemy.SQLAlchemy). π
π¦ Check the SQLAlchemy Documentation π¦
Flask-SQLAlchemy is a wrapper around SQLAlchemy. So β you should follow the SQLAlchemy Tutorial (https://docs.sqlalchemy.org/en/20/tutorial/index.html) to learn how to use it, and consult its documentation for detailed info about its features. These docs show how to set up Flask-SQLAlchemy itself, not how to use SQLAlchemy. The good news? Flask-SQLAlchemy sets up the engine and scoped session automatically, so you can skip those parts of the SQLAlchemy tutorial. Slay, less work for you π
This guide assumes you're using SQLAlchemy 2.x, which has a new API for defining models plus better support for Python type hints and data classes. If you're on SQLAlchemy 1.x, see Legacy Quickstart <>. π§
π Installation π
Flask-SQLAlchemy is available on PyPI (https://pypi.org/project/Flask-SQLAlchemy/) and can be installed with various Python tools. For example, to install or update the latest version using pip:
$ pip install -U Flask-SQLAlchemy
π Initialize the Extension π
First, create the db object using the SQLAlchemy constructor.
Pass a subclass of either DeclarativeBase (https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase) or DeclarativeBaseNoMeta (https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBaseNoMeta) to the constructor.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
Learn more about customizing the base model class in Models and Tables <>. β
π About the SQLAlchemy object π
Once constructed, the db object gives you access to the db.Model class (#flask_sqlalchemy.SQLAlchemy.Model) to define models, and the db.session (#flask_sqlalchemy.SQLAlchemy.session) to execute queries.
The SQLAlchemy object (#flask_sqlalchemy.SQLAlchemy) also takes additional arguments to customize the objects it manages. π«Ά
β¨ Configure the Extension β¨
Next step: connect the extension to your Flask app. The only required Flask app config is the SQLALCHEMY_DATABASE_URI key (#flask_sqlalchemy.config.SQLALCHEMY_DATABASE_URI). That's a connection string that tells SQLAlchemy what database to connect to. Fr fr it's the one thing you can't skip π
Create your Flask application object, load any config, and then initialize the SQLAlchemy extension class (#flask_sqlalchemy.SQLAlchemy) with the application by calling db.init_app (#flask_sqlalchemy.SQLAlchemy.init_app). This example connects to a SQLite database, which is stored in the app's instance folder:
# create the app
app = Flask(__name__)
# configure the SQLite database, relative to the app instance folder
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///project.db"
# initialize the app with the extension
db.init_app(app)
See Configuration <> for an explanation of connection strings and what other configuration keys are used. π
π Define Models π
Subclass db.Model to define a model class. The model will generate a table name by converting the CamelCase class name to snake_case. Automatic, main character energy π
from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column
class User(db.Model):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String, unique=True, nullable=False)
email: Mapped[str] = mapped_column(String)
See Models and Tables <> for more info about defining and creating models and tables. π¦
πΈ Create the Tables πΈ
After all models and tables are defined, call SQLAlchemy.create_all() (#flask_sqlalchemy.SQLAlchemy.create_all) to create the table schema in the database. This requires an application context. Since you're not in a request at this point, create one manually:
with app.app_context():
db.create_all()
β οΈ heads up bestie: if you define models in other modules, you must import them before calling create_all, otherwise SQLAlchemy will not know about them. π
create_all does not update tables if they're already in the database. So if you change a model's columns, use a migration library like Alembic (https://alembic.sqlalchemy.org/) with Flask-Alembic (https://flask-alembic.readthedocs.io/) or Flask-Migrate (https://flask-migrate.readthedocs.io/) to generate migrations that update the database schema. Don't try to brute force it, it's giving chaos π
π Query the Data π
Within a Flask view or CLI command, you can use db.session to execute queries and modify model data. This is the fun part β¨
SQLAlchemy automatically defines an __init__ method for each model that assigns any keyword arguments to corresponding database columns and other attributes.
db.session.add(obj)adds an object to the session, to be inserted.- Modifying an object's attributes updates the object.
db.session.delete(obj)deletes an object.
Remember to call db.session.commit() after modifying, adding, or deleting any data. Not me forgetting to commitβ¦ π
db.session.execute(db.select(...)) constructs a query to select data from the database. Building queries is the main feature of SQLAlchemy, so you'll want to read its tutorial on select (https://docs.sqlalchemy.org/tutorial/data_select.html) to learn all about it. You'll usually use the Result.scalars() method to get a list of results, or the Result.scalar() method to get a single result. π
@app.route("/users")
def user_list():
users = db.session.execute(db.select(User).order_by(User.username)).scalars()
return render_template("user/list.html", users=users)
@app.route("/users/create", methods=["GET", "POST"])
def user_create():
if request.method == "POST":
user = User(
username=request.form["username"],
email=request.form["email"],
)
db.session.add(user)
db.session.commit()
return redirect(url_for("user_detail", id=user.id))
return render_template("user/create.html")
@app.route("/user/<int:id>")
def user_detail(id):
user = db.get_or_404(User, id)
return render_template("user/detail.html", user=user)
@app.route("/user/<int:id>/delete", methods=["GET", "POST"])
def user_delete(id):
user = db.get_or_404(User, id)
if request.method == "POST":
db.session.delete(user)
db.session.commit()
return redirect(url_for("user_list"))
return render_template("user/delete.html", user=user)
π you may see uses ofModel.queryto build queries. That's an older interface for queries that's considered legacy in SQLAlchemy. Prefer usingdb.session.execute(db.select(...))instead. We're moving on bestie.
See Modifying and Querying Data <> for more info about queries. π
π§ What to Remember π§
For the most part, you should use SQLAlchemy as usual. The SQLAlchemy extension instance (#flask_sqlalchemy.SQLAlchemy) creates, configures, and gives access to the following things:
SQLAlchemy.Model(#flask_sqlalchemy.SQLAlchemy.Model) declarative model base class. It sets the table name automatically instead of needing__tablename__. β¨SQLAlchemy.session(#flask_sqlalchemy.SQLAlchemy.session) is a session that is scoped to the current Flask application context. It is cleaned up after every request.SQLAlchemy.metadata(#flask_sqlalchemy.SQLAlchemy.metadata) andSQLAlchemy.metadatas(#flask_sqlalchemy.SQLAlchemy.metadatas) gives access to each metadata defined in the config.SQLAlchemy.engine(#flask_sqlalchemy.SQLAlchemy.engine) andSQLAlchemy.engines(#flask_sqlalchemy.SQLAlchemy.engines) gives access to each engine defined in the config.SQLAlchemy.create_all()(#flask_sqlalchemy.SQLAlchemy.create_all) creates all tables.- You must be in an active Flask application context to execute queries and to access the session and engine. No context = no vibes π
π Configuration π
π Configuration Keys π
Configuration is loaded from the Flask app.config when SQLAlchemy.init_app() (#flask_sqlalchemy.SQLAlchemy.init_app) is called. The configuration is not read again after that. Therefore, all configuration must happen before initializing the application. Order matters, bestie π
flask_sqlalchemy.config.SQLALCHEMY_DATABASE_URI
The database connection URI used for the default engine. It can be either a string or a SQLAlchemy URL instance. See below and Engine Configuration (https://docs.sqlalchemy.org/en/20/core/engines.html) for examples.
At least one of this and SQLALCHEMY_BINDS must be set.
Changed in version 3.0: No longer defaults to an in-memory SQLite database if not set. So yeah, you gotta actually tell it now π«
flask_sqlalchemy.config.SQLALCHEMY_ENGINE_OPTIONS
A dict of arguments to pass to sqlalchemy.create_engine() (https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine) for the default engine.
This takes precedence over the engine_options argument to SQLAlchemy (#flask_sqlalchemy.SQLAlchemy), which can be used to set default options for all engines.
Changed in version 3.0: Only applies to the default bind.
Added in version 2.4.
flask_sqlalchemy.config.SQLALCHEMY_BINDS
A dict mapping bind keys to engine options. The value can be a string or a SQLAlchemy URL instance. Or it can be a dict of arguments, including the url key, that will be passed to sqlalchemy.create_engine() (https://docs.sqlalchemy.org/en/20/core/engines.html#sqlalchemy.create_engine). The None key can be used to configure the default bind, but SQLALCHEMY_ENGINE_OPTIONS and SQLALCHEMY_DATABASE_URI take precedence. π
At least one of this and SQLALCHEMY_DATABASE_URI must be set. β¨ Added in version 0.12.
flask_sqlalchemy.config.SQLALCHEMY_ECHOβ The default value forechoandecho_poolfor every engine. This is literally so useful for quickly debugging the connections and queries that SQLAlchemy is issuing. Changed in version 3.0: Setsecho_poolin addition toecho.flask_sqlalchemy.config.SQLALCHEMY_RECORD_QUERIESβ If enabled, information about each query during a request will be recorded. Useget_recorded_queries()(cross-ref target:#flask_sqlalchemy.record_queries.get_recorded_queries) to get a list of queries that were issued during the request. Changed in version 3.0: Not enabled automatically in debug or testing mode.flask_sqlalchemy.config.SQLALCHEMY_TRACK_MODIFICATIONSβ If enabled, all insert, update, and delete operations on models are recorded, then sent inmodels_committed(#flask_sqlalchemy.track_modifications.models_committed) andbefore_models_committed(#flask_sqlalchemy.track_modifications.before_models_committed) signals whensession.commit()is called.
β οΈ This adds a significant amount of overhead to every session. Prefer using SQLAlchemyβs ORM Events directly at https://docs.sqlalchemy.org/en/20/orm/events.html for the exact information you need.
Changed in version 3.0: Disabled by default.
Added in version 2.0.
Changed in version 3.1: Removed SQLALCHEMY_COMMIT_ON_TEARDOWN.
Changed in version 3.0: Removed SQLALCHEMY_NATIVE_UNICODE, SQLALCHEMY_POOL_SIZE, SQLALCHEMY_POOL_TIMEOUT, SQLALCHEMY_POOL_RECYCLE, and SQLALCHEMY_MAX_OVERFLOW.
π Connection URL Format π
See SQLAlchemyβs documentation on Engine Configuration at https://docs.sqlalchemy.org/en/20/core/engines.html for a complete description of syntax, dialects, and options.
A basic database connection URL uses the following format. Username, password, host, and port are optional depending on the database type and configuration.
dialect://username:password@host:port/database
Here are some example connection strings:
# SQLite, relative to Flask instance path
sqlite:///project.db
# PostgreSQL
postgresql://scott:tiger@localhost/project
# MySQL / MariaDB
mysql://scott:tiger@localhost/project
SQLite does not use a user or host, so its URLs always start with three slashes instead of two. The dbname value is a file path. Absolute paths start with a fourth slash (on Linux or Mac). Relative paths are relative to the Flask applicationβs instance_path (https://flask.palletsprojects.com/en/stable/api/#flask.Flask.instance_path).
πΈ Default Driver Options πΈ
Some default options are set for SQLite and MySQL engines to make them more usable by default in web applications.
SQLite relative file paths are relative to the Flask instance path instead of the current working directory. In-memory databases use a static pool and check_same_thread to work across requests.
MySQL (and MariaDB) servers are configured to drop connections that have been idle for 8 hours, which can result in an error like 2013: Lost connection to MySQL server during query. A default pool_recycle value of 2 hours (7200 seconds) is used to recreate connections before that timeout.
π¦ Engine Configuration Precedence π¦
Because Flask-SQLAlchemy has support for multiple engines, there are rules for which config overrides other config. Most applications will only have a single database and only need to use SQLALCHEMY_DATABASE_URI and SQLALCHEMY_ENGINE_OPTIONS.
- If the
engine_optionsargument is given toSQLAlchemy, it sets default options for all engines.SQLALCHEMY_ECHOsets the default value for bothechoandecho_poolfor all engines. - The options for each engine in
SQLALCHEMY_BINDSoverride those defaults. SQLALCHEMY_ENGINE_OPTIONSoverrides theNonekey inSQLALCHEMY_BINDS, andSQLALCHEMY_DATABASE_URIoverrides theurlkey in that engineβs options.
π Timeouts π
Certain databases may be configured to close inactive connections after a period of time. MySQL and MariaDB are configured for this by default, but database services may also configure this type of limit. This can result in an error like 2013: Lost connection to MySQL server during query.
If you encounter this error, try setting pool_recycle in the engine options to a value less than the databaseβs timeout.
Alternatively, you can try setting pool_pre_ping if you expect the database to close connections often, such as if itβs running in a container that may restart.
See SQAlchemyβs docs on dealing with disconnects at https://docs.sqlalchemy.org/core/pooling.html#dealing-with-disconnects for more information.
π Models and Tables π
Use the db.Model class to define models, or the db.Table class to create tables. Both handle Flask-SQLAlchemyβs bind keys to associate with a specific engine.
β¨ Initializing the Base Class β¨
SQLAlchemy 2.x offers several possible base classes for your models: DeclarativeBase (https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase) or DeclarativeBaseNoMeta (https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBaseNoMeta).
Create a subclass of one of those classes:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
If desired, you can enable SQLAlchemyβs native support for data classes (https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#native-support-for-dataclasses-mapped-as-orm-models) by adding MappedAsDataclass as an additional parent class.
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
class Base(DeclarativeBase, MappedAsDataclass):
pass
You can optionally construct the SQLAlchemy object with a custom MetaData object (https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.MetaData). This allows you to specify a custom constraint naming convention (https://docs.sqlalchemy.org/core/constraints.html#constraint-naming-conventions). This makes constraint names consistent and predictable, useful when using migrations, as described by Alembic (https://alembic.sqlalchemy.org/en/latest/naming.html).
from sqlalchemy import MetaData
class Base(DeclarativeBase):
metadata = MetaData(naming_convention={
"ix": 'ix_%(column_0_label)s',
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s"
})
π Initialize the Extension π
Once youβve defined a base class, create the db object using the SQLAlchemy constructor.
db = SQLAlchemy(model_class=Base)
π Defining Models π
See SQLAlchemyβs declarative documentation at https://docs.sqlalchemy.org/en/20/orm/declarative_tables.html for full information about defining model classes declaratively.
Subclass db.Model to create a model class. Unlike plain SQLAlchemy, Flask-SQLAlchemyβs model will automatically generate a table name if __tablename__ is not set and a primary key column is defined.
from sqlalchemy.orm import Mapped, mapped_column
class User(db.Model):
id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
username: Mapped[str] = mapped_column(db.String, unique=True, nullable=False)
email: Mapped[str] = mapped_column(db.String)
Defining a model does not create it in the database. Use create_all() to create the models and tables after defining them. If you define models in submodules, you must import them so that SQLAlchemy knows about them before calling create_all.
with app.app_context():
db.create_all()
π Defining Tables π
See SQLAlchemyβs table documentation at https://docs.sqlalchemy.org/en/20/core/metadata.html for full information about defining table objects.
Create instances of db.Table to define tables. The class takes a table name, then any columns and other table parts such as columns and constraints. Unlike plain SQLAlchemy, the metadata argument is not required. A metadata will be chosen based on the bind_key argument, or the default will be used.
A common reason to create a table directly is when defining many to many relationships. The association table doesnβt need its own model class, as it will be accessed through the relevant relationship attributes on the related models.
import sqlalchemy as sa
user_book_m2m = db.Table(
"user_book",
sa.Column("user_id", sa.ForeignKey(User.id), primary_key=True),
sa.Column("book_id", sa.ForeignKey(Book.id), primary_key=True),
)
π§ Reflecting Tables π§
If you are connecting to a database that already has tables, SQLAlchemy can detect that schema and create tables with columns automatically. This is called reflection. Those tables can also be assigned to model classes with the __table__ attribute instead of defining the full model.
Call the reflect() method on the extension. It will reflect all the tables for each bind key. Each metadataβs tables attribute will contain the detected table objects.
with app.app_context():
db.reflect()
class User:
__table__ = db.metadatas["auth"].tables["user"]
In most cases, it will be more maintainable to define the model classes yourself. You only need to define the models and columns you will actually use, even if youβre connecting to a broader schema. IDEs will know the available attributes, and migration tools like Alembic can detect changes and generate schema migrations.
π₯ Modifying and Querying Data π₯
π Insert, Update, Delete π
See SQLAlchemyβs ORM tutorial at https://docs.sqlalchemy.org/tutorial/orm_data_manipulation.html and other SQLAlchemy documentation for more information about modifying data with the ORM.
To insert data, pass the model object to db.session.add():
user = User()
db.session.add(user)
db.session.commit()
To update data, modify attributes on the model objects:
user.verified = True
db.session.commit()
To delete data, pass the model object to db.session.delete():
db.session.delete(user)
db.session.commit()
After modifying data, you must call db.session.commit() to commit the changes to the database. Otherwise, they will be discarded at the end of the request.
π Select
Okay so querying with the ORM β the whole vibe is covered in SQLAlchemy's Querying Guide and the rest of the SQLAlchemy docs, so go read those for the deep lore. β¨
Here's the tea though: queries run through db.session.execute(). You build them with select(). When you execute a select you get back a Result object, and that thing is stacked with methods for working with the rows it gives you. π
user = db.session.execute(db.select(User).filter_by(username=username)).scalar_one()
users = db.session.execute(db.select(User).order_by(User.username)).scalars()
πΈ Queries for Views
So if you're writing a Flask view function, you'll very often want to return a 404 Not Found when the entry someone asked for isn't there. Flask-SQLAlchemy has your back with some extra query methods, no cap:
SQLAlchemy.get_or_404()β raises a 404 if the row with the given id doesn't exist, otherwise it hands you back the instance. π«ΆSQLAlchemy.first_or_404()β raises a 404 if the query returns zero results, otherwise it gives you the first result.SQLAlchemy.one_or_404()β raises a 404 if the query doesn't return exactly one result, otherwise it gives you that result. Periodt.
@app.route("/user-by-id/<int:id>")
def user_by_id(id):
user = db.get_or_404(User, id)
return render_template("show_user.html", user=user)
@app.route("/user-by-username/<username>")
def user_by_username(username):
user = db.one_or_404(db.select(User).filter_by(username=username))
return render_template("show_user.html", user=user)
And you can add a custom message to the 404 error, which is lowkey iconic for user experience:
user = db.one_or_404(
db.select(User).filter_by(username=username),
description=f"No user named '{username}'."
)
π Legacy Query Interface
You might see Model.query or session.query out in the wild being used to build queries. Bestie, that query interface is considered legacy in SQLAlchemy. Please prefer using session.execute(select(...)) instead. π
See Legacy Query Interface for the documentation.
π¦ Paging Query Results
Say you've got a lot of results. You probably only wanna show a certain number at a time and let the user click next and previous links to see pages of data. That's called pagination, and the verb is paginate. Slay. β¨
Call SQLAlchemy.paginate() on a select statement and you get yourself a Pagination object.
During a request, this will grab the page and per_page arguments from the query string request.args. Pass max_per_page to prevent users from requesting too many results on a single page (protect the servers, protect the vibes π
). If you don't give them, the defaults are page 1 with 20 items per page.
page = db.paginate(db.select(User).order_by(User.join_date))
return render_template("user/list.html", page=page)
π Showing the Items
The Pagination object's Pagination.items attribute is the list of items for the current page. The object can also be iterated over directly, which honestly? Chef's kiss.
<ul>
{% for user in page %}
<li>{{ user.username }}
{% endfor %}
</ul>
π Page Selection Widget
The Pagination object has attributes you can use to build a page selection widget β iterate over page numbers and check the current page. iter_pages() will produce up to three groups of numbers, separated by None. By default it shows 2 page numbers at either edge, 2 numbers before the current, the current, and 4 numbers after the current. π
So like, if there are 20 pages and the current page is 7, here's what gets yielded:
users.iter_pages()
[1, 2, None, 5, 6, 7, 8, 9, 10, 11, None, 19, 20]
You can use the total attribute to show the total number of results, and first and last to show the range of items on the current page. Obsessed with a clean range display, ngl.
The following Jinja macro renders a simple pagination widget:
{% macro render_pagination(pagination, endpoint) %}
<div class=page-items>
{{ pagination.first }} - {{ pagination.last }} of {{ pagination.total }}
</div>
<div class=pagination>
{% for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>β¦</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
πΈ Flask Application Context
An active Flask application context is required to make queries and to access db.engine and db.session. Why? Because the session is scoped to the context so it gets cleaned up properly after every request or CLI command. It's giving responsible adult energy. π«Ά
And listen β no matter how the application gets initialized with the extension, it is not stored for later use. Instead, the extension uses Flask's current_app proxy to get the active application, which requires an active application context. Iykyk.
Automatic Context
When Flask is handling a request or a CLI command, an application context gets pushed automatically. So you literally don't need to do anything special to use the database during requests or CLI commands. Bestie, it's handled. β¨
Manual Context
If you try to use the database when an application context is not active, you will see this error, and she is loud:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
the current application. To solve this, set up an application context
with app.app_context(). See the documentation for more information.
If you're in a situation where you need the database and don't have a context, you can push one with app_context. This is super common when calling db.create_all to create the tables, for example:
def create_app():
app = Flask(__name__)
app.config.from_object("project.config")
import project.models
with app.app_context():
db.create_all()
return app
Tests
If you're testing your app with the Flask test client to make requests to your endpoints, the context will be available as part of the request. But if you need to test something about your database or models directly, rather than going through a request, you gotta push a context manually. π
Only push a context exactly where and for how long it's needed for each test. Do not push an application context globally for every test, because that can interfere with how the session is cleaned up. Big deal, fr fr. π
def test_user_model(app):
user = User()
with app.app_context():
db.session.add(user)
db.session.commit()
And if you find yourself writing many tests like that, you can use a pytest fixture to push a context for a specific test β so much cleaner:
import pytest
@pytest.fixture
def app_ctx(app):
with app.app_context():
yield
@pytest.mark.usefixtures("app_ctx")
def test_user_model():
user = User()
db.session.add(user)
db.session.commit()
π Multiple Databases with Binds
SQLAlchemy can connect to more than one database at a time. It calls the different engines "binds". π
Flask-SQLAlchemy simplifies how binds work by associating each engine with a short string β a "bind key" β and then associating each model and table with a bind key. The session picks which engine to use for a query based on the bind key of the thing being queried. If no bind key is given, the default engine is used. Simple as that. β¨
Configuring Binds
The default bind is still configured by setting SQLALCHEMY_DATABASE_URI, and SQLALCHEMY_ENGINE_OPTIONS for any engine options. Additional binds go in SQLALCHEMY_BINDS β a dict mapping bind keys to engine URLs. π
To specify engine options for a bind, the value can be a dict of engine options with the "url" key instead of only a URL string:
SQLALCHEMY_DATABASE_URI = "postgresql:///main"
SQLALCHEMY_BINDS = {
"meta": "sqlite:////path/to/meta.db",
"auth": {
"url": "mysql://localhost/users",
"pool_recycle": 3600,
},
}
Defining Models and Tables with Binds
Flask-SQLAlchemy will create a metadata and engine for each configured bind. Models and tables with a bind key get registered with the corresponding metadata, and the session will query them using the corresponding engine. Matching, but make it fashion. π
To set the bind for a model, set the __bind_key__ class attribute. Not setting a bind key is equivalent to setting it to None, the default key:
class User(db.Model):
__bind_key__ = "auth"
id = db.Column(db.Integer, primary_key=True)
Models that inherit from this model will share the same bind key, or can override it β their choice, main character energy. β¨
To set the bind for a table, pass the bind_key keyword argument:
user_table = db.Table(
"user",
db.Column("id", db.Integer, primary_key=True),
bind_key="auth",
)
Ultimately, the session looks up the bind key on the metadata associated with the model or table. That association happens during creation. So changing the bind key after creating a model or table will have no effect. Not me warning you twice about this. π
Accessing Metadata and Engines
You may need to inspect the metadata or engine for a bind. Quick note though: you should execute queries through the session, not directly on the engine. π«Ά
The default engine is SQLAlchemy.engine, and the default metadata is SQLAlchemy.metadata. SQLAlchemy.engines and SQLAlchemy.metadatas are dicts mapping all bind keys. Go off, dicts. π
ποΈ Creating and Dropping Tables ποΈ
okay so create_all() and drop_all() β these two operate on all binds by default, no cap. The bind_key argument is your little remote control here: give it a string or None and it does that single bind; give it a list of strings or None and it does that subset of binds. π
β οΈ Heads up bestie: because these methods reach right into the engines, they must be called inside an application context. No context, no table party. π
# create tables for all binds
db.create_all()
# create tables for the default and "auth" binds
db.create_all(bind_key=[None, "auth"])
# create tables for the "meta" bind
db.create_all(bind_key="meta")
# drop tables for the default bind
db.drop_all(bind_key=None)
π Recording Query Information π
π Warning: This feature is intended for debugging only. Read that again. It's a debug toy, not a production accessory.
So Flask-SQLAlchemy can literally record info about every query that runs during a request, and you can pull that info back out later to debug your performance. It's giving detective energy π β like it can expose that a relationship went and did way too many individual selects, or point at a query that took forever and a day.
To turn it on, set SQLALCHEMY_RECORD_QUERIES to True in the Flask app config. Then hit up get_recorded_queries() to get a list of query info objects. Each object is serving these attributes:
statementβ the string of SQL that SQLAlchemy generated, with parameter placeholders. β¨parametersβ the parameters that got sent along with the SQL statement.start_time/end_timeβ the timing tea β about when the query started executing and when the results came back. Accuracy and value depend on the operating system, so like, your mileage may vary.durationβ how long the query took, in seconds.locationβ a string description of where in your app code the query ran. This may be unknown in certain cases. π
π Tracking Modifications π
π Warning: Tracking changes adds significant overhead. In most cases, you'll be better served by using SQLAlchemy events directly. fr fr, just use the events.
So this feature sets up your session to track inserts, updates, and deletes for models, then sends a Blinker signal with a list of those changes β either before or during calls to session.flush() and session.commit(). πΈ
Wanna enable it? Set SQLALCHEMY_TRACK_MODIFICATIONS in the Flask app config. Then add a listener to models_committed (this one's emitted after the commit) or before_models_committed (this one's emitted before the commit). π
from flask_sqlalchemy.track_modifications import models_committed
def get_modifications(sender: Flask, changes: list[tuple[t.Any, str]]) -> None:
...
models_committed.connect(get_modifications)
π¨ Advanced Customization π¨
The various objects the extension manages? All customizable, bestie β you just pass arguments to the SQLAlchemy constructor. It's giving build-your-own-adventure. π¦
ποΈ Model Class ποΈ
SQLAlchemy models all inherit from a declarative base class. Flask-SQLAlchemy exposes that as db.Model, and every model extends it. Want your own? Subclass the default and hand your custom class over to model_class. π
The example below gives every model an integer primary key β or a foreign key for joined-table inheritance.
π Note: Integer primary keys for everything is not necessarily the best database design (that's up to your project's requirements, bestie). This is only an example. Don't come for me.
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr
class Base(DeclarativeBase):
@declared_attr.cascading
@classmethod
def id(cls):
for base in cls.__mro__[1:-1]:
if getattr(base, "__table__", None) is not None:
return mapped_column(ForeignKey(base.id), primary_key=True)
else:
return mapped_column(Integer, primary_key=True)
db = SQLAlchemy(app, model_class=Base)
class User(db.Model):
name: Mapped[str] = mapped_column(String)
class Employee(User):
title: Mapped[str] = mapped_column(String)
𧬠Abstract Models and Mixins π§¬
If the behavior is only needed on some models rather than all of them, use an abstract model base class to customize just those models. Iconic move. For example β what if some models should track when they're created or updated? π
from datetime import datetime
from sqlalchemy import DateTime, Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr
class TimestampModel(db.Model):
__abstract__ = True
created: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
updated: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Author(db.Model):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String, unique=True, nullable=False)
class Post(TimestampModel):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String, nullable=False)
You can also do this with a mixin class, inheriting from db.Model separately. Same vibe, different technique. β¨
class TimestampMixin:
created: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=datetime.utcnow)
updated: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Post(TimestampMixin, db.Model):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
title: Mapped[str] = mapped_column(String, nullable=False)
π« Disabling Table Name Generation π«
Some projects are like "no thank you, I'll set each model's __tablename__ myself" instead of letting Flask-SQLAlchemy detect and generate it. The simple way: set each __tablename__ and just don't touch the base class. But if you truly want the generation off, set disable_autonaming=True in the SQLAlchemy constructor. π
class Base(sa_orm.DeclarativeBase):
pass
db = SQLAlchemy(app, model_class=Base, disable_autonaming=True)
π Session Class π
Flask-SQLAlchemy's Session class picks which engine to query based on the bind key associated with the model or table. Cute, right? But there are other strategies out there β horizontal sharding, for example β and those can be implemented with a different session class. Use the class_ key in the session_options argument to the extension to swap it out. π
Flask-SQLAlchemy will always pass the extension instance as the db argument to the session, so your custom session has to accept that to keep working. Which is actually useful β you can use it to get at db.engines. π«Ά
from sqlalchemy.ext.horizontal_shard import ShardedSession
from flask_sqlalchemy.session import Session
class CustomSession(ShardedSession, Session):
...
db = SQLAlchemy(session_options={"class_": CustomSession})
π Query Class π
π Warning: The query interface is considered legacy in SQLAlchemy. Yes, that includessession.query,Model.query,db.Query, andlazy="dynamic"relationships. Prefer usingsession.execute(select(...))instead. That's the move, bestie.
You can still customize the query interface that the session, models, and relationships use β handy for adding extra query methods. Example: a get_or method that gets a row, or returns a default. π
from flask_sqlalchemy.query import Query
class GetOrQuery(Query):
def get_or(self, ident, default=None):
out = self.get(ident)
if out is None:
return default
return out
db = SQLAlchemy(query_class=GetOrQuery)
user = User.query.get_or(user_id, anonymous_user)
Passing the query_class argument customizes db.Query, db.session.query, Model.query, and db.relationship(lazy="dynamic") relationships. And yep β you can also customize these on a per-object basis. β¨
To customize a specific model's query property, set the query_class attribute on the model class:
class User(db.Model):
query_class = GetOrQuery
To customize a specific dynamic relationship, pass the query_class argument to the relationship:
db.relationship(User, lazy="dynamic", query_class=GetOrQuery)
And to customize only session.query, pass the query_cls key to the session_options argument of the constructor:
db = SQLAlchemy(session_options={"query_cls": GetOrQuery})
π§Ύ API REFERENCE π§Ύ
π API π
π§© Extension π§©
class flask_sqlalchemy.SQLAlchemy(app=None, *, metadata=None,
session_options=None, query_class=<class
'flask_sqlalchemy.query.Query'>, model_class=<class
'flask_sqlalchemy.model.Model'>, engine_options=None,
add_models_to_shell=True, disable_autonaming=False)
Okay so this class integrates SQLAlchemy with Flask β the whole glow-up π. It handles setting up one or more engines, associating tables and models with specific engines, and cleaning up connections and sessions after each request. Main character energy, honestly.
Only the engine configuration is specific to each application. Everything else β the model, table, metadata, and session β is shared for all applications using that extension instance. Call init_app() to configure the extension on an application.
After creating the extension, you create model classes by subclassing Model, and table classes with Table. And plot twist: these can be accessed before init_app() is called, which makes it possible to define your models separately from the application. Obsessed. π
Accessing session and engine requires an active Flask application context β and yep, that includes methods like create_all() which use the engine. No context, no access. π
This class also gives you access to names in SQLAlchemy's sqlalchemy and sqlalchemy.orm modules. Like, you can use db.Column and db.relationship instead of importing sqlalchemy.Column and sqlalchemy.orm.relationship. So convenient when you're defining models. π¦
Parameters
app(Flask | None) β Callinit_app()on this Flask application now.metadata(sa.MetaData | None) β Use this as the defaultsqlalchemy.schema.MetaData. Useful for setting a naming convention. πsession_options(dict[str, t.Any] | None) β the lil' argument bag thatsessionuses to build every session instance π«Ά. Heads up: ascopefunckey gets passed to the scoped session, not to the session instance itself. Wanna see the full menu? Peepsqlalchemy.orm.sessionmakerfor the list of arguments.query_class(type[Query]) β use this as the default query class for models and dynamic relationships π . Real talk though: the query interface is considered legacy in SQLAlchemy. Just so you know.model_class(_FSA_MCT) β use this as the model base class when the declarative model classModelgets created β¨. You can also hand over a fully created declarative model class if you want to customize it further.engine_options(dict[str, t.Any] | None) β default arguments used when creating every engine π₯. These are lower precedence than application config (so config wins, periodt). Seesqlalchemy.create_engine()for the list of arguments.add_models_to_shell(bool) β adds thedbinstance and all model classes to the flask shell π. Convenient? Iconic.disable_autonaming(bool) β that's it, that's the parameter π.
β³ Changed in version β the deep lore π«
- Changed in version 3.1.0: the
metadataparameter can still be used with SQLAlchemy 1.x classes, but it's ignored when you're using the SQLAlchemy 2.x style of declarative classes. Instead, specifymetadataon yourBaseclass. No cap. - Changed in version 3.1.0: added the
disable_autonamingparameter. - Changed in version 3.1.0: changed
model_classto accept a SQLAlchemy 2.x declarative base subclass. - Changed in version 3.0: an active Flask application context is always required to access
sessionandengine. - Changed in version 3.0: separate metadata are used for each bind key.
- Changed in version 3.0: the
engine_optionsparameter is applied as defaults before per-engine configuration. - Changed in version 3.0: the session class can be customized in
session_options. - Changed in version 3.0: added the
add_models_to_shellparameter. - Changed in version 3.0: engines are created when you call
init_app, rather than the first time they're accessed. - Changed in version 3.0: all parameters except
appare keyword-only. - Changed in version 3.0: the extension instance is stored directly as
app.extensions["sqlalchemy"]. - Changed in version 3.0: setup methods are renamed with a leading underscore. They're considered internal interfaces which may change at any time π.
- Changed in version 3.0: removed the
use_native_unicodeparameter and config. - Changed in version 2.4: added the
engine_optionsparameter. - Changed in version 2.1: added the
metadata,query_class, andmodel_classparameters. - Changed in version 2.1: use the same query class across
session,Model.queryandQuery. - Changed in version 0.16:
scopefuncis accepted insession_options. - Changed in version 0.10: added the
session_optionsparameter.
πΈ Model πΈ
A SQLAlchemy declarative model class π. Subclass this bad boy to define your database models.
If a model doesn't set __tablename__, it gets generated for you by converting the class name from CamelCase to snake_case β¨. It will not be generated if the model looks like it uses single-table inheritance (iykyk).
If a model or parent class sets __bind_key__, it'll use that metadata and database engine. Otherwise, it uses the default metadata and engine. This is ignored if the model sets metadata or __table__.
For code using the SQLAlchemy 1.x API, customize this model by subclassing Model and passing the model_class parameter to the extension π«Ά. A fully created declarative model class can be passed as well, to use a custom metaclass.
For code using the SQLAlchemy 2.x API, customize this model by subclassing sqlalchemy.orm.DeclarativeBase or sqlalchemy.orm.DeclarativeBaseNoMeta and passing the model_class parameter to the extension π
.
π Query π
The default query class used by Model.query and lazy="dynamic" relationships.
Warning: the query interface is considered legacy in SQLAlchemy β οΈ.
Customize this by passing the query_class parameter to the extension.
π¦ Table π¦
A sqlalchemy.schema.Table class that chooses a metadata automatically β¨.
Unlike the base Table, the metadata argument is not required. If it's not given, it's selected based on the bind_key argument.
Parameters
bind_keyβ used to select a different metadata.argsβ arguments passed to the base class. These are typically the table's name, columns, and constraints.kwargsβ arguments passed to the base class.
Changed in version 3.0: this is a subclass of SQLAlchemy's Table rather than a function.
π create_all(bind_key='__all__') π
Create tables that don't exist in the database by calling metadata.create_all() for all or some bind keys π. Real talk: this does not update existing tables β use a migration library for that, bestie.
This requires that a Flask application context is active β.
Parameters
bind_key(str | None | list[str | None]) β a bind key or list of keys to create the tables for. Defaults to all binds.
Return type
None
Changed in version 3.0: renamed the bind parameter to bind_key. Removed the app parameter.
Changed in version 0.12: added the bind and app parameters.
π drop_all(bind_key='__all__') π
Drop tables by calling metadata.drop_all() for all or some bind keys π«Ά.
This requires that a Flask application context is active β.
Parameters
bind_key(str | None | list[str | None]) β a bind key or list of keys to drop the tables from. Defaults to all binds.
Return type
None
Changed in version 3.0: renamed the bind parameter to bind_key. Removed the app parameter.
Changed in version 0.12: added the bind and app parameters.
π§ dynamic_loader(argument, **kwargs) π§
A sqlalchemy.orm.dynamic_loader() that applies this extension's Query class for relationships and backrefs π«.
Changed in version 3.0: the Query class is set on backref.
Parameters
argument(Any)kwargs(Any)
Return type
RelationshipProperty[Any]
π₯ property engine: Engine π₯
The default Engine for the current application, used by session if the Model or Table being queried does not set a bind key π.
To customize, set the SQLALCHEMY_ENGINE_OPTIONS config, and set defaults by passing the engine_options parameter to the extension.
This requires that a Flask application context is active β.
engines π«Ά (property)
Type: Mapping[str | None, Engine] β like, a map of bind keys to sqlalchemy.engine.Engine instances for whatever application you're in right now, bestie.
The None key is the default engine, and you can also grab it as engine. iykyk π
Wanna customize it? Set the SQLALCHEMY_BINDS config, and set your defaults by passing the engine_options parameter to the extension. That's the whole vibe.
Heads up: this literally requires that a Flask application context is active. No context, no engines, no nothing π
Added in version 3.0.
first_or_404(statement, *, description=None) π
Basically Result.scalar(), but instead of returning None it aborts with a 404 Not Found error. So dramatic, so correct π
Parameters
statement(Select) β the select statement to execute.description(str | None) β a custom message to show on the error page.
Return type: Any
Added in version 3.0.
get_engine(bind_key=None, **kwargs) π¦
Get the engine for the given bind key for the current application. Same deal as before: this requires that a Flask application context is active, no cap.
Parameters
bind_key(str | None) β the name of the engine.kwargs(Any)
Return type: Engine
π Deprecated since version 3.0: this will be removed in Flask-SQLAlchemy 3.2. Use engines[key] instead, bestie, it's the moment.
Changed in version 3.0: renamed the bind parameter to bind_key, and removed the app parameter.
get_or_404(entity, ident, *, description=None, **kwargs) β¨
Like session.get() but aborts with a 404 Not Found error instead of returning None. Same energy as first_or_404, different plumbing π§
Parameters
entity(type[_O]) β the model class to query.ident(Any) β the primary key to query.description(str | None) β a custom message to show on the error page.kwargs(Any) β extra arguments passed tosession.get().
Return type: _O
Changed in version 3.1: extra keyword arguments get passed to session.get(). Added in version 3.0.
init_app(app) π
Initialize a Flask application for use with this extension instance. You must call this before accessing the database engine or session with the app β it's giving prerequisites, honestly.
What it does: sets default configuration values, then configures the extension on the application and creates the engines for each bind key. Which means you have to call it after the application has been configured. Changes to application config after this call will not be reflected. Not a single one. π
The following keys from app.config are used:
SQLALCHEMY_DATABASE_URISQLALCHEMY_ENGINE_OPTIONSSQLALCHEMY_ECHOSQLALCHEMY_BINDSSQLALCHEMY_RECORD_QUERIESSQLALCHEMY_TRACK_MODIFICATIONS
Parameters
app(Flask) β the Flask application to initialize.
Return type: None
metadata π (property)
Type: MetaData
The default metadata used by Model and Table if no bind key is set. The main character metadata, basically π
metadatas πΈ
Type: dict[str | None, MetaData]
Map of bind keys to sqlalchemy.schema.MetaData instances. The None key refers to the default metadata, and is available as metadata β yes, same one from up there, they're literally the same thing π
Customize the default metadata by passing the metadata parameter to the extension. This can be used to set a naming convention (so organized, so slay). When metadata for another bind key is created, it copies the default's naming convention. Copying the vibe fr fr.
Added in version 3.0.
one_or_404(statement, *, description=None) β
Like Result.scalar_one(), but aborts with a 404 Not Found error instead of raising NoResultFound or MultipleResultsFound. We do not do exceptions here, we do 404s π₯
Parameters
statement(Select) β the select statement to execute.description(str | None) β a custom message to show on the error page.
Return type: Any
Added in version 3.0.
paginate(select, *, page=None, per_page=None, max_per_page=None, error_out=True, count=True) π¦
Apply an offset and limit to a select statement based on the current page and number of items per page, returning a Pagination object. It's the "give me page 3 of the search results" of the SQLAlchemy world π
The statement should select a model class, like select(User). This applies unique() and scalars() modifiers to the result, so compound selects will not return the expected results β that's a real caveat, not a vibe check, don't say I didn't warn you π
Parameters
select(Select) β the select statement to paginate.page(int | None) β the current page, used to calculate the offset. Defaults to thepagequery arg during a request, or1otherwise.per_page(int | None) β the maximum number of items on a page, used to calculate the offset and limit. Defaults to theper_pagequery arg during a request, or20otherwise.max_per_page(int | None) β the maximum allowed value forper_page, to limit a user-provided value. UseNonefor no limit. Defaults to100.error_out(bool) β abort with a 404 Not Found error if no items are returned andpageis not1, or ifpageorper_pageis less than1, or if either are not ints.count(bool) β calculate the total number of values by issuing an extra count query. For very complex queries this may be inaccurate or slow, so it can be disabled and set manually if necessary.
Return type: Pagination
Changed in version 3.0: the count query is more efficient. Added in version 3.0.
reflect(bind_key='__all__') π§
Load table definitions from the database by calling metadata.reflect() for all or some bind keys. Very "let the database tell me about itself" of it β¨
Requires that a Flask application context is active. Again. It's always the application context π«Ά
okay so continuing right where we left off β here come the params for that one π«Ά
Parameters
bind_key(str|None|list[str | None]) β a bind key, or a whole list of keys, telling it which tables to reflect. leave it out and it defaults to all binds. yes bestie, all of them π- Return type:
None
Changed in version 3.0: renamed the bind parameter to bind_key, and removed the app parameter entirely. she's gone π
Changed in version 0.12: added the bind and app parameters.
relationship(*args, **kwargs)
It's a sqlalchemy.orm.relationship() but it slaps this extension's Query class on dynamic relationships and backrefs. so your relationships get the good query class, no cap β¨
Changed in version 3.0: the Query class is now set on backref too.
Parameters
args(Any)kwargs(Any)
Return type: RelationshipProperty[Any]
session
A sqlalchemy.orm.scoping.scoped_session that creates Session instances scoped to the current Flask application context. when the app context exits, the session gets removed and the engine connection goes right back to the pool. so clean, so polite π§
Customize it by passing session_options to the extension.
this one requires an active Flask application context. no context = no session, bestie, that's just how it is π
Changed in version 3.0: the session is scoped to the current app context now.
πΈ Model πΈ
class flask_sqlalchemy.model.Model
The base class of the SQLAlchemy.Model declarative model class.
To define models, subclass db.Model β not this. To customize db.Model, you subclass this and pass it as model_class to SQLAlchemy. And if you wanna customize db.Model at the metaclass level, pass an already created declarative model class as model_class. ikyk what you're doing at that point π
__bind_key__β the bind key that selects which metadata and engine get associated with this model's table. it gets ignored ifmetadataor__table__is set. and if you don't give one, it uses the default key,None.__tablename__β the name of the table in the database. SQLAlchemy requires this, but Flask-SQLAlchemy will set it automatically if a model has a primary key defined. if__table__or__tablename__is set explicitly, then that's what gets used instead. she respects your choices πquery:t.ClassVar[Query]β a SQLAlchemy query for a model. equivalent todb.session.query(Model). you can customize it per-model by overridingquery_class.
Warning: the query interface is considered legacy in SQLAlchemy. prefer using session.execute(select()) instead, bestie, that's the vibe now π
query_classβ the Query class used byquery. defaults toSQLAlchemy.Query, which defaults toQuery.
alias of Query
π¦ Metaclass mixins (SQLAlchemy 1.x) π¦
If your code uses the SQLAlchemy 1.x API (which is the default for code that doesn't specify a model_class), then these mixins get automatically applied to the Model class. automatic. iconic. β
class flask_sqlalchemy.model.DefaultMeta(name, bases, d, **kwargs)
SQLAlchemy declarative metaclass that provides __bind_key__ and __tablename__ support.
Parameters
name(str)bases(tuple[type, ...])d(dict[str, t.Any])kwargs(t.Any)
class flask_sqlalchemy.model.BindMetaMixin(name, bases, d, **kwargs)
Metaclass mixin that sets a model's metadata based on its __bind_key__.
If the model sets metadata or __table__ directly, __bind_key__ gets ignored. and if the metadata is the same as the parent model's, it won't be set directly on the child model. sharing is caring, but only sometimes π
Parameters
name(str)bases(tuple[type, ...])d(dict[str, t.Any])kwargs(t.Any)
class flask_sqlalchemy.model.NameMetaMixin(name, bases, d, **kwargs)
Metaclass mixin that sets a model's __tablename__ by converting the CamelCase class name into snake_case. a name gets set for non-abstract models that don't otherwise define __tablename__. but if a model doesn't define a primary key, it won't generate a name or __table__ β that's for single-table inheritance, so don't come for her π
Parameters
name(str)bases(tuple[type, ...])d(dict[str, t.Any])kwargs(t.Any)
π Session π
class flask_sqlalchemy.session.Session(db, **kwargs)
A SQLAlchemy Session class that picks which engine to use based on the bind key associated with the metadata associated with the thing you're querying. say that five times fast π
To customize db.session, subclass this and pass it as the class_ key in the session_options to SQLAlchemy.
Changed in version 3.0: renamed from SignallingSession. new name, same energy.
Parameters
db(SQLAlchemy)kwargs(t.Any)
get_bind(mapper=None, clause=None, bind=None, **kwargs)
Selects an engine based on the bind_key of the metadata associated with the model or table being queried. if no bind key is set, it uses the default bind. easy πΈ
Changed in version 3.0.3: fixed finding the bind for a joined inheritance model.
Changed in version 3.0: the implementation now more closely matches the base SQLAlchemy implementation.
Changed in version 2.1: supports joining an external transaction.
Parameters
mapper(Any|None)clause(Any|None)bind(Engine|Connection|None)kwargs(Any)
Return type: Engine | Connection
π Pagination π
class flask_sqlalchemy.pagination.Pagination
A slice of the total items in a query, obtained by applying an offset and limit based on the current page and the number of items per page. literally just a page of your stuff, bestie π
don't create pagination objects manually! they're created bySQLAlchemy.paginate()andQuery.paginate(). hands off β
Changed in version 3.0: iterating over a pagination object iterates over its items.
Changed in version 3.0: creating instances manually is not a public API.
page:intβ the current page.per_page:intβ the maximum number of items on a page.items:list[Any]β the items on the current page. iterating over the pagination object is equivalent to iterating over theitems. same thing, different font πtotal:int|Noneβ the total number of items across all pages.- property
first:intβ the number of the first item on the page, starting from 1, or0if there are no items.
Added in version 3.0.
- property
last:intβ the number of the last item on the page, starting from 1, inclusive, or0if there are no items.
Added in version 3.0.
pages(int) β the total number of pages, bestie. that's it. that's the number. πhas_prev(bool) βTrueif this is not the first page. basically "is there a before?" π¦prev_num(int|None) β the previous page number, orNoneif this is the first page. no before, no number, periodt.prev(*, error_out=False)β go grab the previous page from thePaginationobject. that's the whole vibe.
Parameters:
error_out(bool) β Abort with a 404 Not Found error if no items are returned and page is not 1, or if page or per_page is less than 1, or if either are not ints.
Return type: Pagination
has_next(bool) βTrueif this is not the last page. the "is there more??" check β¨next_num(int|None) β the next page number, orNoneif this is the last page.next(*, error_out=False)β same energy asprevbut forwards. Query thePaginationobject for the next page.
Parameters:
error_out(bool) β Abort with a 404 Not Found error if no items are returned and page is not 1, or if page or per_page is less than 1, or if either are not ints.
Return type: Pagination
iter_pages(*, left_edge=2, left_current=2, right_current=4, right_edge=2)β Yield page numbers for a pagination widget. Skipped pages between the edges and middle are represented by aNone. (yes literally aNonein the stream, that's the whole trick, don't panic π)
For example, if there are 20 pages and the current page is 7, the following values are yielded.
1, 2, None, 5, 6, 7, 8, 9, 10, 11, None, 19, 20
Parameters:
left_edge(int) β How many pages to show from the first page.left_current(int) β How many pages to show left of the current page.right_current(int) β How many pages to show right of the current page.right_edge(int) β How many pages to show from the last page.
Return type: Iterator[int | None]
Changed in version 3.0: Improved efficiency of calculating what to yield.
Changed in version 3.0: right_current boundary is inclusive.
Changed in version 3.0: All parameters are keyword-only.
π¦ Query π¦
class flask_sqlalchemy.query.Query(entities, session=None)
okay so this is the SQLAlchemy Query subclass with some extra methods that are lowkey so useful for querying in a web application. it's giving "everything you already know plus the good stuff." π
This is the default query class for Model.query.
Changed in version 3.0: Renamed to Query from BaseQuery.
Parameters:
entities(Union[_ColumnsClauseArgument[Any], Sequence[_ColumnsClauseArgument[Any]]])session(Optional[Session])
β¨ the methods β¨
first_or_404(description=None)β Likefirst()but aborts with a 404 Not Found error instead of returningNone. so you get a cute lil error page instead of a silentNonehaunting your template π iconic behavior honestly.
Parameters:
description(str|None) β A custom message to show on the error page.
Return type: Any
get_or_404(ident, description=None)β Likeget()but aborts with a 404 Not Found error instead of returningNone. same slay, different lookup.
Parameters:
ident(Any) β The primary key to query.description(str|None) β A custom message to show on the error page.
Return type: Any
one_or_404(description=None)β Likeone()but aborts with a 404 Not Found error instead of raisingNoResultFoundorMultipleResultsFound. no cap, this one is the move β no exceptions sneaking out, just a clean 404. π«Ά
Parameters:
description(str|None) β A custom message to show on the error page.
Return type: Any
Added in version 3.0.
paginate(*, page=None, per_page=None, max_per_page=None, error_out=True, count=True)β Apply an offset and limit to the query based on the current page and number of items per page, returning aPaginationobject. the main character of pagination fr fr π
Parameters:
page(int|None) β The current page, used to calculate the offset. Defaults to thepagequery arg during a request, or1otherwise.per_page(int|None) β The maximum number of items on a page, used to calculate the offset and limit. Defaults to theper_pagequery arg during a request, or20otherwise.max_per_page(int|None) β The maximum allowed value forper_page, to limit a user-provided value. UseNonefor no limit. Defaults to100.error_out(bool) β Abort with a 404 Not Found error if no items are returned and page is not 1, or if page or per_page is less than 1, or if either are not ints.count(bool) β Calculate the total number of values by issuing an extra count query. For very complex queries this may be inaccurate or slow, so it can be disabled and set manually if necessary. (heads up bestie, "inaccurate or slow" is not a joke, that's a real caveat π)
Return type: Pagination
Changed in version 3.0: All parameters are keyword-only.
Changed in version 3.0: The count query is more efficient.
Changed in version 3.0: max_per_page defaults to 100.
πΈ Record Queries πΈ
flask_sqlalchemy.record_queries.get_recorded_queries()
Get the list of recorded query information for the current session. Queries are recorded if the config SQLALCHEMY_RECORD_QUERIES is enabled. it's giving "receipts for every query you ran" π§Ύβ¨
Each query info object has the following attributes:
statementβ The string of SQL generated by SQLAlchemy with parameter placeholders.parametersβ The parameters sent with the SQL statement.start_time/end_timeβ Timing info about when the query started execution and when the results where returned. Accuracy and value depends on the operating system. (yes, where β that's the original's typo, we keep the facts π )durationβ The time the query took in seconds.locationβ A string description of where in your application code the query was executed. This may not be possible to calculate, and the format is not stable. (don't build your whole app on parsing this string, i'm begging π)
Changed in version 3.0: Renamed from get_debug_queries.
Changed in version 3.0: The info object is a dataclass instead of a tuple.
Changed in version 3.0: The info object attribute context is renamed to location.
Changed in version 3.0: Not enabled automatically in debug or testing mode.
Return type: list[_QueryInfo]
π Track Modifications π
flask_sqlalchemy.track_modifications.models_committed
This Blinker signal is sent after the session is committed if there were changed models in the session. so likeβ¦ the "okay we actually saved stuff" ping π₯ only fires when something actually changed, no fake-outs.
The sender is the application that emitted the changes π, and the receiver gets passed the changes argument β a list of tuples shaped exactly like (instance, operation). The operations are "insert", "update", and "delete". Periodt, that's the whole vocabulary π.
flask_sqlalchemy.track_modifications.before_models_committedβ literally the same vibe asmodels_committed, except it fires before the commit takes place. Same signal, earlier timestamp π β¨.
π¦ ADDITIONAL INFORMATION π¦
πΈ BSD-3-Clause License πΈ
Copyright 2010 Pallets. Bestie, this is the boring-but-load-bearing part, so here's the whole thing verbatim β no edits, no vibes, just facts π:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
π Changes π
π Version 3.1.1 π
Released 2023-09-11
- Deprecate the
__version__attribute. Use feature detection, orimportlib.metadata.version("flask-sqlalchemy"), instead.#5230https://github.com/pallets-eco/flask-sqlalchemy/issues/5230
β Version 3.1.0 β
Released 2023-09-11
- Drop support for Python 3.7.
#1251https://github.com/pallets-eco/flask-sqlalchemy/pull/1251 - Add support for the SQLAlchemy 2.x API via
model_classparameter.#1140https://github.com/pallets-eco/flask-sqlalchemy/issues/1140 - Bump minimum version of SQLAlchemy to 2.0.16.
- Remove previously deprecated code.
- Pass extra keyword arguments from
get_or_404tosession.get.#1149https://github.com/pallets-eco/flask-sqlalchemy/issues/1149 - Fix bug with finding right bind key for clause statements.
#1211https://github.com/pallets-eco/flask-sqlalchemy/issues/1211
π Version 3.0.5 π
Released 2023-06-21
Pagination.next()enforcesmax_per_page.#1201https://github.com/pallets-eco/flask-sqlalchemy/issues/1201- Improve type hint for
get_or_404return value to be non-optional.#1226https://github.com/pallets-eco/flask-sqlalchemy/pull/1226
π Version 3.0.4 π
Released 2023-06-19
- Fix type hint for
get_or_404return value.#1208https://github.com/pallets-eco/flask-sqlalchemy/pull/1208 - Fix type hints for pyright (used by VS Code Pylance extension).
#1205https://github.com/pallets-eco/flask-sqlalchemy/issues/1205
π§ Version 3.0.3 π§
Released 2023-01-31
- Show helpful errors when mistakenly using multiple SQLAlchemy instances for the same app, or without calling
init_app.#1151https://github.com/pallets-eco/flask-sqlalchemy/pull/1151 - Fix issue with getting the engine associated with a model that uses polymorphic table inheritance.
#1155https://github.com/pallets-eco/flask-sqlalchemy/issues/1155
π Version 3.0.2 π
Released 2022-10-14
- Update compatibility with SQLAlchemy 2.
#1122https://github.com/pallets-eco/flask-sqlalchemy/issues/1122
π Version 3.0.1 π
Released 2022-10-11
- Export typing information instead of using external typeshed definitions.
#1112https://github.com/pallets-eco/flask-sqlalchemy/issues/1112 - If default engine options are set, but
SQLALCHEMY_DATABASE_URIis not set, an invalid default bind will not be configured.#1117https://github.com/pallets-eco/flask-sqlalchemy/issues/1117
π Version 3.0.0 π
Released 2022-10-04
This one's the main character release, ngl β huge glow-up, tons of breaking changes, read carefully π
- Drop support for Python 2, 3.4, 3.5, and 3.6.
- Bump minimum version of Flask to 2.2.
- Bump minimum version of SQLAlchemy to 1.4.18.
- Remove previously deprecated code.
- The session is scoped to the current app context instead of the thread. This requires that an app context is active. This ensures that the session is cleaned up after every request.
- An active Flask application context is always required to access
sessionandengine, regardless of if an application was passed to the constructor.#508https://github.com/pallets-eco/flask-sqlalchemy/issues/508#944https://github.com/pallets-eco/flask-sqlalchemy/issues/944 - Different bind keys use different SQLAlchemy
MetaDataregistries, allowing tables in different databases to have the same name. Bind keys are stored and looked up on the resulting metadata rather than the model or table. SQLALCHEMY_DATABASE_URIdoes not default tosqlite:///:memory:. An error is raised if neither it norSQLALCHEMY_BINDSdefine any engines.#731https://github.com/pallets-eco/flask-sqlalchemy/pull/731- Configuring SQLite with a relative path is relative to
app.instance_pathinstead ofapp.root_path. The instance folder is created if necessary.#462https://github.com/pallets-eco/flask-sqlalchemy/issues/462 - Added
get_or_404,first_or_404,one_or_404, andpaginatemethods to the extension object. These use SQLAlchemy's preferredsession.execute(select())pattern instead of the legacyqueryinterface.#1088https://github.com/pallets-eco/flask-sqlalchemy/issues/1088 - Setup methods that create the engines and session are renamed with a leading underscore. They are considered internal interfaces which may change at any time.
- All parameters to
SQLAlchemyexceptappare keyword-only. - Renamed the
bindparameter tobind_keyand removed theappparameter from various SQLAlchemy methods. - The extension object uses
__getattr__to alias names from the SQLAlchemy package, rather than copying them as attributes. - The extension object is stored directly as
app.extensions["sqlalchemy"].#698https://github.com/pallets-eco/flask-sqlalchemy/issues/698 - The session class can be customized by passing the
class_key in thesession_optionsparameter.#327https://github.com/pallets-eco/flask-sqlalchemy/issues/327 SignallingSessionis renamed toSession.Session.get_bindmore closely matches the base implementation.- Model classes and the
dbinstance are available without imports inflask shell.#1089https://github.com/pallets-eco/flask-sqlalchemy/issues/1089 - The CamelCase to snake_case table name converter handles more patterns correctly. If model that was already created in the database changed, either use Alembic to rename the table, or set
__tablename__to keep the old name.#406https://github.com/pallets-eco/flask-sqlalchemy/issues/406 - Model
reprdistinguishes between transient and pending instances.#967https://github.com/pallets-eco/flask-sqlalchemy/issues/967 - A custom model class can implement
__init_subclass__with class parameters.#1002https://github.com/pallets-eco/flask-sqlalchemy/issues/1002 db.Tableis a subclass instead of a function.- The
engine_optionsparameter is applied as defaults before per-engine configuration. SQLALCHEMY_BINDSvalues can either be an engine URL, or a dict of engine options including URL, for each bind.SQLALCHEMY_DATABASE_URIandSQLALCHEMY_ENGINE_OPTIONScorrespond to theNonekey and take precedence.#783https://github.com/pallets-eco/flask-sqlalchemy/issues/783- Engines are created when calling
init_apprather than the first time they are accessed.#698https://github.com/pallets-eco/flask-sqlalchemy/issues/698 db.enginesexposes the map of bind keys to engines for the current app.get_engine,get_tables_for_bind, andget_bindsare deprecated.- SQLite driver-level URIs that look like
sqlite:///file:name.db?uri=trueare supported.#998https://github.com/pallets-eco/flask-sqlalchemy/issues/998#1045https://github.com/pallets-eco/flask-sqlalchemy/issues/1045 - SQLite engines do not use
NullPoolifpool_sizeis0. - MySQL engines use the
"utf8mb4"charset by default.#875https://github.com/pallets-eco/flask-sqlalchemy/issues/875 - MySQL engines do not set
pool_sizeto10. - MySQL engines don't set a default for
pool_recycleif not using a queue pool.#803https://github.com/pallets-eco/flask-sqlalchemy/issues/803 Queryis renamed fromBaseQuery.- Added
Query.one_or_404. - The query class is applied to
backrefinrelationship.#417https://github.com/pallets-eco/flask-sqlalchemy/issues/417 - Creating
Paginationobjects manually is no longer a public API. They should be created withdb.paginateorquery.paginate.#1088https://github.com/pallets-eco/flask-sqlalchemy/issues/1088 Pagination.iter_pagesandQuery.paginateparameters are keyword-only.Paginationis iterable, iterating over its items.#70https://github.com/pallets-eco/flask-sqlalchemy/issues/70Paginationcount query is more efficient.Pagination.iter_pagesis more efficient.#622https://github.com/pallets-eco/flask-sqlalchemy/issues/622Pagination.iter_pagesright_currentparameter is inclusive.Paginationper_pagecannot be0.#1091https://github.com/pallets-eco/flask-sqlalchemy/issues/1091Paginationmax_per_pagedefaults to100.#1091https://github.com/pallets-eco/flask-sqlalchemy/issues/1091- Added
Pagination.firstandlastproperties, which give the number of the first and last item on the page.#567https://github.com/pallets-eco/flask-sqlalchemy/issues/567 SQLALCHEMY_RECORD_QUERIESis disabled by default, and is not enabled automatically withapp.debugorapp.testing.#1092https://github.com/pallets-eco/flask-sqlalchemy/issues/1092get_debug_queriesis renamed toget_recorded_queriesto better match the config and functionality.- Recorded query info is a
dataclassinstead of a tuple. Thecontextattribute got renamed tolocation. And finding the location now uses a way more inclusive check. πSQLALCHEMY_TRACK_MODIFICATIONSis disabled by default now.#727βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/727SQLALCHEMY_COMMIT_ON_TEARDOWNis deprecated (rip to a real one π). It can cause various design issues that are difficult to debug. Just calldb.session.commit()directly instead, ok bestie?#216βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/216
Version 2.5.1
Released 2021-03-18
- Fix compatibility with Python 2.7. π
Version 2.5.0
Released 2021-03-18
- Update to support SQLAlchemy 1.4. β¨
- SQLAlchemy URL objects are immutable. Some internal methods have changed to return a new URL instead of
None.#885βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/885
Version 2.4.4
Released 2020-07-14
- Change base class of meta mixins to
type. This fixes an issue caused by a regression in CPython 3.8.4.#852βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/852
Version 2.4.3
Released 2020-05-26
- Deprecate
SQLALCHEMY_COMMIT_ON_TEARDOWNas it can cause various design issues that are difficult to debug. Calldb.session.commit()directly instead.#216βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/216
Version 2.4.2
Released 2020-05-25
- Fix bad pagination when records are de-duped.
#812βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/812
Version 2.4.1
Released 2019-09-24
- Fix
AttributeErrorwhen using multiple binds with polymorphic models.#651βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/651
Version 2.4.0
Released 2019-04-24
- Drop support for Python 2.6 and 3.3.
#687βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/687 - Address SQLAlchemy 1.3 deprecations.
#684βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/684 - Make engine configuration more flexible. Added the
engine_optionsparameter andSQLALCHEMY_ENGINE_OPTIONSconfig. Deprecated the individual engine option config keysSQLALCHEMY_NATIVE_UNICODE,SQLALCHEMY_POOL_SIZE,SQLALCHEMY_POOL_TIMEOUT,SQLALCHEMY_POOL_RECYCLE, andSQLALCHEMY_MAX_OVERFLOW.#684βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/684 get_or_404()andfirst_or_404()now accept adescriptionparameter to control the 404 message.#636βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/636- Use
time.perf_counterfor Python 3 on Windows.#638βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/638 - Add an example of Flask's tutorial project, Flaskr, adapted for Flask-SQLAlchemy.
#720βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/720
Version 2.3.2
Released 2017-10-11
- Don't mask the parent table for single-table inheritance models.
#561βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/561
Version 2.3.1
Released 2017-10-05
- If a model has a table name that matches an existing table in the
metadata, use that table. Fixes a regression where reflected tables were not picked up by models.#551βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/551 - Raise the correct error when a model has a table name but no primary key.
#556βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/556 - Fix
repron models that don't have an identity because they have not been flushed yet.#555βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/555 - Allow specifying a
max_per_pagelimit for pagination, to avoid users specifying high values in the request args.#542βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/542 - For
paginatewitherror_out=False, the minimum value forpageis1andper_pageis0.#558βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/558
Version 2.3.0
Released 2017-09-28
- Multiple bugs with
__tablename__generation are fixed. Names will be generated for models that define a primary key, but not for single-table inheritance subclasses. Names will not override adeclared_attr.PrimaryKeyConstraintis detected.#541βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/541 - Passing an existing
declarative_base()asmodel_classtoSQLAlchemy.__init__will use this as the base class instead of creating one. This allows customizing the metaclass used to construct the base.#546βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/546 - The undocumented
DeclarativeMetainternals that the extension uses for binds and table name generation have been refactored to work as mixins. Documentation is added about how to create a custom metaclass that does not do table name generation.#546βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/546 Modeland metaclass code has been moved to a newmodelsmodule._BoundDeclarativeMetais renamed toDefaultMeta; the old name will be removed in 3.0.#546βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/546- Models have a default
reprthat shows the model name and primary key.#530βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/530 - Fixed a bug where using
init_appwould cause connectors to always use thecurrent_apprather than the app they were created for. This caused issues when multiple apps were registered with the extension.#547βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/547
Version 2.2
Released 2017-02-27, codename Dubnium
- Minimum SQLAlchemy version is 0.8 due to use of
sqlalchemy.inspect. π - Added support for custom
query_classandmodel_classas args to theSQLAlchemyconstructor.#328βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/328 - Allow listening to SQLAlchemy events on
db.session.#364βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/364 - Allow
__bind_key__on abstract models.#373βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/373 - Allow
SQLALCHEMY_ECHOto be a string.#409βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/409 - Warn when
SQLALCHEMY_DATABASE_URIis not set.#443βhttps://github.com/pallets-eco/flask-sqlalchemy/pull/443 - Don't let pagination generate invalid page numbers.
#460βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/460 - Drop support of Flask < 0.10. This means the db session is always tied to the app context and its teardown event.
#461βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/461 - Tablename generation logic no longer accesses class properties unless they are
declared_attr.#467βhttps://github.com/pallets-eco/flask-sqlalchemy/issues/467
Version 2.1
Released 2015-10-23, codename Caesium
- Table names are automatically generated in more cases, including subclassing mixins and abstract models. π¦
- Allow using a custom
MetaDataobject. - Add support for
bindsparameter to session.
Version 2.0
Released 2014-08-29, codename Bohrium
- Changed how the builtin signals are subscribed to skip non-Flask-SQLAlchemy sessions. This will also fix the
AttributeErrorabout model changes not existing. β - Added a way to control how signals for model modifications are tracked.
- Made the
SignallingSessiona public interface and added a hook for customizing session creation. - If the
bindparameter is given to the signalling session it will no longer cause an error that a parameter is given twice. - Added working table reflection support.
- Enabled
autoflushby default. - Consider
SQLALCHEMY_COMMIT_ON_TEARDOWNharmful and remove from docs.
Version 1.0
Released 2013-07-20, codename Aurum
- Added Python 3.3 support. π
- Dropped Python 2.5 compatibility.
- Various bugfixes.
- Changed versioning format to do major releases for each update now.
Version 0.16
- New distribution format (
flask_sqlalchemy). - Added support for Flask 0.9 specifics.
Version 0.15
- Added session support for multiple databases.
Version 0.14
- Make relative sqlite paths relative to the application root.
Version 0.13
- Fixed an issue with Flask-SQLAlchemy not selecting the correct binds.
Version 0.12
- Added support for multiple databases.
- Expose
BaseQueryasdb.Query. - Set default
query_classfordb.relation,db.relationship, anddb.dynamic_loadertoBaseQuery. - Improved compatibility with Flask 0.7.
Version 0.11
- Fixed a bug introduced in 0.10 with alternative table constructors.
Version 0.10
- Added support for signals.
- Table names are now automatically set from the class name unless overridden.
Model.querynow always works for applications directly passed to theSQLAlchemyconstructor. Furthermore the property now raises aRuntimeErrorinstead of beingNone.- Added session options to constructor.
- Fixed a broken
__repr__. db.Tableis now a factory function that creates table objects. This makes it possible to omit themetadata. β¨
Version 0.9
- Applied changes to pass the Flask extension approval process.
Version 0.8
- Added a few configuration keys for creating connections.
- Automatically activate connection recycling for MySQL connections.
- Added support for the Flask testing mode.
Version 0.7
- Initial public release. π literally where it all began, baby!!
π Author
Pallets
π Copyright
2010 Pallets
and that's the whole page, bestie β you ate that entire read π
this was FLASK-SQLALCHEMY(1), living at version 3.1.x and dated Dec 15, 2025. Now go commit your session properly, mwah π«Άπ
π¦ where this page comes from
- arch rolling: python-flask-sqlalchemy 3.1.1-6 Β· BSD-3-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms