Flask 进阶

本周我们会介绍一种大型 Flask 项目的文件架构. 尽管 Flask 并不强制要求项目的具体组织方式(换句话说任凭你放飞自我, 写在单文件里也是没问题的), 但是采用一种具体的组织方式总能使你的编码更清晰.

$ tree -P '*.py' --prune -I 'flask-sqlacodegen|venv'
.
├── app
   ├── api
      ├── __init__.py
      └── posts.py
   └── __init__.py
├── config.py
├── models.py
├── run.py
└── test.py

配置选项

应用经常需要设定多个配置. 这方面最好的例子就是开发、测试和生产环境要使用不同的数据库, 这样才不会彼此影响.

我们可以设计一个名为配置的基类, 里面存放一些通用的配置选项, 例如密钥, 发送邮箱的名称, 等等. 然后在不同的具体配置中, 设置例如数据库地址, 是否使用调试器等.

为了让配置方式更灵活且更安全, 多数配置都可以从环境变量中导入.

  • 你当然可以直接写成字面量: MY_PASSWORD = 'hard to guess haha'

    • 然后你忘记改就把文件上传到 Git 里了, 然后被迫用到了我们学到的永久删除大法.

  • 写成 MY_PASSWORD = os.environ.get('MY_PASSWORD')

    • 或者更好, 对部分字段做一个空值检查或者添加一个默认值 or 'default'

你可以使用 dotenv, 它可以将 .env 文件里的键值对当成系统变量来用. 具体使用方式可以参考文档.

为了再给应用提供一种定制配置的方式, Config 类及其子类可以定义 init_app() 类的静态方法, 其参数为应用实例:

config 字典中注册了不同的配置环境. 怎么让应用加载不同的配置呢? 也就是 Config 类实例化以后怎样让 Flask 的应用实例加载这些配置.

  • 要读取全部字段然后一个个设置 app.config?

    • 太麻烦了. 其实有更简洁的方式.

  • 你可以用 app.config.from_object() 来导入我们的配置类.

工厂函数

啥子是个工厂函数哟?

  • 通俗地来说, 它就像工厂一样, 批量生产.

    • 批量生产啥呢? 那就是应用实例.

  • 换个人话: 把创建实例的过程用一个函数封装起来.

    • 这样, debug 要测试不同环境的应用, 那就批量生产不同的应用实例就可以.

构造文件导入了大多数正在使用的 Flask 扩展(这里是 db), 你还可以添加例如 from flask_mail import Mail, 实例化一个 mail 然后 mail.init_app.

蓝图

上面的示例函数并不完整. 你马上就会出现一个问题:

  • 既然我们的应用实例由 create_app 创建. 那定义路由的那个 app 要写什么?

    • 总不至于 app = create_app(...) 再写吧? 太晚了.

  • 错误页面处理程序使用 app.errorhandler 装饰器定义, 也是同样的问题...

Flask 使用蓝图 (blueprint) 提供了更好的解决方法. 蓝图和应用类似, 也可以定义路由和错误处理程序. 不同的是, 在蓝本中定义的路由和错误处理程序处于休眠状态, 直到蓝本注册到应用上之后, 它们才真正成为应用的一部分.

与应用一样, 蓝图可以在单个文件中定义, 也可使用更结构化的方式在包中的多个模块中创建. 为了方便调整, 我们在 api 文件夹下新建一个 __init__.py.

在 Python 的工程项目中, Python 会把含有 __init__.py 的文件夹作为一个模块(Module).

蓝本通过实例化一个 Blueprint 类对象创建. 这个构造函数有两个必须指定的参数: 蓝本的名称和蓝本所在的包或模块. 与应用一样, 多数情况下第二个参数使用Python 的 __name__ 变量即可. 此外, 在我们的示例中, url_prefix 参数表示这个蓝本路由的 url 会自带一个 '/api' 的前缀.

应用的路由保存在 authentication, posts, subvues, users 这几个模块中, 导入这两个模块就能把路由和错误处理程序(另外添加)与蓝本关联起来.

在 users.py 文件里, 就可以这么写了:

记得在创建应用的工厂函数里加上使用蓝图

然后应用脚本这么用

需求文件

你可能在别人的 Python 项目里见过 requirements.txt 这一个文件. pip 的 -r 选项读取一个文件中需求的库作为输入:

现在问题是, 怎么生成这个文件. 我们有两种方式.

  • 把全部需求文件全部输出. 如数家珍.

  • 只把要用的输出. 比较推荐这个.

测试

  • 自动化测试是很重要的一环.

    • 你也不想每次都打开浏览器输一遍 url 吧? 不想掏出 curl 敲一遍 HTTP 请求吧?

我们用 unitest 这个框架来编写测试代码: 假如你有一个计算器模块

一些常见的断言方法, 由名字也能知道它们是干啥的.

  • assertEqual(a, b)

  • assertNotEqual(a, b)

  • assertTrue(x)

  • assertFalse(x)

  • assertIs(a, b)

  • assertIsNot(a, b)

  • assertIsNone(x)

  • assertIsNotNone(x)

  • assertIn(a, b)

  • assertNotIn(a, b)

  • 试试 coverage: 代码覆盖度工具用于统计单元测试检查了应用的多少功能.

一些工程上的实现

用户身份验证

  • Flask-Login:管理已登录用户的用户会话

  • Flask-HTTPAuth:HTTP 验证用户身份

  • Werkzeug:计算密码散列值并进行核对

  • itsdangerous:生成并核对加密安全令牌

详情见拓展阅读.

拓展阅读

Salted Password Hashing - Doing it Right: 你可能会想, 为啥要大费周章用一套加密的登录机制, 简单地通过某种 hash 映射一下不就完了, 甚至设计了一套自己的算法. 阅读这个文章可能会让你改变一些看法.

Flask Web开发: 基于Python的Web应用开发实战. 第2版: 这也是我们一开始推荐的 Flask 阅读图书. 你可以阅读第七章以及后面的章节(例如十四章讲的是RESTful接口)来进一步学习.

Hello Flask: 测试. 这个开源教程对测试进行了比较详细的介绍.

最后更新于

这有帮助吗?