请求钩子可以对请求的各阶段进行监听, 方便开发者 针对请求完成一些统一的处理, 以便减少重复代码, 作用类比Django中的中间件
开发中中主要会用到以下四种请求钩子:
before_request
after_request
before_first_reques
teardown_request:
from flask import Flask, Response
# 请求钩子: 类比django中间件 ,请求钩子可以对请求的各阶段进行监听, 方便开发者针对请求完成一些统一的处理
app = Flask(__name__)
# 每次执行视图函数之前调用, 对请求进行一些准备处理, 如参数解析, 黑名单过滤, 数据统计等
@app.before_request
def prepare():
print('before_request')
# 每次执行视图函数之后(已经包装为响应对象)调用, 对响应进行一些加工处理, 如设置统一响应头, 设置数据的外层包装
@app.after_request
def process(response:Response): # 必须定义形参接收响应对象
print('after_request:')
# print(response.headers)
# print(response.data)
# print(response.status_code)
return response
# web应用被第一次请求前调用, 可以进行web应用初始化处理, 如数据库连接
@app.before_first_request
def initial():
print('before_first_request')
# 每次执行视图函数之后调用, 无论是否出现异常都会执行, 一般用于请求收尾, 如资源回收, 异常统计
@app.teardown_request # 测试时不要开启调试模式
def request_handle(error): # 必须定义形参来接收具体错误信息, 如果没有错误, error=None
print('teardown_request : %s' % error)
@app.route('/')
def index():
print('执行视图')
a = 1 / 0
return "index"
if __name__ == '__main__':
app.run()
from flask import Flask, Response
app = Flask(__name__)
# 每次执行视图函数之前调用
# @app.before_request
# def prepare():
# print('before_request')
# 另一种语法
def prepare():
print('before_request')
app.before_request(prepare)
@app.route('/')
def index():
print('执行视图')
a = 1 / 0
return "index"
if __name__ == '__main__':
app.run()
--------- project # 工程目录
|------ main.py # 启动文件
|------ user # 用户模块
| |— init.py # 包的初始化文件, 此处创建管理用户模块的蓝图对象
| |— views.py # 视图文件
| |— …
|
|------ home # 首页模块
| |— init.py # 包的初始化文件, 此处创建管理首页模块的蓝图对象
| |— views.py # 视图文件
| |— …
|…
在 home包 的初始化文件 init.py 中, 创建蓝图对象
# home/__init__.py
from flask import Blueprint
# 1. 创建蓝图对象
home_blu = Blueprint("home_b", __name__)
在 home包中创建 views文件, 存放视图函数
定义视图函数时, 使用蓝图对象来定义路由.
# home/views.py
from home import home_blu
# 2. 使用蓝图对象来定义路由
@home_blu.route('/')
def index():
return "index"
想要让蓝图对象能够完成路由定义, 还需要 Flask应用注册蓝图对象
# main.py
from flask import Flask
from home import home_blu
app = Flask(__name__)
# 3.应用注册蓝图对象
app.register_blueprint(home_blu)
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
蓝图的三个使用细节
# home/__init__.py
from flask import Blueprint
# 细节1: 可以通过url_prefix参数给蓝图定义的路由添加统一的URL资源段前缀
home_blu = Blueprint("home_b", __name__, url_prefix='/home')
# 细节3: 蓝图也可以设置请求钩子 只有访问该蓝图定义的路由时才会触发 局部监听
@home_blu.before_request
def home_prepare():
print('home_prepare')
from . import views
# home/views.py
from flask import url_for
from home import home_blu
@home_blu.route('/')
def index():
return "index"
@home_blu.route('/demo1')
def demo1():
# 细节2: 蓝图定义的路由, 其函数标记为 蓝图名.函数名
url1 = url_for('home_b.demo1')
# print(url1)
return 'demo1'
1. 请求上下文
request
session
2. 应用上下文
记录一些和应用有关的数据, 包括current_app和g两个变量
current_app
g
# main.py
from flask import Flask, request, current_app, g
# 上下文变量: 有使用范围 [请求开始, 请求结束]
# 请求上下文: 记录一些和请求有关的数据 request session
# 应用上下文: 记录一些和应用有关的数据 current_app g
import tool
app = Flask(__name__)
@app.route('/')
def index():
# print(request.url)
g.name = 'zs'
tool.func1()
return "index"
@app.route('/demo1')
def demo1():
print(g.name) # 会报错
return 'demo1'
if __name__ == '__main__':
# print(request.url) # 使用范围外, 会报错
app.run(debug=True)
如果是多层函数嵌套,可以使用g变量因为在一个请求内,如果每层嵌套都传参的话会很麻烦
# tool.py
from flask import g, current_app
def func1():
print(g.name)
print(current_app.url_map)
# tool2.func2()
上下文机制 (理解)
问题1: 上下文变量是否为全局变量?
问题2: 上下文为什么设置使用范围?
1. 统一处理
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 '登录成功'
# 需求2: 对部分视图进行访问限制 如个人中心必须登录才能访问
# 解决方案: 使用装饰器封装访问限制 减少代码冗余
def login_required(f): # f = user
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
if g.name: # 用户已登录
return f(*args, **kwargs) # 正常访问视图函数
else: # 用户未登录
abort(401) # 400 语法/参数错误 401 未认证 403 已认证, 权限不足 404 资源不存在 405 请求方式不支持 500 服务器错误
return wrapper
@app.route('/user')
@login_required # user = login_required(user)
def user():
"""个人中心"""
return '访问 %s 的个人中心' % g.name
if __name__ == '__main__':
print(app.url_map)
app.run(debug=True)
functools.wraps
未设置wraps装饰器的情况:
def decorater1(f): # f = demo
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
return f(*args, **kwargs)
return wrapper
@decorater1
def demo():
print("哈哈")
# 调用函数
demo()
# 运行结果
wrapper # 打印闭包函数名
哈哈
设置wraps装饰器的情况:
from functools import wraps
def decorater1(f): # f = demo
@wraps(f) # 设置装饰器
def wrapper(*args, **kwargs):
# 获取函数名
print(wrapper.__name__)
return f(*args, **kwargs)
return wrapper
@decorater1
def demo():
print("哈哈")
# 调用函数
demo()
# 运行结果
demo # 打印原函数名
哈哈
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)
1. 加载配置
rom datetime import timedelta
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'test'
# config属性用于设置配置
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
@app.route('/')
def index():
# 设置session 用于测试配置是否生效
session['name'] = 'zs'
# 读取配置
print(app.config.get('PERMANENT_SESSION_LIFETIME'))
return "index"
if __name__ == '__main__':
app.run(debug=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' # 数据库地址
主文件 main.py 从对象中加载封装的配app.config.from_object()
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)
切换配置
定义工厂函数
定义工厂函数, 封装应用的创建过程
# 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
}
在主文件 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()
动态创建应用
格式: FLASK_APP=“应用所在模块:工厂函数(参数)”
$ export FLASK_APP="main:create_app('dev')" # 设置环境变量, 通过调用函数 设置配置并动态创建Flask应用
$ flask run # 运行web程序