I have set a decorator for functions of my routes to abort(401) if api-key is invalid (or not specified) inside headers of requests.
So I define this function in a dedicated file of my main module (named app) and it is imported inside all of my blueprints definition. But from this file, I have no access to app object to load the API key stored in config. I tried to use current_app, but I have an error telling me I cannot use it outside of the app context. I cannot import directly app object from app module either as it results with a circular importation.
If I declare the decorator inside __init__.py
file, I will be faced with the same issue: I will not be able to import require_apikey function from app module as blueprint modules are already imported in app module for blueprint registration.
I assume there are some issues with my design. Could you point my flaws and help me fixing it ?
My project directory looks like this:
project/
- run.py
- app/
- - __init__.py
- - player.py
- - require_apikey.py
And here is the content of these files:
# 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
Initializing your app
in the __init__
file of your project is very straightforward, but also very limited when the size of your project becomes bigger (and since you are using blueprints for your routes, I guess that the size of your project is big enough already).
The recommanded way of initializing your app
in this case is to use an App factory, which is basically a function that creates and returns the app
instance.
Here's a quick example of working arboresence (probably not the best one you will find, but that should do):
# 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()
That's it for the hierarchy of your project. At this point, you have a myapp/application/app.py
that initialized the app
variable, and that you can import from anywhere you want without worrying about the import loops.
With the arboresence I suggested, and considering you update your imports accordingly, your decorator should now be working as expected.
But what if I tell you Flask offers a way to do what you want, without implementing a decorator? This is where the before_request
kicks in.
This is a special function you can write that will be called within the app-context before every single request on your application.
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")
Now the problem here is that this function will be called for every single endpoint you have defined, and maybe that's not what you want. But do not worry, you can also define a before_request
function to attach to a specific blueprint.
# myapp/my_blueprint.py
from myapp.tools import require_apikey
my_blueprint = Blueprint(...)
my_blueprint.before_request = require_apikey
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With