许多入门的 Flask 教程是在单个 Python 文件中定义了各类视图来阐述一些基本的设计和使用原则的。不过,实际应用每每并无这么简单,这里,咱们尝试搭建一个更接近实际的中等规模应用的代码结构。首先,咱们建立一些目录和文件以下:html
<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
便可。python
config.py
文件用于放置配置信息。shell
子目录 static
和 templates
分别用于放置静态文件和页面模板。数据库
Python 包 views
主要用于放置视图模块,也就是响应处理的接收输入和产生输出的这两个环节。此外,一些和 Flask 关联较紧密的代码,例如一些扩展的配置或必要实现,也放置在 views
包中。flask
真正的业务逻辑处理,咱们放在包 business
中进行,理想状态下,咱们但愿 business
或多或少相对于 Flask 是具备必定的独立性的。浏览器
包 utils
的用途是放置一些能够在不一样应用中通用的代码,这里先不进行讨论。bash
manage
文件实际上也是 Python 代码,它是做为管理用途的命令行工具入口,须要使用 chmod +x
赋予其可执行属性。服务器
appname.py
:包含 WSGI 应用程序实例的模块appname.py
是定义全局的 WSGI 应用程序实例的模块,诸如 uWSGI 或者 Gunicorn 都要求咱们导入这样的一个模块并指定其中符合 WSGI 规范的应用程序实例。咱们能够根据须要命名这个模块。其内容以下:app
# -*- 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
修饰器。module
是 flask.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, 世界!”文本。