在 Flask 应用程序中从外部应用程序上下文访问应用程序 object

Accessing app object from outside app context in Flask application

如果 api-key 在请求的 headers 中无效(或未指定),我已经为我的路由函数设置了一个装饰器以中止(401)。

所以我在我的主模块(名为 app)的专用文件中定义了这个函数,它被导入到我所有的蓝图定义中。但是从这个文件中,我无法访问应用程序 object 来加载存储在配置中的 API 密钥。我尝试使用 current_app,但出现错误提示我无法在应用程序上下文之外使用它。我无法直接从应用程序模块导入应用程序 object,因为它会导致循环导入。

如果我在 __init__.py 文件中声明装饰器,我将面临同样的问题:我将无法从应用程序模块导入 require_apikey 函数,因为蓝图模块已经导入用于蓝图注册的应用程序模块。

我认为我的设计存在一些问题。你能指出我的缺点并帮助我修复它吗?

我的项目目录如下所示:

project/
 - run.py
 - app/
 - - __init__.py
 - - player.py
 - - require_apikey.py

下面是这些文件的内容:

# run.py
from app import app

if __name__ == "__main__":
    app.run()
# app/__init__.py

from flask import (
    Flask,
    render_template,
    jsonify)

from app.db import db

from app.player import player

app = Flask(__name__)
app.config.from_object("app.config.Config")
app.register_blueprint(player, url_prefix="/player/")

db.init_app(app)

with app.app_context():
    db.create_all()
# app/player.py

from flask import (
    Blueprint,
    request,
    abort,
    jsonify)

from app import require_apikey

from app.models.player import Player

from app.db import db

player = Blueprint(__name__, __name__)

@player.route('/', methods=["GET", "POST"])
@require_apikey
def root():
    # Do some stuff
# app/require_apikey.py

from functools import wraps
from flask import (
    request,
    abort,
    current_app
)

API_KEY = current_app.config["SECRET_KEY"]

def require_apikey(view_function):
    @wraps(view_function)
    def decorated_function(*args, **kwargs):
        if request.headers.get("api-key") and request.headers.get("api-key") == API_KEY:
            return view_function(*args, **kwargs)
        else:
            abort(401, "Invalid API key")
    return decorated_function

设置您的应用程序

在你项目的 __init__ 文件中初始化你的 app 非常简单,但当你的项目规模变大时也非常有限(因为你正在为你的路线使用蓝图,我想你的项目规模已经够大了)。

在这种情况下,推荐的初始化 app 的方法是使用 App factory,它基本上是一个创建 returns app 实例的函数。

这是一个简单的工作树状结构示例(可能不是您能找到的最好的,但应该是):

# myapp/application/setup.py

from flask import Flask

from .application.extensions import db


def create_app():
    app = Flask(__name__)
    app.config.from_object("myapp.config.Config")

    # Initialize extensions
    db.init_app(app)

    with app.app_context():
        db.create_all()

        # Register Blueprints
        from myapp.player import player
        app.register_blueprint(player, url_prefix="/player/")
        return app
# myapp/application/extensions.py

from flask_sqlalchemy import SQLAlchemy

# define global extensions in a separate file so that they can be imported from
# anywhere else in the code without creating circular imports
# the proper initialization is made within the `create_app` function 
db = SQLAlchemy()
# myapp/application/app.py

from .setup import create_app

app = create_app()
# myapp/__init__.py

from .application.app import app
# run.py

from myapp import app

if __name__ == "__main__":
    app.run()

这就是您项目的层次结构。此时,您有一个 myapp/application/app.py 初始化了 app 变量,并且您可以从任何地方导入而不必担心导入循环。

调用视图函数前检查headers

根据我建议的树状结构,并考虑到您相应地更新导入,您的装饰器现在应该可以按预期工作。 但是,如果我告诉你 Flask 提供了一种方法来做你想做的事,而不需要实现装饰器呢?这是 before_request 发挥作用的地方。 这是一个您可以编写的特殊函数,它将在您的应用程序的每个请求之前在 app-context 中调用。

from myapp.application.app import app


@app.before_request
def require_apikey():
    if request.headers.get("api-key") != API_KEY:
        abort(401, "Invalid API key")

现在的问题是,将为您定义的每个端点调用此函数,也许这不是您想要的。不过不用担心,您还可以定义一个 before_request 函数来附加到特定的蓝图。

# myapp/my_blueprint.py
from myapp.tools import require_apikey

my_blueprint = Blueprint(...)
my_blueprint.before_request = require_apikey