Flask-SQLAlchemy Basics

Before We Begin
It is best to first read SQLAlchemy Basic and then come back to this section.
Flask-SQLAlchemy Basic
for flask-sqlalchemy 3.x and sqlalchemy 2.x
Initialize
1 2 3 4 5 6 7 8 | |
Detailed Explanation of the Initialization Code
flask VS Flask
flask (lowercase) is the name of the Python package. This is the standard naming style for a module/package.
Flask (capitalized) is the class name. In Python, class names typically start with an uppercase letter (PascalCase naming convention).
This is a common Python naming convention, not something confusing: - Package/module names use lowercase (for example: flask, os, sys) - Class names start with uppercase letters (for example: Flask, String, Dict)
SQLAlchemy in flask_sqlalchemy
The SQLAlchemy class is an integration class. Its main purpose is:
To act as a bridge between Flask and SQLAlchemy, providing a unified interface for managing:
database connections, session management, model definitions, and query construction
db = SQLAlchemy(model_class=Base)
Native SQLAlchemy Usage
In native SQLAlchemy, we define models like this:
1 2 3 4 5 6 7 8 9 10 | |
Flask-SQLAlchemy Usage
In Flask-SQLAlchemy:
1 2 3 4 5 6 7 8 9 10 11 | |
Key Points Explained
What does db = SQLAlchemy(model_class=Base) do?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Why use db.Model?
db.Modelis a base class enhanced by Flask-SQLAlchemy- It inherits from the original
Baseclass, but adds extra functionality: - Automatically generates table names (no need for
__tablename__) - Integrates Flask's context management
- Provides a more convenient query interface
- Adds session management features
The relationship between Flask-SQLAlchemy and SQLAlchemy
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Define and Create Table
Define TABLE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
Because of how SQLAlchemy and Flask-SQLAlchemy work:
- When your model class inherits from
db.Model, that model is automatically registered in SQLAlchemy's metadata. - When you call
db.create_all(), SQLAlchemy checks all registered model classes and creates their tables.
However, there is one important prerequisite here: your model class must be imported and executed by the Python interpreter before db.create_all() is called.
Create Table
1 2 | |
This code will:
- Create tables that do not exist
- Not modify tables that already exist
- Not delete tables that already exist
- Not overwrite existing data
db.create_all()
Let's look at how create_all() works:
1 2 3 4 5 6 7 8 | |
Common scenario example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
note
db.create_all()will automatically create the database file based onapp.config['SQLALCHEMY_DATABASE_URI']
How should schema updates be handled?
Delete and recreate (development environment):
1 2 3 | |
Use database migrations (recommended, production environment):
1 2 3 4 5 6 7 8 9 10 11 12 | |
app.app_context()
Application instance and its context
First, we need to understand that every Flask object represents an independent application instance. When you create a Flask application, you are actually creating an instance of that application:
1 | |
This app object is a specific application instance. And app.app_context() creates an application context, which is an independent execution environment where operations are associated with a specific application instance.
Flask uses _app_ctx_stack to track the currently active application context. When you use with app.app_context()::
The context of that application is pushed onto _app_ctx_stack.
Inside the with block, the current_app proxy points to the application context at the top of the stack. When the with block exits, the context is popped.
1 2 3 4 | |
In multi-application scenarios, each application has its own context. For example:
1 2 3 4 5 6 7 8 9 10 | |
Let's look at a complete example to understand this process:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
note:
In CLI commands: if you use the
@app.cli.command()decorator, Flask will also provide the context automatically.In tests, you can use
app.test_request_context()to simulate a request context.
Why does db.create_all() need an application context?
There are several reasons why db.create_all() needs an application context:
-
Database configuration: Database connection information is usually stored in the application configuration (such as
app.config['SQLALCHEMY_DATABASE_URI']). -
Multi-application support: If your project has multiple Flask applications and each uses a different database, the context ensures that the correct configuration is used.
-
Lazy initialization: Flask-SQLAlchemy uses lazy initialization, and some settings can only be completed within an application context.
Automatic handling in view functions
When handling web requests, Flask automatically creates and manages an application context for each request, so there is no need to explicitly use with app.app_context() inside view functions.
1 2 3 4 | |
CUDR
Flask-SQLAlchemy CUDR is very similar to SQLAlchemy ORM CUDR.
Insert
Instantiate the table class -> add -> commit
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Query
The query statement stmt needs to be executed inside db.session.execute(). You only need to add db. before select, because db.select is essentially sqlalchemy.select.
For example, a complex query might look like this:
1 2 3 4 5 6 | |
Exploring the essence of db.select
From the source code of flask-sqlalchemy extension.py, we can see that the SQLAlchemy class does not directly define a select method. Instead, it handles undefined attribute access through the __getattr__ magic method. This mechanism is quite elegant. Let's see how it works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
The workflow of this code is:
- When we access
db.select, Python first checks whether theSQLAlchemyclass has this attribute - If it does not find it, it calls the
__getattr__method - Inside
__getattr__, it looks for the attribute in thesa(SQLAlchemy) andsa_orm(SQLAlchemy ORM) modules in sequence - Since the
selectfunction exists in thesqlalchemymodule,hasattr(sa, 'select')returns True - It then returns the original SQLAlchemy
selectfunction throughgetattr(sa, 'select')
This explains why:
db.selectis actuallysqlalchemy.select- You cannot find a direct definition of
selectin the source code type(db.select())displays<class 'sqlalchemy.sql.selectable.Select'>, which is SQLAlchemy'sSelectobject
This design pattern is called the Proxy Pattern. In this way, Flask-SQLAlchemy proxies most of SQLAlchemy's functionality onto the db object, allowing us to access SQLAlchemy features directly through db.
sqlalchemy.select VS db.select
Since we now know that db.select is actually sqlalchemy.select, would directly using sqlalchemy.select be faster because it avoids loading through __getattr__()?
In practice, the execution speed of the two approaches is exactly the same. There is no performance difference at all.
1 2 3 4 5 6 | |
Although db.select needs to obtain the function through the __getattr__ magic method, this process:
- Only happens the first time
db.selectis accessed - Later accesses directly use the already cached attribute
- This extra overhead is negligible compared with the actual query execution time
But db.select is still recommended because:
-
All database-related operations go through the
dbobject. If Flask-SQLAlchemy ever needs to extend or modifyselectin the future, your code does not need to change -
From a software engineering perspective, using
db.selectis better practice. This follows the principles of dependency injection and separation of concerns, and it also makes the code easier to maintain and test.
Delete & Update
First, use a query statement to select a specific entry, for example:
1 2 3 4 | |
Delete
1 2 | |
Update
1 2 | |