使用 Flask 開發 Web 應用(二)

目錄結構

許多入門的 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

子目錄 statictemplates 分別用於放置靜態文件和頁面模板。數據庫

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 修飾器。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, 世界!」文本。

相關文章
相關標籤/搜索