最全的零基础Flask教程
1 Flask介绍
1.1 为什么要使用Flask
Django和Flask是Python使用最多的两个框架
1.2 Flask是什么
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
1.3 Flask与Django框架对比
1.3.1 框架轻重
重量级的框架:为方便业务程序的开发,提供了丰富的工具、组件,如Django
轻量级的框架:只提供Web框架的核心功能,自由、灵活、高度定制,如Flask、Tornado
1.3.2 与Django对比
django提供了:
- django-admin快速创建项目工程目录
- manage.py 管理项目工程
- orm模型(数据库抽象层)
- admin后台管理站点
- 缓存机制
- 文件存储系统
- 用户认证系统
- 而这些,flask都没有,都需要扩展包来提供
1.4 Flask常用扩展包
- Flask-SQLalchemy:操作数据库;
- Flask-script:插入脚本;
- Flask-migrate:管理迁移数据库;
- Flask-Session:Session存储方式指定;
- Flask-WTF:表单;
- Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证;
- Flask-RESTful:开发REST API的工具;
- Flask-Bootstrap:集成前端Twitter Bootstrap框架;
- Flask-Moment:本地化日期和时间;
- Flask-Admin:简单而可扩展的管理接口的框架
1.5 Flask文档
2 工程搭建
2.1 环境安装
2.1.1 Anaconda常用虚拟环境命令
# 虚拟环境
conda create -n 虚拟环境名称 python=python版本号 # 创建虚拟环境
conda remove -n 虚拟环境名称 --all # 删除虚拟环境
conda activate 虚拟环境名称 # 激活虚拟环境
conda env list 或 conda info -e # 查看全部虚拟环境
deactivate 虚拟环境名称 # 退出虚拟环境
2.1.2. 创建虚拟环境
Flask 再2.3.0
版本上已经放弃对Python3.7
的支持,当前支持的Python把版本区间为3.7~3.12
,因此这里将Python的版本定为3.8
conda create -n FlaskWeb python=3.8
2.1.3. 安装Flask
使用flask 2.3.0版本
pip install flask==2.3.0
2.2 HelloWorld程序
2.2.1 Flask编写
2.2.2 Flaks手动运行
1 Pycharm
运行
2 命令行运行
3 访问测试
2.3 参数说明
2.3.1 Flask对象初始化参数
Flask 程序实例在创建的时候,需要默认传入当前 Flask 程序所指定的包(模块),下面说明一些常用的Flask参数:
1 import_name
- Flask程序所在的包(模块),传
__name__
就可以 - 其可以决定 Flask 在访问静态文件时查找的路径
2 static_url_path
- 静态文件访问路径,可以不传,默认为:
/ + static_folder
3 static_folder
- 静态文件存储的文件夹,可以不传,默认为
static
template_folder
- 模板文件存储的文件夹,可以不传,默认为
templates
默认参数情况下,访问静态资源
app = Flask(__name__)
修改参数的情况下,访问静态资源
# 定义静态资源的访问路径为url_path_param,静态资源文件夹名称为folder_param
app = Flask(__name__, static_url_path='/url_path_param', static_folder='folder_param')
2.3.2 应用程序配置参数
Flask将配置信息保存到了app.config
属性中,该属性可以按照字典类型进行操作。
1 读取配置参数
app.config.get(name)
app.config[name]
2 设置
Flask配置参数,主要使用以下三种方式:
从配置对象中加载
app.config.from_object(配置对象)
# 定义默认配置类
class DefultConfig(object):
"""
默认配置
"""
SECRET_KEY = "lalalalalala"
# 定义开发环境下的配置类,继承自默认配置类
class DevDefultConfig(DefultConfig):
DEBUG=True
app = Flask(__name__)
# 从配置对象中添加配置
app.config.from_object(DefultConfig)
应用场景:
从配置文件中加载
app.config.from_pyfile(配置文件)
新建一个配置文件setting.py
SECRET_KEY = 'TPmi4aLWRbyVq8zu9v82dWYW1'
在Flask程序文件中读取该配置文件
# 从配置文件中添加配置
app.config.from_pyfile("setting.py")
@app.route("/")
def index():
return app.config["SECRET_KEY"]
访问测试
3 从环境变量中加载配置信息
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。
环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。
通俗的理解,环境变量就是我们设置在操作系统中,由操作系统代为保存的变量值
Flask使用环境变量加载配置的本质是通过环境变量值找到配置文件,再读取配置文件的信息,其使用方式为:
代码查询环境变量的方式:
# 环境变量中存储的是配置文件的绝对地址
app.config.from_envvar('环境变量名')
两种定义环境变量的方式
Windows环境下定义临时环境变量
set 临时环境变量名称 = 变量的值
Pycharm下定义环境变量的方式
示例:
再运行程序,即可。
app = Flask(__name__)
app.config.from_envvar('PROJECT_SETTING', silent=True)
@app.route("/")
def index():
print(app.config['SECRET_KEY'])
return "hello world"
关于
silent
参数的说明:表示系统环境变量中没有设置相应值时是否抛出异常
- False 表示不安静的处理,没有值时报错通知,默认为False
- True 表示安静的处理,即时没有值也让Flask正常的运行下去
访问测试
项目中的常用方式
使用工厂模式创建Flask app,并结合使用配置对象与环境变量加载配置
- 使用配置对象加载默认配置
- 使用环境变量加载不想出现在代码中的敏感配置信息
def create_flask_app(config):
"""
创建Flask应用
:param config: 配置对象
:return: Flask应用
"""
app = Flask(__name__)
app.config.from_object(config)
# 从环境变量指向的配置文件中读取的配置信息会覆盖掉从配置对象中加载的同名参数
app.config.from_envvar("PROJECT_SETTING", silent=True)
return app
方便了程序的使用,是一种常用的方式
2.3.3 app.run 参数
可以指定运行的主机IP地址,端口,是否开启调试模式
app.run(host="0.0.0.0", port=5000, debug = True)
关于DEBUG调试模式
- 程序代码修改后可以自动重启服务器
- 在服务器出现相关错误的时候可以直接将错误信息返回到前端进行展示
2.4 开发服务器启动方式
在1.0版本之后,Flask调整了开发服务器的启动方式,由代码编写app.run()
语句调整为命令flask run
启动。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello World'
# 程序中不用再写app.run()
启动
# 指定FLASK_APP环境变量(Windows CMD环境),demo01是文件名
set FLASK_APP=demo01
# 执行
flask run
说明
-
环境变量 FLASK_APP 指明flask的启动实例
-
flask run -h 0.0.0.0 -p 8000
绑定地址 端口 -
flask run --help
获取帮助 -
生产模式与开发模式的控制
通过
FLASK_ENV
环境变量指明set FLASK_ENV=production
运行在生产模式,未指明则默认为此方式set FLASK_ENV=development
运行在开发模式
扩展:指定参数运行
flask run --host 127.0.0.1 --port 6666
3 路由与蓝图
3.1 路由
3.1.1 查询路由信息
需要提前设置好FLASK_APP
环境变量的值,并且cmd
处于管理员模式
命令行方式
flask routes
在程序中获取
在应用中的url_map属性中保存着整个Flask应用的路由映射信息,可以通过读取这个属性获取路由信息
print(app.url_map)
在程序中遍历路由信息
for rule in app.url_map.iter_rules():
print('name={} path={}'.format(rule.endpoint, rule.rule))
测试
实现通过访问/routes
地址,以json的方式返回应用内的所有路由信息
import json
from flask import Flask
app = Flask(__name__)
@app.route("/flaskRoutes")
def getRoutes():
context = {}
for rule in app.url_map.iter_rules():
context[rule.endpoint] = rule.rule
return json.dumps(context)
3.1.2 指定请求方式
在 Flask 中,定义路由其默认的请求方式为:
- GET
- OPTIONS(自带)
- HEAD(自带)
利用methods
参数可以自己指定一个接口的多个请求方式,@app.get()
指定的是get
请求方式,@app.post()
指定的是post请求方式,以此类推
这里只测试了两个,其余的基本相似(这里采用的是Apifox进行测试)
3.2 蓝图
如果一个大的项目有多个模块,在Flask中可以采用蓝图进行开发,对蓝图的理解可以对比Django中的子应用。在Flask中,使用蓝图Blueprint来分模块组织管理。
3.2.1 蓝图的特点
蓝图实际可以理解为是一个存储一组视图方法的容器对象,其具有如下特点:
- 一个应用可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/user”、“/goods”
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个应用初始化时,就应该要注册需要使用的Blueprint
但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
3.2.2 蓝图的使用方式
使用蓝图可以分为三个步骤
-
创建一个蓝图对象
# 1.创建蓝图对象,两个参数分别为蓝图名称(name)和import_name user_bp =Blueprint('user',__name__)
-
在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
# 2.蓝图对象注册路由 @user_bp.get("/user") def user_profile(): return "user_profile"
-
在应用对象上注册这个蓝图对象
# 3.在app中注册蓝图对象 app.register_blueprint(user_bp)
3.2.3 单文件蓝图
可以将创建蓝图对象与定义视图放到一个文件中 ,也就是按照上面看到的代码
3.2.4 目录(包)蓝图
对于一个打算包含多个文件的蓝图,通常将创建蓝图对象放到Python包的__init__.py
文件中
--------- project # 工程目录
|------ main.py # 启动文件
|------ user #用户蓝图
| |--- __init__.py # 此处创建蓝图对象
| |--- view.py
注意:如果不在
__init__.py
中导入view.py
文件,则会导致404
错误
3.2.5 扩展用法
1 指定蓝图的url前缀
在应用中注册蓝图时使用url_prefix
参数指定前缀
app.register_blueprint(user_bp,url_prefix="/user")
2 蓝图内部静态文件
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
下面的示例将蓝图所在目录下的user_admin
目录设置为静态目录
user_bp = Blueprint("user",__name__,static_folder="user_admin")
app.register_blueprint(user_bp,url_prefix="/user")
现在就可以使用/user/user_admin/<path:filename>
访问user_admin
目录下的静态文件了。
也可通过static_url_path
改变访问路径
user_bp = Blueprint("user",__name__,static_folder="user_admin",static_url_path="static")
app.register_blueprint(user_bp,url_prefix="/user")
3 蓝图内部模板目录
蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用template_folder
关键字参数设置模板目录
user_bp = Blueprint("user",__name__,
static_folder="user_admin",
static_url_path="static",
template_folder="templates")
4 请求与响应
4.1 处理请求
请求携带的数据可能出现在HTTP报文中的不同位置,需要使用不同的方法来获取参数。
4.1.1. URL路径参数(动态路由)
Flask不同于Django直接在定义路由时编写正则表达式的方式,而是采用转换器语法:
# 这里本质上进行的是正则匹配
@app.route('/users/<user_id>')
def user_info(user_id):
print(type(user_id))
return 'hello user {}'.format(user_id)
此处的<>
即是一个转换器,默认为字符串类型,即将该位置的数据以字符串格式进行匹配、并以字符串为数据类型类型、 user_id
为参数名传入视图。
Flask也提供其他类型的转换器
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
将上面的例子以整型匹配数据,可以如下使用:
@app.route('/users/<int:user_id>')
def user_info(user_id):
print(type(user_id))
return 'hello user {}'.format(user_id)
自定义转换器
如果遇到需要匹配提取/users/18512345678
中的手机号数据,Flask内置的转换器就无法满足需求,此时需要自定义转换器。
定义方法
自定义转换器主要做3步
-
创建转换器类,保存匹配时的正则表达式
from werkzeug.routing import BaseConverter # 1.自定义匹配手机号的转换器 class PhoneConverter(BaseConverter): regex = "^1[3456789]\d{9}$"
- 注意
regex
名字固定
- 注意
-
注册转换器,将自定义的转换器告知Flask应用
# 2. 注册转换器 app.url_map.converters["phone"] = PhoneConverter
-
使用转换器:在使用转换器的地方定义使用
@app.get('/users/phone/<phone:phoneNum>') def getPhone(phoneNum): return f"你的手机号是:{phoneNum}"
4.1.2. 其他参数
如果想要获取其他地方传递的参数,可以通过Flask提供的request对象来读取。
不同位置的参数都存放在request的不同属性中
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转换为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
例如 想要获取请求/articles?channel_id=1
中channel_id
的参数,可以按如下方式使用:
@app.get("/request/change")
def getChangeId():
changeId = request.args.get("change_id")
return f"change_id = {changeId}"
上传图片
客户端上传图片到服务器,并保存到服务器中
@app.post("/request/images")
def putImages():
image = request.files.get("image")
image.save("./Saber.png")
return "图片上传成功"
4.2 处理响应
4.2.1 返回模板
使用render_template
方法渲染模板并返回
# 其中template表示的是html模板的名称,*kwargs是传递的参数
render_template(template,*kwargs)
实例:
测试:
4.2.2 重定向
from flask import redirect
@app.get("/redirectTest/")
def redirectTest():
return redirect("https://www.baidu.com/")
4.2.3 返回JSON
from flask import jsonify
@app.get("/jsonTest/")
def jsonTest():
json_dict = {
"user_id": 10086,
"user_name": "张三",
"user_age": 30
}
return jsonify(json_dict)
4.2.4 自定义状态码和响应头
1 元组方式
可以返回一个元组,这样的元组必须是
# 其中response为相应内容,status响应状态码,headers为响应头的添加内容
(response, status, headers)
的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
# 第一种自定义响应头的方式
@app.get("/demoTestOne/")
def demoTestOne():
return ("状态码为 202",202)
2 make_response方式
@app.get("/demoTestTwo/")
def demoTestTwo():
resp = make_response("状态码为 201")
resp.headers['name'] = "zhangsan"
resp.status = 201
return resp
4.3 Cookie与Session
4.3.1 Cookie
1 设置Cookie
@app.get("/setCookie/")
def setCookie():
resp = make_response("set Cookie")
resp.set_cookie("username","zhangsan")
return resp
2 设置Cookie有效期
# 设置Cookie有效期
@app.get("/setCTime")
def setCTime():
resp = make_response("set cookie time")
resp.set_cookie("username","zhangsan",max_age=180)
return resp
3 读取Cookie
# 读取Cookie
@app.get("/getCookie")
def getCookie():
requ = request.cookies.get("username")
return f"username:{requ}"
4 删除Cookie(实际是就是将有效期设置为0)
# 删除Cookie
@app.get("/deleteCookie")
def deleteCookie():
resp = make_response("delete Cookie")
resp.delete_cookie("username")
return resp
4.3.2 Session
1 需要先设置SECRET_KEY
# 1 设置SECRET_KEY
class DefaultConfig(object):
SECRET_KEY = "zhansganlisiwangu"
app.config.from_object(DefaultConfig)
2 设置Session
# 2 设置Session
from flask import session
@app.get("/setSession/")
def setSession():
session['username'] = "username"
return "set Session"
3 读取Session
@app.get("/getSession/")
def getSession():
username = session.get("username")
return f"get session username {username}"
这里的Session并不是真正意义上的session,而是伪装成Session的Cookie,因为它实际上并没有将值存储在服务器上
5 请求钩子与上下文
5.1 异常处理
5.1.1 HTTP 异常主动抛出
- abort 方法
- 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)。
- 参数:
- code – HTTP的错误状态码
# abort(404)
abort(500)
抛出状态码的话,只能抛出 HTTP 协议的错误状态码
from flask import abort
@app.get("/getExcetion")
def getExcetion():
print("异常请求测试")
abort(500)
return "500异常测试"
5.1.2 捕获错误
errorhandler
装饰器
- 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
参数:
-
code_or_exception – HTTP的错误状态码或指定异常
-
例如统一处理状态码为500的错误给用户友好的提示:
# 捕获异常
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
return "除数不能为0"
@app.get("/zeroTest")
def zeroDivision():
a = 10086/0
return f"结果是:{a}"
- 捕获指定异常
@app.errorhandler(500)
def errorCode500():
return "呜呜呜,服务器出错了,请稍后访问"
@app.get("/getExcetion")
def getExcetion():
print("异常请求测试")
abort(500)
return "500异常测试"
5.2 请求钩子
5.2.1 请求钩子概念
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下三种请求钩子(before_first_request已经被废止
):
1 before_request
- 在每次请求前执行
- 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
2 after_request
- 如果没有抛出错误,在每次请求后执行
- 接受一个参数:视图函数作出的响应
- 在此函数中可以对响应值在返回之前做最后一步修改处理
- 需要将参数中的响应在此参数中进行返回
3 teardown_request
- 在每次请求后执行
- 接受一个参数:错误信息,如果有相关错误抛出
5.2.2 代码测试
# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
print("before_request")
# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
print("after_request")
response.headers["Content-Type"] = "application/json"
return response
# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
@app.teardown_request
def teardown_request(response):
print("teardown_request")
# 测试请求
@app.route('/testRequest')
def index():
return 'index'
请求时打印的内容,可以看出,三个钩子函数都被调用
5.3 上下文
上下文:即语境,语意,在程序中可以理解为在代码执行到某一时刻时,根据之前代码所做的操作以及下文即将要执行的逻辑,可以决定在当前时刻下可以使用到的变量,或者可以完成的事情。
Flask中有两种上下文,请求上下文和应用上下文
Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
5.3.1 请求上下文(request context)
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
1 request
- 封装了HTTP请求的内容,针对的是
http
请求。举例:user = request.args.get('user')
,获取的是get请求的参数。
# request获取请求参数
@app.get("/requestContext")
def requestContext():
user = request.args.get('user')
return f"你的名字是:{user}"
2 session
- 用来记录请求会话中的信息,针对的是用户信息。举例:
session['name'] = user.id
,可以记录用户信息。还可以通过session.get('name')
获取用户信息。
# session存储用户id
@app.get("/setRequestSession")
def setRequestSession():
user_id = "zhangsan"
session['name'] = user_id
return "Session 设置成功"
# 获取Session
@app.get("/getRequestSession")
def setRequestSession():
username = session.get('name')
return f"你的名字是:{username}"
5.3.2 应用上下文(application context)
它的字面意思是 应用上下文
,但它不是一直存在的,它只是request context
中的一个对app
的代理(人),所谓local proxy
。它的作用主要是帮助 request
获取当前的应用,它是伴 request
而生,随 request
而灭的。
应用上下文对象有:current_app
,g
1 current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name
打印当前app
的名称,也可以在current_app
中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,
IP
多少,内存多大
示例
创建current_app_demo.py
from flask import current_app
app1 = Flask(__name__)
app2 = Flask(__name__)
app1.redis_cli = 'app1 redis client'
app2.redis_cli = 'app2 redis client'
@app1.route('/route11')
def route11():
# current_app获取的是当前正在使用的app
return current_app.redis_cli
@app2.route('/route21')
def route21():
return current_app.redis_cli
运行
set FLASK_APP=main:app1
flask run
set FLASK_APP=main:app2
flask run
作用
current_app
就是当前运行的flask app
,在代码不方便直接操作flask的app对象时,可以操作current_app
就等价于操作flask app
对象,而不会出现app
调用app
对象自身导致的循环依赖问题
2 g对象
g
作为flask
程序全局的一个临时变量,充当中间媒介的作用,我们可以通过它在一次请求调用的多个函数间传递一些数据。每次请求都会重设这个变量。
示例
from flask import g
def query():
user_id = g.user_id
user_name = g.user_name
return f"userid={user_id},name={user_name},已执行内部函数"
@app.post("/getUser/")
def get_user():
userid = request.args.get("userid")
username = request.args.get("username")
g.user_id = userid
g.user_name = username
text = query()
return text
5.3.3 app_context 与 request_context
思考
在Flask程序未运行的情况下,调试代码时需要使用current_app
、g
、request
这些对象,该如何进行测试。
1 app_context
app_context
为我们提供了应用上下文环境,允许我们在外部使用应用上下文curren
t_app、
g`
可以通过with
语句进行使用
>>> from flask import Flask
>>> app = Flask('')
>>> app.redis_cli = 'redis client'
>>>
>>> from flask import current_app
>>> current_app.redis_cli # 错误,没有上下文环境
报错
>>> with app.app_context(): # 借助with语句使用app_context创建应用上下文
... print(current_app.redis_cli)
...
redis client
2 request_context
request_context
为我们提供了请求上下文环境,允许我们在外部使用请求上下文request
、session
可以通过with语句进行使用
>>> from flask import Flask
>>> app = Flask('')
>>> request.args # 错误,没有上下文环境
报错
>>> environ = {'wsgi.version':(1,0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'} # 模拟解析客户端请求之后的wsgi字典数据
>>> with app.request_context(environ): # 借助with语句使用request_context创建请求上下文
... print(request.path)
6 Flask-RESTful
6.1 起步
Flask-RESTful是用于快速构建REST API的Flask扩展。
6.1.1 安装
pip install flask-restful
6.1.2 Hello World
from flask import Flask
from flask_restful import Resource,Api
app = Flask(__name__)
api = Api(app)
class HelloWordResource(Resource):
def get(self):
return {"hello":"world"}
def post(self):
return {"msg":"post Hello,World"}
api.add_resource(HelloWordResource,"/hello")
# 也可以选择flask run的启动方式
if __name__=="__main__":
app.run(debug=True)
6.2 关于视图
6.2.1 为路由起名
通过endpoint参数为路由起名
# 这个名称在后续会用到
api.add_resource(HelloWorldResource, '/', endpoint='HelloWorld')
6.2.2 蓝图中使用
# 1 定义蓝图
user_bp = Blueprint('user',__name__,url_prefix="/user")
# 2 api配置蓝图
user_api = Api(user_bp)
# 3 app配置蓝图
app.register_blueprint(user_bp)
# 4 定义蓝图请求
class UserBlueResource(Resource):
def get(self):
context = {
"massage":2001,
"data":{
"name":"zhangsan",
"age":16
}
}
return context
def post(self):
context = {
"massage":2001,
"data":{
"name":"This is post Man",
"age":"*****(查看请充值vip)"
}
}
return context
# 5 配置api路由
user_api.add_resource(UserBlueResource,"/blueUser")
6.2.3 装饰器
使用method_decorators
添加装饰器
为类视图中的所有方法添加装饰器
def decorator1(func):
def wrapper(*args, **kwargs):
print('装饰器1正在执行')
return func(*args, **kwargs)
return wrapper
def decorator2(func):
def wrapper(*args, **kwargs):
print('装饰器2正在执行')
return func(*args, **kwargs)
return wrapper
class DemoRequest(Resource):
method_decorators = [decorator1,decorator2]
def get(self):
context = {
"msg":200,
"data":{
"text":"一切正常"
}
}
return context
def post(self):
context = {
"msg":200,
"data":{
"text":"I am PostMax"
}
}
return context
api.add_resource(DemoRequest,"/demo")
为类视图中不同的方法添加不同的装饰器
class DemoResource(Resource):
method_decorators = {
'get': [decorator1, decorator2],
'post': [decorator1]
}
# 使用了decorator1 decorator2两个装饰器
def get(self):
return {'msg': 'get view'}
# 使用了decorator1 装饰器
def post(self):
return {'msg': 'post view'}
# 未使用装饰器
def put(self):
return {'msg': 'put view'}
get请求测试
post请求测试
put请求测试
6.3 关于请求
6.3.1 概述
Flask-RESTful
提供了RequestParser
类,用来帮助我们检验和转换请求数据。
6.3.2 使用步骤:
-
创建
RequestParser
对象 -
向
RequestParser
对象中添加需要检验或转换的参数声明 -
使用
parse_args()
方法启动检验处理 -
检验之后从检验结果中获取参数时可按照字典操作或对象属性操作
args.rate 或 args['rate']
6.3.3 参数说明
1 required
描述请求是否一定要携带对应参数,默认值为False
-
True 强制要求携带
若未携带,则校验失败,向客户端返回错误信息,状态码400
-
False 不强制要求携带
若不强制携带,在客户端请求未携带参数时,取出值为None
from flask_restful.reqparse import RequestParser
class rqparseRequest(Resource):
# 1 required 参数测试
def get(self):
rqp = RequestParser()
# 强制要求携带该参数
rqp.add_argument("user",required=True)
args = rqp.parse_args()
return {"message":200,"data":{"user":args.user}}
api.add_resource(rqparseRequest,"/rpone")
2 help
参数检验错误时返回的错误描述信息
from flask_restful.reqparse import RequestParser
class rqparseRequest(Resource):
# 2 help参数测试
def post(self):
rqp = RequestParser()
# 强制要求携带该参数
rqp.add_argument("user",required=True,help="没有user参数")
args = rqp.parse_args()
return {"message":200,"data":{"user":args.user}}
api.add_resource(rqparseRequest,"/rpone")
3 action
描述对于请求参数中出现多个同名参数时的处理方式
action='store'
保留出现的第一个, 默认action='append'
以列表追加保存所有同名参数的值
from flask_restful.reqparse import RequestParser
class rqparseRequest(Resource):
# 3 action参数测试
def put(self):
rp = RequestParser()
rp.add_argument("user",required=True,action="append")
args = rp.parse_args()
return {"message": 200, "data": {"user": args.user}}
api.add_resource(rqparseRequest,"/rpone")
4 type
描述参数应该匹配的类型,可以使用python的标准数据类型string、int,也可使用Flask-RESTful
提供的检验方法,还可以自己定义
-
标准类型
from flask_restful.reqparse import RequestParser class rqparseRequest(Resource): # 4 type参数测试 def delete(self): rp = RequestParser() rp.add_argument("user",required=True,type=int) args = rp.parse_args() return {"message": 200, "data": {"user": args.user}} api.add_resource(rqparseRequest,"/rpone")
-
Flask-RESTful提供
检验类型方法在
flask_restful.inputs
模块中-
url
-
regex(指定正则表达式)
from flask_restful import inputs class rqparseRequestTwo(Resource): # Flask指定的检验格式 def get(self): rp = RequestParser() rp.add_argument('user', type=inputs.regex(r'^1[3-9]\d{9}$')) args = rp.parse_args() return {"message": 200, "data": {"user": args.user}} api.add_resource(rqparseRequestTwo,"/rptwo")
-
natural
自然数0、1、2、3... -
positive
正整数 1、2、3... -
int_range(low ,high)
整数范围rp.add_argument('a', type=inputs.int_range(1, 10))
-
boolean
-
-
自定义
def mobile(mobile_str): """ 检验手机号格式 :param mobile_str: str 被检验字符串 :return: mobile_str """ if re.match(r'^1[3-9]\d{9}$', mobile_str): return mobile_str else: raise ValueError('{} is not a valid mobile'.format(mobile_str)) rp.add_argument('a', type=mobile)
5 location
描述参数应该在请求数据中出现的位置
# Look only in the POST body
parser.add_argument('name', type=int, location='form')
# Look only in the querystring
parser.add_argument('PageSize', type=int, location='args')
# From the request headers
parser.add_argument('User-Agent', location='headers')
# From http cookies
parser.add_argument('session_id', location='cookies')
# From json
parser.add_argument('user_id', location='json')
# From file uploads
parser.add_argument('picture', location='files')
也可指明多个位置
parser.add_argument('text', location=['headers', 'json'])
测试
from flask_restful import inputs
class rqparseRequestTwo(Resource):
# location测试
def post(self):
parser = RequestParser()
# form表单
parser.add_argument('name', type=int, location='form')
# 位置参数
parser.add_argument('PageSize', type=int, location='args')
args = parser.parse_args()
return {"message": 200, "data": {"name": args.name,"PageSize": args.PageSize}}
api.add_resource(rqparseRequestTwo,"/rptwo")
6.4 关于响应
6.4.1 序列化数据
Flask-RESTful
提供了marshal
工具,用来帮助我们将数据序列化为特定格式的字典数据,以便作为视图的返回值。
# 1 导包
from flask_restful import Resource,fields,marshal_with,marshal
# 2 定义类
class Book(object):
def __init__(self,bookName,author):
self.bookName = bookName
self.author = author
# 3 定义模板
resource_fields = {
'bookName':fields.String,
"author":fields.String
}
# 4 定义返回函数
class BookTodo(Resource):
# 使用装饰器实现
@marshal_with(resource_fields,envelope="context")
def get(self):
# 对象无法直接返回,这个装饰器相当于将对象序列化了
book = Book("《射雕英雄传》","金庸")
return book
api.add_resource(BookTodo,"/booktodo")
也可以不使用装饰器的方式
# 不使用装饰器,而是调用函数实现
def post(self):
book = Book("《射雕英雄传》","金庸")
return marshal(book,resource_fields,envelope="contextPost")
还可以直接自己实现
# 直接自己写,单次返回更简单
def put(self):
book = Book("《射雕英雄传》", "金庸")
context = {
"context": {
"bookName": book.bookName,
"author": book.author
}
}
return context
6.4.2 定制返回的JSON格式
1 需求
想要接口返回的JSON数据具有如下统一的格式
{"message": "描述信息", "data": {要返回的具体数据}}
在接口处理正常的情况下, message返回ok即可,但是若想每个接口正确返回时省略message字段
class DemoResource(Resource):
def get(self):
return {'user_id':1, 'name': 'itcast'}
对于诸如此类的接口,能否在某处统一格式化成上述需求格式?
{"message": "OK", "data": {'user_id':1, 'name': 'itcast'}}
2 解决
Flask-RESTful的Api对象提供了一个representation
的装饰器,允许定制返回数据的呈现格式
api = Api(app)
@api.representation('application/json')
def handle_json(data, code, headers):
# TODO 此处添加自定义处理
return resp
Flask-RESTful原始对于json的格式处理方式如下:
代码出处:flask_restful.representations.json
from flask import make_response, current_app
from flask_restful.utils import PY3
from json import dumps
def output_json(data, code, headers=None):
"""Makes a Flask response with a JSON encoded body"""
settings = current_app.config.get('RESTFUL_JSON', {})
# If we're in debug mode, and the indent is not set, we set it to a
# reasonable value here. Note that this won't override any existing value
# that was set. We also set the "sort_keys" value.
if current_app.debug:
settings.setdefault('indent', 4)
settings.setdefault('sort_keys', not PY3)
# always end the json dumps with a new line
# see https://github.com/mitsuhiko/flask/pull/1262
dumped = dumps(data, **settings) + "\n"
resp = make_response(dumped, code)
resp.headers.extend(headers or {})
return resp
为满足需求,做如下改动即可
@api.representation('application/json')
def output_json(data, code, headers=None):
"""Makes a Flask response with a JSON encoded body"""
# --------------------此处为自己添加--------------------------
if 'message' not in data:
data = {
'message': 'OK',
'data': data
}
# ---------------------------------------------------------
settings = current_app.config.get('RESTFUL_JSON', {})
if current_app.debug:
settings.setdefault('indent', 4)
settings.setdefault('sort_keys', not PY3)
dumped = dumps(data, **settings) + "\n"
resp = make_response(dumped, code)
resp.headers.extend(headers or {})
return resp
测试代码
# 测试代码
class userTest(Resource):
def get(self):
return {"name":"zhangsan"}
api.add_resource(userTest,"/userTest")
—Flask2.x
新变化
嵌套蓝图(#3923)
对于一个比较大的项目,一般会使用蓝本来组织不同的模块。而如果你的项目非常大,那么嵌套蓝本就可以派上用场了。借助嵌套蓝本支持,你可以在某个蓝本之内再创建多个子蓝本,对项目进行多层模块化组织(而且支持无限嵌套,你可以嵌套很多层):
parent = Blueprint("parent", __name__) # 创建父蓝本
child = Blueprint("child", __name__) # 创建子蓝本
parent.register_blueprint(child, url_prefix="/child") # 把子蓝本注册到父蓝本上
app.register_blueprint(parent, url_prefix="/parent") # 把父蓝本注册到程序实例上
这样在生成子蓝本的 URL 时需要传入完整的端点链:
url_for('parent.child.create')
/parent/child/create
这个特性来源于一个 2012 年创建的 feature request issue。
基本的 async/await 支持
Flask 2.0 带来了基本的异步支持,现在你可以定义异步视图(以及异步错误处理函数、异步请求钩子函数):
import asyncio
from flask import Flask
app = Flask(__name__)
@app.route('/')
async def say_hello():
await asyncio.sleep(1)
return {'message': 'Hello!'}
注意要先安装额外依赖:
pip install -U flask[async]
顺便说一句,如果你在 Windows 上使用 Python 3.8,那么会有一个来自 Python 或 asgiref 的 bug 导致出错:ValueError: set_wakeup_fd only works in main thread
。可以通过下面两种方式(任选一种)处理(具体参考这个 SO 回答):
- 升级到 Python 3.9
- 在你的入口脚本顶部添加临时修复代码:
# top of the file
import sys, asyncio
if sys.platform == "win32" and (3, 8, 0) <= sys.version_info < (3, 9, 0)::
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
不过目前只是一个基于 asgiref 的异步实现,作为异步支持的第一步,后续还会进行更多的优化和改进,更多相关信息可以参考文档。
快捷路由装饰器(#3907)
新增了下面的快捷路由装饰器:
app.get()
app.post()
app.delete()
app.put()
app.patch()
举例来说,使用 app.post()
等同于 app.route(methods=['POST'])
:
from flask import Flask
app = Flask(__name__)
@app.post('/')
def index():
return {'message': 'Hello!'}
注意不要在这些快捷装饰器里传入 methods
参数。如果需要在单个视图处理多个方法的请求,使用 app.route()
。
我在某次 pallets 会议上提议添加这些装饰器时一开始是被拒绝的,后来 Phil Jones 创建了 #3907 经过二次讨论后才最终合并(被拒绝后我就把当时正在开发的 APIFlask 从扩展改成了继承 Flask 基类的框架,然后加了这些装饰器)。
其他变动
- 优化了浏览器缓存控制,对 CSS、图片等静态文件做出的变动会在程序重载后立刻更新,不再需要手动清除页面缓存。
- Werkzeug 的 multipart 解析(尤其是大文件上传处理)性能提高了 15 倍。
- 配置对象增加
Config.from_file()
方法支持从任意文件加载器导入配置(比如toml.load
、json.load
),未来会取代Config.from_json()
方法。 - 在使用环境变量
FLASK_APP
指定工厂函数时支持传入关键字参数。 flask shell
支持 tab 和历史补全(需要安装readline
)。- CLI 系统优化了找不到程序时的错误处理和错误输出显示,同时修正了 Windows 上的命令行颜色输出。