• flask入门教程之请求与响应


    Flask是一个轻量级的web开发框架,依赖jinja2和Werkzeug WSGI服务的一个微型框架。
    官方文档:https://flask.palletsprojects.com/en/2.0.x/
    中文文档:http://docs.jinkan.org/docs/flask/
    中文文档的版本会比较低,如果英语OK的话,可以看官方文档。

    安装&入门例子

    使用pip命令安装:pip install flask

    新建一个py脚本,这里是flask_hello.py脚本,脚本内容如下:

    from flask import Flask
    app =  Flask(__name__)
    
    @app.route("/")
    def hello_world():
        return "

    Hello World!

    "
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    脚本中,需要导入Flask类,并创建Flask对象app,然后使用app.route()装饰器设置hello_world()的路由(即接口路径)。
    脚本编辑好之后,启动该脚本的flask服务,切换到脚本所在目录,执行flask run命令:flask --app flask_hello run(flask_hello为脚本名称)
    在这里插入图片描述
    在这里插入图片描述

    启动flask服务的命令,不同的flask版本不太一样,我这里是2.2.2版本的,如果想知道其他的版本的,可以去官网选择对应版本查看
    在这里插入图片描述

    启动flask服务还有另一个方法,就是在脚本里写上启动代码:

    from flask import Flask
    app =  Flask(__name__)
    
    @app.route("/")
    def hello_world():
        return "

    Hello World!

    --python命令启动"
    if __name__=="__main__": # 启动flask服务 app.run()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    python命令执行该脚本,结果如下:
    在这里插入图片描述
    在这里插入图片描述

    路由

    我们访问http或https链接(即url)的时候,是有接口路径,在flask服务中,可以根据接口路径确定是哪个程序处理该请求。路由有静态路由和动态路由。设置路由的方式是先定义一个Flask对象,然后根据Flask对象中的route(rule: str, **options: t.Any)定义路由

    静态路由

    静态路由即路径是固定的,没有变动的,如入门的例子中,@app.route("/")就是静态的根路由。
    示例如下:
    py脚本内容定义如下:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/search/video")
    def search_videos():
        return "查找视频资源"
    
    @app.route("/search/picture")
    def search_pictures():
        return "查找图片资源"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其中,/search/video/search/picture是定义的静态路由,访问时的接口路径无变动
    在这里插入图片描述

    动态路由

    有时候,接口路径是变动的,比如查询用户信息,查询用户A、用户B、用户C,并且用户信息不可控,不能每一个用户都造个路由,flask提供了动态路由,将变动的信息定义为一个变量。
    添加路由时,将变动的部分使用<变量名>定义,然后被装饰的函数或方法,参数的名称与定义的变量名一致,这样函数或方法就可以使用该变量及其值。
    示例:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/userinfo//info")
    def search_videos(user_id):
        return f"这个是{user_id}的用户信息"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行脚本,启动flask服务后,访问如下:
    在这里插入图片描述
    从结果中可以看出,/userinfo/后/info前的内容可以变动,也能处理成功。

    限定类型

    动态路由中,可以给变量限制其数据类型,格式:<类型:变量名>
    数据类型有如下:

    类型描述
    string(缺省值)接受任何不包含斜杠的文本
    int接受正整数
    float接受正浮点数
    path类似string,但可以包含斜杠
    uuid接受UUID字符串

    示例:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/userinfo//info")
    def search_videos(user_id):
        return f"用户编号{user_id}的信息如下:巴拉巴拉巴拉。。。"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    脚本执行后,flask服务启动,访问如下:
    在这里插入图片描述

    在动态路由中的变量的数据类型限制比较简单,如果是复杂的校验,则可以在函数内部进行校验。

    地址尾部的/

    有时候我们在接口路径末尾加/和不加/的时候,访问是一致的,但有些加了/访问又报错,flask对路由尾部的/是有做处理的:

    • flask添加路由的尾部带有/:访问时加/和不加/效果是一样的
    • flask添加路由的尾部没有/:访问时加/访问会报错,未加/才能访问成功

    示例如下:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/add1/")
    def add1():
        return f"路由的末尾有'/'"
    
    @app.route("/add2")
    def add2():
        return f"路由的末尾没有'/'"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    启动flask服务后,执行结果如下:
    在这里插入图片描述

    请求方式设置

    请求方式有GET、POST、PUT等等,常用的有GET和POST,我们可以在Flask()对象设置路由时传入methods参数来设置路由的请求方式,格式:@Flask().route(rule, methods=["GET", xxx, xxx])
    脚本内容如下:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/get_demo/info", methods=["GET"])
    def only_get():
        return "只支持get请求方式"
    
    @app.route("/post_and_get_demo/info", methods=["GET", "POST"])
    def both_post_get():
        return "支持get和post的请求方式"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    启动对应的flask服务后,接口访问如下:
    在这里插入图片描述

    请求数据处理

    发送请求的时候,必定会接触到上传的参数,然后根据上传的参数进行逻辑处理。那我们怎么获取请求上传的数据呢?flask框架提供了request类,可以通过该类获取对应的请求数据。

    属性/方法说明
    args记录了请求中的查询参数,返回类似于字典的数据
    json记录了请求中的json数据(dict类型)
    files记录了请求上传的文件,返回类似于字典的数据
    form记录了请求中的表单数据,返回类似于字典的数据
    method记录了请求使用的HTTP方法
    url记录了请求中的URL地址
    host记录了请求的域名
    headers记录了请求的请求头信息,返回类似于字典的数据

    示例:

    from flask import Flask, request
    
    wen = Flask(__name__)
    
    @wen.route("/request/info", methods=["GET"])
    def get_request_info():
        print(f"request.args类型:{type(request.args)}")
        print(f"request.args = {request.args}\n")
    
        print(f"request.json类型:{type(request.json)}")
        print(f"request.json = {request.json}\n")
    
        print(f"request.files类型:{type(request.files)}")
        print(f"request.files = {request.files}\n")
    
        print(f"request.form类型:{type(request.form)}")
        print(f"request.form = {request.form}\n")
    
        print(f"request.method类型:{type(request.method)}")
        print(f"request.method = {request.method}\n")
    
        print(f"request.url类型:{type(request.url)}")
        print(f"request.url = {request.url}\n")
    
        print(f"request.host类型:{type(request.host)}")
        print(f"request.host = {request.host}\n")
    
        print(f"request.headers类型:{type(request.headers)}")
        print(f"request.headers = {request.headers}\n")
    
        return {"status": 0, "message": "get success"}
    
    if __name__ == "__main__":
        wen.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    执行python脚本(即启动flask服务),发送请求
    在这里插入图片描述
    控制台输出如下:
    在这里插入图片描述

    请求参数处理

    url链接中,有时会有请求参数,我们可以通过flask.request下的args属性获取到请求参数
    示例:

    from flask import Flask, request
    
    wen = Flask(__name__)
    
    @wen.route("/get/info", methods=["GET", "POST"])
    def get_request_info():
        data = {}
        data.update(request.args)
        print(data)
        return data
    
    if __name__ == "__main__":
        wen.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动flask服务后,发送请求,查看响应报文:
    在这里插入图片描述
    在这里插入图片描述

    请求体json数据处理

    发送请求时,有时请求体数据是json数据,flask可以通过flask.request下的json属性获取到json请求体数据
    脚本内容如下:

    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route("/post/info", methods=[ "POST"])
    def post_json_data():
        print(f"请求json报文:{request.json}")
        return {"code": 0, "body_data": request.json}
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    启动flask服务后,发送post请求,结果如下:
    在这里插入图片描述
    在这里插入图片描述

    表单请求数据处理

    发送请求时,有时请求体数据是json数据,flask可以通过flask.request下的form属性获取到form表单数据
    脚本内容如下:

    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route("/form/info", methods=[ "POST"])
    def post_form_data():
        print(f"form数据:{request.form}")
        return {"code": 0, "form_data": request.form}
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行脚本,启动flask服务后,发送请求,相关结果如下:
    在这里插入图片描述
    在这里插入图片描述

    文件请求数据处理

    有些请求是上传文件,flask可以通过flask.request下的files属性获取到上传的文件数据。

    • request.files:返回类似一个字典类型的数据,包含了所有上传的文件对象FileStorage,可以通过get(“xxx”)获取到具体的文件对象,如request.files.get("data")表示获取参数是data的文件对象

    文件对象FileStorage有多个方法支持操作,常用的有:

    属性/方法说明
    filename文件名称
    stream文件流对象
    save(dst, buffer_size=16384)将文件对象的内容保存到指定路径dst

    脚本内容如下:

    from flask import Flask, request
    
    app = Flask(__name__)
    
    @app.route("/form/info", methods=[ "POST"])
    def post_form_data():
        print(f"files数据:{request.files}")
        fo = request.files.get("data")
        for data in fo.stream:
            print(data.decode("utf-8"), end="")
        return {"code": 0, "file_name": fo.filename}
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行脚本(启动flask服务),发送文件上传的请求
    在这里插入图片描述
    控制台输出内容为:
    在这里插入图片描述

    响应数据设置

    返回的响应报文有多种格式,比如文本、json、html等。

    返回文本

    响应报文如果是文本内容时,直接在函数内return字符串内容,flask会封装成文本格式的响应报文
    示例:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/text", methods=[ "POST"])
    def text_resp():
        return "响应报文内容是文本内容"
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行脚本后,postman发送请求如下:
    在这里插入图片描述
    从响应报文中可以看到响应头的Content-Type是text/html,响应报文体是个文本内容

    设置状态码和请求头

    除了设置响应报文体外,我们还可以返回元组,元组必须包含报文体,可以包含响应状态码和响应头,返回的元组设置如下:

    • (response, status)
    • (response, headers)
    • (response, status, headers)

    响应状态码status默认为200,headers会根据response进行简单的调整。
    示例:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/demo1", methods=[ "POST"])
    def response_status():
        return "响应报文内容是文本内容", 201
    
    @app.route("/demo2", methods=[ "POST"])
    def resp_json_status():
        return {"name": "wenxiaoba", "age": 18}, 201
    
    @app.route("/demo3", methods=[ "POST"])
    def resp_headers():
        return "设置了响应报文和响应头", {"token": "123456abcde", "address": "where are you"}
    
    @app.route("/demo4", methods=[ "POST"])
    def resp_status_headers():
        return "设置了响应报文和响应头", 202, {"token": "123456abcde", "address": "where are you"}
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    执行脚本(启动flask服务),发送请求,相关内容如下:
    请求/demo1时,可以看到响应报文体和响应状态码是设置的内容
    在这里插入图片描述
    请求/demo2时,响应报文体是json格式的
    在这里插入图片描述
    在这里插入图片描述
    请求/demo3时,返回了设置的响应报文体,响应状态码是flask默认的200,响应头也有包含设置的字段
    在这里插入图片描述
    在这里插入图片描述
    请求/demo4时,返回的响应报文体、状态码均为设置的,响应头也包含了设置的字段
    在这里插入图片描述
    在这里插入图片描述

    返回json

    大多数接口返回的响应数据是json格式,flask框架中,有2种方式返回json格式的响应报文:

    • 直接返回dict:flask底层会将dict转成json格式
    • 使用jsonify()方法,通过参数传入键值对或字典返回json数据,需要导入flask.jsonify

    返回dict(即字典)数据时,客户端实际接收到的是json数据
    在这里插入图片描述
    /jsonify1接口是通过jsonify()传入字典参数处理的响应报文
    在这里插入图片描述
    /jsonify2接口是通过jsonify()传入键值对参数处理的响应报文
    在这里插入图片描述

    返回其他格式

    除了经常用的json和文本格式外,还会返回其他格式,比如html、xml等,我们可以使用render_template()函数进行处理。
    使用render_template()函数需要先导入flask.render_template,函数如下:
    render_template(template_name_or_list, **context)

    • template_name_or_list:一般传入模板的名称,模板必须存放在与脚本同级的templates目录下
    • context:上下文,一般模板里面有些要替换的变量(格式是:{{变量名}}),可以通过这个参数传入,传入键值对,如:变量名=值

    py脚本内容如下:

    from flask import Flask, render_template
    
    app = Flask(__name__)
    
    @app.route("/gethtml", methods=["POST"])
    def get_html():
        return render_template('demo.html', name="wenxiaoba")
    
    @app.route("/getxml", methods=["POST"])
    def get_xml():
    	# 由于返回的是xml数据,所以需要设置响应头的content-type内容,说明传输的是xml数据,否则会被当成html数据
        return render_template('data.xml', book_name="随便起的书名"), {"content-type": "application/xml"}
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在脚本同一目录下,创建templates目录,在templates目录下创建demo.html、data.xml文件。
    demo.html的内容如下:

    <html>
        <body>
            <h1>{{name}}的博客h1>
            <p>这是一个非常勤奋的娃,她想做测试开发,赚更多的钱p>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    data.xml的内容如下:

    <book>
        <body>
            <book_name>python简明教程book_name>
            <author>谁谁谁author>
            <price>56price>
        body>
        <body>
            <book_name>{{book_name}}book_name>
            <author>不知道author>
            <price>62.3price>
        body>
    book>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    执行py脚本(启动flask服务),发送请求,结果如下:
    在这里插入图片描述
    在这里插入图片描述

    设置额外数据——make_response()

    如果想设置更多的响应信息,比如cookie,可以通过make_response()获取一个响应对象,可以在该对象中设置cookie、请求头等内容。
    示例:
    py脚本内容如下:

    from flask import Flask, request, make_response, render_template
    
    app = Flask(__name__)
    
    @app.route("/setcookie", methods=["POST"])
    def set_cookie():
        type = request.json.get("type")
        if "html" == type:	# 如果请求上传的json中,type为html时,返回html内容
            resp = make_response(render_template("demo.html", name="wenxiaoba"))
        else:
            dict_data = {
                "name": "wenxiaoba",
                "age": 32,
                "gender": True
            }
            resp = make_response(dict_data)
        # 设置cookie,
        resp.set_cookie("cookie1", "cookie1_value")
        resp.set_cookie("cookie2", "cookie2 value")
        # 设置响应头
        resp.headers["test"] = "test headers value"
        return resp
    
    if __name__ == "__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在脚本执行(即启动flask服务)后,发送请求,结果如下:
    在这里插入图片描述
    在这里插入图片描述

    启动服务配置

    在前面例子中,我们基本上是通过脚本执行run()方法来启动flask服务,run()方法可以传入多个参数,对服务内容进行配置,但是我们均未设置参数,现在来说下比较常用的参数。
    run()方法:run(host, port, debug, load_dotenv=True, **options)

    • host:默认启动127.0.0.1,这个只能本机访问,0.0.0.0只能局域网访问,如果想要公网正常访问,则将flask服务部署到公网服务器,并将服务器ip作为host参数传入
    • port:监听端口号,默认为5000,int数据类型,可以传入其他端口号
    • debug:是否开启debug模式,默认为False(即production),如果为True,则会监听脚本是否被保存,如果有保存操作,则按照最新保存的脚本重新启动flask服务

    示例:
    py脚本内容为:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/demo", methods=["POST"])
    def demo():
        return "content data text"
    
    if __name__ == "__main__":
        app.run(host="192.168.1.105", port=8888, debug=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行脚本(即启动flask服务)后,从控制台日志可以看到,现在的flask服务在192.168.1.105的8888端口进行监听(当然flask服务也部署在192.168.1.105设备上),而且也提示了Debugger模式在生效中
    在这里插入图片描述
    我们发送请求到8888端口
    在这里插入图片描述
    当我们对py脚本进行修改,并保存,保存的时候可以看到控制台重新加载了flask服务,这就是debug模式的优势,脚本编辑了之后,不用手动重新启动flask,只需要保存就会重新加载flask服务。
    在这里插入图片描述
    在这里插入图片描述

    RESTFul风格规范

    flask-restx插件简单使用

    一般接口使用的规范是RESTFul风格规范(我也不太清楚),flask-restx是一个支持RESTFul的flask插件,用于规范化接口的编写,并且支持swagger文档
    官方说明:https://github.com/python-restx/flask-restx
    官方文档:https://flask-restx.readthedocs.io/en/latest/
    安装:pip install flask-restx

    简单入门

    我们按照官方的示例来入门,官方脚本如下:

    from flask import Flask
    from flask_restx import Resource, Api
    
    # 创建Flask对象app
    app = Flask(__name__)
    # 创建Api对象api,创建时,将Flask对象app作为参数传入
    api = Api(app)
    
    # 使用Api对象api来添加路由(不使用Flask对象app来添加路由)
    # 至于methods,不在添加路由时限制,在装饰的类方法中限制
    @api.route('/hello')
    class HelloWorld(Resource):     # 创建HelloWorld类,必须继承Resource模块
        def get(self):  # 定义RESTFul风格的get方法(对应get请求方式)
            return {'hello': 'world'}
    
    if __name__ == '__main__':
        app.run(debug=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    执行脚本(启动flask服务),发送请求,结果如下:
    在这里插入图片描述
    根据示例中的注释,如果我们想定义一个接口,支持get和post的请求方式,则需要在对应的类中定义get()和post()方法,简单示例如下:
    py脚本内容如下:

    from flask import Flask, request
    from flask_restx import Resource, Api
    
    app = Flask(__name__)
    api = Api(app)
    
    @api.route("/person/info")
    class Person(Resource):
        def get(self):
            return "get方法不安全,没有权限查看个人信息"
    
        def post(self):
            name = request.json.get("name")
            return {"name": name, "age": 27, "gender": True}
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    执行py脚本(启动flask服务)后,发送请求,结果如下:
    get请求:
    在这里插入图片描述
    post请求:
    在这里插入图片描述
    如果请求方式非get或post,则报错
    在这里插入图片描述

    添加路由

    简单入门 的示例中,我们知道了怎么添加路由,其实flask_restx插件还提供了add_resource()方法来添加路由。即flask_restx插件有2种方法来添加路由:

    • route()装饰器添加路由
    • add_resource()方法添加路由
    route()装饰器添加路由

    route()可以一次性添加多个路由,示例如下:
    py脚本内容如下:

    from flask import Flask, request
    from flask_restx import Resource, Api
    
    app = Flask(__name__)
    api = Api(app)
    
    @api.route("/path1/demo", "/path2/demo", "/path3/demo")
    class demo(Resource):
        def post(self):
            return f"接口路径是:{request.path}"
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    执行脚本(启动flask服务),发送请求,结果如下:
    在这里插入图片描述

    add_resource()方法添加路由

    add_resource()是给指定的类添加路由,示例如下:
    py脚本内容如下:

    from flask import Flask, request
    from flask_restx import Resource, Api
    
    app = Flask(__name__)
    api = Api(app)
    
    class Demo(Resource):
        def post(self, name):
            return {"path": request.path, "name": name}
    # 给Demo类添加路由
    api.add_resource(Demo, '/demo1/', '/demo2//info')
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    执行脚本(启动flask服务),发送请求,结果如下:
    在这里插入图片描述

    编写RESTFul风格的接口

    在设计框架的时候,一般遵循复用性、高内聚、低耦合,即容易维护,减少冗余。
    高耦合:可以理解为程序非常复杂难以维护,若修改了程序的某一处内容,涉及到方方面面(其他功能)都要跟着改,比如一个程序有100个函数执行正常,然而修改了其中1个函数,其他99个函数都要跟着修改,就是高耦合的场景。低耦合要求将完整的流程拆分成几个独立的模块、独立的功能,模块内的修改不影响模块之间的交互逻辑,比如商户订单管理和用户订单支付都是不同的模块,修改了商户订单管理的某个功能后,用户订单支付的大部分功能不需要修改。

    高耦合示例

    from flask import Flask, request
    
    app = Flask(__name__)
    @app.route("/demo", methods=["GET", "POST", "PUT", "DELETE"])
    def demo():
        if request.method == "GET":
            return "获取订单"
        elif request.method == "POST":
            return "生成订单"
        elif request.method == "PUT":
            return "修改订单"
        else:
            return "删除订单"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    该示例中,将订单的增删改查都在一个函数里完成,如果突然某天需要增加审批订单的功能,其他订单功能都受影响,就需要阅读一大坨代码,然后又是增加代码,又是修改代码的,都在一个函数下,非常不美观(一看到这么多代码,会比较泄气),也不好维护。

    低内聚示例

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route("/order", methods=["GET"])
    def query_order():
        return "查询订单"
    
    @app.route("/order", methods=["POST"])
    def create_order():
        return "生成订单"
    
    @app.route("/order", methods=["PUT"])
    def modify_order():
        return "修改订单"
    
    @app.route("/order", methods=["DELETE"])
    def delete_order():
        return "删除订单"
    
    @app.route("/approval", methods=["POST"])
    def approvaled():
        return "审批通过"
    
    @app.route("/approval", methods=["DELETE"])
    def reject():
        return "审批驳回"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    该示例中,有/order接口的不同请求方法,有/approval接口的不同请求方法,总之,都是一个接口路径对应多个请求方法。在该示例中,代码没有复用性,维护起来比较杂乱,包含了操作人员的订单管理(增删改查)、审批人员的订单管理(审批通过、审批驳回),归纳起来,可以分为2个维度:操作人员的订单管理、订单的审批流程。如果一个模块有很多个功能,有许多个维度,则维护成本也是逐渐增加,且不易分辨,需要去看注释或代码来确定当前函数的处理内容。

    RESTFul风格示例

    设计过程中需要遵循复用性、高内聚、低耦合,RESTFul风格规范会根据请求方式来设计不同的逻辑。

    请求方式说明
    GET获取服务器资源
    POST新增服务器资源
    PUT更新服务器资源(客户端提供改变后的完整资源)
    DELETE删除服务器资源

    示例:

    from flask import Flask
    from flask_restx import Api, Resource
    
    app = Flask(__name__)
    api = Api(app)
    
    @api.route("/order")
    class OrderOpt(Resource):
        def get(self):
            return "查询订单"
    
        def post(self):
            return "生成订单"
    
        def put(self):
            return "修改订单"
    
        def delete(self):
            return "删除订单"
    
    @api.route("/approval")
    class ApprovalProcess(Resource):
        def post(self):
            return "审批通过"
    
        def delete(self):
            return "审批驳回"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在该示例中,从2个维度去维护订单功能:操作人员的订单管理、订单的审批流程,虽然订单管理和审批流程之间可能会有影响,但从一定程度上降低了高耦合,另一方面也更好的进行维护管理。

    flask-restx插件集成swagger

    一般而言,开发在设计、开发接口的时候,需要提供接口文档给到其他开发进行联调,或给到测试进行接口测试。flask-restx插件集成了swagger,可以对接口进行模块管理,也可对接口文档进行配置。
    flask-restx插件集成swagger,依赖于namespace的使用。

    namespace的使用

    RESTFul风格示例 的示例中,如果我们访问接口根路径,发现界面如下:
    在这里插入图片描述
    从图中可以看出,接口都在default namespace下,如果接口很多的话,就不利于管理,flask-restx插件的namespace可以对接口进行分类管理。
    使用namespace进行接口分类管理,需要进行如下步骤:

    • 步骤1:定义Namespace对象
    • 步骤2:为类添加装饰器:Namespace对象的route()方法
    • 步骤3:Api对象添加Namespace对象访问路径

    使用到的类或方法说明如下:

    Namespace类:

    • 定义对象:Namespace(name, description=None, path=None, decorators=None, validate=None, authorizations=None, ordered=False, **kwargs)
    • 参数说明:
      • name:分类名称
      • description:分类描述
      • path:前置路径(后面会根据步骤2的装饰器和步骤3的路径设置决定接口的完整路径)

    @Namespace().route(“”)是将类归纳到某个Namespace对象下,即将类对应的接口归纳到某个层级下,route()可以设置子路由,受步骤1中Namespace的path参数和步骤3的add_namespace()方法影响,如果不设置子路由,则传入空字符串(不要什么都不传)

    Api对象的add_namespace(ns, path=None)是将对应的Namespace对象ns添加到flask服务中(如果不加则识别不到对应类的接口),并设置前置路径path。

    关于namespace的接口分类管理,我们从步骤1到步骤3可以看到,有3个地方设置路由,分别是Namespace实例化时的参数path、装饰器Namespace对象的route()方法、Api对象的add_namespace()方法中的path参数,关于这3个地方对对应接口的接口路径的最终结果,如下:

    • 如果Api对象的add_namespace()方法中的path参数有传入,则该path参数值作为前置路径
    • 如果Api对象的add_namespace()方法中的path参数无传入
      • 如果Namespace实例化时有传入参数path,则path值会被作为前置路径
      • 如果Namespace实例化时未传入参数path,则默认为Namespace实例化时的分类名称作为前置路径
    • 接口完整路径是:前置路径 + route()方法传入的参数值

    示例:
    py脚本内容如下:

    from flask import Flask
    from flask_restx import Api, Resource, Namespace
    
    app = Flask(__name__)
    api = Api(app)
    
    # 定义Namespace实例
    ns1 = Namespace("demo management", "add_namespace()和Namespace()都有path参数传入", path="/name1")
    ns2 = Namespace("demo2 management", "add_namespace()无path参数传入,Namespace()有path参数传入", path="/name2")
    ns3 = Namespace("demo3 management", "add_namespace()和Namespace()都无path参数传入")
    ns4 = Namespace("demo4 management", "add_namespace()和Namespace()都有path参数传入,route()传入空字符串", path="/name4")
    ns5 = Namespace("demo5 management", "add_namespace()和Namespace()都有path参数传入,route()未传参", path="/name5")
    
    @ns1.route("/route1")
    class Demo1(Resource):
        def get(self):
            return "demo1 get"
    
        def post(self):
            return "demo1 post"
    
    @ns2.route("/route2")
    class Demo2(Resource):
        def get(self):
            return "demo2 get"
    
        def post(self):
            return "demo2 post"
    
    @ns3.route("/route3")
    class Demo3(Resource):
        def get(self):
            return "demo3 get"
    
    @ns4.route("")
    class Demo4(Resource):
        def get(self):
            return "demo4 get"
    
    @ns5.route()
    class Demo5(Resource):
        def get(self):
            return "demo5 get"
    
    api.add_namespace(ns1, "/api_add")
    api.add_namespace(ns2)
    api.add_namespace(ns3)
    api.add_namespace(ns4, "/api_add4")
    api.add_namespace(ns5, "/api_add5")
    
    if __name__=="__main__":
        app.run()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    执行脚本(启动flask服务),访问接口根路径,结果如下:
    在这里插入图片描述

    swagger接口文档配置

    flask-rest中swagger文档配置有2种方式:

    • 第一种:使用 @Api对象.doc()或者@namespace对象.doc()装饰请求方法
    • 第二种:使用parser = api.parser()配合`@api.expect(parser)装饰器入参的校验和传入

    推荐使用第二种方式

    doc()方式

    py脚本内容是:

    from flask import Flask, request
    from flask_restx import Api, Resource, Namespace, fields
    
    app = Flask(__name__)
    api = Api(app)
    
    ns = Namespace("分类名称", description="分类的描述")
    
    @ns.route("")
    class Demo(Resource):
        # doc()种对请求参数params(即url参数)进行字段说明
        @ns.doc(params={"id": "用户编号", "subject": "科目"})
        def get(self):
            return {"code": 0, "data": request.args}
    
        #
        post_check_model = api.model("PostModel", {
            "name": fields.String(description="姓名", required=True),
            "age": fields.Integer(min=0),
            "gender": fields.String(description="性别", enum=["男", "女"])
        })
        @ns.doc(body=post_check_model)
        def post(self):
            data = request.json
            return f"{data.get('name')}, {data.get('age')}岁, {data.get('gender')}性"
    
    api.add_namespace(ns, "/demo")
    
    if __name__=="__main__":
        app.run(debug=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    执行脚本(启动flask服务),访问根路径:
    在这里插入图片描述

    doc()的方式相当于对接口字段进行了描述,并没有很好的对接口字段进行限制,所以不推荐这种方式。

    Api对象parser方式

    比较推荐的swagger接口文档配置步骤如下:

    • 步骤1:在类中,通过Api对象的parser()方法获取RequestParser对象
    • 步骤2:在RequestParser对象中,通过add_argument()方法对接口请求字段进行设置
    • 步骤3:在对应的请求方法中,添加namespace对象的装饰器,并通过expect()方法将RequestParser对象传入,表明该接口使用该RequestParser对象的接口字段定义。

    RequestParser对象的add_argument(*args, **kwargs)方法是对接口请求字段进行配置,相关的关键参数说明如下:

    • 第一个参数是参数名,即接口请求字段的字段名称
    • 后面是关键字传参,常用的关键字有:
      • type:类型
        • 参数值:int(整型),bool(布尔型)、float(浮点型)、string(字符串)、FileStorage(文件对象)
        • 说明:对接口字段值的类型进行控制,指定接口字段值的数据类型
      • required:约束控制
        • 参数值:True、False
        • 说明:如果设置了为True,则表示该字段是必传的,False表示非必传
      • help:字段说明,字符串,可以对字段进行说明
      • choices:枚举参数
        • 说明:参数值应该是个列表、元组等对象,表示字段值必须在指定的范围中
      • location:对应flask.request对象中的属性
        • 参数值:参数值是flask.request对象中的属性,比如args、form、json、files、headers等
        • 说明:用来指定该字段所在位置,比如在url请求参数、form表单或json数据中

    示例:

    from flask import Flask, request
    from flask_restx import Api, Resource, Namespace
    from werkzeug.datastructures import FileStorage
    
    app = Flask(__name__)
    api = Api(app)
    
    ns = Namespace("分类名称", description="分类的描述")
    
    @ns.route("")
    class Demo(Resource):
        # 定义RequestParser解析器对象
        get_parser = api.parser()
        # 通过RequestParser对象添加接口请求字段参数配置
        get_parser.add_argument("id", type=int, help="身份证号", location="args")
        get_parser.add_argument("subject", type=str, help="科目", location="args")
    
        # 通过Namespace对象的expect(RequestParser对象)方法,对get请求进行装饰(即对该get请求接口字段指定字段配置
        @ns.expect(get_parser)
        def get(self):
            return {"code": 0, "args_data": request.args}
    
        post_parser = api.parser()
        post_parser.add_argument("file_data", type=FileStorage, help="上传文件", location="files")
        post_parser.add_argument("account", type=str, help="帐号", location="form")
        post_parser.add_argument("password", help="密码", location="form")
        @ns.expect(post_parser)
        def post(self):
            return {"status_code": "success", "form_data": request.form, "file_name": request.files.get("file_data").filename}
    
        put_parser = api.parser()
        put_parser.add_argument("name", type=str, help="姓名", location="json", required=True)
        put_parser.add_argument("age", type=int, help="年龄", location="json")
        put_parser.add_argument("gender", help="性别", choices=["男", "女"], location="json")
        @ns.expect(put_parser)
        def put(self):
            return {"code": 0, "message": "success", "json_data": request.json}
    
    api.add_namespace(ns, "/demo")
    
    if __name__=="__main__":
        app.run(debug=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    执行脚本(启动flask服务),访问根目录并发送请求,结果如下:
    在这里插入图片描述
    注意:Swagger 的检验比较虚(即字段校验一般不生效),真要强制约束请求信息,还是要在代码里

  • 相关阅读:
    idea compile项目正常,启动项目的时候build失败,报“找不到符号”等问题
    DIY正则图片下载工具
    为何要实现发票信息的数字化管理?
    2022最火的Linux性能分析工具--perf
    Ubuntu 22.04安装深度学习框架PyTorch-GPU版
    创建模拟器
    如何通过更好的文档管理减轻小型企业的压力
    LeetCode-496-下一个更大元素
    常用的openssl命令
    杭电oj 2049 不容易系列之(4)——考新郎 C语言
  • 原文地址:https://blog.csdn.net/wenxiaoba/article/details/128053226