Flask-2

Posted 贰号猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)

 

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

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)
 

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)

 


执行上下文[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)
​
 

 

应用上下文(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")
 

 

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)
​
 

 

两者区别:

  • 请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端的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()

 

启动终端脚本的命令:

# 端口和域名不写,默认为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()

使用效果:

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()
 

终端下的运行效果:

(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()

 

终端调用:

flask startapp home
flask startapp users

 

 

 

 

 

 

 

 

 

 

 

Flask

第一章 初识flask

01 初识Flask

第二章 flask注册路由

01 详解url

第三章 flask视图

01 HTTP请求
02 视图函数和视图类

第四章 模板

01 模板

第五章 数据库

01 数据库

第六章 flask脚本

01 Flask_脚本

第七章 flask小点补充

01 Flask知识点补充

第八章 flask高级

01 flask高级
02 flask之分析线程和协程
03 阅读flask上下文前夕补充
04 flask请求上下文
05 数据库连接池

以上是关于Flask-2的主要内容,如果未能解决你的问题,请参考以下文章

Flask 2.0 版本发布

Flask数据库迁移

学习flask---2

flask 2 进阶

flask_sqlalchemy简单用法

apache+mod_wsgi+flask 环境搭建