zoukankan      html  css  js  c++  java
  • 5.flask框架-请求钩子[hook]/异常捕获/执行上下文context/Flask-Script 扩展

    一、请求钩子[hook]

    1.1 请求钩子函数简介

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

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

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

    • 在请求结束视图返回数据时,指定数据的交互格式;

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

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

    • before_first_request

      • 在处理第一个请求前执行[项目初始化时的钩子]

    • before_request

      • 在每一次请求前执行

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

    • after_request

      • 如果没有抛出错误,在每次请求后执行

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

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

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

    • teardown_request:

      • 在每一次请求后执行

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

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

    1.2  钩子函数的用法实例

    from flask import Flask,request
    
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    @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_request(response):
        print("----after_request----")
        print("在处理请求以后,执行这个钩子方法")
        print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
        response.headers["Content-Type"] = "application/json"
        response.headers["Company"] = "python oldboy..."
        # 必须返回response参数
        return response
    
    
    @app.teardown_request
    def teardown_request(exc):
        print("----teardown_request----")
        print("在每一次请求以后,执行这个钩子方法")
        print("如果有异常错误,则会传递错误异常对象到当前方法的参数中")
        # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中 
        print(exc)
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        print("-----------视图函数执行了---------------")
        return "<h1>hello world!</h1>"
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")
    • 在第1次请求时的打印:
    ----before_first_request----
    系统初始化的时候,执行这个钩子方法
    会在接收到第一个客户端请求时,执行这里的代码
    ----before_request----
    127.0.0.1 - - [04/Aug/2020 14:40:22] "GET / HTTP/1.1" 200 -
    每一次接收到客户端请求时,执行这个钩子方法
    一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
    -----------视图函数执行了---------------
    ----after_request----
    在处理请求以后,执行这个钩子方法
    一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
    ----teardown_request----
    在每一次请求以后,执行这个钩子方法
    如果有异常错误,则会传递错误异常对象到当前方法的参数中
    None
    • 在第2次请求时的打印:
    ----before_request----
    每一次接收到客户端请求时,执行这个钩子方法
    一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
    127.0.0.1 - - [04/Aug/2020 14:40:49] "GET / HTTP/1.1" 200 -
    -----------视图函数执行了---------------
    ----after_request----
    在处理请求以后,执行这个钩子方法
    一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
    ----teardown_request----
    在每一次请求以后,执行这个钩子方法
    如果有异常错误,则会传递错误异常对象到当前方法的参数中
    None   

    二、异常捕获

    2.1 主动抛出HTTP异常

    • abort 方法

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

    • 参数:

      • code – HTTP的错误状态码

    # abort(404)
    abort(500)

    抛出状态码的话,只能抛出 HTTP 协议的错误状态码

    abort在工作中基本不会被使用,工作中的异常抛出往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。

    abort一般用于权限等页面上错误的展示提示.

     

    2.2 捕获错误

    • errorhandler 装饰器

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

    • 参数:

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

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

    @app.errorhandler(500)
    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__)
    
    # errorhandler 可以捕获指定http状态码的异常
    @app.errorhandler(400)
    def internal_server_error(e):
        print(e)
        return '对不起,参数有误!'
    
    # errorhandler 也可以捕获指定的内置异常或者自定义的异常
    class APIError(Exception):
        pass
    
    # 捕获自定义异常
    @app.errorhandler(APIError)
    def api_error(e):
        print(e)
        return "api请求有误"
    
    # 捕获标准异常[python内置声明的]
    @app.errorhandler(ZeroDivisionError)
    def zero_division_error(e):
        print(e)
        return '除数不能为0'
    
    @app.route("/")
    def index():
        # # 主动抛出HTTP异常
        # if not request.args.get("uname"):
        #     abort(400)
    
        # 抛出自定义异常并进行处理
        # raise APIError("api接口发生异常!!!!")
    
        1/0
    
        return "ok"
    
    
    if __name__ == '__main__':
        app.run(host="0.0.0.0",port=5000,debug=True)

    三、上下文 context

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

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

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

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

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

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

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

     

    3.1 请求上下文(request context)

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

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

    • request

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

    • session

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

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

     

    3.2 应用上下文(application context)

    它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。

    应用上下文对象有:current_app,g

    3.2.1 current_app

    print(app == current_app) #true

    应用程序上下文,用于存储应用实例对象中的变量,可以通过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")

    3.2.2 g变量

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

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

    注意:不同的请求,会有不同的全局变量g

    from flask import Flask,request,session,current_app,g
    
    # 初始化
    app = Flask(import_name=__name__)
    
    # 声明和加载配置
    class Config():
        DEBUG = True
    app.config.from_object(Config)
    
    @app.before_request
    def before_request():
        g.name = "root"
    
    def get_two_func():
        name = g.name
        print("g.name=%s" % name)
    
    def get_one_func():
        get_two_func()
    
    # 编写路由视图
    @app.route(rule='/')
    def index():
        # 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用
        # 请求上下文里面信息来源于每次客户端的请求,所以每个视图中请求上下文的信息都不一样
        # print(session)
    
        # 应用上下文提供给我们使用的变量,也是只能在视图或者被视图调用的地方进行使用,
        # 但是应用上下文的所有数据来源于于app,每个视图中的应用上下文基本一样
        print(current_app.config)   # 获取当前项目的所有配置信息
        print(current_app.url_map)  # 获取当前项目的所有路由信息
        get_one_func()
        return "<h1>hello world!</h1>"
    
    
    if __name__ == '__main__':
        # 运行flask
        app.run(host="0.0.0.0")

    3.3 两者区别

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

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

    from flask import Flask,request,session,current_app,g
    
    app = Flask(__name__)
    
    
    def func():
        # 只有在视图关联的操作下,才可以调用请求上下文提供的request对象和session对象
        print(id(request)) # 140294400781376
        print(id(session)) # 140294400794880
        print(request.method)
    
    app.my_num = 100
    
    def func2():
        """调用应用上下文提供的对象"""
        print(current_app) # <Flask 'manage'> current_app就是flask应用实例对象,就是上面代码中的 app = Flask(__name__)
        print( current_app.my_num ) # 此处证明,这里的current_app就是app的代理对象,app提供的所有属性方法都可以直接通过 current_app
        # 之所以会出现curret_app代理对象,主要原因是 Flask(__name__) 的实例对象对象的变量名是不固定的,每个开发者都会产生不同的名字。
        # 所以在一些插件/第三方模块中如果要调用当前flask实例对象,则可以使用current_app来调用
    
        # print(g) # <flask.g of 'manage'>,global的缩写,代表的全局共享变量
        print(g.my_mobile) # 1331234456 此处在客户端请求视图时,进行的赋值数据就可以被获取到
    
    @app.route("/")
    def index():
        """视图中的代码必然是在用户请求了以后才会被执行"""
        # func()
        g.my_mobile = "1331234456"
        func2()
        print(g)
        print(current_app.url_map)
        return "ok"
    
    if __name__ == '__main__':
        # 此处调用func会出现异常,因此此时客户端不可能发送请求过来
        # func()
    
        with app.app_context():
            g.my_mobile = None
            func2() # 理论上,flask不建议在此处调用这个应用上下文的内容,但是如果硬是要使用,需要基于app.app_context()这个上下文管理器来使用
            # func()  # 这里虽然可以调用请求上下文提供的对象,主要是flask是在项目运行时就已经创建了请求上下文的对象,但是这些对象此时没有被数据填充,所以调用对象方法则会报错
    
        app.run(host="0.0.0.0",port=5000,debug=True)
    课堂实例

     四、Flask-Script 扩展

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

    安装命令:

    pip install flask-script -i https://pypi.douban.com/simple

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

    from flask import Flask
    
    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()

    启动终端脚本的命令:

    # 端口和域名不写,默认为127.0.0.1:5000
    python run.py runserver
    
    # 通过-h设置启动域名,-p设置启动端口 -d
    python run.py runserver -h127.0.0.1 -p8888  # 关闭debug模式
    python run.py runserver -h127.0.0.1 -p8888  -d # 开启debug模式

    4.1 自定义终端命令

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

    1. 引入Command命令基类
    2. 创建命令类必须直接或间接继承Command,并在内部实现run方法或者__call__()方法,
       同时如果有自定义的其他参数,则必须实现get_options或者option_list
    3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。
    from flask import Flask
    from flask_script import Manager,Command,Option
    
    app = Flask(__name__)
    
    """使用flask_script启动项目"""
    manage = Manager(app)
    
    """自定义终端命令"""
    class PrintCommand(Command):
        """命令的相关描述: 打印数据"""
        def get_options(self):
            options = (
                Option('-h', '--host', dest='host', default="127.0.0.1"),
                Option('-q', '--quit', dest='quit', default="退出",type=str),
                Option('-p', '--port', dest='port', type=int, default="7000"),
            )
            return options
    
        # 也可以使用option_list来替代get_options
        # option_list = (
        #     Option('-h', '--host', dest='host', default="127.0.0.1"),
        #     Option('-q', '--quit', dest='quit', default="退出",type=str),
        #     Option('-p', '--port', dest='port', type=int, default="7000"),
        # )
    
        def run(self,host,port,quit): # 没有app对象
            print("测试命令")
            print(f"self.host={host}")
            print(f"self.port={port}")
            print(f"self.quit={quit}")
        # def __call__(self, app, *args, **kwargs): # 会自动传递当前flask实例对象进来
        #     print("测试命令")
        #     print(app)
    
    manage.add_command("print", PrintCommand)
    
    @app.route("/")
    def index():
        return "ok"
    
    if __name__ == '__main__':
        manage.run()
    
    # python manage.py print -h0.0.0.0 -p3000 -qhello
  • 相关阅读:
    leetcode 155. Min Stack 、232. Implement Queue using Stacks 、225. Implement Stack using Queues
    leetcode 557. Reverse Words in a String III 、151. Reverse Words in a String
    leetcode 153. Find Minimum in Rotated Sorted Array 、154. Find Minimum in Rotated Sorted Array II 、33. Search in Rotated Sorted Array 、81. Search in Rotated Sorted Array II 、704. Binary Search
    leetcode 344. Reverse String 、541. Reverse String II 、796. Rotate String
    leetcode 162. Find Peak Element
    leetcode 88. Merge Sorted Array
    leetcode 74. Search a 2D Matrix 、240. Search a 2D Matrix II
    Android的API版本和名称对应关系
    spring 定时任务执行两次解决办法
    解析字符串为泛型的方法
  • 原文地址:https://www.cnblogs.com/yj0405/p/14797721.html
Copyright © 2011-2022 走看看