使用 Flask 开发 Web 应用(二)

目录结构

许多入门的 Flask 教程是在单个 Python 文件中定义了各种视图来阐述一些基本的设计和使用原则的。不过,实际应用往往并没有这么简单,这里,我们尝试搭建一个更接近实际的中等规模应用的代码结构。首先,我们创建一些目录和文件如下:

<workdir>
+- business
|  `- __init__.py
+- static
+- templates
+- utils
|  `- __init__.py
+- views
|  +- __init__.py
|  `- main.py
+- appname.py
+- config.py
`- manage

其中 <workdir> 表示工作目录。我们在 appname.py 文件中创建 WSGI 应用的实例,这个文件可以更名为任何我们期望的名字,只要它是合法的 Python 模块名,可以被 import 即可。

config.py 文件用于放置配置信息。

子目录 statictemplates 分别用于放置静态文件和页面模板。

Python 包 views 主要用于放置视图模块,也就是响应处理的接收输入和产生输出的这两个环节。此外,一些和 Flask 关联较紧密的代码,例如一些扩展的配置或必要实现,也放置在 views 包中。

真正的业务逻辑处理,我们放在包 business 中进行,理想状态下,我们希望 business 或多或少相对于 Flask 是具有一定的独立性的。

utils 的用途是放置一些可以在不同应用中通用的代码,这里先不进行讨论。

manage 文件实际上也是 Python 代码,它是作为管理用途的命令行工具入口,需要使用 chmod +x 赋予其可执行属性。

基础的应用程序代码

appname.py:包含 WSGI 应用程序实例的模块

appname.py 是定义全局的 WSGI 应用程序实例的模块,诸如 uWSGI 或者 Gunicorn 都要求我们导入这样的一个模块并指定其中符合 WSGI 规范的应用程序实例。我们可以根据需要命名这个模块。其内容如下:

# -*- coding: utf-8 -*-

"""
WSGI Application
================

The ``app`` object in this module is a WSGI application, acccording to the
`PEP 333 <https://www.python.org/dev/peps/pep-0333/>`_ specification, it should
be worked with any WSGI server.
"""

from flask import Flask
import config
import business
import views


# (1). Instantiate the WSGI application.
app = Flask(__name__, static_url_path="")

# (2). Load configurations.
app.config.from_object(config)

# (3). Initialize the business logic library.
business.init_app(app)

# (4). Import view modules.
views.init_app(app)

我们希望这个模块尽可能简单,它实际上就是只是做最基本的初始化操作,包括 WSGI 应用程序的实例化、加载配置、初始化业务逻辑库以及导入视图模块等。

在第(1)步实例化 Flask 对象时使用了参数 static_url_path="" 只是个人偏好,开发人员可以根据需要使用默认的设置或其它设置。根据笔者这样的设置,当访问一个没有定义的路由规则时,最后会尝试检查是否有匹配的静态文件。这允许静态文件无需默认的使用 static/ 基础路径,方便提供一些通常保存在根目录的静态文件(如 favicon.ico、robot.txt)。但是也存在实际布署时 Web 服务器的 Rewrite 规则稍微麻烦,以及对于不存在的内容,需要额外付出一点代价的缺点。

config.py 配置文件

appname.py 的第(2)步加载的配置文件 config.py,最初看起来形如:

# -*- coding: utf-8 -*-

"""
Configurations
==============
"""

# Flask Builtin Configuration Values
# http://flask.pocoo.org/docs/0.12/config/#builtin-configuration-values

DEBUG = False
TESTING = False
SECRET_KEY = b"A_VERY_SECRET_BYTES"
LOGGER_NAME = "appname"

其中各配置项的含义,请参考 Flask 关于配置 的文档。由于这个配置文件本身也是 Python 代码,因此使用 Python 的表达式、运算和/或标准库甚至第三方库生成配置也是可以的。当然,笔者认为在使用这些灵活机制的同时,一定要拿捏好可读性、可维护性的度。

appname.py 完成这个步骤后,通常我们使用 app.config 这个属性而不再使用 config 模块来访问配置对象,它在表现上更接近 Python 字典。

例如,在第(3)步中我们就将这个 app.config 作为参数用于初始化业务逻辑库。业务逻辑库如何组织,开发人员可以拥有很大的自主性。后续我们会展开讨论编写业务逻辑库的一些思路。现在,可以暂时只在 business/__init__.py 写入下面简单的函数:

def init_app(app):
    """Setup the business library."""

views 模块

本文提出的目录结构和常见的 Flask 教程最大的区别在于将 WSGI 应用程序的实例化和视图函数分开了。让我们首先编写一个简单的视图模块 views/main.py 如下:

from flask import Blueprint

module = Blueprint(__name__.split(".")[-1], __name__, url_prefix="")

@module.route("/")
@module.route("/index.html")
def index():
    return "<meta charset=utf8><p>Hello, 世界!"

相比常见的教程使用 app.route 修饰器来设置路由规则,这里改为了使用 module.route 修饰器。moduleflask.Blueprint 的实例,具有和 flask.Flask 接近的 API,在编写视图函数时我们可以使用 Blueprint 来方便一些模块化的工作。

实际的视图模块当然不会这么简单,之后我们再深入展开讨论。作为示例,现在不要在意 index() 函数的返回字符串写得有些古怪,它是可以被合法解释的 HTML,即使是很古老的浏览器都能正确的处理它。

但是,这样定义的视图模块,全局的 Flask 对象并不知道它的存在,我们需要专门加载,这是在 views/__init__.py 中实现的:

# -*- coding: utf-8 -*-

"""
Views
=====

Although the name of this package is ``views``, we place not only
view modules, but also other flask related codes in it. For example,
some flask extensions such as Flask-Login require additional
configuring and/or implementing.


View Modules
------------

Each view module should has a ``module`` object, it's an instance of
class ``flask.Blueprint``. View functions in a view module should be
decorated by ``route()`` method of this ``module`` object.

Push view modules' name into the list ``_view_modules``, function
``_import_view_modules()`` will import these modules and register
their ``module`` objects on the flask application passed in.
"""

from importlib import import_module


# View modules to be imported.
_view_modules = ["main"]


def _import_view_modules(app):
    for name in _view_modules:
        module = import_module("." + name, __name__)
        blueprint = module.module
        app.register_blueprint(blueprint)


def init_app(app):
    # place flask extensions configuring code here...

    # import view modules
    _import_view_modules(app)

我们在 views/__init__.py 中定义了 _import_view_modules() 函数,它会一次导入列表 _view_modules 中声明的模块,然后注册到传入的 WSGI 应用程序上。

我们约定,所谓的视图模块是这样的 Python 模块,它一个名为 module 的类 flask.Blueprint 的实例,在模块中定义的 Flask 视图函数,使用 module.route() 方法作为修饰器,而不是传统上的全局的 app.route()。将这些模块名添加到 _view_modules 列表中,这样我们在 appname.py 的第(4)步就可以导入各视图模块了。

管理脚本

在生产环境中,我们可以配置 uWSGI 或者 Gunicorn 或者其它我们使用的机制导入 appname.py 并获取其中的 app 对象作为 WSGI 应用程序。然是在开发的时候,我们需要有更简单的机制来运行开发服务器或执行一些必要的操作(比如准备数据库,或者进入一个 Python 交互环境允许我们做一些尝试)。我们通过 manage 脚本来实现这些操作:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Management Script
=================

This file is used to perform the necessary management operations
for development or deployment via the command-line interface.

Note: Please make sure this file is executable.
"""

import os
import click
from flask.cli import FlaskGroup, pass_script_info


# This environment variable is used to discover the WSGI application.
os.environ["FLASK_APP"] = os.path.join(os.path.dirname(__file__), "appname.py")


# Keyword arguments for the `click.group()` decorator.
group_arguments = {
    "context_settings": {
        "help_option_names": ['-h', '--help']  # make `-h` as same as `--help`
    },
    "invoke_without_command": True,  # make the group can be invoked without sub-command
    "cls": FlaskGroup,               # use FlaskGroup as the group class
    "add_default_commands": False,   # do not use the built-in `run` and `shell` commands
    "add_version_option": False,     # do not set up the built-in `--version` option
}


@click.group(**group_arguments)
@click.pass_context
def main(ctx):
    """Management script of the application."""
    # make `run()` be the default command
    if ctx.invoked_subcommand is None:
        ctx.invoke(run)


@main.command(help="Runs a development server.")
@click.option("--address", "-a", default="127.0.0.1",
              metavar="ADDRESS", show_default=True,
              help="The interface to bind to.")
@click.option("--port", "-p", default=8000,
              metavar="PORT", show_default=True,
              help="The port to bind to.")
@pass_script_info
def run(script_info, address, port):
    application = script_info.load_app()
    application.run(address, port, debug=True,
                    use_reloader=True,
                    use_debugger=True)


@main.command(help="Runs a shell in the application context.")
def shell():
    from IPython import embed
    embed()


if __name__ == "__main__":
    main()

Click 是 Python 生态系统中一个非常方便的命令行处理库,与 Flask 出自同门,因此 Flask 与 Click 的集成起来相当方便。当然要深入理解这段代码,还是需要通读 Click 文档以及 Flask 关于命令行处理的文档,这里就不赘述了。

当开发人员增加自己的命令时,可以使用 main.command() 修饰命令,它与 click.command() 的修饰类似,但可以确保相应的命令运行在 Flask 应用程序上下文中。当定义命令的函数需要使用全局的 Flask 对象时,可以模仿 run() 使用 pass_script_info 修饰器,接受参数 script_info 并通过 script_info.load_app() 获取全局的 Flask 对象。

别忘了使用 chmod +x 赋予 manage 文件可执行属性,我们尝试在终端中执行它:

$ ./manage --help
sage: manage [OPTIONS] COMMAND [ARGS]...

  Management script of the application.

Options:
  -h, --help  Show this message and exit.

Commands:
  run    Runs a development server.
  shell  Runs a shell in the application context.

如果不带任何参数,将会启动默认的开发服务器:

$ ./manage
 * Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger pin code: xxx-xxx-xxx

打开浏览器访问 http://127.0.0.1:8000/,应该可以看到能够正常显示我们在之前设定的“Hello, 世界!”文本。


(●—●)大约是全栈(●—●)

1.9k 声望
34 粉丝
0 条评论
推荐阅读
uwsgi 注意事项
http 和 http-socket 选项是完全不同的。第一个生成一个额外的进程,转发请求到一系列的worker (将它想象为一种形式的盾牌,与apache或者nginx同级),而第二个设置worker为原生使用http协议。

zed2015阅读 2.4k

2023 新年好(顺便记 Flask 的 Bad request 错误)
如果你用 Flask 实现过 restful 的接口,可能经常会遇到发送 POST 请求的时候,返回 400 BAD REQUEST 的情况,这时候即使可以调试,也看不到更多的错误信息了,因为还没执行到自己的业务代码呢。这十有八九是因为...

songofhawk阅读 1.5k

封面图
自定义数据采集export到prometheus使用 Flask实现
如图 想要取到 url get请求的值,使用prometheus blackbox 无法获取,所以考虑使用flask自定义exporter 获取

台湾省委书记阅读 945

封面图
使用Flask快速部署PyTorch模型
对于数据科学项目来说,我们一直都很关注模型的训练和表现,但是在实际工作中如何启动和运行我们的模型是模型上线的最后一步也是最重要的工作。今天我将通过一个简单的案例:部署一个PyTorch图像分类模型,介绍这...

deephub阅读 699

封面图
为什么很多公司都开始使用Go语言了?
写在前面最近和几个小伙伴们在写字节跳动第五届青训营后端组的大作业。接近尾期了,是时候做一些总结了,那从什么地方开始呢?那就从我们为什么要选择Go语言开始吧~

一口鸭梨阅读 428

CompletableFuture实现异步编排
场景:电商系统中获取一个完整的商品信息可能分为以下几步:①获取商品基本信息 ②获取商品图片信息 ③获取商品促销活动信息 ④获取商品各种类的基本信息 等操作,如果使用串行方式去执行这些操作,假设每个操作执行1...

视角线阅读 423

快速上手python的简单web框架flask
python可以做很多事情,虽然它的强项在于进行向量运算和机器学习、深度学习等方面。但是在某些时候,我们仍然需要使用python对外提供web服务。

flydean阅读 414

(●—●)大约是全栈(●—●)

1.9k 声望
34 粉丝
宣传栏