Flask SQLAlchemy 基本操作

最近练手 Vue.js,用 Flask 做后端来提供 API 调用,发现对 SQLAlchemy 的操作又有些陌生了,遂生出写博客记录的想法。

Flask SQLAlchemy 是对 SQLAlchemy 的一个封装,主要简化了 Flask 中 SQLAlchemy 的操作。

连接数据库,定义、初始化表

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
import os
import forgery_py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data.sqlite')
db = SQLAlchemy(app)


# 实体类
class User(db.Model):
__tablename__ = 'user' # 表名
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(16), unique=True, index=True, comment="This is user's username")
password = db.Column(db.String(128), nullable=False, comment="This is user's password")


if __name__ == '__main__':
db.drop_all()
db.create_all()
for i in range(20):
user = User(username=forgery_py.name.full_name(),
password=forgery_py.basic.password())
db.session.add(user)
db.session.commit()
print('Init success')

运行上述代码,会在同级目录下创建一个 data.sqlite 数据库文件,并且初始化 20 条用户名密码信息。

关系

关系数据库通过关系在不同的表之间建立连接。下面的代码展示了用户与角色的多对一关系:

1
2
3
4
5
6
7
8
class Role(db.Model):
# ...
users = db.relationship('User', backref='role')


class User(db.Model):
# ...
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))

User 模型中 role_id 字段被定义为外键,与 role 表建立多对一关系。db.ForeignKey('role.id') 指定了外键对应的表中的列。

添加到 Role 模型的 users 属性则体现了关系的面对对象理念。给定 Role 类的实例,便可以通过 users 属性返回一组连接到该角色的用户。

db.relationship() 函数的第一个参数表明关系的另一边,如果对应的模型类还未定义,可以用字符串提供。backref 参数通过 给 User 模型增加一个 role 属性 来定义反向关系,User 类实例可以通过 role 属性返回对应的 Role 对象,从而无需使用 role_id

常用SQLAlchemy关系选项

选项名 描述
backref 在关系的其他模型中添加一个反向引用
primaryjoin 显示地指定两个模型之间的连接条件。当关系模糊的时候这是必要的
lazy 指定如何加载相关条目。可能值有 select(使用标准 select 语句一气呵成加载条目)、immediate(当源对象加载后加载条目)、joined(立即加载条目,但是作为连接)、subquery(立即加载条目,但是作为子查询)、noload(条目永不加载)、dynamic(会返回另一个查询对象,可以在加载这些条目的时候进一步提取)
uselist 如果设置为 False,使用一个标量而不是列表
order_by 指定关系中条目的排序
secondary 在多对多关系中指明要使用的关联表名称
secondaryjoin 当 SQLAlchemy 不能自己决定多对多关系的时候指定第二个连接条件

Flask 支持 Shell

要观察增、删、改、查,利用 Python Shell 会更直观,为此我们要让 Flask 提供 Shell 功能。

安装 Flask-Script:

1
pip install flask-script

添加 Shell:

1
2
3
4
5
6
7
8
9
10
11
from flask_script import Manager, Shell
# ...
manager = Manager(app)
# ...
def make_shell_context():
return dict(app=app, db=db, User=User, Role=Role, )


if __name__ == '__main__':
manager.add_command('shell', Shell(make_shell_context))
manager.run()

插入

1
2
3
4
5
6
7
>>> u = User(username='1234', password='asdas')
>>> print(u.id) # 尚未插入
None
>>> db.session.add(u) # 修改数据库的操作由 Flask-SQLAlchemy 提供的 db.session 数据库会话来管理,准备写入到数据库中的对象必须添加到会话中
>>> db.session.commit() # 为了写对象到数据库,需要通过它的 commit() 方法来提交会话
>>> u.id # 已被写入数据库
21

数据库会话也可以回滚,如果调用 db.session.rollback(),任何添加到数据库会话中的对象都会恢复到它们曾经在数据库中的状态。

删除

数据库会话对象同样有 delete() 方法:

1
2
3
4
5
6
7
8
9
>>> User.query.all()
[<User 1>, <User 2>, <User 3>, <User 4>, <User 5>, <User 6>, <User 7>, <User 8>, <User 9>, <User 10>, <User 11>, <User 12>, <User 13>, <User 14>, <User 15>, <User 16>, <User 17>, <User 18>, <User 19>, <User 20>]
>>> u1 = User.query.all()[0]
>>> u1.id
1
>>> db.session.delete(u1)
>>> db.session.commit()
>>> User.query.all()
[<User 2>, <User 3>, <User 4>, <User 5>, <User 6>, <User 7>, <User 8>, <User 9>, <User 10>, <User 11>, <User 12>, <User 13>, <User 14>, <User 15>, <User 16>, <User 17>, <User 18>, <User 19>, <User 20>]

注意,删除与插入更新一样,都在数据库会话提交后执行。

更新

数据库会话的 add() 方法同样适用于更新模型:

1
2
3
4
5
6
7
8
9
10
11
>>> u2 = User.query.all()[0]
>>> u2.id
2
>>> u2.username
'Rachel Wright'
>>> u2.username = 'new user 2 username'
>>> db.session.add(u2)
>>> db.session.commit()
>>> newuser = User.query.all()[0]
>>> newuser.id, newuser.username
(2, 'new user 2 username')

查询

Flask SQLAlchemy 为每个模型类创建了一个 query 对象。最基本的查询是返回表中全部数据(列表):

1
2
>>> User.query.all()
[<User 2>, <User 3>, <User 4>, <User 5>, <User 6>, <User 7>, <User 8>, <User 9>, <User 10>, <User 11>, <User 12>, <User 13>, <User 14>, <User 15>, <User 16>, <User 17>, <User 18>, <User 19>, <User 20>]

使用过滤器可以配置查询对象去执行更具体的搜索:

1
2
3
4
5
>>> dr = Role.query.all()[1]
>>> dr.name
'Dr'
>>> User.query.filter_by(role=dr).all()
[<User 2>, <User 5>, <User 6>, <User 11>, <User 17>, <User 20>]

对于给定查询还可以检查 SQLAlchemy 生成的原生 SQL 查询,并将查询对象转换为一个字符串:

1
2
>>> str(User.query.filter_by(role=dr))
'SELECT user.id AS user_id, user.username AS user_username, user.password AS user_password, user.role_id AS user_role_id FROM user WHERE ? = user.role_id'

可以通过调用 all() 或 first() 等触发 query 执行:

1
2
3
>>> dr_role = Role.query.filter_by(name='Dr').first()
>>> dr_role.id, dr_role.name
(2, 'Dr')

过滤器等如 filter_by() 通过 query 对象来调用,且返回经过提炼后的 query,多个过滤器可以依次调用直到需要的查询配置结束为止。

常用的过滤器:

选项 描述
filter() 在原查询对象后追加的过滤器,然后返回新的查询对象
filter_by() 在原查询对象后追加等式过滤器,然后返回新的查询对象
limit() 给定数量来限制原查询对象的结果数量,然后返回新的查询对象
offset() 运用一个偏移量在原查询对象的结果列表中,然后返回新的查询对象
order_by() 根据给定的条件,给原查询对象结果排序,然后返回新的查询对象
group_by() 根据给定的条件,给原查询对象结果分组,然后返回新的查询对象

在需要的过滤器已经全部运用于 query 后,调用 all() 会触发 query 执行并返回一组结果,但除了 all() 以外还有其他方式可以触发执行。

常用的 SQLAlchemy 查询执行器:

选项 描述
all() 将所有查询结果作为列表返回
first() 返回查询的第一个结果,如果没有则返回 None
first_or_404() 返回查询的第一个结果,如果没有则中断请求并发送 404 错误作为响应
get() 返回匹配行中给出的主键,如果没有匹配行则返回 None
get_or_404() 返回匹配行中给出的主键,如果没有则中断请求并发送 404 错误作为响应
count() 返回查询结果的个数
paginate() 返回包含指定范围结果的 Pagination 对象

关系

关系的原理类似于查询:

1
2
3
4
5
6
7
8
>>> mrs_role = Role.query.first()
>>> mrs_role.id, mrs_role.name
(1, 'Mrs')
>>> users = mrs_role.users
>>> users
[<User 1>, <User 6>, <User 10>, <User 11>, <User 16>]
>>> users[0].role.name
'Mrs'

这里,我们通过 mrs_role.users 直接获取到了用户列表(内部调用 all() 通过隐式查询返回),查询对象对我们是不可见的,也就无法对 user 结果集做一些筛选。如果我们需要对用户集进行一定的筛选,可以设置 lazy='dynamic' 参数:

1
2
3
class Role(db.Model):
# ...
users = db.relationship('User', backref='user', lazy='dynamic')

用这种方式配置关系,表达式 mrs_role.users 返回的就是查询对象,我们可以对它增加过滤器:

1
2
3
4
5
6
>>> mrs_role.users
<sqlalchemy.orm.dynamic.AppenderBaseQuery object at 0x000002574839EFD0>
>>> mrs_role.users.all()
[<User 'Jose Banks'>, <User 'Marie Ford'>, <User 'Karen Gonzalez'>, <User 'Carolyn Black'>, <User 'Carolyn Rogers'>, <User 'Lillian Frazier'>, <User 'Emily Fisher'>, <User 'Amy Lawrence'>]
>>> mrs_role.users.order_by(User.username).all()
[<User 'Amy Lawrence'>, <User 'Carolyn Black'>, <User 'CarolynRogers'>, <User 'Emily Fisher'>, <User 'Jose Banks'>, <User 'Karen Gonzalez'>, <User 'Lillian Frazier'>, <User 'Marie Ford'>]

PyCharm 支持

默认 Flask-SqlAlchemy 是动态添加 SQLAlchemy 方法的,所以 PyCharm 无法对其进行补全,本质上是类型无法识别。
但是我们可以通过一些特殊的方法让 PyCharm 知道我们使用的变量的类型,就是 typehint

1
2
db = SQLAlchemy()
session = db.create_scoped_session(None) # type:sqlalchemy.org.Session

之后我们使用:

1
session.query(SomeDBModel).filter...

就会成功补全。

-------------本文结束感谢阅读-------------
  • 本文标题:Flask SQLAlchemy 基本操作
  • 本文作者:xlui
  • 发布时间:2018年05月12日 - 20:05
  • 最后更新:2019年04月23日 - 17:04
  • 本文链接: https://xlui.me/t/python-flask-sqlalchemy/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!