翼度科技»论坛 编程开发 python 查看内容

8 最全的零基础Flask教程

2

主题

2

帖子

6

积分

新手上路

Rank: 1

积分
6
最全的零基础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常用虚拟环境命令
  1. # 虚拟环境
  2. conda create -n 虚拟环境名称 python=python版本号  # 创建虚拟环境
  3. conda remove -n 虚拟环境名称 --all  # 删除虚拟环境
  4. conda activate 虚拟环境名称  # 激活虚拟环境
  5. conda env list 或 conda info -e # 查看全部虚拟环境
  6. deactivate 虚拟环境名称  # 退出虚拟环境
复制代码
2.1.2. 创建虚拟环境

Flask 再2.3.0版本上已经放弃对Python3.7的支持,当前支持的Python把版本区间为3.7~3.12,因此这里将Python的版本定为3.8
  1. conda create -n FlaskWeb python=3.8
复制代码

2.1.3. 安装Flask

使用flask 2.3.0版本
  1. 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

默认参数情况下,访问静态资源
  1. app = Flask(__name__)
复制代码


修改参数的情况下,访问静态资源
  1. # 定义静态资源的访问路径为url_path_param,静态资源文件夹名称为folder_param
  2. 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(配置对象)
  1. # 定义默认配置类
  2. class DefultConfig(object):
  3.     """
  4.     默认配置
  5.     """
  6.     SECRET_KEY = "lalalalalala"
  7. # 定义开发环境下的配置类,继承自默认配置类
  8. class DevDefultConfig(DefultConfig):
  9.     DEBUG=True
  10. app = Flask(__name__)
  11. # 从配置对象中添加配置
  12. app.config.from_object(DefultConfig)
复制代码
应用场景:

从配置文件中加载
app.config.from_pyfile(配置文件)
新建一个配置文件setting.py
  1. SECRET_KEY = 'TPmi4aLWRbyVq8zu9v82dWYW1'
复制代码

在Flask程序文件中读取该配置文件
  1. # 从配置文件中添加配置
  2. app.config.from_pyfile("setting.py")
  3. @app.route("/")
  4. def index():
  5.     return app.config["SECRET_KEY"]
复制代码

访问测试

3 从环境变量中加载配置信息
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。
环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。
通俗的理解,环境变量就是我们设置在操作系统中,由操作系统代为保存的变量值
Flask使用环境变量加载配置的本质是通过环境变量值找到配置文件,再读取配置文件的信息,其使用方式为:
代码查询环境变量的方式:
  1. # 环境变量中存储的是配置文件的绝对地址
  2. app.config.from_envvar('环境变量名')
复制代码
两种定义环境变量的方式
Windows环境下定义临时环境变量
  1. set 临时环境变量名称 = 变量的值
复制代码

Pycharm下定义环境变量的方式



示例:
再运行程序,即可。
  1. app = Flask(__name__)
  2. app.config.from_envvar('PROJECT_SETTING', silent=True)
  3. @app.route("/")
  4. def index():
  5.     print(app.config['SECRET_KEY'])
  6.     return "hello world"
复制代码
关于silent参数的说明:
表示系统环境变量中没有设置相应值时是否抛出异常

  • False 表示不安静的处理,没有值时报错通知,默认为False
  • True 表示安静的处理,即时没有值也让Flask正常的运行下去


访问测试

项目中的常用方式

使用工厂模式创建Flask app,并结合使用配置对象与环境变量加载配置

  • 使用配置对象加载默认配置
  • 使用环境变量加载不想出现在代码中的敏感配置信息
  1. def create_flask_app(config):
  2.     """
  3.     创建Flask应用
  4.     :param config: 配置对象
  5.     :return: Flask应用
  6.     """
  7.     app = Flask(__name__)
  8.     app.config.from_object(config)
  9.     # 从环境变量指向的配置文件中读取的配置信息会覆盖掉从配置对象中加载的同名参数
  10.     app.config.from_envvar("PROJECT_SETTING", silent=True)
  11.     return app
复制代码

方便了程序的使用,是一种常用的方式
2.3.3 app.run 参数

可以指定运行的主机IP地址,端口,是否开启调试模式
  1. app.run(host="0.0.0.0", port=5000, debug = True)
复制代码


关于DEBUG调试模式

  • 程序代码修改后可以自动重启服务器
  • 在服务器出现相关错误的时候可以直接将错误信息返回到前端进行展示
2.4 开发服务器启动方式

在1.0版本之后,Flask调整了开发服务器的启动方式,由代码编写app.run()语句调整为命令flask run启动。
  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.route('/')
  4. def index():
  5.     return 'Hello World'
  6. # 程序中不用再写app.run()
复制代码

启动
  1. # 指定FLASK_APP环境变量(Windows CMD环境),demo01是文件名
  2. set FLASK_APP=demo01
  3. # 执行
  4. 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运行在开发模式

扩展:指定参数运行
  1. flask run --host 127.0.0.1 --port 6666
复制代码

3 路由与蓝图

3.1 路由


3.1.1  查询路由信息

需要提前设置好FLASK_APP环境变量的值,并且cmd处于管理员模式
命令行方式
  1. flask routes
复制代码

在程序中获取
在应用中的url_map属性中保存着整个Flask应用的路由映射信息,可以通过读取这个属性获取路由信息
  1. print(app.url_map)
复制代码


在程序中遍历路由信息
  1. for rule in app.url_map.iter_rules():
  2.     print('name={} path={}'.format(rule.endpoint, rule.rule))
复制代码


测试
实现通过访问/routes地址,以json的方式返回应用内的所有路由信息
  1. import json
  2. from flask import Flask
  3. app = Flask(__name__)
  4. @app.route("/flaskRoutes")
  5. def getRoutes():
  6.     context = {}
  7.     for rule in app.url_map.iter_rules():
  8.         context[rule.endpoint] = rule.rule
  9.     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. # 1.创建蓝图对象,两个参数分别为蓝图名称(name)和import_name
    2. user_bp =Blueprint('user',__name__)
    复制代码
  • 在这个蓝图对象上进行操作,注册路由,指定静态文件夹,注册模版过滤器
    1. # 2.蓝图对象注册路由
    2. @user_bp.get("/user")
    3. def user_profile():
    4.     return "user_profile"
    复制代码
  • 在应用对象上注册这个蓝图对象
    1. # 3.在app中注册蓝图对象
    2. app.register_blueprint(user_bp)
    复制代码
3.2.3 单文件蓝图

可以将创建蓝图对象与定义视图放到一个文件中 ,也就是按照上面看到的代码


3.2.4 目录(包)蓝图

对于一个打算包含多个文件的蓝图,通常将创建蓝图对象放到Python包的__init__.py文件中
  1. --------- project # 工程目录
  2.   |------ main.py # 启动文件
  3.   |------ user  #用户蓝图
  4.   |  |--- __init__.py  # 此处创建蓝图对象
  5.   |  |--- view.py
复制代码


注意:如果不在__init__.py中导入view.py文件,则会导致404错误
3.2.5 扩展用法

1 指定蓝图的url前缀
在应用中注册蓝图时使用url_prefix参数指定前缀
  1. app.register_blueprint(user_bp,url_prefix="/user")
复制代码


2 蓝图内部静态文件
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在 创建时指定 static_folder 参数。
下面的示例将蓝图所在目录下的user_admin目录设置为静态目录
  1. user_bp = Blueprint("user",__name__,static_folder="user_admin")
  2. app.register_blueprint(user_bp,url_prefix="/user")
复制代码


现在就可以使用/user/user_admin/访问user_admin目录下的静态文件了。
也可通过static_url_path改变访问路径
  1. user_bp = Blueprint("user",__name__,static_folder="user_admin",static_url_path="static")
  2. app.register_blueprint(user_bp,url_prefix="/user")
复制代码


3 蓝图内部模板目录
蓝图对象默认的模板目录为系统的模版目录,可以在创建蓝图对象时使用template_folder关键字参数设置模板目录
  1. user_bp = Blueprint("user",__name__,
  2.                     static_folder="user_admin",
  3.                     static_url_path="static",
  4.                     template_folder="templates")
复制代码

4 请求与响应

4.1 处理请求

请求携带的数据可能出现在HTTP报文中的不同位置,需要使用不同的方法来获取参数。
4.1.1. URL路径参数(动态路由)

Flask不同于Django直接在定义路由时编写正则表达式的方式,而是采用转换器语法:
  1. # 这里本质上进行的是正则匹配
  2. @app.route('/users/<user_id>')
  3. def user_info(user_id):
  4.     print(type(user_id))
  5.     return 'hello user {}'.format(user_id)
复制代码


此处的即是一个转换器,默认为字符串类型,即将该位置的数据以字符串格式进行匹配、并以字符串为数据类型类型、 user_id为参数名传入视图。
Flask也提供其他类型的转换器
  1. DEFAULT_CONVERTERS = {
  2.     'default':          UnicodeConverter,
  3.     'string':           UnicodeConverter,
  4.     'any':              AnyConverter,
  5.     'path':             PathConverter,
  6.     'int':              IntegerConverter,
  7.     'float':            FloatConverter,
  8.     'uuid':             UUIDConverter,
  9. }
复制代码
将上面的例子以整型匹配数据,可以如下使用:
  1. @app.route('/users/<int:user_id>')
  2. def user_info(user_id):
  3.     print(type(user_id))
  4.     return 'hello user {}'.format(user_id)
复制代码

自定义转换器
如果遇到需要匹配提取/users/18512345678中的手机号数据,Flask内置的转换器就无法满足需求,此时需要自定义转换器。
定义方法
自定义转换器主要做3步

  • 创建转换器类,保存匹配时的正则表达式
    1. from werkzeug.routing import BaseConverter
    2. # 1.自定义匹配手机号的转换器
    3. class PhoneConverter(BaseConverter):
    4.     regex = "^1[3456789]\d{9}$"
    复制代码

    • 注意regex名字固定

  • 注册转换器,将自定义的转换器告知Flask应用
    1. # 2. 注册转换器
    2. app.url_map.converters["phone"] = PhoneConverter
    复制代码
  • 使用转换器:在使用转换器的地方定义使用
    1. @app.get('/users/phone/<phone:phoneNum>')
    2. def getPhone(phoneNum):
    3.     return f"你的手机号是:{phoneNum}"
    复制代码


4.1.2. 其他参数

如果想要获取其他地方传递的参数,可以通过Flask提供的request对象来读取。
不同位置的参数都存放在request的不同属性中
属性说明类型data记录请求的数据,并转换为字符串*form记录请求中的表单数据MultiDictargs记录请求中的查询参数MultiDictcookies记录请求中的cookie信息Dictheaders记录请求中的报文头EnvironHeadersmethod记录请求使用的HTTP方法GET/POSTurl记录请求的URL地址stringfiles记录请求上传的文件*例如 想要获取请求/articles?channel_id=1中channel_id的参数,可以按如下方式使用:
  1. @app.get("/request/change")
  2. def getChangeId():
  3.     changeId = request.args.get("change_id")
  4.     return f"change_id = {changeId}"
复制代码


上传图片
客户端上传图片到服务器,并保存到服务器中
  1. @app.post("/request/images")
  2. def putImages():
  3.     image = request.files.get("image")
  4.     image.save("./Saber.png")
  5.     return "图片上传成功"
复制代码


4.2 处理响应

4.2.1 返回模板

使用render_template方法渲染模板并返回
  1. # 其中template表示的是html模板的名称,*kwargs是传递的参数
  2. render_template(template,*kwargs)
复制代码
实例:

测试:

4.2.2 重定向
  1. from flask import redirect
  2. @app.get("/redirectTest/")
  3. def redirectTest():
  4.     return redirect("https://www.baidu.com/")
复制代码

4.2.3 返回JSON
  1. from flask import jsonify
  2. @app.get("/jsonTest/")
  3. def jsonTest():
  4.     json_dict = {
  5.         "user_id": 10086,
  6.         "user_name": "张三",
  7.         "user_age": 30
  8.     }
  9.     return jsonify(json_dict)
复制代码

4.2.4 自定义状态码和响应头

1 元组方式
可以返回一个元组,这样的元组必须是
  1. # 其中response为相应内容,status响应状态码,headers为响应头的添加内容
  2. (response, status, headers)
复制代码
的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
  1. # 第一种自定义响应头的方式
  2. @app.get("/demoTestOne/")
  3. def demoTestOne():
  4.     return ("状态码为 202",202)
复制代码

2 make_response方式
  1. @app.get("/demoTestTwo/")
  2. def demoTestTwo():
  3.     resp = make_response("状态码为 201")
  4.     resp.headers['name'] = "zhangsan"
  5.     resp.status = 201
  6.     return resp
复制代码

4.3 Cookie与Session

4.3.1 Cookie

1 设置Cookie
  1. @app.get("/setCookie/")
  2. def setCookie():
  3.     resp = make_response("set Cookie")
  4.     resp.set_cookie("username","zhangsan")
  5.     return resp
复制代码

2 设置Cookie有效期
  1. # 设置Cookie有效期
  2. @app.get("/setCTime")
  3. def setCTime():
  4.     resp = make_response("set cookie time")
  5.     resp.set_cookie("username","zhangsan",max_age=180)
  6.     return resp
复制代码

3 读取Cookie
  1. # 读取Cookie
  2. @app.get("/getCookie")
  3. def getCookie():
  4.     requ = request.cookies.get("username")
  5.     return f"username:{requ}"
复制代码

4 删除Cookie(实际是就是将有效期设置为0)
  1. # 删除Cookie
  2. @app.get("/deleteCookie")
  3. def deleteCookie():
  4.     resp = make_response("delete Cookie")
  5.     resp.delete_cookie("username")
  6.     return resp
复制代码

4.3.2 Session

1 需要先设置SECRET_KEY
  1. # 1 设置SECRET_KEY
  2. class DefaultConfig(object):
  3.     SECRET_KEY = "zhansganlisiwangu"
  4. app.config.from_object(DefaultConfig)
复制代码
2 设置Session
  1. # 2 设置Session
  2. from flask import session
  3. @app.get("/setSession/")
  4. def setSession():
  5.     session['username'] = "username"
  6.     return "set Session"
复制代码

3 读取Session
  1. @app.get("/getSession/")
  2. def getSession():
  3.     username = session.get("username")
  4.     return f"get session username {username}"
复制代码
这里的Session并不是真正意义上的session,而是伪装成Session的Cookie,因为它实际上并没有将值存储在服务器上

5 请求钩子与上下文

5.1 异常处理

5.1.1 HTTP 异常主动抛出


  • abort 方法

    • 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)。

  • 参数:

    • code – HTTP的错误状态码

  1. # abort(404)
  2. abort(500)
复制代码
抛出状态码的话,只能抛出 HTTP 协议的错误状态码
  1. from flask import abort
  2. @app.get("/getExcetion")
  3. def getExcetion():
  4.     print("异常请求测试")
  5.     abort(500)
  6.     return "500异常测试"
复制代码


5.1.2 捕获错误

errorhandler 装饰器

  • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
参数:

  • code_or_exception – HTTP的错误状态码或指定异常
  • 例如统一处理状态码为500的错误给用户友好的提示:
  1. # 捕获异常
  2. @app.errorhandler(ZeroDivisionError)
  3. def zero_division_error(e):
  4.     return "除数不能为0"
  5. @app.get("/zeroTest")
  6. def zeroDivision():
  7.     a = 10086/0
  8.     return  f"结果是:{a}"
复制代码


  • 捕获指定异常
  1. @app.errorhandler(500)
  2. def errorCode500():
  3.     return "呜呜呜,服务器出错了,请稍后访问"
  4. @app.get("/getExcetion")
  5. def getExcetion():
  6.     print("异常请求测试")
  7.     abort(500)
  8.     return "500异常测试"
复制代码

5.2 请求钩子

5.2.1 请求钩子概念

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下三种请求钩子(before_first_request已经被废止):
1 before_request

  • 在每次请求前执行
  • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
2 after_request

  • 如果没有抛出错误,在每次请求后执行
  • 接受一个参数:视图函数作出的响应
  • 在此函数中可以对响应值在返回之前做最后一步修改处理
  • 需要将参数中的响应在此参数中进行返回
3 teardown_request

  • 在每次请求后执行
  • 接受一个参数:错误信息,如果有相关错误抛出
5.2.2 代码测试
  1. # 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
  2. # 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
  3. @app.before_request
  4. def before_request():
  5.     print("before_request")
复制代码
  1. # 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
  2. @app.after_request
  3. def after_request(response):
  4.     print("after_request")
  5.     response.headers["Content-Type"] = "application/json"
  6.     return response
复制代码
  1. # 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
  2. @app.teardown_request
  3. def teardown_request(response):
  4.     print("teardown_request")
复制代码
  1. # 测试请求
  2. @app.route('/testRequest')
  3. def index():
  4.     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请求的参数。
  1. # request获取请求参数
  2. @app.get("/requestContext")
  3. def requestContext():
  4.     user = request.args.get('user')
  5.     return f"你的名字是:{user}"
复制代码

2 session

  • 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
  1. # session存储用户id
  2. @app.get("/setRequestSession")
  3. def setRequestSession():
  4.     user_id = "zhangsan"
  5.     session['name'] = user_id
  6.     return "Session 设置成功"
  7. # 获取Session
  8. @app.get("/getRequestSession")
  9. def setRequestSession():
  10.     username = session.get('name')
  11.     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
  1. from flask import current_app
  2. app1 = Flask(__name__)
  3. app2 = Flask(__name__)
  4. app1.redis_cli = 'app1 redis client'
  5. app2.redis_cli = 'app2 redis client'
  6. @app1.route('/route11')
  7. def route11():
  8.     # current_app获取的是当前正在使用的app
  9.     return current_app.redis_cli
  10. @app2.route('/route21')
  11. def route21():
  12.     return current_app.redis_cli
复制代码
运行
  1. set FLASK_APP=main:app1
  2. flask run
复制代码
  1. set FLASK_APP=main:app2
  2. flask run
复制代码

作用
current_app 就是当前运行的flask app,在代码不方便直接操作flask的app对象时,可以操作current_app就等价于操作flask app对象,而不会出现app调用app对象自身导致的循环依赖问题
2 g对象
g 作为flask程序全局的一个临时变量,充当中间媒介的作用,我们可以通过它在一次请求调用的多个函数间传递一些数据。每次请求都会重设这个变量。
示例
  1. from flask import g
  2. def query():
  3.     user_id = g.user_id
  4.     user_name = g.user_name
  5.     return f"userid={user_id},name={user_name},已执行内部函数"
  6. @app.post("/getUser/")
  7. def get_user():
  8.     userid = request.args.get("userid")
  9.     username = request.args.get("username")
  10.     g.user_id = userid
  11.     g.user_name = username
  12.     text = query()
  13.     return text
复制代码

5.3.3 app_context 与 request_context

思考
在Flask程序未运行的情况下,调试代码时需要使用current_app、g、request这些对象,该如何进行测试。
1 app_context
app_context为我们提供了应用上下文环境,允许我们在外部使用应用上下文current_app、g`
可以通过with语句进行使用
  1. >>> from flask import Flask
  2. >>> app = Flask('')
  3. >>> app.redis_cli = 'redis client'
  4. >>>
  5. >>> from flask import current_app
  6. >>> current_app.redis_cli   # 错误,没有上下文环境
  7. 报错
  8. >>> with app.app_context():  # 借助with语句使用app_context创建应用上下文
  9. ...     print(current_app.redis_cli)
  10. ...
  11. redis client
复制代码


2 request_context
request_context为我们提供了请求上下文环境,允许我们在外部使用请求上下文request、session
可以通过with语句进行使用
  1. >>> from flask import Flask
  2. >>> app = Flask('')
  3. >>> request.args  # 错误,没有上下文环境
  4. 报错
  5. >>> environ = {'wsgi.version':(1,0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'}  # 模拟解析客户端请求之后的wsgi字典数据
  6. >>> with app.request_context(environ):  # 借助with语句使用request_context创建请求上下文
  7. ...     print(request.path)
复制代码

6 Flask-RESTful

6.1 起步

Flask-RESTful是用于快速构建REST API的Flask扩展。
6.1.1 安装
  1. pip install flask-restful
复制代码

6.1.2 Hello World
  1. from flask import Flask
  2. from flask_restful import Resource,Api
  3. app = Flask(__name__)
  4. api = Api(app)
  5. class HelloWordResource(Resource):
  6.     def get(self):
  7.         return {"hello":"world"}
  8.     def post(self):
  9.         return {"msg":"post Hello,World"}
  10. api.add_resource(HelloWordResource,"/hello")
  11. # 也可以选择flask run的启动方式
  12. if __name__=="__main__":
  13.     app.run(debug=True)
复制代码


6.2 关于视图

6.2.1 为路由起名

通过endpoint参数为路由起名
  1. # 这个名称在后续会用到
  2. api.add_resource(HelloWorldResource, '/', endpoint='HelloWorld')
复制代码

6.2.2 蓝图中使用
  1. # 1 定义蓝图
  2. user_bp = Blueprint('user',__name__,url_prefix="/user")
  3. # 2 api配置蓝图
  4. user_api = Api(user_bp)
  5. # 3 app配置蓝图
  6. app.register_blueprint(user_bp)
  7. # 4 定义蓝图请求
  8. class UserBlueResource(Resource):
  9.     def get(self):
  10.         context = {
  11.             "massage":2001,
  12.             "data":{
  13.                 "name":"zhangsan",
  14.                 "age":16
  15.             }
  16.         }
  17.         return context
  18.     def post(self):
  19.         context = {
  20.             "massage":2001,
  21.             "data":{
  22.                 "name":"This is post Man",
  23.                 "age":"*****(查看请充值vip)"
  24.             }
  25.         }
  26.         return context
  27. # 5 配置api路由
  28. user_api.add_resource(UserBlueResource,"/blueUser")
复制代码


6.2.3 装饰器

使用method_decorators添加装饰器
为类视图中的所有方法添加装饰器
  1. def decorator1(func):
  2.     def wrapper(*args, **kwargs):
  3.         print('装饰器1正在执行')
  4.         return func(*args, **kwargs)
  5.     return wrapper
  6. def decorator2(func):
  7.     def wrapper(*args, **kwargs):
  8.         print('装饰器2正在执行')
  9.         return func(*args, **kwargs)
  10.     return wrapper
  11. class DemoRequest(Resource):
  12.     method_decorators = [decorator1,decorator2]
  13.     def get(self):
  14.         context = {
  15.             "msg":200,
  16.             "data":{
  17.                 "text":"一切正常"
  18.             }
  19.         }
  20.         return context
  21.     def post(self):
  22.         context = {
  23.             "msg":200,
  24.             "data":{
  25.                 "text":"I am PostMax"
  26.             }
  27.         }
  28.         return context
  29. api.add_resource(DemoRequest,"/demo")
复制代码


为类视图中不同的方法添加不同的装饰器
  1.   class DemoResource(Resource):
  2.       method_decorators = {
  3.           'get': [decorator1, decorator2],
  4.           'post': [decorator1]
  5.       }
  6.       # 使用了decorator1 decorator2两个装饰器
  7.       def get(self):
  8.           return {'msg': 'get view'}
  9.       # 使用了decorator1 装饰器
  10.       def post(self):
  11.           return {'msg': 'post view'}
  12.       # 未使用装饰器
  13.       def put(self):
  14.           return {'msg': 'put view'}
复制代码
get请求测试


post请求测试


put请求测试


6.3 关于请求

6.3.1 概述

Flask-RESTful 提供了RequestParser类,用来帮助我们检验和转换请求数据。
6.3.2 使用步骤:


  • 创建RequestParser对象
  • 向RequestParser对象中添加需要检验或转换的参数声明
  • 使用parse_args()方法启动检验处理
  • 检验之后从检验结果中获取参数时可按照字典操作或对象属性操作
    1. args.rate
    2. args['rate']
    复制代码
6.3.3 参数说明

1 required
描述请求是否一定要携带对应参数,默认值为False

  • True 强制要求携带
    若未携带,则校验失败,向客户端返回错误信息,状态码400
  • False 不强制要求携带
    若不强制携带,在客户端请求未携带参数时,取出值为None
  1. from flask_restful.reqparse import RequestParser
  2. class rqparseRequest(Resource):
  3.     # 1 required 参数测试
  4.     def get(self):
  5.         rqp = RequestParser()
  6.         # 强制要求携带该参数
  7.         rqp.add_argument("user",required=True)
  8.         args = rqp.parse_args()
  9.         return {"message":200,"data":{"user":args.user}}
  10. api.add_resource(rqparseRequest,"/rpone")
复制代码


2 help
参数检验错误时返回的错误描述信息
  1. from flask_restful.reqparse import RequestParser
  2. class rqparseRequest(Resource):
  3.     # 2 help参数测试
  4.     def post(self):
  5.         rqp = RequestParser()
  6.         # 强制要求携带该参数
  7.         rqp.add_argument("user",required=True,help="没有user参数")
  8.         args = rqp.parse_args()
  9.         return {"message":200,"data":{"user":args.user}}
  10. api.add_resource(rqparseRequest,"/rpone")
复制代码


3 action
描述对于请求参数中出现多个同名参数时的处理方式

  • action='store' 保留出现的第一个, 默认
  • action='append' 以列表追加保存所有同名参数的值
  1. from flask_restful.reqparse import RequestParser
  2. class rqparseRequest(Resource):
  3.     # 3 action参数测试
  4.     def put(self):
  5.         rp = RequestParser()
  6.         rp.add_argument("user",required=True,action="append")
  7.         args = rp.parse_args()
  8.         return {"message": 200, "data": {"user": args.user}}
  9. api.add_resource(rqparseRequest,"/rpone")
复制代码

4 type
描述参数应该匹配的类型,可以使用python的标准数据类型string、int,也可使用Flask-RESTful提供的检验方法,还可以自己定义

  • 标准类型
    1. from flask_restful.reqparse import RequestParser
    2. class rqparseRequest(Resource):
    3.     # 4 type参数测试
    4.     def delete(self):
    5.         rp = RequestParser()
    6.         rp.add_argument("user",required=True,type=int)
    7.         args = rp.parse_args()
    8.         return {"message": 200, "data": {"user": args.user}}
    9. api.add_resource(rqparseRequest,"/rpone")
    复制代码



  • Flask-RESTful提供
    检验类型方法在flask_restful.inputs模块中

    • url
    • regex(指定正则表达式)
      1. from flask_restful import inputs
      2. class rqparseRequestTwo(Resource):
      3.     # Flask指定的检验格式
      4.     def get(self):
      5.         rp = RequestParser()
      6.         rp.add_argument('user', type=inputs.regex(r'^1[3-9]\d{9}$'))
      7.         args = rp.parse_args()
      8.         return {"message": 200, "data": {"user": args.user}}
      9. api.add_resource(rqparseRequestTwo,"/rptwo")
      复制代码



    • natural 自然数0、1、2、3...
    • positive 正整数 1、2、3...
    • int_range(low ,high) 整数范围
      1. rp.add_argument('a', type=inputs.int_range(1, 10))
      复制代码
    • boolean

  • 自定义
    1. def mobile(mobile_str):
    2.     """
    3.     检验手机号格式
    4.     :param mobile_str: str 被检验字符串
    5.     :return: mobile_str
    6.     """
    7.     if re.match(r'^1[3-9]\d{9}$', mobile_str):
    8.         return mobile_str
    9.     else:
    10.         raise ValueError('{} is not a valid mobile'.format(mobile_str))
    11. rp.add_argument('a', type=mobile)
    复制代码
5 location
描述参数应该在请求数据中出现的位置
  1. # Look only in the POST body
  2. parser.add_argument('name', type=int, location='form')
  3. # Look only in the querystring
  4. parser.add_argument('PageSize', type=int, location='args')
  5. # From the request headers
  6. parser.add_argument('User-Agent', location='headers')
  7. # From http cookies
  8. parser.add_argument('session_id', location='cookies')
  9. # From json
  10. parser.add_argument('user_id', location='json')
  11. # From file uploads
  12. parser.add_argument('picture', location='files')
复制代码
也可指明多个位置
  1. parser.add_argument('text', location=['headers', 'json'])
复制代码
测试
  1. from flask_restful import inputs
  2. class rqparseRequestTwo(Resource):
  3.     # location测试
  4.     def post(self):
  5.         parser = RequestParser()
  6.         # form表单
  7.         parser.add_argument('name', type=int, location='form')
  8.         # 位置参数
  9.         parser.add_argument('PageSize', type=int, location='args')
  10.         args = parser.parse_args()
  11.         return {"message": 200, "data": {"name": args.name,"PageSize": args.PageSize}}
  12. api.add_resource(rqparseRequestTwo,"/rptwo")
复制代码

6.4 关于响应

6.4.1 序列化数据

Flask-RESTful 提供了marshal工具,用来帮助我们将数据序列化为特定格式的字典数据,以便作为视图的返回值。
  1. # 1 导包
  2. from flask_restful import Resource,fields,marshal_with,marshal
  3. # 2 定义类
  4. class Book(object):
  5.     def __init__(self,bookName,author):
  6.         self.bookName = bookName
  7.         self.author = author
  8. # 3 定义模板
  9. resource_fields = {
  10.     'bookName':fields.String,
  11.     "author":fields.String
  12. }
  13. # 4 定义返回函数
  14. class BookTodo(Resource):
  15.     # 使用装饰器实现
  16.     @marshal_with(resource_fields,envelope="context")
  17.     def get(self):
  18.         # 对象无法直接返回,这个装饰器相当于将对象序列化了
  19.         book = Book("《射雕英雄传》","金庸")
  20.         return book
  21. api.add_resource(BookTodo,"/booktodo")
复制代码

也可以不使用装饰器的方式
  1. # 不使用装饰器,而是调用函数实现
  2. def post(self):
  3.     book = Book("《射雕英雄传》","金庸")
  4.     return marshal(book,resource_fields,envelope="contextPost")
复制代码

还可以直接自己实现
  1. # 直接自己写,单次返回更简单
  2. def put(self):
  3.     book = Book("《射雕英雄传》", "金庸")
  4.     context = {
  5.         "context": {
  6.             "bookName": book.bookName,
  7.             "author": book.author
  8.         }
  9.     }
  10.     return context
复制代码


6.4.2 定制返回的JSON格式

1 需求
想要接口返回的JSON数据具有如下统一的格式
  1. {"message": "描述信息", "data": {要返回的具体数据}}
复制代码
在接口处理正常的情况下, message返回ok即可,但是若想每个接口正确返回时省略message字段
  1. class DemoResource(Resource):
  2.     def get(self):
  3.         return {'user_id':1, 'name': 'itcast'}
复制代码
对于诸如此类的接口,能否在某处统一格式化成上述需求格式?
  1. {"message": "OK", "data": {'user_id':1, 'name': 'itcast'}}
复制代码
2 解决
Flask-RESTful的Api对象提供了一个representation的装饰器,允许定制返回数据的呈现格式
  1. api = Api(app)
  2. @api.representation('application/json')
  3. def handle_json(data, code, headers):
  4.     # TODO 此处添加自定义处理
  5.     return resp
复制代码
Flask-RESTful原始对于json的格式处理方式如下:
代码出处:flask_restful.representations.json
  1. from flask import make_response, current_app
  2. from flask_restful.utils import PY3
  3. from json import dumps
  4. def output_json(data, code, headers=None):
  5.     """Makes a Flask response with a JSON encoded body"""
  6.     settings = current_app.config.get('RESTFUL_JSON', {})
  7.     # If we're in debug mode, and the indent is not set, we set it to a
  8.     # reasonable value here.  Note that this won't override any existing value
  9.     # that was set.  We also set the "sort_keys" value.
  10.     if current_app.debug:
  11.         settings.setdefault('indent', 4)
  12.         settings.setdefault('sort_keys', not PY3)
  13.     # always end the json dumps with a new line
  14.     # see https://github.com/mitsuhiko/flask/pull/1262
  15.     dumped = dumps(data, **settings) + "\n"
  16.     resp = make_response(dumped, code)
  17.     resp.headers.extend(headers or {})
  18.     return resp
复制代码
为满足需求,做如下改动即可
  1. @api.representation('application/json')
  2. def output_json(data, code, headers=None):
  3.     """Makes a Flask response with a JSON encoded body"""
  4. # --------------------此处为自己添加--------------------------
  5.     if 'message' not in data:
  6.         data = {
  7.             'message': 'OK',
  8.             'data': data
  9.         }
  10. # ---------------------------------------------------------
  11.     settings = current_app.config.get('RESTFUL_JSON', {})
  12.     if current_app.debug:
  13.         settings.setdefault('indent', 4)
  14.         settings.setdefault('sort_keys', not PY3)
  15.     dumped = dumps(data, **settings) + "\n"
  16.     resp = make_response(dumped, code)
  17.     resp.headers.extend(headers or {})
  18.     return resp
复制代码
测试代码
  1. # 测试代码
  2. class userTest(Resource):
  3.     def get(self):
  4.         return {"name":"zhangsan"}
  5.    
  6. api.add_resource(userTest,"/userTest")
复制代码


—Flask2.x新变化

嵌套蓝图(#3923
对于一个比较大的项目,一般会使用蓝本来组织不同的模块。而如果你的项目非常大,那么嵌套蓝本就可以派上用场了。借助嵌套蓝本支持,你可以在某个蓝本之内再创建多个子蓝本,对项目进行多层模块化组织(而且支持无限嵌套,你可以嵌套很多层):
  1. parent = Blueprint("parent", __name__)  # 创建父蓝本
  2. child = Blueprint("child", __name__)  # 创建子蓝本
  3. parent.register_blueprint(child, url_prefix="/child")  # 把子蓝本注册到父蓝本上
  4. app.register_blueprint(parent, url_prefix="/parent")  # 把父蓝本注册到程序实例上
复制代码
这样在生成子蓝本的 URL 时需要传入完整的端点链:
  1. url_for('parent.child.create')
  2. /parent/child/create
复制代码
这个特性来源于一个 2012 年创建的 feature request issue
基本的 async/await 支持
Flask 2.0 带来了基本的异步支持,现在你可以定义异步视图(以及异步错误处理函数、异步请求钩子函数):
  1. import asyncio
  2. from flask import Flask
  3. app = Flask(__name__)
  4. @app.route('/')
  5. async def say_hello():
  6.      await asyncio.sleep(1)
  7.      return {'message': 'Hello!'}
复制代码
注意要先安装额外依赖:
  1. 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
  • 在你的入口脚本顶部添加临时修复代码:
[code]# top of the fileimport sys, asyncioif sys.platform == "win32" and (3, 8, 0)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具