0%

Flask 初探(2)

在了解 Flask 的基本接口后,为了更好的组织代码,我们希望将不同资源的接口定义放在不同的模块中,本文介绍如何将 Flask 代码模块化,给出了一个可以尝试的项目结构,并介绍了模块化的重要途经:蓝图(Blueprint)。

项目结构

在第一次接触 Flask 时,我是这样创建一个 Flask 应用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask
from flask_myextension import Myextension

myextension = Myextension()

app = Flask(__name__)
app.config['SECRET_KEY'] = 'dev'

myextension.init_app(app)

@app.route('/user')
def index():
return 'User Page'


@app.route('/doctor')
def login():
return 'Doctor Page'

以上代码通过全局变量的形式(app = Flask(__name__))声明一个 Flask 应用,所有的路由、配置信息(app.config['SECRET_KEY'] = 'dev')和插件初始化(myextension.init_app(app))都在一个 Python 模块里完成。虽然这样做并没有错,但当我们的应用变得越来越复杂庞大的时候,把所有代码放在一个文件里会很混乱,所以我们需要把他们拆开,通过关注点分离,使项目结构更清晰。

比如对于用户和医生的路由,由于他们是不同的实体,将他们分开放在不同的 Module 中是更好的选择。或者从 RESTful 的角度来说,他们是不同的资源,我们应该将他们对应的 API 放在不同的文件里。

尽管 Flask 十分灵活(不像 Django),没有规定开发者必须遵循的项目结构,但对于初学者来说,我们往往需要一个更明确的指引来避免出错,下面是一个可行的项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.
├── README.md
├── db
│   ├── custom.cnf
│   └── schema.sql
└── project
├── app
│   ├── __init__.py
│   ├── case.py
│   ├── doctor.py
│   ├── user.py
│   ├── static
│   │   └── img/
│   └── templates
│      ├── API.html
│      └── README.html
├── instance
│   ├── config.py
│   └── config_local.py
├── requirements.txt
└── start.py

项目根目录下有两个目录,db 和 project,分别存放着数据库和 Flask 应用相关的文件,将两者分开可以在部署时将数据库和 Flask 应用部署到不同的地方(如不同的 Docker 容器)。

db 目录下包含数据库配置文件 custom.cnf 和数据库初始化文件 schema.sql

project 目录下有一个 Package app,里面存放着应用相关的核心代码和文件,例如 doctor.pyuser.py 两个 Module 分别包含了医生相关的 API 和用户相关的 API;static/ 目录下存放项目的静态文件,如一些图片、CSS 文件和 JavaScript 脚本;templates/ 目录下存放 HTML 文件,并可以借助 Jinja2 模板语法加强功能。

instance 目录下存放着 Flask 配置文件,由于其中有一些密钥和隐私信息,所以 instance/ 是不会被提交的,对于 git 我们把它添加到 .gitignore 中。

顺带一提,要在 Windows 下生成目录的树形结构,可以在命令行中用 tree 命令,但我感觉生成出来的样式没有上面的好看。我是在 Windows Linux 子系统 WSL 下用 linux 的 tree 命令生成的,在 WSL 中 Windows 文件的路径在 /mnt

应用工厂

不同于最初的直接创建一个全局变量 app 作为 Flask 的实例,我们可以创建一个函数,在这个函数中创建 Flask 应用,并对其进行一系列的初始化,最后返回这个应用,这样的函数被称为应用工厂(Application Factory),我们可以把这个工厂函数写在 project 目录下有一个 Package app__init__.py 中,具体的语法可以参考官方文档

since you’re using a factory function, that instance isn’t available when writing the functions. Instead, write a function that takes an application and does the registration, by convention, we call it init_app().

蓝图

蓝图可用于组织相关的视图函数,例如上面文件结构中的 doctor.py 里就初始化了一个医生的蓝图,并在其中定义了医生相关的视图函数(如 CRUD),在蓝图上注册视图函数就像我们最开始在全局变量 app 上注册视图函数,但是蓝图将不同的实体的视图函数分开了,最后再将蓝图在应用工厂中注册于 app 上,下面是一个定义蓝图的例子

1
2
3
from flask import Blueprint

bp = Blueprint('doctors', __name__, url_prefix='/doctors')

蓝图带来了许多好处,url_prefix 使得这个蓝图所有的视图函数 url 都以之开头,第一个参数是蓝图的名字,之后可以用于在url_for()中生成对应视图函数的 URL

下面是在应用工厂中注册蓝图的语法

1
app.register_blueprint(auth.bp)

蓝图中常用的对象 gcurrent_app

g is a special object that is unique for each request. It is used to store data that might be accessed by multiple functions during the request (the one request).

current_app is another special object that points to the Flask application handling the request. Since you used an application factory, there is no application object when writing the rest of your code.

奥里给,老铁们,干了!