最近我在为 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