許多入門的 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, 世界!」文本。