最近练手 Vue.js,用 Flask 做后端来提供 API 调用,发现对 SQLAlchemy 的操作又有些陌生了,遂生出写博客记录的想法。
Flask SQLAlchemy 是对 SQLAlchemy 的一个封装,主要简化了 Flask 中 SQLAlchemy 的操作。
连接数据库,定义、初始化表
1 | import os |
运行上述代码,会在同级目录下创建一个 data.sqlite
数据库文件,并且初始化 20 条用户名密码信息。
关系
关系数据库通过关系在不同的表之间建立连接。下面的代码展示了用户与角色的多对一关系:
1 | class Role(db.Model): |
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 | from flask_script import Manager, Shell |
插入
1 | '1234', password='asdas') u = User(username= |
数据库会话也可以回滚,如果调用 db.session.rollback(),任何添加到数据库会话中的对象都会恢复到它们曾经在数据库中的状态。
删除
数据库会话对象同样有 delete() 方法:
1 | User.query.all() |
注意,删除与插入更新一样,都在数据库会话提交后执行。
更新
数据库会话的 add() 方法同样适用于更新模型:
1 | 0] u2 = User.query.all()[ |
查询
Flask SQLAlchemy 为每个模型类创建了一个 query 对象。最基本的查询是返回表中全部数据(列表):
1 | User.query.all() |
使用过滤器可以配置查询对象去执行更具体的搜索:
1 | 1] dr = Role.query.all()[ |
对于给定查询还可以检查 SQLAlchemy 生成的原生 SQL 查询,并将查询对象转换为一个字符串:
1 | str(User.query.filter_by(role=dr)) |
可以通过调用 all() 或 first() 等触发 query 执行:
1 | 'Dr').first() dr_role = Role.query.filter_by(name= |
过滤器等如 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 | mrs_role = Role.query.first() |
这里,我们通过 mrs_role.users
直接获取到了用户列表(内部调用 all() 通过隐式查询返回),查询对象对我们是不可见的,也就无法对 user 结果集做一些筛选。如果我们需要对用户集进行一定的筛选,可以设置 lazy='dynamic'
参数:
1 | class Role(db.Model): |
用这种方式配置关系,表达式 mrs_role.users
返回的就是查询对象,我们可以对它增加过滤器:
1 | mrs_role.users |
PyCharm 支持
默认 Flask-SqlAlchemy 是动态添加 SQLAlchemy 方法的,所以 PyCharm 无法对其进行补全,本质上是类型无法识别。
但是我们可以通过一些特殊的方法让 PyCharm 知道我们使用的变量的类型,就是 typehint
:
1 | db = SQLAlchemy() |
之后我们使用:
1 | session.query(SomeDBModel).filter... |
就会成功补全。