标签归档:Flask

欢迎参加 FlaskCon 2020!

FlaskCon 是一个社区举办的 Flask 大会,将会在 7 月 4 号举行(为期两天)。按照官方的介绍,它:

  • 100% Remote
  • 100% Free
  • 100% Community-driven

和 DjangoCon 类似,FlaskCon 会专注于 Flask 相关话题,包括 Flask 扩展介绍、开发经验、最佳实践、类似框架的对比等等。

如果你想分享 Flask 开发相关的经验,介绍你对 Flask 的有趣应用,或者是有任何和 Flask 相关而且你很想拿出来聊一聊的话题,欢迎报名演讲。演讲有 20 分钟和 40 分钟两种类型,需要使用英语。

你可能会觉得大会的 Logo 不是很好看……不要担心,这些设计还在慢慢改进中,如果你有任何和大会网站、议程设置、设计相关的建议,可以发邮件到 flaskcon@gmail.com,或是在 Discord 频道和 Reddit 主题帖里反馈。

P.S. 如果你想帮忙改进大会网站,最直接的方式是自己动手修改源码

相关链接:

一个困扰我两年的 Flask「Bug」

TL;DR 版本:

如果你的程序存储在单脚本里,比如 app.py,那么 .flaskenv 和 .env 应该放在程序脚本的同级目录:

myproject/
    - app.py
    - templates
    - static
    - .flaskenv
    - .env

如果你的程序存储在程序包里,那么 .flaskenv 和 .env 应该放在程序包的同级目录:

myproject/
    - app/
        - templates
        - static
        - __init__.py
        - views.py
    - .flaskenv
    - .env

如果你把 .flaskenv 和 .env 放到了再往上一层(myproject 再往上),或是把你的程序包或程序脚本放到了一个子目录(比如 /myproject/myapp),那么执行 flask run 就会出错。


严格来说算不上 bug,而是一个很容易导致出错的设计。具体行为是,如果你安装了 python-dotenv,同时在 Flask 程序的上层目录创建了 .env 或 .flaskenv 文件,那么你将没法成功执行 flask run 等命令,因为这会导致 Flask 没法正确找到对应的 Flask 程序实例。

这个问题从 Flask 开始引入 CLI 机制开始就存在了,困扰了我两年。18 年偶然在用户根目录创建了一个 .env 文件,发现 Flask 程序没法运行了,当时遇到的各种 bug 太多,没仔细考虑这两者之间的关联。后来经过几次测试,才确定下来是上层目录的 .env 和 .flaskenv 文件导致,但是一时找不到原因,就暂时放下了。直到 19 年 11 月,花了几个小时排查,还是没找到原因。

中间花了很长时间来追踪 Windows 特定的 Flask 程序无法启动的 bug(TypeError: environment can only contain strings),实在是怕了。因为 Flask 的 CLI 涉及太多东西,有时你要钻进 python-dotenv(#101) 和 Werkzeug(#1320) 才能找到问题的原因。

但是问题不解决的话,你永远睡不好觉。《Flask Web 开发实战》第一部分的示例程序都放在了一个程序仓库,而且都放在了子目录,这意味着如果读者错误的在仓库根目录创建 .env 和 .flaskenv 文件的话,就会导致子目录下的六个示例程序没法运行。前后大概收到 6 个相关的读者反馈,虽然后续在网站上添加了提醒,在重印的书里介绍创建 .env/.flaskenv 文件的地方追加提醒,但这终究没有真正解决问题,而且总会有人可以完美的错过所有提示。

如果每个人都可以在书上标记出错位置并共享,那么这一页应该会有很多红色小叉号(参考《超级马里奥制造》,也许未来某个电子书平台会做出来这个功能 :P)。

前几天在这个 Issue 的提醒下,又花了两个小时排查,这次终于找到原因。加上写 PR(#3560)和 Issue(#3561),前后两年一共花了 8 个小时,这个问题终于有了着落。没意外的话,预计会在下一个版本的 Flask 中更新。下面是具体原因。

本来以为这个问题和 python-dotenv 或 Werkzeug 相关,没想到只是 Flask 本身代码的问题,我太笨了,这个问题本可以早一点解决。

按照预定的行为,当安装了 python-dotenv,Flask 会自动加载 .env 和 .flaskenv 里的环境变量。python-dotenv 在搜索存储环境变量的文件时,会从当前目录开始向上搜索,如果找到就返回对应的文件路径。但是这时 Flask 如果发现 .env 或 .flaskenv 的所在目录不是当前目录,就会把当前工作目录切换到 .env 和 .flaskenv 所在的目录(相关源码)。而如果你的程序模块或程序包不是和 .env/.flaskenv 同级目录的话,就会导致找不到程序实例。

使用下面的步骤可以重现:

$ git clone https://github.com/greyli/flask-env-test
$ cd flask-env-test
$ pip install -r requirements.txt  # or just pip install flask[dotenv]
$ cd hello
$ flask run

示例项目的文件结构如下:

- flask-env-test
    - .env
    - hello
        - app.py

像示例程序这样把程序存储在 app.py 文件中时,运行 flask run 你会看到下面的报错:

$ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not locate a Flask application. You did not provide the "FLASK_APP" environment variable, and a "wsgi.py" or "app.py" module was not found in the current directory.

如果你使用 FLASK_APP 指定了程序的导入路径,那么错误大概会是这样:

$ flask run
 * Serving Flask app "myapp"
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: Could not import "myapp".

相关链接

安装 Python 依赖出现 MarkupSafe ImportError … Feature 报错的解决方法

这个报错在 3 月 8 号 setuptools 发布新版本之后出现,通常会在安装 Python 依赖时触发。

报错信息

使用 pip 安装依赖时的报错如下:

Collecting markupsafe==1.0
  Downloading https://.../MarkupSafe-1.0.tar.gz (14 kB)
    ERROR: Command errored out with exit status 1:
     command: '...\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"'; __file__='"'"'...\\pip-install-bsormril\\markupsafe\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base '...\pip-install-bsormril\markupsafe\pip-egg-info'
         cwd: ...\pip-install-bsormril\markupsafe\
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "...\pip-install-bsormril\markupsafe\setup.py", line 6, in <module>
        from setuptools import setup, Extension, Feature
    ImportError: cannot import name 'Feature'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

使用 Pipenv 安装依赖时的报错如下:

An error occurred while installing markupsafe==1.0 --hash=sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665! Will try again.
Installing initially failed dependencies…
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 1874, in do_install
[pipenv.exceptions.InstallError]:       keep_outdated=keep_outdated
[pipenv.exceptions.InstallError]:   File "...Python\Python36\site-packages\pipenv\core.py", line 1253, in do_init
[pipenv.exceptions.InstallError]:       pypi_mirror=pypi_mirror,
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 859, in do_install_dependencies
[pipenv.exceptions.InstallError]:       retry_list, procs, failed_deps_queue, requirements_dir, **install_kwargs
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 763, in batch_install
[pipenv.exceptions.InstallError]:       _cleanup_procs(procs, not blocking, failed_deps_queue, retry=retry)
[pipenv.exceptions.InstallError]:   File "...\Python\Python36\site-packages\pipenv\core.py", line 681, in _cleanup_procs
[pipenv.exceptions.InstallError]:       raise exceptions.InstallError(c.dep.name, extra=err_lines)
[pipenv.exceptions.InstallError]: ['Looking in indexes: https://.../pypi/simple', 'Collecting markupsafe==1.0', '  Using cached https://.../MarkupSafe-1.0.tar.gz (14 kB)']
[pipenv.exceptions.InstallError]: ['ERROR: Command errored out with exit status 1:', '
command: \'...\\.virtualenvs\\helloflask-evdb6idn\\scripts\\python.exe\' -c \'import sys, setuptools, tokenize; sys.argv[0] = \'"\'"\'...\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\'; 
__file__=\'"\'"\'...\\\\pip-install-pkgojp4t\\\\markupsafe\\\\setup.py\'"\'"\';f=getattr(tokenize, \'"\'"\'open\'"\'"\', open)(__file__);code=f.read().replace(\'"\'"\'\\r\\n\'"\'"\', \'"\'"\'\\n\'"\'"\');f.close();exec(compile(code, __file__, \'"\'"\'exec\'"\'"\'))\' egg_info --egg-base \'...\\pip-install-pkgojp4t\\markupsafe\\pip-egg-info\'', '
cwd: ...\\pip-install-pkgojp4t\\markupsafe\\', '    Complete output (5 lines):', '    
Traceback (most recent call last):', '      
File "<string>", line 1, in <module>', ' 
File "...\\pip-install-pkgojp4t\\markupsafe\\setup.py", line 6, in <module>', '
from setuptools import setup, Extension, Feature', "
ImportError: cannot import name 'Feature'", '----------------------------------------',
'ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.']
ERROR: ERROR: Package installation failed...

其他工具输出类似,主要异常信息是 MarkupSafe setup.py: ImportError: cannot import name Feature。通常会在安装 Flask 项目的依赖时发生,因为 MarkupSafe 是 Flask 的依赖之一。

原因和解决方法

出现这个报错的原因是因为 Python 打包工具 setuptools 在 46.0.0 版本删掉了弃用的 Feature,而 MarkupSafe 刚好在 setup.py 文件里导入了这个类,所以会出现报错。

解决方法很多,最直接的是更新 MarkupSafe 到最新版本(1.1.1),新版本去掉了对 Feature 类的导入。如果使用 requirements.txt 存储依赖列表,那就把 MarkupSafe 的版本号改成 1.1.1(找到 MarkupSafe 开头那一行,替换版本号):

MarkupSafe==1.1.1

然后重新执行:

$ pip install -r requirements.txt

对于 Pipenv,可以直接执行:

$ pipenv install markupsafe==1.1.1

如果你是《Flask Web 开发实战》的读者,正在为第一部分的示例程序安装依赖,那你还需要执行下面的命令固定 sendgrid-python 的版本(它在新版本添加了不向后兼容的 API 变动):

$ pipenv install sendgrid==5.3.0

《Flask Web 开发实战》读者备注

如果你在 2020 年 3 月 8 号到 4 月 5 号之间买了《Flask Web 开发实战》,把示例程序克隆到了本地,然后尝试运行 pipenv install 或 pip install -r requirements.txt 命令来安装依赖,那很大概率你会遇到这个问题。

除了使用上面的方法之外,你还可以通过更新本地代码来解决。我最近给所有示例程序的依赖文件做了一次更新,除了书里涉及的 API 产生变动的依赖,其他依赖都已经更新到最新版本。

你可以使用下面的命令来更新你在本地的程序仓库(注意这会重置你对源码进行的修改):

$ git fetch --all
$ git fetch --tags
$ git reset --hard origin/master

然后重新执行一次 pipenv install 或 pip install -r requirements.txt 即可解决这个问题。如果遇到其他虚拟环境和依赖安装的问题可以参考这篇文章解决。

相关链接:

《Flask Web 开发实战》虚拟环境/依赖/Pipenv 等问题解决方法

注:这篇文章的主要受众是《Flask Web 开发实战》的读者。

注2:文中的 $ 符号标识一条命令行命令的开始,$ 前面是当前工作目录,# 号后面是注释。你实际只需要输入 $ 符号和 # 号之间的内容,不包括开头和结尾的空格。

在群聊和论坛里总是看到和虚拟环境和依赖安装相关的各类问题,这篇文章希望能够提供一个统一的解决方案。下次如果遇到有人问虚拟环境/Pipenv/依赖安装相关的问题,请把这篇文章的链接丢过去。

安装 Python 库非常慢?

在进入正题之前,你需要先解决基础设施问题。你在执行 pip install 命令或 pipenv install 等命令时会不会网速非常慢?20k/s 或者干脆看到 Time out,Connection reset 之类的报错,这种情况下,你需要设置 PyPI 镜像。具体操作可以在这篇《从国内的 PyPI 镜像(源)安装 Python 包》看到。

要不要继续使用 Pipenv?

因为书里面在一开始介绍了使用 Pipenv 管理依赖和虚拟环境,同时所有的安装第三方库的命令也都是使用 Pipenv,所以我们要解决的第一个问题是「要不要继续使用 Pipenv?」

我的建议是,如果你在使用的过程中没有遇到过任何报错,那么就继续使用它。直到你觉得它在某些地方不再让你满意。

但是如果你在使用的过程中遇到了问题(首先确保你使用的是最新版本的 Pipenv),比如:

  • 锁定依赖很慢,停留在「locking…」这样的提示不动
  • 执行正确的命令但是总是出现报错

附注 如果你安装依赖时的报错是「MarkupSafe setup.py: ImportError: cannot import name Feature」,请参考这篇文章解决。

那么就继续看下去。

如果不用 Pipenv,我该怎么办?

解决方法和替代工具非常多,这里给出两个。

方法一:不用虚拟环境

最简单的解决方法就是不用虚拟环境。如果你是一个初学者,那么不用虚拟环境完全没问题。现在你把所有的 Python 包全都安装在一个盒子里,你只需要会使用 pip 安装依赖,也就是使用下面的 pip install 命令:

$ pip install flask

附注 顺便说一句,不用虚拟环境时,如果你是使用 Linux 和 macOS 系统的 Python 3 用户,那么执行 Python 和 pip 相关命令的时候需要输入的是 python3 和 pip3,比如:pip3 install flask。后面不再提示。

这个命令会为你安装 Flask。每到需要安装一个包,你就执行这个命令,把上面的 flask 替换成你要安装的包名即可。类似的,书中所有 pipenv install xxx 形式的命令也都替换为 pip install xxx。

这时你可以跳过 Pipenv 和虚拟环境相关的内容。在第一章,当你把当前工作目录切换到 helloflask 文件夹内之后,整个 1.1 小节你只需要执行下面这行命令:

$ pip install -r requirements.txt

而第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ pip install -r requirements.txt

这个命令会安装对应项目的所有依赖,所以后续介绍各个 Python 库时的安装命令不需要再执行。

方法二:使用 virtualenv/venv 来管理虚拟环境,搭配 pip 来管理依赖

第二种方法是改用更基础的工具:你仍然使用 pip 安装依赖,同时搭配 virtualenv 或 venv 来管理虚拟环境。如果你选择这个方法,那就跳过书中对 Pipenv 的介绍,改为阅读这篇《要不我们还是用回 virtualenv/venv 和 pip 吧》。阅读完上述文章后,再继续阅读。

现在你可以跳过 Pipenv 相关的内容。第一章切换进 helloflask 目录后,整个 1.1 小节你只需要执行下面的命令:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

附注 :上面命令里 # 号及之后的文字是注释,不需要输入。如果你使用 Python2,第一条命令需要改为 virtualenv env。这三行命令的作用依次为:创建虚拟环境、激活虚拟环境、从 requirements.txt 文件安装依赖列表。

类似的,第二部分每章开头的下面这两行命令:

$ pipenv install --dev
$ pipenv shell

都要替换为:

$ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
$ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
$ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

同时书中所有 pipenv install xxx 形式的命令都替换为 pip install xxx(不过你并不需要一个一个执行,因为每章开头执行 pip install -r requirements.txt 时会安装所有项目相关的依赖)。

对于 PyCharm 设置 Python 解释器部分的内容,大致可以沿用,只不过在图 1-4 的位置你需要从列表里选择当前目录 env 文件夹(虚拟环境文件夹)中的 Python 解释器,根据操作系统的不同,将会是 env/bin/python 或 env\Scripts\python.exe。

退出虚拟环境时,使用下面的命令:

$ deactivate

使用书中同样的代码,但是却出现报错?/ 我该怎么安装依赖?

有一些 Python 库在版本变化时会带来 API 的变化,而书中示例程序代码是基于每章开头注明的 Python 库来开发的,所以在更新依赖版本的时候可能会导致示例程序的代码出错。我建议你按照书中的命令来安装依赖,这会从项目依赖文件里安装固定版本的依赖列表,不要跳过第二部分每章开头的命令。

具体来说,如果使用 Pipenv,对应的安装依赖的命令是:

$ pipenv install --dev

如果使用 pip,对应的命令则是(如果创建了虚拟环境,需要先激活虚拟环境):

$ pip install -r requirements.txt

如果是第一章的示例程序,那么在 helloflask 目录下执行一次即可。如果是第二章的四个程序,那么在每一个程序的根目录执行一次即可。后续所有介绍新的 Python 包时给出的安装命令不用再执行。

关于第一部分示例程序的项目结构和启动问题

在第一部分的源码中,一共有 6 个 Flask 程序,分别保存在 helloflask/demos/ 目录下的六个子文件夹内。用来存储环境变量的 .env 和 .flaskenv 文件需要在这些子文件夹内创建,而不是放到顶层目录(helloflask/)。同时为了方便操作,这 6 个程序共用同一个虚拟环境,所以在 helloflask/ 目录下创建虚拟环境。

注意 不要在 helloflask/ 目录下创建 .env 和 .flaskenv 文件,这会导致子目录下的程序无法正确启动(issue #3561)。

如果你按照书里给出的提示执行命令,一般不会出现问题。但是为了防止各种意外情况,这里列一下本书第一部分的操作流程($ 符号前是当前工作目录)。

具体的操作顺序就是,克隆仓库以后,切换到 helloflask 目录:

$ cd helloflask

然后创建虚拟环境、激活虚拟环境、安装依赖:

/helloflask $ python -m venv env  # Linux、macOS 系统的 Python3 用户,使用 python3 -m venv env
/helloflask $ env\Scripts\activate  # Linux、macOS 系统使用 source env/bin/activate
/helloflask $ pip install -r requirements.txt  # 这个命令会安装对应项目的所有依赖

接着看到介绍 Flask 最小程序部分,启动第一部分的示例程序:

helloflask/ $ cd demos/hello
helloflask/demos/hello $ flask run

现在看到了第二章,启动第二部分的示例程序(先执行 cd .. 回到上层目录,即 demos/ 目录):

helloflask/demos/hello $ cd ..
helloflask/demos $ cd http
helloflask/demos/http $ flask run

看到第三章,启动第三部分的示例程序:

helloflask/demos/http $ cd ..
helloflask/demos $ cd template
helloflask/demos/template $ flask run

以此类推。

遇到了自己没法解决的问题怎么办?

如果遇到问题,你可以先尝试:

  • 看一下本书 GitHub 仓库里的 FAQ 页面 有没有你要找的答案
  • 在搜索引擎搜索你的错误信息关键字,尝试自己解决

自己无法解决的话,可以:

  • 发到 GitHub(issue)和论坛(用文字形式贴出完整的错误信息、相关代码和命令,尽可能的详细描述相关信息)。
  • 发到交流群(建议优先选择发到 GitHub 和论坛,我会定期回复,群聊沟通效率很低,更适合闲聊)

Flask 新书完成时间推迟

很抱歉没能按照预期时间完成 Flask 新书。本来这个消息要到四月一号(预估的完成时间)发布的,但是怕被当做愚人节玩笑,所以还是提前一点比较好。

去年大部分时间花在了组织活动和准备演讲上,而今年前几个月又大都用来玩游戏和做外包了,所以一直没有全身心投入到新书写作上,目前大约只完成了一半……

为新书写了一个简单的主页,地址在 http://helloflask.com/book/2。我在页面上放了一个写作进度条,方便感兴趣的人了解进度。同时也加了一个订阅功能,你可以用 Email 来订阅新书的发布消息。

感谢大家的关注和支持,祝大家一切顺利。

明天开始专心写作。

《Flask 入门教程》第二版发布

Flask 入门教程》第二版发布了!新版本主要有以下变化(详见 commit 列表):

  • 去掉了 Pipenv 的介绍,改为使用 venv/virtualenv+pip
  • 所有提示和注意段落使用引用样式标注
  • 修正了大量笔误
  • 调整前言和后记内容
  • 调整部分措辞

访问本书主页下载 PDF 或在线阅读。

PyCon China 2019 Tutorial:Python Web 开发第一课

这是在 PyCon China 2019 上海场 9 月 22 号上午九点开始的 Tutorial(实践课程)《Python Web 开发第一课》 的介绍和相关信息。

购票链接:https://www.bagevent.com/event/5886131(Tutorial T3,优惠码 hellogrey)

标题

Python Web 开发第一课

介绍

这是一个面向 Python 程序员的 Web 开发课程,目标听众需要对 Python 基本语法有一定的了解,但对 Web 开发的了解程度没有要求。

在这个课程里,我会将 Python Web 开发所涉及的相关概念进行一个系统的梳理和介绍,包括 HTTP 协议、前端基础知识、常用的 Python Web 框架以及其他各种工具。

这个课程还会包含一个动手编程的环节。我会从最让人头疼的开发环境搭建开始,一步一步教你如何使用 Flask 开发一个简单的 Web 程序。

在课程过后,参与者会对整个 Python Web 开发技术栈有一个全局认识,并掌握基本的 Web 开发知识,而且会对接下来的学习路径有一个清晰的了解。

流程

时长:三小时

一、基本概念

  • Python Web 开发技术栈地图
  • HTTP 协议基础知识(请求与响应、URL 等)
  • 前端基础知识(HTML、CSS、JavaScript、AJAX 等)
  • Python 后端框架的特点和选择(Flask、Django 等)
  • 传统 Web 程序和 Web API 的对比
  • 测试、部署、持续集成等相关概念快速扫盲

二、动手编程

  • 开发环境搭建
  • 运行和调试程序
  • 编写 HTML 模板
  • 添加表单支持
  • 添加数据库支持

三、Q&A

  • 介绍常见的学习误区和建议的学习方向
  • 关于代码或其他任何相关内容的提问

内容难度

初级

目标听众

  • 想了解 Web 开发的前端、运维、测试或其他工程师
  • 想自己做网站的编程爱好者
  • Web 开发或 Python 初学者

听众要求

  • 了解 Python 基本语法
  • 有一台安装了 Python 和浏览器的电脑,并且了解命令行基本操作

讲者介绍

李辉,Flask 等相关项目的维护者,《Flask 入门教程》和《Flask Web 开发实战》的作者,HelloFlask 社区创建者。他撰写过大量技术文章,回答过大量技术问题,在这个过程中积累了一些编程教学的技巧,擅长用简单的语言解释复杂的编程概念。你可以在他的个人网站 greyli.com 了解到更多相关信息。

PyCon China 2019:基于 Flask 的 Web API 开发指南(北)

Meta

成都场 2.0 版本(推荐)

上海场 1.0 版本


这是在 PyCon China 2019 上海场 9 月 21 号分会场 B 下午 1:30 开始的演讲《基于 Flask 的 REST API 开发指南》 的介绍和相关信息。

这场演讲也会参加 PyCon China 2019 成都场(10 月 26 号)。

标题

基于 Flask 的 Web API 开发指南

介绍

作为一个微框架,轻量灵活的 Flask 很适合用来开发 Web API。相对于 Django REST Framework 和 APIStar,Flask 有什么优势和缺点?为了减少工作量,我们通常会使用一些工具来辅助编写,面对 Flask-RESTful、Flask-RESTPlus、Flask-API、Webargs、Marshmallow 等扩展和工具库,我们应该如何选择?虽然我们经常使用 REST API 这个名称,但是大部分的 API 都不够 RESTful,那么什么样的 API 才能算是 REST API?在这个议题中,我们将对这几个问题逐一进行探讨,并了解如何使用 Flask 编写出功能完善的 Web API。

总结

尽管完成了两版,但是比预先计划的内容少了很多,没能完成 Flask 扩展和其他 Web API 框架的深入对比。这些估计要放到新书里了。

  • 上海站第一版花费时间:23h 31m
  • 成都站第二版花费时间:31h 51m

开始写作第二本 Flask 书

把自己的目标公之于众,有可能会因为受到监督而更容易完成目标,也有可能会让你潜意识里感觉自己好像已经完成了目标,从而让计划更难执行。我更相信前一种理论,所以决定现在公布第二本 Flask 书的写作计划。

为什么要再写一本 Flask 书?

尽管我很想早一点深入学习更多的东西,而不是局限在 Flask(或 Python) 领域,但事实是,在这一个领域就已经有太多的东西需要研究和学习……目前来说,我最想解决的就是 Web API 的编写问题。《Flask Web 开发实战》虽然在第十章介绍了 Web API 的大部分基础概念,但是只实现了一种 OAuth 认证流程,也没能深入更多内容,包括数据校验、请求封装等。因此,我决定再写一本书来覆盖这个主题。

另一个原因是,我在上一本书的电商页面、豆瓣条目还有其他地方收集到了一些批评,其中有一些很中肯,所以我想写一本更好的 Flask 书。除了克服这些批评里提到的缺点,我也会尝试更科学的写作方式,不会像上一本书那样在早期印刷版本包含那么多的笔误和疏漏。

作为试水,我在 PyCon China 2019 上海场会有一个相关主题的演讲:《基于 Flask 的 Web API 开发指南》,如果你感兴趣的话,可以考虑报名参加

新书会包含哪些内容?

不同于《Flask Web 开发实战》所追求的大而全,这本书的定位是一个小而精的 Flask 书。它会包含下面这几部分:

  • 一个更轻松简单的入门部分。
  • 进阶部分:开发一个传统 Web 程序。
  • 作为重点的 Web API 开发部分。
  • Flask 相关的进阶部分,包括缓存、异步任务、容器部署等。
  • 可能会加入的其他内容:FastAPI、Django REST Framework、GraphQL。

这本书一来可以衔接《Flask 入门教程》,二来可以补充《Flask Web 开发实战》没有覆盖的内容。当然,对于学习 Flask,囊括几乎所有相关主题的《Flask Web 开发实战》仍然是一个不错的选择。

对于相同的主题,我会考虑使用不同的工具,比如《Flask Web 开发实战》里单元测试使用 unitttest,那么这本书就会介绍用 pytest;上一本书里编辑器介绍使用 PyCharm,这一本书或许就会介绍使用 VSCode。

下面是这本书的其他具体设计:

  • 只使用一个示例程序,贯穿全书。
  • 使用中文作为示例程序的界面语言。
  • 使用 Python3,但在书中对 Python2 兼容部分添加必要的提示。
  • 对书中的代码块添加尽可能多的注释。
  • 添加一个「术语表」,收集所有 Flask 和 Web 开发相关的术语,尝试给它们下一个简单易懂的定义。
  • 添加一个「常见错误速查表」,列出常见错误、错误解释和对应的解决方法(在维护上一本书的时间里,我处理了大量提问,见识过各种错误和误区)。

作为后续,在这本书完成后,我计划写一本电子书来介绍如何使用 Vue.js 基于这本 Flask 书编写的 Web API 来开发客户端。尽管我现在还没入门 Vue.js……但是我已经把放相关内容的网站域名准备好了:HelloVuejs.com(它和 HelloFlask.com 是兄弟域名 :p)

什么时间能完成?

预计的发售时间是明年愚人节,即 2020 年 4 月 1 日。因为 Flask 的诞生时间是 2010 年的愚人节,所以明年愚人节会是 Flask 诞生十周年纪念日,一个很完美的发售时间。

如果你对这本书感兴趣,可以关注我的微信公众号Twitter豆瓣账号获取最新动态,或是访问这本书的主页

2020/4/1 更新:Flask 新书完成时间推迟

你想看到什么内容?

在公开上一本写作消息的文章里,我征集到了大约 40 条建议,虽然没能完全采纳,但我都一一考虑过这些很有价值的建议。对于这本新书,在内容、形式或是其他任何方面,你有什么意见和建议?欢迎发评论分享你的想法,谢谢。

Jinja2 和 JavaScript 模板引擎语法冲突处理

Jinja2 有三种定界符语法:

  • {{ ... }} 用来标记变量;
  • {% ... %} 用来标记语句;
  • {# ... #} 用来标记注释;

如果你同时使用了 JavaScript 模板引擎,而该 JavaScript 模板引擎也使用相同的语法标记符,那就会产生冲突。一般来说,有下面三种兼容性处理方式:

1. 使用 Jinja2 的 raw 标签标记 JavaScript 模板代码

第一种方式最直观,使用 Jinja2 的 raw 标签声明原生代码块,也就是不需要进行后端渲染的代码块。使用 raw 和 endraw 标签把 JavaScript 模板部分标记出来即可,比如:

{% raw %}
<div id="app">
    {{ js_var }}
</div>
{% endraw %}

这种方式的副作用最少,尽管需要多几行代码,但不会影响你写 Jinja2 或其他 JavaScript 库的语法习惯。

2. 修改 Jinja2 的语法定界符号

第二种方式是修改 Jinja2 的语法定界符号,一般只修改变量定界符即可,其他的按需修改。具体通过修改程序实例的下面几个属性来实现:

from flask import Flask

app = Flask(__name__)

app.jinja_env.block_start_string = '(%' # 修改块开始符号
app.jinja_env.block_end_string = '%)' # 修改块结束符号
app.jinja_env.variable_start_string = '((' # 修改变量开始符号
app.jinja_env.variable_end_string = '))' # 修改变量结束符号
app.jinja_env.comment_start_string = '(#' # 修改注释开始符号
app.jinja_env.comment_end_string = '#)' # 修改注释结束符号

3. 修改 JavaScript 模板的语法定界符号

第三种方式是修改 JavaScript 模板的语法定界符号,具体方法因 JavaScript 模板/框架而异,可以参见相关文档了解。以 Vuejs 为例,下面将模板定界符改为中括号:

var app = new Vue({
  el: "#app",
  delimiters: ["[[", "]]"],
  data: {
    message: "Hello Vue!"
  }
})

折中方案

如果你觉得使用 raw 标签太麻烦,而修改语法定界符又不习惯,这里还有一个折中方法:两边都使用双花括号作为定界符,但根据花括号内部是否添加空格来进行区分。

具体来说,对 Jinja2 变量使用 Jinja2 标准语法,也就是使用 {{ 作为变量开始符号,注意花括号右侧有一个空格,结束符号类似,需要在花括号左侧加入一个空格,即 }}。实际示例如下:

{{ jinja_var }}

而 JavaScript 模板使用没有空格的双花括号,即:

 {{js_var}}

这是一种更适合心细的懒人的方法,如果是团队项目,可能会对不习惯这种用法的人造成困扰,记得在文档里注明。这种方式只需要修改 Jinja2 定界符:

app.jinja_env.variable_start_string = '{{ '
app.jinja_env.variable_end_string = ' }}'

另外需要注意的是,如果你使用了其他 Flask 扩展的内置 Jinja2 模板或宏,需要确保它们都使用了包含空格的标准 Jinja2 语法。举例来说,用来方便集成 Bootstrap 的 Flask-Bootstrap 就没法使用,需要使用替代的 Bootstrap-Flask。其他扩展,比如 Flask-Admin,Flask-Security 暂未测试,欢迎了解的同学反馈兼容情况。

在 Flask 项目中使用 Celery(with 工厂模式 or not)

本文隶属于《Flask Web 开发实战》番外系列。这篇文章会介绍如何在 Flask 项目中集成 Celery。

创建 Celery 程序

第一步是创建一个 Celery 程序实例。因为 Flask 程序实例通常会命名为 app,为了避免冲突,我们一般会把 Celery 实例命名为 celery 或 celery_app:

from celery import Celery

celery = Celery(__name__, broker='pyamqp://guest@localhost//')

@celery.task
def add(x, y):
    return x + y

组织和加载配置

大多数教程,包括目前的 Flask 文档里都会介绍用下面的方式加载配置:

celery.conf.update(app.config)  # 这里的 app 是 Flask 程序实例

也就是把 Celery 配置和 Flask 配置写在一起,然后从 Flask 程序实例的配置字典里更新配置。

但问题是,Celery 从 4.0 开始启用新的小写配置名,某些配置被新的名称替换。虽然旧的大写配置仍然支持,但如果你打算使用小写配置名,或是打算在未来进行迁移,这里的配置加载方式就会失效,因为 Flask 在从文件或对象导入配置时只会导入大写形式的配置变量。

因此,我建议将 Celery 配置写在单独的文件里,不要和 Flask 配置混在一起。按照 Celery 文档的示例,你可以在当前目录创建一个 celeryconfig.py 文件(或是其他名字)保存配置:

broker_url = 'pyamqp://'
result_backend = 'rpc://'

task_serializer = 'json'
result_serializer = 'json'
accept_content = ['json']
timezone = 'Europe/Oslo'
enable_utc = True

然后使用下面的方法加载配置(使用其他模块名,或是在其他路径时,记得修改传入的字符串):

celery.config_from_object('celeryconfig')

如果需要在创建 Celery 实例时传入 broker 和 backend 配置,可以直接写出或是从配置模块中导入:

from celeryconfig import broker_url

celery = Celery(__name__, broker=broker_url)

在 Flask 程序中初始化 Celery

你可以单独创建 Celery 程序,但我们通常会需要为它添加 Flask 程序上下文支持,因为有时候你的 Celery 任务函数会用到依赖于 Flask 程序上下文的某些变量。

下面我们为 Celery 创建了一个工厂函数,在工厂函数中创建 Celery 实例,加载配置,并实现 Flask 程序上下文支持:

from flask import Flask
from celery import Celery

from celeryconfig import broker_url


def make_celery(app):
    celery = Celery(__name__, broker=broker_url)
    celery.config_from_object('celeryconfig')

    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)

    celery.Task = ContextTask
    return celery

app = Flask(__name__)

celery = make_celery(app)

在定义 Celery 任务的模块里,比如 tasks.py,你可以导入这个 Celery 程序实例:

from app import celery

@celery.task
def add(x, y):
    return x + y

在使用工厂函数的 Flask 程序中初始化 Celery

当 Flask 程序也使用工厂函数创建时,我们可以全局创建 Celery 程序实例,然后在创建 Flask 程序实例的工厂函数里更新 Celery 程序配置并进行上下文设置:

from flask import Flask
from celery import Celery

from celeryconfig import broker_url
 
celery = Celery(__name__, broker=broker_url)
 

def create_app():
    app = Flask(__name__)
 
    register_celery(app)
    return app
 

def register_celery(app):
    celery.config_from_object('celeryconfig')
 
    class ContextTask(celery.Task):
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return self.run(*args, **kwargs)
 
    celery.Task = ContextTask

同样直接导入 Celery 实例并创建任务:

from app import celery

@celery.task
def add(x, y):
    return x + y

这本来是一个完整的 Celery 入门教程,但因为去年的一次硬盘损坏,对应的示例程序弄丢了,暂时没有毅力重写一遍,所以这篇文章只抽取了其中和 Flask 相关的内容。

因为距离初稿写作的时间已经过去半年多,Celery 的最新版本也已经是 4.3.0,如果文中有什么疏漏,或是有更好的实现方式,欢迎评论指出。

为已存在的数据库生成 SQLAlchemy / Flask-SQLAlchemy 模型类

SQLAlchemy 基于模型类对数据库表进行操作,所以,如果你想对已存在的数据库表进行操作,就要先为它编写对应的模型类。

对于简单的数据库,比如只有几张表,没有复杂的关系,表字段也很少,你可以直接对照表模式手写模型类。

其他情况下,使用自动化工具 SQLAcodegen / Flask-SQLAcodegen 自动生成模型类定义会更加方便,根据单独使用 SQALchemy 还是使用扩展 Flask-SQLAlchemy,你可以选择阅读对应的章节。

单独使用 SQLAlchemy

首先使用 pip 安装:

$ pip install sqlacodegen

执行下面的命令将模型类输出到 models.py 文件里(将覆盖目标文件原内容):

$ sqlacodegen --outfile models.py sqlite:///database.db

这个命令的格式如下:

$ sqlacodegen --outfile <输出的文件名> <数据库连接 URI>

–outfile / -o 选项设置输出的目标文件,不给出这个选项将直接在命令行输出生成的模型类定义,比如:

$ sqlacodegen sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()
metadata = Base.metadata


class Message(Base):
    __tablename__ = 'message'

    id = Column(Integer, primary_key=True)
    name = Column(String(20))
    body = Column(String(200))
    timestamp = Column(DateTime, index=True)

提示 如上所示,生成的模型类定义会包含 Base 和 metadata 对象定义和相关导入语句,你或许需要进行细微的调整。

你可以使用下面的命令查看更多可用的设置选项:

$ sqlacodegen --help

提示 除了使用 SQLAcodegen,你也可以直接使用内置的 Automap 扩展生成模型类。

使用 Flask-SQLAlchemy

首先使用 pip 安装 Flask-SQLAcodegen:

$ pip install flask-sqlacodegen

执行下面的命令将模型类输出到 models.py 文件里(将覆盖目标文件原内容):

$ flask-sqlacodegen --flask --outfile models.py sqlite:///database.db

这个命令的格式如下:

$ flask-sqlacodegen --flask --outfile <输出的文件名> <数据库连接 URI>

–flask 选项设置输出 Flask-SQLAlchemy 模型类,不给出这个选项将直接输出 SQLAlchemy 原生模型类定义;–outfile 选项设置输出的目标文件,不给出这个选项将直接在命令行输出生成的模型类定义,比如:

$ flask-sqlacodegen --flask sqlite:///data.db
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, String
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class Message(db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(20))
    body = db.Column(db.String(200))
    timestamp = db.Column(db.DateTime, index=True)

提示 如上所示,生成的模型类定义会包含一个 db 对象定义和相关导入语句,你或许需要进行细微的调整。

你可以使用下面的命令查看更多可用的设置选项:

$ flask-sqlacodegen --help