Flask-2

请求全局钩子[hook]

此处的全局钩子,其实就是类似django里面的中间件。 也就是只要调用或者注册了,在http请求响应中是必然执行的。

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在项目运行开始时,建立数据库连接,或创建连接池;

  • 在客户端请求开始时,根据需求进行身份识别,权限校验;

  • 在请求结束视图返回数据时,指定转换数据的格式,或者记录操作日志;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子(注意:钩子的装饰器名字是固定):

  • before_first_request

    • 在处理第一个请求前执行[项目刚运行第一次被客户端请求时执行的钩子]

  • before_request

    • 在每一次请求前执行[项目运行后,每一次接收到客户端的request请求都会执行一次]

    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用

  • after_request

    • 如果没有抛出错误,在每次请求后执行视图结束以后,都会执行一次

    • 接受一个参数:视图函数作出的响应

    • 在此函数中可以对响应值在返回之前做最后一步修改处理

    • 需要将参数中的响应在此参数中进行返回

  • teardown_request:

    • 在每一次请求后执行

    • 接受一个参数:错误信息,如果有相关错误抛出

    • 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。

 

代码

复制代码
from flask import Flask
​
​
app = Flask(__name__)
​
# @app.before_first_request  #这个在3.2版本后被废弃了
def before_first_request():
    """
    当项目启动以后,首次被客户端访问时自动执行被 @app.before_first_request 所装饰的函数
    用于项目初始化
    可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
    :return:
    """
    print("before_first_request执行了!!!!")
    print("系统初始化的时候,执行这个钩子方法")
    print("会在接收到第一个客户端请求时,执行这里的代码")
​
​
app.before_first_request_funcs.append(before_first_request) # 3.2之后用这个
​
​
@app.before_request
def before_request():
    """
    每次客户端访问,视图执行之前,都会自动执行被 @app.before_request 所装饰的函数
    用于每次视图访问之前的公共逻辑代码的运行[身份认证,权限判断]
    :return:
    """
    print("before_request执行了!!!!")
​
​
@app.after_request
def after_request(response):
    """
        每次客户端访问,视图执行之后,都会自动执行被 @app.after_request 所装饰的函数
    用于每次视图访问之后的公共逻辑代码的运行[返回结果的加工,格式转换,日志记录]
    :param response: 本次视图执行的响应对象
    :return:
    """
    print("after_request执行了!!!!!")
    response.headers["Content-Type"] = "application/json"
    response.headers["Company"] = "python.Edu..."
    return response
​
​
@app.teardown_request
def teardown_request(exc):
    """
    每次客户端访问,视图执行报错以后,会自动执行 @app.teardown_request 所装饰的函数
    注意:在flask2.2之前,只有在DEBUG=False时,才会自动执行 @app.teardown_request 所装饰的函数
    :param exc: 本次出现的异常实例对象
    :return:
    """
    print("teardown_request执行了!!!!!")
    print(f"错误提示:{exc}")  # 异常提示
​
​
@app.route("/")
def index():
    print("----------------视图执行了!!!!--------------")
    return "ok"
​
​
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)
View Code
复制代码

 

通过打印终端,可以看到各种钩子执行的时间:

复制代码
before_first_request执行了!!!!
before_request执行了!!!!
----------------视图执行了!!!!--------------
after_request执行了!!!!!
teardown_request执行了!!!!!
错误提示:None
​
before_request执行了!!!!
----------------视图执行了!!!!--------------
after_request执行了!!!!!
teardown_request执行了!!!!!
错误提示:None
 
复制代码

 

异常抛出和捕获异常

主动抛出HTTP异常

  • abort 方法

    • 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)

  • 参数:

    • code – HTTP的错误状态码

复制代码
from flask import Flask, request, abort
​
​
app = Flask(__name__)
​
​
@app.route("/")
def index():
    password = request.args.get("password")
    if password != "123456":
        # 主动抛出异常!
        # abort的第一个参数:表示本次抛出的HTTP异常状态码,后续其他参数,表示错误相关的提示内容。
        abort(400)
    return "ok"
​
​
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)
View Code
复制代码
 

abort,只能抛出 HTTP 协议的错误状态码,一般用于权限等页面上错误的展示提示.

abort 在有些前后端分离的项目里面不会被使用,往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。

 

捕获错误

  • app.errorhandler 装饰器

    • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法

  • 参数:

    • code_or_exception – HTTP的错误状态码或指定异常

  • 例如统一处理状态码为500的错误给用户友好的提示:

@app.errorhandler(500)  # 此处的errorhandler的参数不仅可以是abort抛出的HTTP异常,也可以是系统抛出的。
def internal_server_error(e):
    return '服务器搬家了'

 

  • 捕获指定异常类型

@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除数不能为0'

 

代码:

复制代码
from flask import Flask, request, abort
​
​
app = Flask(__name__)
​
class NetWorkError(Exception):
    pass
​
@app.route("/")
def index():
    password = request.args.get("password")
    if password != "123456":
        # 主动抛出HTTP异常!
        # abort的第一个参数:表示本次抛出的HTTP异常状态码,后续其他参数,表示错误相关的提示内容。
        # abort(400, "密码错误!")
        raise NetWorkError("网络请求出错!") # 抛出任意异常raise
        # print(hello)
    return "ok"
​
​
# @app.errorhandler的参数是异常类型或者HTTP状态码
@app.errorhandler(NameError)
def NameErrorFunc(exc):
    """
    针对变量命名的异常处理
    :param exc:
    :return:
    """
    print(exc.__traceback__)
    return {"error": f"{exc}"} #饭hi比较友好的提示
​
​
@app.errorhandler(400)
def error_400(exc, *args, **kwargs):
    print(exc.__traceback__)
    print(exc.code)        # 上面abort传递的错误状态码
    print(exc.description) # 上面abort传递的错误描述
    return {"error": f"{exc.description}"}
​
​
@app.errorhandler(404)
def error_404(exc):
    print(exc.code)        # 上面abort传递的错误状态码
    print(exc.description) # 上面abort传递的错误描述
    return {"error": "当前页面不存在!"}
​
@app.errorhandler(NetWorkError)
def network_error(exc):
    return {"error": f"{exc}"} #
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)
View Code
复制代码

 


执行上下文[context]

执行上下文:即语境,语意,在程序中可以理解为在代码执行到某一行时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。

Flask中提供的执行上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。

Flask中有两种上下文,请求上下文(request context)和应用上下文(application context)。

  1. application 指的就是当服务端调用app = Flask(__name__)创建的这个对象app

  2. request 指的是每次客户端发生http请求时,WSGI server(比如uwsgi/gunicorn)调用Flask.__call__()之后,在Flask对象内部创建本次客户端的Request对象;

  3. application 表示用于响应WSGI请求的应用本身,request 表示服务端每次响应客户端的http请求;

  4. application的生命周期大于request,一个application存活期间,可能发生多次http请求,所以也就会有多个request

 

请求上下文(request context)

思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等

在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文提供的对象,保存了当前本次请求的相关数据,请求上下文提供的对象有:request、session

所以每次客户端发生不同的HTTP请求时,得到的request和session的对象都是同一个,但是内部的数据都是不一样的。

  • request

    • 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。

  • session

    • 用来记录请求会话中的信息,针对的是会话状态。举例:session['name'] = user.id,可以记录用户的状态信息。还可以通过session.get('name')获取用户的状态信息。

注意:

请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用。

代码:

复制代码
from flask import Flask, request, session
​
​
app = Flask(__name__)
​
app.config["SECRET_KEY"] = "my secret key"def test():
    print(request) # 请求上下文所提供的对象[request或session]只能被视图直接或间接调用!
​
@app.route("/")
def index():
    print(request)
    print(session)
    test()
    return "ok"
​
​
if __name__ == '__main__':
    # print(request) # 没有发生客户端请求时,调用request会超出请求上下文的使用范围!
    app.run(host="0.0.0.0", port=5000, debug=True)
​
 
View Code
复制代码

 

应用上下文(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上运行,内存多大

复制代码
from flask import Flask,request,session,current_app,g
​
# 初始化
app = Flask(import_name=__name__)
​
# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)
​
# 编写路由视图
@app.route(rule='/')
def index():
    # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
    # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
    print(current_app.config)   # 获取当前项目的所有配置信息
    print(current_app.url_map)  # 获取当前项目的所有路由信息
return "<h1>hello world!</h1>"if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0")
 
View Code
复制代码

 

g变量

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别

g.name='abc' # name是举例,实际要保存什么数据到g变量中,可以根据业务而定,你可以任意的数据进去

注意:

客户端不同的请求,会有不同的全局变量g,或者说,每一个客户端都拥有属于自己的g变量。

复制代码
from flask import Flask, current_app, g
​
​
app = Flask(__name__)
​
app.config["SECRET_KEY"] = "my secret key"
​
@app.route("/")
def index():
    print(app == current_app)  # current_app就是app应用实例对象在视图中的本地代理对象
    print(g)  # 全局数据存储对象,用于保存服务端存储的全局变量数据[可以理解为用户级别的全局变量存储对象]
    t1()
    t2()
    return "ok"def t1():
    # 存储数据
    g.user_id = 100def t2():
    # 提取数据
    print(g.user_id)
​
if __name__ == '__main__':
    # print(app)
    # with app.app_context(): # 构建一个应用上下文环境
    #     print(current_app)
    # print(request) # 没有发生客户端请求时,调用request会超出请求上下文的使用范围!
    app.run(host="0.0.0.0", port=5000, debug=True)
​
 
View Code
复制代码

 

两者区别:

  • 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端的HTTP请求。

  • 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

    应用上下文提供的对象,可以直接在请求上下文中使用,但是如果在请求上下文之外调用,则需要使用

    with app.app_context()创建一个应用上下文环境才能调用。

终端脚本命令

flask在0.11版本之前都是采用flask-script第三方模块来实现终端脚本命令的执行,flask在0.11版本以后不再使用这个模块了,因为存在兼容性问题,所以内置了Click模块来实现终端脚本命令的执行。

 

flask1.0的终端命令使用[了解]

flask-script模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

官方文档:https://flask-script.readthedocs.io/en/latest/

安装命令:

# 在实际工作中需要不同的python版本需要这样做
conda create -n py38 python=3.8
conda activate py38
​
pip install -U flask==1.1.4
pip install -U flask-script -i https://pypi.douban.com/simple

 

集成 Flask-Script到flask应用中,创建一个主应用程序,一般我们叫manage.py/run.py/main.py都行。

manage.py,代码:

复制代码
from flask import Flas 
​
app = Flask(__name__)
​
"""使用flask_script启动项目"""
from flask_script import Manager
manage = Manager(app)
​
@app.route('/')
def index():
    return 'hello world'if __name__ == "__main__":
    manager.run()
View Code
复制代码

 

启动终端脚本的命令:

复制代码
# 端口和域名不写,默认为127.0.0.1:5000
python manage.py runserver
​
# 通过-h设置启动域名,-p设置启动端口 -d
python manage.py runserver -h0.0.0.0 -p8888     # 关闭debug模式
python manage.py runserver -h0.0.0.0 -p8888  -d # 开启debug模式
​
​
# 进入flask交互终端,在这个终端下,可以直接调用flask代码进行测试。
python manage.py shell
复制代码

 

安装flask==1.1.4版本启动项目时,如果出现错误如下:

from markupsafe import soft_unicode

 

则找到报错代码位置,修改如下:

from markupsafe import soft_str as soft_unicode

 

 

自定义终端命令

Flask-Script 还可以为当前应用程序添加脚本命令

1. 引入Command命令基类
    from flask_script import Command
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法或者__call__()方法(这两个方法互斥的只能写一个),
   同时如果有自定义的其他参数,则必须实现get_options方法或者option_list属性来接收参数
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。

manage.py,代码:

复制代码
from flask import Flask
​
​
app = Flask(__name__)
​
"""使用flask_script管理项目"""
# 注册manage插件
from flask_script import Manager
manager = Manager(app)
​
from abc import ABC
from flask_script import Command, Option
​
class PrintCommand(Command, ABC):
    """
    命令的相关描述: 打印数据
    """
    def get_options(self):
        # 必须返回选项
        return (
            # Option('简写选项名', '参数选项名', dest='变量名', type=数据类型, default="默认值"),
            Option('-h', '--host', dest='host', type=str, default="127.0.0.1"),
            Option('-p', '--port', dest='port', type=int, default=8000),
            Option('-d', '--debug', dest='debug', type=bool, default=False)
        )
​
    # 也可以使用option_list来替代get_options
    # option_list = (
    #     Option('-h', '--host', dest='host', type=str, default="127.0.0.1"),
    #     Option('-p', '--port', dest='port', type=int, default="7000"),
    #     Option('-d', '--debug', dest='debug', type=bool, default=False)
    # )
# 没有flask的应用实例对象---->app对象
    # def run(self, host, port, debug):
    #     print("测试命令")
    #     print(f"self.host={host}")
    #     print(f"self.port={port}")
    #     print(f"self.debug={debug}")
def __call__(self, app, host, port, debug):  # 会自动传递当前flask实例对象进来
        print(f"测试命令,{app}")
        print(f"self.host={host}")
        print(f"self.port={port}")
        print(f"self.debug={debug}")
​
​
# manage.add_command("终端命令名称", 命令类)
manager.add_command("print", PrintCommand)  # python manage.py print
​
@app.route("/")
def index():
    return "ok"if __name__ == '__main__':
    manager.run()
View Code
复制代码

使用效果:

python manage.py

 

复制代码
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ python manage.py print -h 0.0.0.0 -p 8000
测试命令
self.host=0.0.0.0
self.port=8000
self.debug=False
​
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ python manage.py print -h 0.0.0.0 -p 8000 -d true
测试命令
self.host=0.0.0.0
self.port=8000
self.debug=True
​
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ python manage.py print -h 0.0.0.0 -d true
测试命令
self.host=0.0.0.0
self.port=8000
self.debug=True
​
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ python manage.py print --host=0.0.0.0 -debug=true
测试命令
self.host=0.0.0.0
self.port=8000
self.debug=True
 
复制代码

 

flask2.0的终端命令使用

flask0.11.0版本以后,flask内置了一个Click模块,这个模块是终端命令模块,可以让我们直接通过Click的装饰器,编写和运行一些终端命令。在flask2.0版本已经不能兼容flask-script模块了,所以需要改成使用Click模块来运行和自定义管理终端命令了。

文档地址:https://dormousehole.readthedocs.io/en/latest/cli.html#id10

click文档:https://click.palletsprojects.com/en/8.0.x/

conda activate flask
pip install -U flask==2.2.2

 

安装了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_DEBUG=True   # 开发环境,开启DEBUG模式
# export FLASK_DEBUG=False    # 生产环境,关闭DEBUG模式
复制代码

 

默认情况下,flask命令提供的子命令。

flask routes  # 显示当前项目中所有路由信息
flask run     # 把flask项目运行在内置的测试服务器下
# flask run --host=0.0.0.0 --port=5055
flask shell   # 基于项目的应用上下文提供终端交互界面,可以进行代码测试。

 

Click自定义终端命令

官方文档:https://flask.palletsprojects.com/en/2.2.x/cli/

复制代码
import click
from flask import Flask
​
​
app = Flask(__name__)
​
​
@app.cli.command("faker")  # 假设这个用于生成测试数据
@click.argument("data", type=str, default="user") # argument位置参数
@click.argument("position", type=str, default="mysql") # argument位置参数
@click.option('-n', '--number', 'number', type=int, default=1, help='生成的数据量.')  # option选项参数
def faker_command(data, position, number):
    """
    命令的说明文档:添加测试信息
    """
    print("添加测试信息")
    print(f"数据类型:data={data}")
    print(f"数据类型:position={position}")
    print(f"生成数量:number={number}")
​
​
@app.route("/")
def index():
    return "ok"if __name__ == '__main__':
    app.run()
View Code
复制代码
 

终端下的运行效果:

(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker -n10 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  # 位置参数
数据类型:data=mysql
生成数量:number=1
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker goods redis
添加测试信息
数据类型:data=goods  # 位置参数
数据类型:data=redis
生成数量:number=1
(flask) moluo@ubuntu:~/Desktop/flaskdemo$ flask faker goods -n200
添加测试信息
数据类型:data=goods  # 位置参数
数据类型:data=mysql
生成数量:number=200  #选项参数
 
复制代码

 

练习: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()
View Code
复制代码

 

终端调用:

flask startapp home
flask startapp users

 

 

 

 

 

 

 

 

 

 

 
posted @   贰号猿  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示