最近我在为 openEuler 社区添加一个 FAQ 模块, 这一系列正是我在这一过程中的总结

全部内容: 如何编写一个 Python Web 应用(零)


对于 Flask 最核心的有三点:

  • Application Context: 将整个应用连成一体.
  • View Function & CLI Command: 应用暴漏给外界的操作接口
  • Blueprints (蓝图): 实现模块化开发

当然还有其他细节,但有了这三点,就可以编写一个完整的 Web 引用了

Application Context

参考: Flask 2.0.x

我更习惯使用 工厂模式 创建 Context: 在 __init__.py 中写 create_app():

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    configure(app, test_config)

    db.init_app(app)

    from faq.blueprints import review
    app.register_blueprint(review.bp)

    return app

当在命令行输入命令 flask run 时, flask 将自动寻找 create_app 函数来执行, 以创建 Application Context.

PS: flask run 便是一个默认的 CLI Command

默认在 View Function 和 CLI Command 中自动就在 当前应用的 Context 下. 而此外其他的场景下, 需要手动的创建/引入 Application Context. flask-sqlalchemy 中的例子:

with app.app_context():
    user = db.User(...)
    db.session.add(user)
    db.session.commit()

但实际上 有 View Function 和 CLI 就足够了

View Functions & CLI Command

View Function 主要做到了路径和函数的映射绑定, 同时借助 Flask 其他的模块 (request, current_app, make_response 等), 获得参数和返回响应

参考: Flask 2.0.x Doc

这是一个 View Function 的例子.

bp = Blueprint('review_show', __name__, url_prefix='/review/show', cli_group=None)


@bp.route('/requests/<user_id>', methods=['GET'])
def show_requests(user_id):
    page = int(request.args.get('page'))
    per_page = int(request.args.get('page_size'))
    data: dict = request.get_json()
    print(data)

    current_app.logger.info("request user id: %s", user_id)

    requests: Pagination = ERequest.query \
        .order_by(ERequest.time.desc()) \
        .paginate(page=int(page), per_page=int(per_page), error_out=False)
    body = [RequestEntry(rq) for rq in requests.items]
    return make_response(jsonify(body), 200)

获得 param 参数:

page = int(request.args.get('page'))
per_page = int(request.args.get('page_size'))

获得json 格式的 request body:

data: dict = request.get_json()
print(data)

响应

return make_response(jsonify(body), 200)

接下来是 CLI Command:

参考:

bp = Blueprint('review_show', __name__, cli_group='cli')

@bp.cli.command('test')
@click.option('--who', default='tom')
@click.argument('what', default='good morning')
def hello(who, what):
    print(f'hello: {who},{what}')

运行:

PS D:\my\flask\app> $env:FLASK_APP = "faq"
PS D:\my\flask\app> flask cli hello
hello: tom,good morning
PS D:\my\flask\app> flask cli hello goodby --who bob
hello: bob,goodby
PS D:\my\flask\app>

Blueprints 蓝图

从 Java 转来的我十分希望实现像 Spring boot Controller 那样的多模块开发. 即将不同类型或不同需求模块的 API 放在不同的 Controller 中, 然后通过命名加以区分.

Blueprints 就实现了这点. 蓝图可以注册到 app 中去, 同时又可以注册其他子蓝图, 从而可以实现一个树状的注册结构.

比如, 这是我的文件目录

│  models.py
│  __init__.py
│
└─blueprints
   │  __init__.py
   │
   └─ review
     │  handle.py
     │  show.py
     └─ __init__.py

/blueprints/show.py 中 我定义了具体的 View Functions 和 CLI

bp = Blueprint('review_show', __name__, url_prefix='/review/show', cli_group='cli')


@bp.cli.command('hello')
@click.option('--who', default='tom')
@click.argument('what', default='good morning')
def hello(who, what):
    print(f'hello: {who},{what}')


@bp.route('/requests/<user_id>', methods=['GET'])
def show_requests(user_id):
    pass

这个蓝图 review_show 注册到另一个蓝图 review 中:

/blueprints/review/__init__.py:

from flask import Blueprint

from faq.blueprints.review import show, handle

bp = Blueprint('review', __name__)

bp.register_blueprint(show.bp)
bp.register_blueprint(handle.bp)

最终, 蓝图 review 注册到 app 中:

/__init__.py:

def create_app(test_config=None):
    ......

    from faq.blueprints import review
    app.register_blueprint(review.bp)

    return app
03-06 00:11