Flask 2.0 版本发布

Flask 以及 Flask 依赖的 5 个 Pallets 项目都在今天发布了新的主版本(下面的链接指向各个项目的主版本变动日志):

​你可以使用下面的命令更新 Flask:

 pip install -U flask

如果你使用的国内 PyPI 镜像还没有同步最新版本,可以通过下面的命令临时切换到官方 PyPI 源:

 pip install -U flask -i https://pypi.org/simple/

这篇文章会介绍一些 Flask 新增的特性,完整的变动可以参考上面各个项目的变动日志。

三个核心特性

嵌套蓝本(#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 支持(#3412

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 基类的框架,然后加了这些装饰器)。

我添加的三个特性

修复执行 flask run 找不到程序的奇怪设定(#3560

这个像 bug 又像是 feature 的设定我在《一个困扰我两年的 Flask「Bug」》里详细说过,最终终于在两年后修复了。

支持在 .flaskenv 和 .env 文件里写中文(#3932

我在《Flask Web 开发实战》第一章介绍 .flaskenv 文件时给了一个示例,演示如何添加注释:

SOME_VAR=1
# 这是注释

但这个示例没有实际测试……加了中文其实会报错,后来有两次收到读者反馈,最终终于在三年后修复了。

为文档添加命令切换面板(#3714

这个或许算不上特性,不过在我看来是对用户非常友好的变动。除了个别不需要区分操作系统和命令行程序的命令外,我给文档里所有的命令添加了支持切换 Bash/CMD/Powershell 以及 macOS/Linux/Windows 的切换面板(面板的样式后续会有一些优化):

其他的有用特性

  • 优化了浏览器缓存控制,对 CSS、图片等静态文件做出的变动会在程序重载后立刻更新,不再需要手动清除页面缓存。
  • Werkzeug 的 multipart 解析(尤其是大文件上传处理)性能提高了 15 倍。
  • 配置对象增加 Config.from_file() 方法支持从任意文件加载器导入配置(比如 toml.loadjson.load),未来会取代 Config.from_json() 方法。
  • 在使用环境变量 FLASK_APP 指定工厂函数时支持传入关键字参数。
  • flask shell 支持 tab 和历史补全(需要安装 readline)。
  • CLI 系统优化了找不到程序时的错误处理和错误输出显示,同时修正了 Windows 上的命令行颜色输出。

破坏性变动

主要的破坏性变动(breaking change)是意外的把 send_from_directory() 函数的第二个参数名称直接由 filename 重命名为 path,将会在 2.0.1 加回来(#4019)。

另外 send_file() 函数的三个参数也进行了重命名(旧名称将在 2.1.0 移除):

  • attachment_filename -> download_name
  • cache_timeout -> max_age
  • add_etags -> etag

其他重要变化

  • 不再支持 Python 2 和 Python 3.5。
  • 所有 Pallets 项目都添加了 type hinting,这意味着更好的 IDE 自动补全体验。
  • 所有仓库的主分支由 master 改为 main。如果你在本地克隆了 Flask 等仓库,可以使用下面的命令来更新:
 git branch -m master main
 git fetch origin
 git branch -u origin/main main
 git remote set-head origin -a

感谢支持

这次更新对于整个 Pallets 项目来说是一个新的里程碑。接下来还有许多事情要做:FlaskCon 2021 正在准备中,新建立的 Flask 社区工作小组(Flask Community Work Group)正在进行 Flask 文档翻译(如果你对中文翻译感兴趣,可以订阅这个讨论)、被遗弃扩展收容计划(这是我一直想做的事情)等等。感谢支持,敬请期待!

欢迎通过下列途径关注 Pallets 项目:

相关文章:

Flask 2.0 版本发布》上有8条评论

  1. 头像mkvoya

    前端时间在部署 docker flask 的时候,看到说 ASGI 的性能比 WSGI 更好,刚好在这里碰到了行家,请问实际情况是这样吗?以及 flask 之后会有支持 ASGI 的计划吗?

    回复
  2. 头像weeds

    嵌套蓝图后child蓝图得不到预期的url, 代码如下,请帮忙看一下,感谢您

    wepapi/__init__.py 文件:

    from flask import Blueprint

    parent = Blueprint(“parent”, __name__, url_prefix=”/parent”)

    child = Blueprint(“child”, __name__, url_prefix=”/child”)

    parent.register_blueprint(child)

    from . import view

    ———————————————
    app.py 文件:
    from flask import Flask

    from webapi import parent

    def create_app():
    api: Flask = Flask(__name__)
    api.register_blueprint(parent)
    return api

    if __name__ == ‘__main__’:
    app = create_app()
    app.run()

    ———————————————
    flask routes 命令的输出

    Endpoint Methods Rule
    —————— ——- ———————–
    parent.child.index GET /index
    parent.index GET /parent/index
    static GET /static/

    回复
    1. 头像李辉 文章作者

      你好,感谢反馈。这里有一个 bug,我去试着处理下,临时你可以改为在注册(调用 register_blueprint 方法)时设置 URL 前缀,即:

      parent.register_blueprint(child, url_prefix=’/child’)
      app.register_blueprint(parent, url_prefix=’/parent’)

      回复

撰写评论

电子邮件地址不会被公开,必填项已用 * 标出。