一、请求全局钩子【hook】
此处的全局钩子,其实就是类似django里面的中间件。 也就是只要调用或者注册了,在http请求响应中是必然执行的。
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
-
在项目运行开始时,建立数据库连接,或创建连接池;
-
在客户端请求开始时,根据需求进行身份识别,权限校验;
-
在请求结束视图返回数据时,指定转换数据的格式,或者记录操作日志;
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子(注意:钩子的装饰器名字是固定):
-
before_first_request
-
在处理第一个请求前执行[项目刚运行第一次被客户端请求时执行的钩子]
-
-
before_request
-
在每一次请求前执行[项目运行后,每一次接收到客户端的request请求都会执行一次]
-
如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
-
-
after_request
-
如果没有抛出错误,在每次请求后执行
-
接受一个参数:视图函数作出的响应
-
在此函数中可以对响应值在返回之前做最后一步修改处理
-
需要将参数中的响应在此参数中进行返回
-
-
teardown_request:
-
在每一次请求后执行
-
接受一个参数:错误信息,如果有相关错误抛出
-
需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
-
代码
1 from flask import Flask, session 2 3 # 应用实例对象 4 app = Flask(__name__) 5 6 """给app单独设置配置项""" 7 # 设置秘钥 8 app.config["SECRET_KEY"] = "my SECRET KEY" 9 10 @app.before_first_request 11 def before_first_request(): 12 """ 13 这个钩子会在项目启动后第一次被用户访问时执行 14 可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置 15 """ 16 print("----before_first_request----") 17 print("系统初始化的时候,执行这个钩子方法") 18 print("会在接收到第一个客户端请求时,执行这里的代码") 19 20 21 @app.before_request 22 def before_request(): 23 """ 24 这个钩子会在每次客户端访问视图的时候执行 25 # 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。.. 26 """ 27 print("----before_request----") 28 print("每一次接收到客户端请求时,执行这个钩子方法") 29 print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据") 30 31 32 @app.after_request 33 def after_request(response): 34 print("----after_request----") 35 print("在处理请求以后,执行这个钩子方法") 36 print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作") 37 38 response.headers["Content-Type"] = "application/json" 39 response.headers["Company"] = "python.Edu..." 40 41 # 必须返回response参数 42 return response 43 44 45 @app.teardown_request 46 def teardown_request(exc): 47 print("----teardown_request----") 48 print("在每一次请求以后,执行这个钩子方法") 49 print("如果有异常错误,则会传递错误异常对象到当前方法的参数中") 50 # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中 51 print(f"错误提示:{exc}") # 异常提示 52 53 54 @app.route("/") 55 def index(): 56 print("-----------视图函数执行了---------------") 57 return "ok" 58 59 if __name__ == '__main__': 60 # 启动项目的web应用程序 61 app.run(host="0.0.0.0", port=5000, debug=False)
在第1次请求时的打印(关闭DEBUG模式,视图代码正确执行,没有异常的情况):
----before_first_request---- 系统初始化的时候,执行这个钩子方法 会在接收到第一个客户端请求时,执行这里的代码 ----before_request---- 每一次接收到客户端请求时,执行这个钩子方法 一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据 -----------视图函数执行了--------------- ----after_request---- 在处理请求以后,执行这个钩子方法 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作 ----teardown_request---- 在每一次请求以后,执行这个钩子方法 如果有异常错误,则会传递错误异常对象到当前方法的参数中 错误提示:None
在第2次请求时的打印(关闭DEBUG模式,视图代码正确执行,没有异常的情况):
----before_request---- 每一次接收到客户端请求时,执行这个钩子方法 一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据 -----------视图函数执行了--------------- ----after_request---- 在处理请求以后,执行这个钩子方法 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作 ----teardown_request---- 在每一次请求以后,执行这个钩子方法 如果有异常错误,则会传递错误异常对象到当前方法的参数中 错误提示:None
在第1次请求时的打印(关闭DEBUG模式,视图执行错误,有异常的情况):
----before_first_request---- 系统初始化的时候,执行这个钩子方法 会在接收到第一个客户端请求时,执行这里的代码 ----before_request---- 每一次接收到客户端请求时,执行这个钩子方法 一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据 -----------视图函数执行了--------------- ----after_request---- 在处理请求以后,执行这个钩子方法 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作 ----teardown_request---- 在每一次请求以后,执行这个钩子方法 如果有异常错误,则会传递错误异常对象到当前方法的参数中 错误提示:division by zero
在第2次请求时的打印(关闭DEBUG模式,视图执行有异常的情况):
----before_request---- 每一次接收到客户端请求时,执行这个钩子方法 一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据 -----------视图函数执行了--------------- ----after_request---- 在处理请求以后,执行这个钩子方法 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作 ----teardown_request---- 在每一次请求以后,执行这个钩子方法 如果有异常错误,则会传递错误异常对象到当前方法的参数中 错误提示:division by zero
钩子装饰器装饰了多个函数的执行顺序如下:
from flask import Flask, session # 应用实例对象 app = Flask(__name__) """给app单独设置配置项""" # 设置秘钥 app.config["SECRET_KEY"] = "my SECRET KEY" @app.before_first_request def before_first_request(): """ 这个钩子会在项目启动后第一次被用户访问时执行 可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置 """ print("----before_first_request----") print("系统初始化的时候,执行这个钩子方法") print("会在接收到第一个客户端请求时,执行这里的代码") @app.before_request def before_request(): """ 这个钩子会在每次客户端访问视图的时候执行 # 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。.. """ print("----before_request----") print("每一次接收到客户端请求时,执行这个钩子方法") print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据") @app.after_request def after_request1(response): print("----after_request1----") print("在处理请求以后,执行这个钩子方法") print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作") response.headers["Content-Type"] = "application/json" response.headers["Company"] = "python oldboy..." # 必须返回response参数 return response @app.after_request def after_request2(response): print("----after_request2----") # 必须返回response参数 return response @app.after_request def after_request3(response): print("----after_request3----") # 必须返回response参数 return response @app.teardown_request def teardown_request(exc): print("----teardown_request----") print("在每一次请求以后,执行这个钩子方法") print("如果有异常错误,则会传递错误异常对象到当前方法的参数中") # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中 print(f"错误提示:{exc}") # 异常提示 @app.route("/") def index(): print("-----------视图函数执行了---------------") return "ok" if __name__ == '__main__': # 启动项目的web应用程序 app.run(host="0.0.0.0", port=5000, debug=False)
执行效果:
----before_first_request---- 系统初始化的时候,执行这个钩子方法 会在接收到第一个客户端请求时,执行这里的代码 ----before_request---- 每一次接收到客户端请求时,执行这个钩子方法 一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据 -----------视图函数执行了--------------- ----after_request3---- ----after_request2---- ----after_request1---- 在处理请求以后,执行这个钩子方法 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作 ----teardown_request---- 在每一次请求以后,执行这个钩子方法 如果有异常错误,则会传递错误异常对象到当前方法的参数中 错误提示:None
结论:以视图执行为中心点,before_request之前(请求过程)的先装饰先执行,after_request之后(响应过程),后装饰的先执行。
二、
主动抛出http异常
-
-
抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)
-
-
参数:
-
from flask import Flask,abort,request app = Flask(import_name=__name__) # 配置类 class Config(object): DEBUG = True # 开启调试模式 # 加载配置 app.config.from_object(Config) @app.route("/") def index(): # try: # 1/0 # except: # abort(500) username = request.args.get("username") if username is None: abort(400) return "ok" if __name__ == '__main__': app.run()
abort,只能抛出 HTTP 协议的错误状态码,一般用于权限等页面上错误的展示提示.
捕获错误
-
-
注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
-
-
参数:
-
code_or_exception – HTTP的错误状态码或指定异常
-
-
@app.errorhandler(500) def internal_server_error(e): return '服务器搬家了'
捕获指定异常类型
@app.errorhandler(ZeroDivisionError) def zero_division_error(e): return '除数不能为0'
代码:
1 from flask import Flask, request, abort 2 3 # 应用实例对象 4 app = Flask(__name__) 5 6 class APIError(Exception): 7 pass 8 9 """异常抛出""" 10 @app.route("/") 11 def index(): 12 username = request.args.get("username") 13 if username is None: 14 abort(400) 15 if username != "xiaoming": 16 raise APIError 17 return "ok" 18 19 20 @app.errorhandler(400) 21 def internal_server_error(e): 22 return { 23 "errno": 400, 24 "errmsg": "参数有误!", 25 } 26 27 28 @app.errorhandler(APIError) 29 def api_error(e): 30 return { 31 "errno": 500, 32 "errmsg": "接口访问有误!", 33 } 34 35 36 if __name__ == '__main__': 37 # 启动项目的web应用程序 38 app.run(host="0.0.0.0", port=5000, debug=True)
三、context
执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
Flask中提供的执行上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。
Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。
-
application 指的就是当你调用
app = Flask(__name__)
创建的这个对象app
; -
request 指的是每次
http
请求发生时,WSGI server
(比如gunicorn/uwsgi)调用Flask.__call__()
之后,在Flask
对象内部创建的Request
对象; -
application 表示用于响应WSGI请求的应用本身,request 表示服务端每次响应客户单的http请求;
-
application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以也就会有多个
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文提供的对象,保存了当前本次请求的相关数据,请求上下文提供的对象有:request、session
所以每次客户端不同的请求,得到的request和session的对象都是同一个,但是内部的数据都是不一样的。
-
request
-
封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
-
-
session
-
用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户的状态信息。还可以通过session.get('name')获取用户的状态信息。
-
请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
代码:
1 from flask import Flask, request, session 2 3 4 app = Flask(__name__) 5 6 app.config.update({ 7 "DEBUG": True, 8 "SECRET_KEY": "sklaasle3k2334" 9 }) 10 11 12 @app.route("/") 13 def index(): 14 print(request, id(request), request.args) 15 return "ok!" 16 17 18 if __name__ == '__main__': 19 # request写在这里,就表示超出了用户请求和响应流程之外的地方了.会报错. 20 # print(request) # RuntimeError: Working outside of request context. requset不能在情趣上传文以外的地址被调用 21 # print(session) 22 app.run()
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理对象,就是所谓本地代理(local proxy)。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。
应用上下文提供的对象有:current_app,g
current_app
应用程序上下文,用于存储flask应用实例对象中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
-
应用的启动脚本是哪个文件,启动时指定了哪些参数
-
加载了哪些配置文件,导入了哪些配置
-
连接了哪个数据库
-
有哪些可以调用的工具类、常量
-
当前flask应用在哪个机器上,哪个IP上运行,内存多大
代码:
1 from flask import Flask,request,session,current_app,g 2 3 # 初始化 4 app = Flask(import_name=__name__) 5 6 # 声明和加载配置 7 class Config(): 8 DEBUG = True 9 app.config.from_object(Config) 10 11 # 编写路由视图 12 @app.route(rule='/') 13 def index(): 14 # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用, 15 # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样 16 print(current_app.config) # 获取当前项目的所有配置信息 17 print(current_app.url_map) # 获取当前项目的所有路由信息 18 19 return "<h1>hello world!</h1>" 20 21 if __name__ == '__main__': 22 # 运行flask 23 app.run(host="0.0.0.0")
g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
g.name='abc' # name是举例,实际要保存什么数据到g变量中,可以根据业务而定,你可以任意的数据进去
代码:
1 from flask import Flask,request,session,current_app,g 2 3 # 初始化 4 app = Flask(import_name=__name__) 5 6 # 声明和加载配置 7 class Config(): 8 DEBUG = True 9 app.config.from_object(Config) 10 11 @app.before_request 12 def before_request(): 13 g.name = "root" 14 15 # 编写路由视图 16 @app.route(rule='/') 17 def index(): 18 # 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用 19 # 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样 20 # print(session) 21 22 # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用, 23 # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样 24 print(current_app.config) # 获取当前项目的所有配置信息 25 print(current_app.url_map) # 获取当前项目的所有路由信息 26 index2() 27 index3() 28 return "ok!" 29 30 31 def index2(): 32 print(g.name) 33 34 def index3(): 35 print(g.name) 36 37 38 if __name__ == '__main__': 39 # 默认情况下,应用上下文提供的对象,也只能在客户端的请求与响应阶段进行调用。 40 # 但是工作中往往,需要在请求响应之外,调用服务端信息,此时,就必须要在请求响应以外的地方调用current_app 41 # 例如:回头使用celery实现异步任务或是定时任务,那么如果任务里面需要操作数据,则必须调用项目配置,那么就一定要使用current_app 42 with app2.app_context(): 43 print(current_app) 44 print(g) 45 app2.run()
两者的区别:
-
-
应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等
应用上下文提供的对象,可以直接在请求上下文中使用,但是如果在请求上下文之外调用,则需要使用
with app.app_context()
四、终端脚本命令
在flask1.1版本之前版本中都是采用flask-script模块来执行终端脚本命令,flask1.1版本以后不再使用这个模块了,因为存在兼容性问题。
flask0.11.0版本以后,flask内置了一个Click模块,这个模块是终端命令模块,可以让我们直接通过Click的装饰器,编写和运行一些终端命令。在flask2.0版本已经不能兼容flask-script模块了,所以需要改成使用Click模块来运行和自定义管理终端命令了。
安装了flask2.0以后,当前项目所在的python环境就提供了一个全局的flask命令,这个flask命令是Click提供的。
# 要使用Click提供的终端命令flask,必须先在环境变量中声明当前flask项目的实例对象所在的程序启动文件。 # 例如:manage.py中使用了 app = Flask(__name__),则manage.py就是程序启动文件 # 使用flask终端命令之前,可以配置2个环境变量。 # 指定入口文件,开发中入口文件名一般:app.py/run.py/main.py/index.py/manage.py/start.py export FLASK_APP=manage.py # 指定项目所在环境 export FLASK_ENV=development # 开发环境,默认开启DEBUG模式 # export FLASK_ENV=production # 生成环境,默认关闭DEBUG模式
默认情况下,flask命令提供的子命令。
flask routes # 显示当前项目中所有路由信息 flask run # 把flask项目运行在内置的测试服务器下 # flask run --host=0.0.0.0 --port=5055 flask shell # 基于项目的应用上下文提供终端交互界面,可以进行代码测试。
import click from flask import Flask, views app = Flask(__name__) # 配置 app.config.update({ "DEBUG": False, }) # 自定义终端命令 @app.cli.command("faker") # 假设这个用于生成测试数据 @click.argument("data", default="user") # data表示生成数据的类型[参数argument是命令调用时的必填参数] @click.option('-n', 'number', type=int, default=1, help='生成的数据量.') # num表示测试数据的生成数量[选项option是命令调用时的可选参数] def faker_command(data, number): """添加测试信息""" print("添加测试信息") print(f"数据类型:data={data}") print(f"生成数量:number={number}") @app.route("/") def index(): return "ok" if __name__ == '__main__': app.run() """ flask faker --help flask faker -n10 user flask faker user """
终端下的运行效果:
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker -n10 user 添加测试信息 数据类型:data=user 生成数量:number=10 (flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker user 添加测试信息 数据类型:data=user 生成数量:number=1 (flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker goods 添加测试信息 数据类型:data=goods 生成数量:number=1
练习:
1. flask2.0的终端下,输入 python manage.py startapp home 则可以在当前目录下创建以下目录和文件 项目目录/ └── home ├── views.py ├── models.py ├── urls.py └── tests.py
代码:
import click, os from flask import Flask app = Flask(__name__) # 配置 app.config.update({ "DEBUG": False }) @app.cli.command("startapp") @click.argument("name") # @click.option('-n', 'name', help='app name') def startapp(name): """生成子模块或子应用""" if os.path.isdir(name): print(f"当前{name}目录已存在!请先处理完成以后再创建。") return os.mkdir(name) open(f"{name}/views.py", "w") open(f"{name}/models.py", "w") open(f"{name}/documents.py", "w") open(f"{name}/ws.py", "w") open(f"{name}/services.py", "w") open(f"{name}/urls.py", "w") open(f"{name}/test.py", "w") print(f"{name}子应用创建完成....") @app.route("/") def index(): return "ok" if __name__ == '__main__': app.run()
终端调用:
flask startapp home flask startapp users
六、jinja2模板引擎
Flask内置的模板语言Jinja2,它的设计思想来源于 Django 的模板引擎DTP(DjangoTemplates),并扩展了其语法和一系列强大的功能。
-
Flask提供的 render_template 函数封装了该模板引擎Jinja2
-
render_template
app = Flask(__name__,template_folder='templates')
2、
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ title }}</title> </head> <body> <h1>{{ content }}</h1> </body> </html>
3、在视图函数设置渲染模板并设置模板数据
from flask import Flask, render_template # 默认情况下,flask默认直接支持视图加载模板的。 # Flask类在实例化的时候,默认当前根目录下的templates目录作为模板目录 app = Flask(__name__, template_folder="templates") # 配置 app.config.update({ "DEBUG": True }) @app.route("/") def index(): data = { "title": "我的模板标题", "content": "我的模板内容" } return render_template("index.html", **data) @app.route("/user/<id>") def user(id): title = "User Center" content = "我的个人中心" print(locals()) # {'id': '100', 'title': 'User Center', 'content': '我的个人中心'} return render_template("user.html", **locals()) if __name__ == '__main__': app.run()