note

Flask

postman中,form-data可以上传文件,具体区别看这里

尾至返回类型的一个写法: -> typing.Any # 先import typing

Flask官方中文文档地址

Flask诞生于2010年,是用Python语言基于==Werkzeug==工具箱编写的轻量级Web开发框架

还有一个扩展包:Flask-Monment:本地化日期和时间

模板,这个Flask教程用的jinja2的模板,如果以后用到再来看吧,

数据库的扩展包用的是flask-sqlalchemy,是基于SQLAlchemy

还有数据迁移的扩展包,就不写了

还能使用flask发送邮件,(”\15 flask框架\11-数据库迁移扩展包Flask-Migrate、 邮件扩展包Flask-Mail\03_使用flask发送邮件.flv”)

然后说是Django中也是可以发送邮件的,就不写了

一、Flask基础

这视频教程用的flask版本是flask==0.10.1,然后学习时,自己用的版本是flask==2.1.2

1.1. flask创建app对象

自己学习这时对应的github代码

1.1.1 初始化参数

==__name__==:

​ 假设在一个名字a_first.py文件中初始化一个Flask程序:

from flask import Flask app = Flask(__name__)


==flask应用对象其它初始化参数说明==:(这几个一般不去设置,知晓一下就行)

静态文件直接访问的一个示例,跟Django一样,不需要去路由转发

注意以上的网址的static并不是静态资源文件夹的名字,原理同Django中的STATIC_URL(这里一定要注意,不理解就去Django中看,不然就找不到静态文件),

app = Flask(__name__,
            static_url_path="/python",  # 同Django中的STATIC_URL,指定访问静态资源的url前缀,默认值是static
            static_folder="static",   # 静态文件的目录,默认就是static
            template_folder="templates"  # 模板文件的目录,默认就是templates
      )

后面两个参数可以不写,因为没变,static_url_path参数不写那就是向上面那样的网址访问,这样该饿了的话,访问就应该是127.0.0.1:8000/python/hello.html

Tips:

1.1.2 全局参数设置

这个就相当于Django的setting.py的一个全局配置,以DEBUG为例,有如下三种配置方式:

  1. ==使用配置文件==:(”settings.py”和”config.cfg”内容都一样,后缀不重要) 配置文件settings.py里的内容都是 key = value 这样的键值对

    app.config.from_pyfile("settings.py")
    app.config.from_pyfile("config.cfg")    # 俩效果一样
    
  2. ==使用类对象配置参数==:

    class MyConfig(object):
        DEBUG = True   # 把需要配置的参数都写成类属性  
        My_IP = "127.0.0.1"  # 添加这样不属于flask的参数,自己要用的,后续get("My_IP")去取的时候这个是None,上面的DEBUG是可以正确取到值的,后续用到的时候再去解决吧
    app.config.from_object(MyConfig)
    

    后续取值的一个问题:

    • app.config.get(“My_IP”) # 结果是None,取不到,看里面注释
    • print(app.config.get(“DEBUG”)) # True
  3. ==把app.config理解为一个字典==:(然后配置参数比较少的时候,可以直接新增)

    app.config["DEBUG"] = True
    app.config["My_IP"] = "127.0.0.123"
    

    app.config.get(“My_IP”) # 这个结果就不是None,就能正确到 print(app.config.get(“DEBUG”)) # True

  4. 特别写一下DEBUG参数:还可以写进 app.run(debug=True) # 一定注意,这里是小写的,千万别写错了

    • 只有在debug模式下,修改了主文件才会自动重启服务,不然不会。

Tips: 教程里写,如果在别的模块里没有app这个对象,如果要获取参数呢? from flask import current_app # 这个current_app就相当于是app的引用吧,这么理解 print(current_app.config.get(“DEBUG”)) # 这会直接报错,视频里没有报错,暂时不去深究了

1.2. 路由

1.2.1 一些简单路由

  1. 查看==所有路由==规则:print(app.url_map)

  2. ==路由参数==:@app.route(“/index”)

    • 不给methods参数的话,默认请求方式是GET,
    • @app.route(“/index”, methods=[“POST”]) # 这时再网页直接请求(默认是GET),就会报错,这只能是POST了,当然还可以写成methods=[“POST”, “GET”]
      • 报错会得到405的一个状态码,就应该是代表请求方法错了
      • 302这个状态码是代表重定向
  3. 同一视图函数添加几个路由:(这样访问这两个网址,都是对应的index()这个视图函数)

    @app.route("/index123")
    @app.route("/index456")
    def index():
        return "hello world"
    

1.2.2 重定向|url反向解析

使用重定向,以及url反向解析(跟Django一个原理):(注意这俩导包)

from flask import redirect, url_for
@app.route("/login")
def login():
    # 使用uel_for函数,通过视图名字(这里是index)找到对应的url路由路径,而不是下面那样写死了
    url = url_for("index")  
    return redirect(url)
    # return redirect("https://www.baidu.com/")    # 重定向,

1.2.3 动态路由(转换器)

动态路由:因为flask不能直接向Django那样正则处理

转化器有下面几种:

  1. 什么都不写的话,即<> # 默认为普通字符串,除了/这一字符,路由传递的参数默认当做string处理,
  2. int 即<int:变量名> # 接受整数
  3. float # 同int,得接受浮点数
  4. path # 和默认的相似,但也可以接受斜线

示例:

@app.route("/goods/<int:good_id>")   # good_id自己随意起
def goods(good_id):
    return "hello world {}".format(good_id)
# 访问 127.0.0.1/goods/123   # 那么good_id就是123

==这种<>的方式就叫做转换器==,然后这个goods_id

==自定义转换器==:

​ 以上的写法比较固定,那想要实现正则,那就写一个通用的自定义转换器(以后要写就直接看这个)

from werkzeug.routing import BaseConverter
# 1、定义自己的转换器
class MyRegexConverter(BaseConverter):
    def __init__(self, url_map, my_regex):
        # 1.1 url_map参数是固定的,不管,相当于把app.url_map传进来让父类实现先
        # my_regex就是我们下面写的正则表达式
        super(MyRegexConverter, self).__init__(url_map)
        self.regex = my_regex  # 1.2 重写BaseConverter继承下来的regex这个类属性
        # flask会使用这个属性来进行路由的正则撇撇

# 2、将自定义的转换器添加到flask的应用中去(当做字典一样)
app.url_map.converters["my_re"] = MyRegexConverter   # "my_re"名字随意起

@app.route("/send/<my_re(r'1[37]\d{9}'):phone_num>")
def send_mgs(phone_num):
    return "send message to {}".format(phone_num)

# 127.0.0.1:5000/send/13512345678  可以访问,

解读:@app.route("/send/<my_re(r'1[37]\d{9}'):phone_num>")

==自定义转换器进阶==:

​ 其实在BaseConverter中还有两个方法:to_python、to_url

​ 上面的例子,我们的到phone_num结果就是方法to_python返回给我们的,那我们就可以重写这个方法,对数据进行一些处理再返回,但用的还是比较少

class MyRegexConverter(BaseConverter):
    def __init__(self, url_map, my_regex):
        super(MyRegexConverter, self).__init__(url_map)
        self.regex = my_regex  
    def to_python(self, value):   # 这就是原本BaseConverter类中的实现
        """这里的value就是拿到正则匹配后的结果,然后我们还可以在这里进行一些处理在返回,
        然后phone_num拿到的就是就是这个函数的返回结果;一般来说不重写这个函数"""
        return value

​ 那如果上面 to_python 我写个return “abcd”,访问127.0.0.1:5000/send/13512345678,正则匹配是通过的,但是phone_num拿到的就是“abcd”了,就是这么个意思。

那个to_url方法是在url反解析的视图函数中带有这种自定义转换器时,会先把传进来的关键词参数给到to_url处理

from flask import redirect, url_for
class MyRegexConverter(BaseConverter):
    def __init__(self, url_map, my_regex):
        super(MyRegexConverter, self).__init__(url_map)
        self.regex = my_regex  
    def to_python(self, value):   # 这就是原本BaseConverter类中的实现
        return value
    def to_url(self, value):
        return value
	
@app.route("/login")
def login():
    # phone_num一定要与send_mgs视图函数参数名字想同
    url = url_for("send_mgs", phone_num="13512345678")
    return redirect(url)

解读:(要结合上面的代码一起看)

==请求静态资源的实例==

​ 这里就用到了上面的动态路由转换,实现了网页直接请求一个静态资源(图片(浏览器直接展示)、音频文件(直接下载)等)。核心是用到“send_from_directory”

“http_server.py”

from flask import Flask, send_from_directory

app = Flask(__name__)

@app.route("/images/<path:img_name>")
def send_img(img_name):
    return send_from_directory(app.static_folder, "images/" + img_name)


@app.route("/audios/<path:audio_name>")
def send_audio(audio_name):
    return send_from_directory(app.static_folder, "audios/" + audio_name)
    
if __name__ == '__main__':
    app.run(host="192.168.108.147", port=5000)

注意:

1.3. ==request请求参数==

from flask import Flask, request

直接导包的这个request中表示当前请求的request对象,里面保存了一次HTTP请求的一切信息。

request常用的属性如下:

Tips:

1.4. 其它

1.4.1 abort函数

from flask import abort

​ 使用abort函数可以立即终止视图函数的返回,并可以给前端特定的信息(它自己定义的一些标准页面),一般可以这用:

from flask import abort
@app.route("/index")
def index():
    if True:    # 
        abort(400)  # 执行了这里后就会直接退出了
    return "login success"

1.4.2 自定义异常页面

自定义异常处理:就是类似于Django中重写了404.html页面一样

语法:@app.errorhandler(要重写的http标准状态码)

​ 比如重写400返回的页面:

@app.errorhandler(400)     # 重写其它标准页面,就把对应的状态码放这里
def my_error_hand(err):  # 一定要接受一个参数,里面是错误信息,可以不用
    return "这是我的400错误:%s" % err    # 这就是页面看到的信息

1.4.3 return响应信息

设置响应信息的方法:

第一种:(里面的几个写法都是可以的)

@app.route("/index")
def index():
    #       返回的数据    状态码,可以自己写      后面这是返回到header中的响应头(2种写法)
    # return "index page", 200, [("name", "zhangsan"), ("age", 18)]
    # return "index page", 400, {"name": "zhangsan", "age": 19}
    # return "index page", 600   # 甚至返回一个非标准的http状态码 (响应头、状态码都可以不要)
    return "index page", "600 my_status", {"name": "zhangsan", "age": 19}  # 状态码加注释

效果:

第二种:使用==make_response==来构造响应信息

from flask import Flask, make_response
@app.route("/index")
def index():
    # 第二种:
    resp = make_response("index page 2")
    # resp.status_code = 200
    resp.status_code = "999 rand_my_status"
    resp.headers["city"] = "sc"   # 把resp.headers当成一个字典,添加键值对就行

1.4.4 返json数据

返回json数据: (json其实就是一个字符串)(后面就用第二种)

import json
from flask import jsonify
@app.route("/login")
def login():
    data = dict(
        name="zhangsan",
        age=18
    )
    # 第一种,直接返回json的字符串
    # json_str = json.dumps(data)
    # return json_str, 200, {"Content-Type": "application/json"}  # 好像一定需要后面这个header的设定

    # 第二种:jsonify (用这个)
    return jsonify(data)   # 这个会自动帮我们设定"Content-Type"这个header
    # 或者 return jsonify(name="zhangsna", age=18)  # 直接以关键字参数传进去也行

解读:

1.4.5 cookie的使用

注意:取cookie时,是用的request.cookies,而session是从flask导包的,跟Django不一样。

from flask import Flask, make_response, request

app = Flask(__name__)
"""设置cookie"""
@app.route("/set_cookie")
def set_cookie():
    # 设置cookie需要 make_response 函数来创建
    resp = make_response("成功设置cookie")
    # 设置cookie,默认是临时cookie,浏览器关闭就失效
    resp.set_cookie("my_cookie1", "a_num1")  # 前面试key,后面是value
    resp.set_cookie("my_cookie2", "a_num2")  # 可以设置多个cookie
    # max_age:设置过期时间,以秒为单位
    resp.set_cookie("new_cookie", "flask_study", max_age=3600)
    # 也可以通过这样的方式添加header
    resp.headers["Set-Cookie"] = "other_cookie=my_123; Expires=Tue, 10 May 2022 15:25:10 GMT; Max-Age=3600; Path=/" 
    return resp

"""获取cookie"""
@app.route("/get_cookie")
def get_cookie():
    value = request.cookies.get("new_cookie")
    return value

"""删除cookie"""
@app.route("/delete_cookie")
def delete_cookie():
    # 也是需要 make_response 来创建
    resp = make_response("cookie 删除成功")
    # 删除cookie
    resp.delete_cookie("my_cookie1")
    return resp

if __name__ == '__main__':
    app.run(debug=True)

Tips:

1.4.6 session

from flask import session # 注意不是request.session

from flask import Flask, session
app = Flask(__name__)

# 1、要设置session,必须要配置一下(key的名字是固定的,值随便给的)
app.config["SECRET_KEY"] = "abcdefg_dasdw"

# 2、设置session
@app.route("/login")
def login():
    session["name"] = "zhnagsan"
    session["age"] = 18
    return "login success"

@app.route("/index")
def index():
    name = session.get("name")
    return "拿到的session中的值是:%s" % name

if __name__ == '__main__':
    app.run()

解读:

1.4.7 请求上下文与应用上下文

请求上下文对象:

​ request和session都属于==请求上下文对象==:(from flask import request, session)

​ Django里的request是每个视图函数都有的,各用各的没问题,但是falsk中,每个视图函数用的都是from flask import request,这个request是一个全局变量,那么怎么保证各自请求从reques中拿到的是自己请求的数据,flask就用到了一个==线程局部变量==(就是这个request全局变量),简单来说,一个请求就会开一个线程,把request当做一个字典,key就是线程编号,value就是其提交的数据,这样就达到了一个全局变量request,各自请求用各自的数据。这就是==请求上下文==


应用上下文对象:

​ current_app和g都属于==应用上下文对象==:(from flask import current_app, g)

1.4.8 hook钩子

请求钩子是通过装饰器的形式实现的(同Django中的中间件),Flask常用的四种请求钩子:

  1. @app.before_first_request # 视图函数第一次被请求时才会被执行,后续不会;
  2. @app.before_request # 在每次请求前运行;
  3. @app.after_request # 如果没有未处理的异常抛出,视图函数运行后,这会运行;
  4. @app.teardown_request # 每次请求后,即使有未处理的异常抛出,这也会执行。
from flask import Flask
app = Flask(__name__)

@app.route("/index")
def index():
    print("index视图函数运行!")
    return "hello world!"

@app.before_first_request
def my_first_handle():
    print("第一次")

@app.before_request
def my_before_handle():
    print("视图函数处理前运行")

@app.after_request
def my_after_handle(resp):
    print("视图函数正常运行后,这里执行(有异常就不会执行)")
    return resp
"""注意后面这两个需要一个参数,这个参数内容就是上一个处理完返回的内容"""
@app.teardown_request
def my_teardown_handle(resp):
    print("每次请求完后,即便有未处理的异常抛出,这也会执行")
    return resp

if __name__ == '__main__':
    app.run(debug=True)

效果:

使用Tips:

注意:

1.4.9 Flask-Script扩展

就是前面讲到的扩展,目的就是让flask向Django那样命令行运行:

环境安装:pip install Flask-Script

"""假设这个py文件叫:my_manage.py """
from flask import Flask
from flask_script import Manager  # 导入管理类

app = Flask(__name__)
manager = Manager(app)

@app.route("/index")
def index():
    return "hello world!"

if __name__ == '__main__':
    # app.run(debug=True)
    manager.run()  # 这样去运行

使用Tips:

二、Flask进阶

2.1. flask中的蓝图

​ 简单来说,视图路由全都写在一个py文件中,不好维护,也想像Django那样一个应用的视图函数放在一个文件夹中,flask中的蓝图就是来做到这个的。

简单使用:视图函数写到flask启动文件同级的一个py文件

blue.py

from flask import Blueprint

# 1、创建蓝图对象
admin = Blueprint("admin", __name__)

# 2、注册蓝图路由
@admin.route("/admin_index")
def admin_index():
    return "这是蓝图中注册的index函数"

main.py

from flask import Flask, render_template
from blue import admin   # 导入蓝图对象
app = Flask(__name__)

# 3、在程序实例中注册该蓝图
app.register_blueprint(admin, url_prefix="/my_order")

@app.route("/index")
def index():
    return render_template("index.html")

if __name__ == '__main__':
    app.run()

解读:


进阶:像Django那样把一个应用的放到一个文件夹

  1. 创建一个名为car的购物车应用,那就要创建car这个包(带__init__.py),然后再这个init的py文件中去创建蓝图: __init__.py

    from flask import Blueprint
       
    app_car = Blueprint("app_car", __name__)
       
    # 要把这个视图函数导进来,让代码看到,不然视图函数get_car就没出现,也没被导包什么的
    from .views import get_car     # 一定要写成 .view 点代表这个包,不加的话会报找不到views这个包
    

    views.py

    from . import app_car   # 注意这个写法,.好像就代表这个包,那就代表__init__.py
       
    @app_car.route("get_car")
    def get_car():
        return "获取购物车信息"
    

    main.py

    from flask import Flask, render_template
    from car import app_car
       
    app = Flask(__name__)
       
    # 注册蓝图路由
    app.register_blueprint(app_car, url_prefix="/car")  # 这里的前缀car自己随意写
       
    @app.route("/index")
    def index():
        return render_template("index.html")
       
    if __name__ == '__main__':
        app.run(debug=True)
    

    访问:127.0.0.1:5000/car/get_car, 以下是上面文件的一个示意:

  2. 蓝图里,静态文件、模板文件路径设置:

    Tips:

    • app_car = Blueprint() 这个参数跟Flask初始化参数差不多,还可以指定 static_folder 、static_url_path 、 url_prefix等,所以静态文件的做法类似;
    • 但Blueprint这里面的 template_folder 这些参数不像Flask初始化参数,是没有默认值的,所以要用的话一定要指定;
    • 如上图所说,模板的搜索路径是总的模板路径,再是子应用中的模板路径,前面找到了,就不再往后去看了。

2.2. flask的单元测试

​ 所有单元测试,按照正常的测试用例来写好了,flask也可以用requests来模拟post请求什么的,除此之外,flask还提供了自己一些方便测试的方法,具体不细写了,看下图:

进一步配置:

说明:

三、Flask部署

Supervisor的一个使用,Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支持Windows系统。它可以很方便的监听、启动、停止、重启一个或多个进程。用Supervisor管理的进程,当一个进程意外被杀死,supervisort监听到进程死后,会自动将它重新拉起,很方便的做到进程自动恢复的功能,不再需要自己写shell脚本来控制。(只是放这里,如果后续用得到的话,就去深入吧)

3.1. gunicorn

​ 说明:在生产环境中,flask自带的服务器,一般无法满足性能要求,这里采用Gunicorn做wsgi容器来部署flask程序。

​ ==Gunicorn==(绿色独角兽)是一个Python WSGI的HTTP服务器,它与各种web框架兼容,轻量级的资源消耗,可直接用命令启动,不需要编写配置文件,相对uWSGI要容易很多。 # uWSGI是指实现了uwsgi协议WSGI的web服务器。

Tips:


部署的方式:gunicorn + flask

安装:pip install gunicorn

通过命令行 gunicorn -h 来查看各种参数,以下是常用参数:(假定有my_server.py)

总的: gunicorn -w 4 -b 192.168.125.135:5000 –access-logfile ./logs/log1.txt my_server:app

3.2. 通过nginx

生产环境的部署的方式:nginx + gunicorn + flask

my_server.py

from flask import Flask
from datetime import datetime
app = Flask(__name__)
@app.route("/index")
def index():
	return "this is index pages!"

@app.route("/login")
def login():
	return "login success!"

@app.route("/time")
def time():
	return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	
if __name__ == "__main__":
	app.run()
  1. 假设三台机子上启动了三个flask服务:(启动程序在my_server.py中)

    • gunicorn -D -b 127.0.0.1:8123 –access-logfile ./logs/port8123.txt my_server:app
    • gunicorn -D -b 192.168.125.125:8456 –access-logfile ./logs/port8456.txt my_server:app
    • gunicorn -D -b 192.168.125.135:8789 –access-logfile ./logs/port8789.txt my_server:app
  2. 配置nginx,在其conf目录下,编译nginx.conf,然后启动:就在http板块写了部分,

    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        #tcp_nopush     on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
        #gzip  on;
           
    	# 1、重点是这里,三个服务
    	upstream my_flask {
    		server 127.0.0.1:8123;
    		server 192.168.125.125:8456;
    		server 192.168.125.135:8789;
    	}
        server {
            listen       80;
            server_name  localhost;
       
            #charset koi8-r;
            #access_log  logs/host.access.log  main;
       		
    		# 2、设置转发规则
            location / {
              	# proxy_pass http://127.0.0.1:8123;  # 如果就是本地的一个服务
              	proxy_pass http://my_flask;  # 注意这行写法
              	# 设置请求头,并将头信息传递给服务器
                proxy_set_header Host $host;  
                proxy_set_header X-Real-IP $remote_addr;
            }
    		# 后面还有一些东西就删了
        }
    

    Tips:

    • proxy_set_header应该不是必须的,视频里说这个就是可以将发起访问的客机的真实ip一起转发到服务器,不然访问的信息全是来自nginx,我们些服务器时,拿到的request.url可能就需要这个设置吧。写上嘛,暂时没感觉到差别
  3. 假设启动nginx服务的服务器ip是192.168.125.135,那么就可以用其它局域网内互通的机子来进行访问,比如: 192.168.125.135:80/time # 可能要去防火墙那里开启80端口 192.168.125.135:80/login

    • 然后就可以通过./logs/port8***.txt看到这些日志文件大小的变化,就发现访问的一个地址,这三个服务的各自的日志文件大小都在变化,就达到了负载均衡的转发。

3.3. 并发压力测试

一定要看这些,其它web server应该也是一样适用这些压力测试。

四、yolov5-falsk

这个是基于王新宇的tensorrt-yolov5的一个用flask改的server:

一个实测的结果: