Flaskz介绍1--数据库初始化&常用函数
Flaskz简介
Flaskz[源码] 是Flask和SQLAlchemy ORM的功能扩展,源于项目积累,主要对数据库/API/日志/常用函数等进行了封装和扩展,可以快速灵活的实现各种业务场景和提供API。
通过 pip install flaskz / pip install 'flaskz[ext]' 安装使用。
Flaskz代码目录
-auth/ #授权/JWT
---_jws.py #提供了JSONWebSignatureSerializer和TimedJSONWebSignatureSerializer,用于token生成和校验
-ext/ #扩展功能(需要安装依赖)
---cypher.py/ #提供了RSACipher和AESCipher(基于pycryptodome),用于加密解密
---ssh.py #ssh操作(基于paramiko)
-log/ #日志
---__init__.py #提供了flaskz_logger用于系统日志记录
-models/ #数据库操作(SQLAlchemy)
---__init__.py #提供了init_model函数, 用于初始化Model操作
---_base.py #提供了BaseModelMixin, 扩展&简化了数据库操作
---_model.py #提供了ModelMixin,在BaseModelMixin的基础上添加了流程&异常处理等功能
---_query_util.py #提供了query相关功能函数
---_util.py #提供了ORM常用操作函数
-rest/ #REST API功能
---__init__.py #提供了多个register函数, 用于自动生成数据模型相关API
---_mgmt.py #提供了ModelRestManager, 用于设置API控制函数(权限控制/操作日志记录)
---_util.py #提供了生成API所需常用函数(权限控制/日志记录)
-utils/ #工具函数集合
---_app.py #flask/celery应用路径&配置
---_cache.py #应用缓存工具函数(g/app)
---_cls.py #工具类&对象操作
---_common.py #通用工具函数(list操作/dict操作/...)
---_request_api.py #发送API请求工具函数
---_request_args.py #API请求数据处理工具函数
---_response.py #API响应工具函数
---_timer.py #周期&定时执行函数
-res_status_codes.py #API响应code集合
以_开头的py文件中的函数/变量,其所在package提供了import功能,请通过其package使用
本系列介绍文章主要包括以下内容
开发过程,请遵守开发规范,相关知识点一般在 Flaskz-admin 模板中都可以找到,建议对照着学习。
如果对Flask和SQLAlchemy感兴趣,请参考 Flask 和 SQLAlchemy。
本文主要介绍数据库的初始化和操作的常用方法。
数据库初始化
调用flaskz.models.init_model(flask_app)方法对数据库操作进行初始化,初始化操作会读取配置信息,初始化数据库引擎并进行连接测试。
数据库相关配置
有如下配置属性
- FLASKZ_DATABASE_URI
数据库URI,格式为:{db_type}+{db_drive}://{username}:{password}@{hostname}:{port}/{db}, 主要包括数据库地址/账号/密码/端口/驱动等。
示例:
- sqlite数据库--sqlite:///D:/PyDev/WorkSpace/flasky/_sqlite/flasky_test.db,Python默认支持sqlite数据库,所以不需要驱动。
- MySQL数据库--mysql+pymysql://root:123456@10.20.30.60:3306/flasky,需要安装PyMySQL驱动。
请注意不同数据库可能需要安装对应的驱动,比如PyMySQL或PostgreSQL等
- FLASKZ_DATABASE_ECHO
对应SQLAlchemy中create_engine方法的echo参数,如果为True,引擎将记录并打印所有SQL语句, echo功能一般用于开发和测试环境,不适用于线上/产品环境。
- FLASKZ_DATABASE_POOL_RECYCLE
单位是秒,对应SQLAlchemy中create_engine方法的pool_recycle参数,此设置使数据库连接池在经过给定秒数后回收连接。默认为-1,表示不会回收。例如,设置为3600意味着连接将在一小时后循环使用。
请注意,如果在8小时内未检测到任何活动,MySQL默认会自动断开连接。
示例:FLASKZ_DATABASE_POOL_RECYCLE = int(timedelta(hours=2).total_seconds()) # 1小时以后刷新数据库连接
- FLASKZ_DATABASE_POOL_PRE_PING
对应SQLAlchemy中create_engine方法的pool_pre_ping参数,如果为True,每次和数据库交互之前先测试连接是否可用,如果不可用会重连(HA/数据库重启),虽然有性能损耗,但可以增加系统的健壮性。
- FLASKZ_DATABASE_ENGINE_KWARGS
engine自定义参数,可以根据需求和场景自定义参数设置,例如){'pool_timeout': 20, 'pool_size': 20, "poolclass": QueuePool, 'max_overflow': 20}
- FLASKZ_DATABASE_SESSION_KWARGS
session自定义参数,可以根据需求和场景自定义参数设置,例如){'expire_on_commit': False}
- FLASKZ_DATABASE_DEBUG
是否启用调试模式,如果为True则会对数据库的每次操作进行记录(执行时间/SQL语句等),主要用于性能调优,debug模式下slow_time和access_times才会起作用。
- FLASKZ_DATABASE_DEBUG_SLOW_TIME
单位是毫秒,如果数据库的单次操作时间大于指定的slow_time,则会以warning日志的方式将其打印,主要用于数据库调优操作,如果值<=0,则不启用。
示例:FLASKZ_DATABASE_DEBUG_SLOW_TIME = 200 # 如果单次SQL执行时间大于200ms,则将其打印。
- FLASKZ_DATABASE_DEBUG_ACCESS_TIMES
一次API请求处理过程中,如果与数据库的交互操作次数大于指定的access_times,则会以日志的方式将次数打印,主要用于优化数据库模型(比如调整relationship的lazy等),如果值<=0,则不启用。
示例:FLASKZ_DATABASE_DEBUG_ACCESS_TIMES = 20 # 如果一次请求过程中数据库交互次数大于20次,则打印出具体的次数。
以上配置参数,请参考 Flaskz-admin 模板。
ModelBase
初始化操作会创建一个SQLAlchemy的declarative_base实例ModelBase,其他数据库业务模型类必须要继承ModelBase,所有继承自ModelBase的模型类都会跟配置的数据库引擎进行绑定。
示例如下:
class User(ModelBase, ModelMixin):
__tablename__ = 'users' # User对应的数据库表名
id = Column(Integer, primary_key=True, autoincrement=True) # 自增主键,请注意SQLAlchemy ORM要求必须要有一个主键,用于区分不同的数据对象
name = Column(String(32), unique=True, nullable=False)
age = Column(Integer)
addresses = relationship(Address, cascade='all,delete,delete-orphan', lazy='joined') # Address关系映射
#......
多个数据库
有些业务场景,需要同时连接多个数据库进行操作,例如,数据采集和数据分析在不同的数据库的场景。
下面通过示例代码,介绍如何操作多个数据库
ABase = declarative_base() # 1.创建A数据库对应的Base对象
def init_app(app):
app_config = get_app_config_items(app) or {}
database_uri = app_config.get('A_DATABASE_URI')
try:
engine_kwargs = {}
custom_engine_kwargs = app_config.get('A_DATABASE_ENGINE_KWARGS')
if type(custom_engine_kwargs) is dict:
engine_kwargs.update(custom_engine_kwargs)
for engine_key, config_key in {'echo': 'A_DATABASE_ECHO',
'pool_recycle': 'A_DATABASE_POOL_RECYCLE',
'pool_pre_ping': 'A_DATABASE_POOL_PRE_PING'}.items():
if config_key in app_config:
engine_kwargs[engine_key] = app_config.get(config_key)
engine = create_engine(database_uri, **engine_kwargs) # 2.创建数据库引擎
binds = DBSession.kw.get('binds')
binds.update({ABase: engine})
DBSession.configure(binds=binds) # 3.将ABase对象和数据库引擎绑定,并更新DBSession配置
with engine.connect(): # connect test # 4.连接测试
flaskz_logger.info('A Database ' + database_uri + ' is ready\n')
except Exception as e:
flaskz_logger.exception('Connect to A database ' + database_uri + ' error\n')
return
class TDevice(ABase, ModelMixin): # 5.使用ABase创建模型类,所有针对TDevice的操作都会对应到ABase设置的数据库
__tablename__ = 't_device'
__table_args__ = {'info': {'skip_autogenerate': True}} # 6.设置Alembic跳过对A数据库结构的更新
id = Column(Integer, primary_key=True)
name = Column(String(255))
mgmt_ip = Column(String(255))
SQLAlchemy会根据模型类绑定的数据库,对指定的数据库进行操作,在业务代码层面,不同数据库的模型类,使用起来没有任何差异。
请注意示例代码注释6,如果指定的数据库表不需维护其结构,可以设置Alembic跳过对其的维护。
常用函数
以下为操作数据库经常用到的一些函数介绍。
- create_instance(model_cls, data, create_relationship=True)
- 功能:使用dict字典数据,创建指定的模型类对象,主要用于向数据库中添加数据
- 参数:
- {class}model_cls --指定的模型类
- {dict}data --创建对象的属性集合
- {boolean}create_relationship --是否创建relationship(列表/对象)
-如果为True,则会使用data中relationship相关的数据创建对应的relationship属性
-如果为False,则不会创建relationship,默认为True,即只要data中有relationship相关的属性,就会创建relationship属性
- 返回:创建好的instance对象
- 示例:
instance = create_instance(User, { "name":"taozh", "age":18, "addressess":[ # 除了创建User对象,还会创建User关联的addresses关系列表,添加到数据库时,会一起插入(User表一条,Address表两条) {"city":"Shanghai"}, {"city":"Shandong"} ]}); with db_session() as session: # 使用db_session上下文管理器引用session,将对象添加到数据库中 session.add(instance) return instance
- db_session(do_commit=True)
- 功能:数据库session上下文管理器,SQLAlchemy ORM和数据库的所有交互操作,都要通过 session 进行。
针对session的增删改查操作完成以后,会调用commit方法启动事务将数据插入到数据库中(commit之前数据只是存在于session中),如果操作失败则会进行rollback操作
同时为了提升效率,一次请求(API)过程中只会生成一个session,并缓存于g应用上下文中,请求结束时销毁
- 参数:
- {boolean}do_commit --操作完成以后是否commit,默认为True
-如果为True,则会执行commit,一般用于增删改操作
-如果为False,则不会执行commit操作,一般用于查询操作
- {boolean}do_commit --操作完成以后是否commit,默认为True
- 示例:请参考create_instance示例代码
- 功能:数据库session上下文管理器,SQLAlchemy ORM和数据库的所有交互操作,都要通过 session 进行。
- query_all_models(*models)/query_multiple_model(*cls_list)
- 功能:一次性查询多个数据模型的所有数据(query_all),并以二维列表的形式返回
- 参数:
- *models --需要查询的多个模型类(关键字参数)
- 返回:多个模型类的所有数据组成的二维列表,如果查询失败,会返回(False,reason)的一个元组
- 示例:
result = query_all_models(Role, User) # 请求用户列表数据时,也会将系统角色数据一起返回,用于用户的增删改查操作。 if result[0] is False: # 如果查询失败, success = False res_data = model_to_dict(result[1]) else: success = True res_data = { 'roles': model_to_dict(result[0]), 'users': model_to_dict(result[1]) }
两个函数功能完全相同,1.5版本添加了query_all_models方法。
- model_to_dict(ins, option=None)
- 功能:将模型类的对象或对象列表列表转换为字典或列表,主要用于将模型数据返回到请求客户端,请参考BaseModelMixin的 数据序列化 介绍
- 参数:
- ins --要转换的模型类对象或对象列表
- option --转换选项,请参考 BaseModelMixin.to_dict 方法
- 返回:转换以后的字典对象或对象列表
- parse_pss(cls, pss_payload)
- 功能:将对模型数据的分页,排序,搜索等查询参数进行解析并生成操作数据库对应的方法和参数
此函数主要是为了简化 ModelMixin.query_pss 的使用, 将查询参数转换成查询语句,并解决SQL注入等问题
- 版本:1.6.1添加
- 参数:
- cls --模型类,例如)SysUser
- pss_payload --分页,排序,搜索等参数
- page --分页参数
- offset/skip - 查询结果集的偏移起始值,即跳过多少条记录,默认为0
- limit/size - 查询结果集的限制条数,为了避免大量数据导致的性能问题,如果参数为空,默认限制为100000
- search/query -- 查询参数,参数主要包括以下几种类型
- 列/属性 - 数据模型类列/属性,使用==进行完全匹配,根据属性值类型有不同的处理逻辑
- 字符串
- 如果字符串不包含str_sep(字符串分隔符)/str_sep为空,直接使用, 例如)"name":"admin"
- 如果字符串包含str_sep,会将值拆分成多个值使用, 例如)"email": "t1@focus-ui.com||t2@flaskz.com" 会被拆分成email==t1@focus-ui.com OR email = t2@flaskz.com
- 为了更好的适配前端请求,默认会忽略值为""/None的属性,如果查询的值就是""/None,有两种处理方式
- 设置全局_ignore_null属性为False
- 使用字典方式传值,例如)"name": {"==": ""}
- 字典/对象 - 字典的键为操作符(==/in/between/...),值为操作符对应的值,例如)"name":{"in":["t1","t2"]} / "age":{">":10,"<":20}}
- 其他类型 - 直接使用,例如)"age":18
- 字符串
- 模糊查询 - payload或数据模型类中指定的like_columns列才会被模糊查询
- _like/like - 不区分大小写,例如)"like":"hello"
- _ilike/ilike - 区分大小写(需要数据库支持),例如)"ilike":"Hello"
- _notlike/notlike - 不区分大小写,例如)"notlike":"hello"
- _notilike/notilike - 区分大小写(需要数据库支持),例如)"notilike":"Hello"
注意事项
- 如果payload和模型类中都没有指定like_columns,则不会模糊查询
- 多个like/ilike字段之间默认通过OR的方式进行连接(任意一个字段包含即符合), 多个notlike和/notalike之间默认通过AND的方式进行连接(必须所有字段都不包含)
- 如果参数值不包含%,系统默认使用%text%进行匹配查询,可以在参数值的开头/结束添加%以改变默认匹配
- "text" - name like '%text%' (默认)
- "%text" - name like '%text'
- "text%" - name like 'text%'
- ★关系/relationship - 数据模型类relationship关系相关的查询参数
有两种传值方式
- 层次字符串 - 以.连接的层次字符串,例如)"address.city":"New York"
- 字典/对象 - 例如)"address":{"city":"New York"}
值的类型跟上述search中的查询参数一致
关于模糊匹配
- 如果relationship中的like/ilike=True,则会使用search中的like字段对relationship的like_columns进行模糊查询, 并且跟数据模型类的模糊查询默认以OR的方式进行连接
- 如果relationship中的notlike/notilike=True,则会使用search中的notlike字段对relationship的like_columns进行模糊查询, 并且跟数据模型类的模糊查询默认以AND的方式进行连接
注意事项
- 主表和relationship表是通过左外连接outerjoin的方式进行的关联
- 如果是一对多的relationship(例如:一个用户多个地址),只要有一个relationship中的数据符合条件,所有的relationship(符合条件的+不符合条件的)都会被返回
- 只支持当前模型类的relationship(一级),不支持relationship的relationship(多级)
- 关键字
- 在跟列名不冲突的情况下,关键字可以省略开头的_,例如) ignore_null/ors/like
- 如果两者都存在,优先使用以_开头的属性,例如) _like > like
有如下关键字
- _ignore_null/ignore_null - 是否忽略空值(None/''/null),默认为True(忽略),例如)"_ignore_null": False
- _str_sep/str_sep - 字符串分隔符,默认为 "||",ex) "_ors": {"email": "t1@focus-ui.com||t2@flaskz.com"}会被拆分成email==t1@focus-ui.com OR email = t1@flaskz.com
- _like/like - 模糊查询(包含)
- _ilike/ilike - 模糊查询(包含&区分大小写)
- _likejoin/likejoin - 模糊查询(包含)的连接方式,可选值为'or'/'and',默认为or(任意一个字段包含即符合)
- _notlike/notlike - 模糊查询(不包含)
- _notilike/notilike - 模糊查询(不包含&区分大小写)
- _notilikejoin/notlikejoin - 模糊查询(不包含)的连接方式,可选值为'or'/'and',默认为and(所有字段都不包含)
- _like_columns/like_columns - 模糊查询的列,如果不指定,默认使用模型类的like_columns,例如)"like_columns":["name"]
- _ors/ors - 通过OR进行连接的多个查询条件集合,例如)"_ors": {"name":{"in":["t1","t2"]},"age":{"between":[10,20]}}
- _ands/ands - 通过AND进行连接的多个查询条件集合,例如)"_ands": {"name":{"in":["t1","t2"]},"age":{">":10,"<":20}}
以上参数,_ors以外的其他查询参数,都会使用AND进行连接
- 列/属性 - 数据模型类列/属性,使用==进行完全匹配,根据属性值类型有不同的处理逻辑
- sort --排序参数
- field - 排序属性,即以哪个属性值进行排序,支持relationship属性(以.连接的层次字符串),例如)"field":"name" / "field":"address.city"
- order - 排序的类型,正序还是倒序,如果order是['desc', 'descend', 'descending']中的值,则按倒序进行排序,否则按正序进行排序
- page --分页参数
- 示例:
User.query_pss(parse_pss({ "search": { # WHERE "name": "admin" # 精确查询, name='admin' "updated_at": { # operator ">=": "2022-01-01 00:00:00", "<": "2023-01-01 00:00:00" }, "age": { # between "between": [10,20] }, "like": "t", # 模糊查询, name like '%t%' OR description like '%t%' (TemplateModel.like_columns = ['name', description]) # "notlike": "t", # 不包含模糊查询, name notlike '%t%' AND description notlike '%t%' "address.city": "New York", # -relation字段精确查询 "address": { # -relation精确查询 "like": True, # -使用search中的like字段对address关系的like_columns进行模糊查询 "country": "USA" # -使用search中的like字段对address关系的like_columns进行模糊查询 }, # 关键字 # 1. 在不冲突的情况下,可以省略开头的‘_’ ex)ignore_null/ors/like, # 2. 如果两者都存在,优先使用以'_'开头的属性 ex)_like > like "_ignore_null": False, # 是否忽略空值(null/''),默认为True(忽略) "_str_sep": "||", # 字符串分隔符,默认为 "||",ex) "_ors": {"email": "t1@focus-ui.com||t2@flaskz.com"}会被拆分成email==t1@focus-ui.com OR email = t1@flaskz.com "_like": "t", # 模糊查询关键字==like,like字段冲突时使用,优先级_like>_like "_ilike": "t", # 不区分大小写模糊查询关键字==ilike(需DB支持),ilike字段冲突时使用,优先级_ilike>ilike "_likejoin": "or", # 模糊查询(包含)的连接方式,可选值为'or'/'and',默认为or(任意一个字段包含即符合) "_notlike": "t", # 不包含模糊查询关键字==notlike,notlike字段冲突时使用,优先级_notlike>_notlike "_notilike": "t", # 不区分大小写不包含模糊查询关键字==notilike(需DB支持),notilike字段冲突时使用,优先级_notilike>notilike "_notilikejoin": "and", # 模糊查询(不包含)的连接方式,可选值为'or'/'and',默认为and(所有字段都不包含) "_like_columns": ["name"], # 模糊查询的列,默认使用模型类的like_columns "_ors": { # 多个OR连接的条件关键字 "email": "t1@focus-ui.com||t2@flaskz.com", # email = t1@focus-ui.com OR email = t2@flaskz.com "name": { "in": [ "t1", "t2" ] }, "age": { "between": [ 10, 20 ] } }, "_ands": { # 多个AND连接的条件关键字 "age": { # age >10 AND age<20 ">": 10, "<": 20 } } }, "sort": { # 查询结果集的排序方式, ORDER BY users.name ASC "field": "name", # 进行排序的属性 "order": "asc" # 排序方式,asc/desc }, # "sort": [ # 多重排序 # { # "field": "name", # "order": "asc" # asc/desc # }, # { # "field": "address.city", # -relation字段排序 # "order": "desc" # asc/desc # } # ], "page": { # 分页属性, LIMIT ? OFFSET ? (20, 0) "offset": 0, # 偏移为0 "size": 20 # 每页20条记录 } # ,"group": "email" # Group BY分组 }))
通过parse_pss函数转换以后的结果,示例如下
- 返回:供ModelMixin.query_pss调用的查询条件集合
- 功能:将对模型数据的分页,排序,搜索等查询参数进行解析并生成操作数据库对应的方法和参数