在 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
如果 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