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
  • 相关阅读:
    Cloudstack安装(二)
    Cloudstack介绍(一)
    [Python爬虫] 之二十二:Selenium +phantomjs 利用 pyquery抓取界面网站数据
    [Python爬虫] 之二十一:Selenium +phantomjs 利用 pyquery抓取36氪网站数据
    [Python爬虫] 之二十:Selenium +phantomjs 利用 pyquery通过搜狗搜索引擎数据
    [Python爬虫] 之十九:Selenium +phantomjs 利用 pyquery抓取超级TV网数据
    [Python爬虫] 之十八:Selenium +phantomjs 利用 pyquery抓取电视之家网数据
    [Python爬虫] 之十七:Selenium +phantomjs 利用 pyquery抓取梅花网数据
    pycharm2016序列号失效问题解决办法
    scrapy-splash抓取动态数据例子十六
  • 原文地址:https://www.cnblogs.com/yj0405/p/14797721.html
Copyright © 2011-2022 走看看