从 Flask 应用程序中的外部应用程序上下文访问应用程序对象

Cou*_*cha 4 python api config key flask

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

因此,我在主模块(名为 app)的专用文件中定义了此函数,并将其导入到我的所有蓝图定义中。但从这个文件中,我无法访问应用程序对象来加载存储在 config.json 中的 API 密钥。我尝试使用 current_app,但出现错误告诉我无法在应用程序上下文之外使用它。我无法直接从应用程序模块导入应用程序对象,因为它会导致循环导入。

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

我认为我的设计存在一些问题。你能指出我的缺陷并帮助我改正吗?

我的项目目录如下所示:

project/
 - run.py
 - app/
 - - __init__.py
 - - player.py
 - - require_apikey.py
Run Code Online (Sandbox Code Playgroud)

以下是这些文件的内容:

# run.py
from app import app

if __name__ == "__main__":
    app.run()
Run Code Online (Sandbox Code Playgroud)
# 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()
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)

jul*_*enc 6

设置您的应用程序

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

在这种情况下,推荐的初始化方法app是使用App 工厂,它基本上是一个创建并返回实例的函数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
Run Code Online (Sandbox Code Playgroud)
# 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()
Run Code Online (Sandbox Code Playgroud)
# myapp/application/app.py

from .setup import create_app

app = create_app()
Run Code Online (Sandbox Code Playgroud)
# myapp/__init__.py

from .application.app import app
Run Code Online (Sandbox Code Playgroud)
# run.py

from myapp import app

if __name__ == "__main__":
    app.run()
Run Code Online (Sandbox Code Playgroud)

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

在调用视图函数之前检查标题

根据我建议的树木,并考虑到您相应地更新导入,您的装饰器现在应该按预期工作。但是,如果我告诉您 Flask 提供了一种无需实现装饰器即可完成您想要的操作的方法,该怎么办?这就是发挥作用的地方before_request。这是一个您可以编写的特殊函数,将在应用程序上的每个请求之前在应用程序上下文中调用。

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")
Run Code Online (Sandbox Code Playgroud)

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

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

my_blueprint = Blueprint(...)
my_blueprint.before_request = require_apikey
Run Code Online (Sandbox Code Playgroud)