- 请求钩子
请求钩子可以对请求的各阶段进行监听, 方便开发者 针对请求完成一些统一的处理, 以便减少重复代码, 作用类比Django中的中间件
开发中中主要会用到以下四种请求钩子:
before_request: 每次执行视图函数之前; 对请求进行一些准备处理; 如果在函数中返回了一个响应,视图函数将不再被调用
after_request: 没有异常,每次执行视图函数之后(已经包装为响应对象)调用; 在此函数中可以对象应值再返回之前做最后一步修改处理
before_first_request(不常用): 只在第一次访问该应用时执行,可以进行web应用初始化处理
teardown_request: 每次执行视图函数之后调用;无论是否出现异常都会执行, 一般用于请求收尾;接受一个参数:错误信息,如果有相关错误抛出 - 蓝图(重点)
蓝图的作用: 实现Flask项目 模块化
项目模块化主要是 将业务以功能模块进行划分, 每个功能模块对应一个包, 用于存放和其有关的视图/工具/模型文件等, 如home
,user
对于大型项目, 一般 每个功能模块对应创建一个蓝图, 由多个蓝图代替应用来分别管理各模块的视图大型项目目录结构示意图:
--------- project # 工程目录 |------ main.py # 启动文件 |------ user # 用户模块 | |--- __init__.py # 包的初始化文件, 此处创建管理用户模块的蓝图对象 | |--- views.py # 视图文件 | |--- ... | |------ home # 首页模块 | |--- __init__.py # 包的初始化文件, 此处创建管理首页模块的蓝图对象 | |--- views.py # 视图文件 | |--- ... |...
蓝图的基本使用:
1).创建蓝图对象 (创建home模块包,在里面的__init__.py文件里添加代码)
from flask import Blueprint
home_blue = Blueprint("home_b", __name__, url_prefix="/home")
# 4. 让视图文件和程序建立关联(一旦遇到导包错误,解决办法:查看并调整代码顺序)
from . import views
2). 使用蓝图对象来定义路由 (home下视图文件views.py里)from home import home_blue
@home_blue.route("/demo")
def demo():
return "demo"3).注册蓝图对象(在工程目录下的主成序启动文件的main.py里)
from flask import Flask
app = Flask(__name__)
# 3. 注册蓝图对象
from project.home import home_blue
app.register_blueprint(home_blue)
if __name__ == '__main__':
# print(app.url_map)
print(app.view_functions)
app.run(debug=True)
4).让视图文件和程序建立关联(这一步可能遇到导包错误,解决办法: 查看并调整代码的执行顺序)这一步是在home包下的__init__.py文件下操作
- 上下文(context)
上下文:是一个 数据容器,用于保存Flask的各相关数据
上下文分类: 请求上下文 和 应用上下文
共同特点: 上下文中数据有使用范围 [请求开始:请求结束]
请求上下文: 记录一些和请求有关的数据 request, session 等
应用上下文: 记录一些和应用有关的数据 g current_app
g: flask给开发者预留的一个容器,用于存储一些自定义数据 特点: 每次请求,g记录的数据会重置
g的应用场景: 1> 在视图函数 和 钩子函数之间传递数据 2> 函数嵌套调用是, 可以使用g代替参数传递数据
current_app: 会自定引用创建的flask应用,想要在项目的其他文件中使用app时,应该使用current_app,
这样可以减少循环导入的错误 - 综合认证: 统一处理 ; 访问限制
1.统一处理:
需求: 获取用户身份
分析: 除了静态资源, 基本所有视图都需要获取用户身份, 每个视图单独获取出现大量的代码冗余
解决办法: 设置 请求钩子, 并通过 g变量 将数据传递给视图函数
代码:
===================================================================================================================from flask import Flask, session, g app = Flask(__name__) app.secret_key = 'test' # 需求1: 所有视图都需要获取用户身份 # 解决办法: 用钩子函数进行封装 减少代码冗余 @app.before_request def prepare(): # 必须使用g变量来传递数据, 使用全局变量不能记录并发的多个请求数据 g.name = session.get('username') @app.route('/') def index(): if g.name: return "欢迎回来, %s" % g.name else: return '首页' @app.route('/demo1/') def demo1(): print(g.name) return 'demo1' @app.route('/login') def login(): """登录""" session['username'] = 'zs' return '登录成功' if __name__ == '__main__': app.run(debug=True)
2.访问限制
需求: 对指定的路由进行访问限制
分析:部分视图需要身份校验,这不是图每个单独校验仍会出现大量的代码冗余
解决办法: 封装 装饰器 完成身份校验逻辑, 对指定视图函数设置装饰器# 访问限制代码实现及优化 from flask import Flask, session, g, abort from functools import wraps app = Flask(__name__) app.secret_key = 'test' @app.before_request def prepare(): g.name = session.get('username') @app.route('/') def index(): if g.name: return "欢迎回来, %s" % g.name else: return '首页' @app.route('/login') def login(): """登录""" session['username'] = 'zs' return '登录成功' # 使用装饰器封装访问限制 def login_required(f): # f = user @wraps(f) # 会将被装饰的函数(wrapper)的函数信息替换为指定函数(f)的函数信息(__name__ 函数名, __doc__ 函数注释) # 设置该装饰器后, 可以让闭包函数使用原函数名, 避免函数标记出现冲突(函数标记是根据函数名来生成的) def wrapper(*args, **kwargs): if g.name: return f(*args, **kwargs) else: abort(401) return wrapper @app.route('/user') @login_required def user(): """个人中心""" return '访问 %s 的个人中心' % g.name @app.route('/demo1') @login_required def demo1(): return 'demo1' if __name__ == '__main__': print(app.url_map) app.run(debug=True)
- 应用配置
主文件 main.py 从对象中加载封装的配置 app.config.from_object()
加载隐私配置:app.config.from_envvar('ENV_CONFIG',
silent=True
)
加载配置时, 设置参数
silent=True
, 则配置加载失败也不会报错config.py文件里代码示例:
# config.py from datetime import timedelta class BaseConfig: """配置基类 可以将相同的配置抽取到基类中, 减少重复代码""" # 定义和配置同名的类属性 PERMANENT_SESSION_LIFETIME = timedelta(days=7) class DevelopmentConfig(BaseConfig): """开发环境""" SQL_URL = '127.0.0.1:3306/test1' # 数据库地址 class ProductionConfig(BaseConfig): """生产环境""" SQL_URL = '222.10.15:3306/users' # 数据库地址
# 定义工厂函数需要的配置(方便主文件提取配置类)
# 定义字典来记录 配置类型 和 配置子类 之间的映射关系 config_dict = { 'dev': DevelopmentConfig, 'pro': ProductionConfig }1). main.py加载配置示例:
# main.py from datetime import timedelta from flask import Flask app = Flask(__name__) # 从对象中加载配置 # 优点: 面向对象的设计有利于 减少重复代码 以及 代码解耦合 from config import DevelopmentConfig app.config.from_object(DevelopmentConfig) @app.route('/') def index(): print(app.config.get('PERMANENT_SESSION_LIFETIME')) return "index" if __name__ == '__main__': app.run(debug=True)
2). 切换配置方案: 定义工厂函数, 封装应用的创建过程; 利用环境变量,调用工厂函数, 指定配置并动态创建应用
定义工厂函数main.py代码示例:
from flask import Flask, current_app, Config from config import config_dict # 工厂函数: 根据参数需求, 内部封装对象的创建过程 def create_app(config_type): """封装应用的创建过程""" # 创建应用 flask_app = Flask(__name__) # 根据配置类型取出对应的配置子类 config_class = config_dict[config_type] # 加载普通配置 flask_app.config.from_object(config_class) return flask_app # 创建应用对象 app = create_app('dev') @app.route("/") def index(): print(app.config.get('SQL_URL')) return "index" if __name__ == '__main__': app.run()
3). 加载隐私配置示例
在项目目录外 创建隐私配置文件secret_config.py
, 并以全局变量形式设置隐私配置
# secret_config.py SECRET_KEY = 'heima123' # 隐私配置
主文件main.py
通过环境变量方式来加载隐私配置
# main.py from datetime import timedelta from flask import Flask app = Flask(__name__) # 从环境变量中加载配置 # 优点: 可以保护隐私配置 export ENV_CONFIG="隐私配置的文件路径" app.config.from_envvar('ENV_CONFIG') @app.route('/') def index(): print(app.config.get('SECRET_KEY')) return "index" # if __name__ == '__main__': # app.run(debug=True)
环境变量&终端命令 启动程序
$ export FLASK_APP="main" # 设置内置环境变量 $ export ENV_CONFIG="/xx/secret_config.py" # 设置隐私配置对应的环境变量 $ flask run # 启动web程序
实际开发中的方案:
- 开发阶段, 只加载普通配置
- 生产阶段, 先加载普通配置, 再通过环境变量的方式加载项目以外的隐私配置并覆盖原有配置
代码示例:
def create_app(config_type): """封装应用的创建过程""" # 创建应用 flask_app = Flask(__name__) # 根据配置类型取出对应的配置子类 config_class = config_dict[config_type] # 先加载普通配置 flask_app.config.from_object(config_class) # 再加载隐私配置 silent=True, 配置加载失败也不报错 flask_app.config.from_envvar('ENV_CONFIG', silent=True) return flask_app
加载配置时, 设置参数 silent=True
, 则配置加载失败也不会报错