I want to register a CLI command that's defined in a separate file from the Flask app factory. This command needs access to app.config. However, accessing current_app.config from the command raises RuntimeError: Working outside of application context.
app/__init__.py
from flask import Flask
from app.commands import my_command
def create_app():
app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile("config.py")
app.add_command(my_command)
return app
instance/config.py
TEST_VARIABLE = "TESTVALUE"
app/commands.py
from flask import current_app
#click.command()
def my_command():
click.echo(current_app.config["TEST_VARIABLE"])
I expect running flask my_command to output TESTVALUE. However, I get the following error:
RuntimeError: Working outside of application context.
This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context(). See the
documentation for more information.
I need to use with app.app_context(): for current_app to work, but I don't have access to app since it's defined in a factory. If I use #app.cli.command() in the factory, this would be no problem, as there I have access to the app variable, and wouldn't even need to push an app context.
def create_app():
...
#app.cli.command()
def my_command():
click.echo(current_app.config["TEST_VARIABLE"])
...
However, I want to define my commands in other files and have them use values from the app config, while this requires all the commands to be nested in the factory function.
I tried creating an app using the factory inside the command, and that worked, but I don't think doing that is a good idea just to have access to the config variables.
import click
import app
#click.command()
def my_command():
app_config = app.create_app().config
click.echo(app_config["TEST_VARIABLE"])
How can I define a command that has access to the application context when using the application factory pattern?
Flask's docs about the CLI discuss this.
#app.cli.command() automatically ensures there's an app context when the command runs. However, this is less convenient to use when using an app factory because you don't have access to the app when defining the command.
When using the app factory, you use #click.command() and app.cli.add_command() to separate defining and registering the command, but #click.command doesn't know about the app context. Use the #with_appcontext decorator to ensure the command runs in an app context.
#click.command()
#with_appcontext
def my_command():
config = current_app.config
click.echo(config["TEST_VARIABLE"])
def create_app():
app = Flask(__name__)
...
app.cli.add_command(my_command)
...
return app
$ flask my-command
TESTVALUE
Related
I try to implement unit tests with Pytest on a Flask app and I have a hard time to do it.
My Flask application uses configuration files on most of the functions (here some_method) to illustrate. So it seems that I should provide a context for each call to any method that I would like to test. It seems that I can achieve it with "with app.app_context():" on each call.
I read the official testing documentation but they talk about creating a client. As I would like to do unit tests, I need to call sub functions which are not top level.
Is there a way to always provide a context without pushing the context manually on each call?
Please find below my current implementation:
main.py
from flask import current_app
def main(request):
current_app.config.from_envvar('APPLICATION_SETTINGS')
print(some_method())
return 'OK'
def some_method():
# doing some stuff using the context
world = current_app.config['SECRET_KEY']
return world
test_main.py
import pytest
from flask import current_app, Flask
from main import main, some_method
#pytest.fixture
def app():
app = Flask(__name__)
# load here any potential configuration configuration
return app
def test_some_method(app):
with app.app_context():
# calling and doing some assertion
some_method()
PS: I do not have app = Flask(name) in my main file because I am running on the Functions Framework
pytest-flask seems to configure the context on any call.
conftest.py
import pytest
from flask import Flask
#pytest.fixture
def app():
app = Flask(__name__)
return app
test_main.py
import pytest
from flask import current_app, Flask
from main import main, some_method
def test_some_method(app):
#with app.app_context():
# calling and doing some assertion
some_method()
works.
I'm trying to define some global constants for my app, and have found that this can be done with a function that's decorated as #app.context_processor.
However, the issue is that I don't have an app variable. My application uses an application factory, and I'd like to keep it that way. Is there a different way to register a function as the context_processor for my app?
One option I have seen is to apply the decorator to each Blueprint instead of applying it to the app. That's something I would like to avoid though, since it would lead to a lot of duplicate code.
The issue is that there is no app object in case of factories. You have a create_app function where the app gets created.
So to install the context processors you can use create_app itself
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
from yourapplication.context_processor import myprocessor
app.context_processor(myprocessor)
from yourapplication.views.frontend import frontend
app.register_blueprint(frontend)
return app
You could also have the function in the same app.py file (wherever the create_app() function is written). In such a case, you could simply register the context_processor without importing it.
Another approach is to do it in a blueprint as shown in below
Flask context processors functions
from flask import Blueprint
thingy = Blueprint("thingy", __name__, template_folder='templates')
#thingy.route("/")
def index():
return render_template("thingy_test.html")
#thingy.context_processor
def utility_processor():
def format_price(amount, currency=u'$'):
return u'{1}{0:.2f}'.format(amount, currency)
return dict(format_price=format_price)
spinngod.py - flask app starter code
from app import create_app
import sys
run_profile = str(sys.argv[1]) if len(sys.argv) >= 2 else 'development'
app = create_app(run_profile)
print("App Root Path:" + app.root_path)
if __name__ == '__main__':
print sys.path
app.run(debug=True, host='0.0.0.0')
app/init.py - creates flask app
def create_app(profile_name):
print "currently active profile:" + profile_name
app = Flask(__name__)
############# configurations ####################
app.config.from_object(config[profile_name])
configure_app(app)
configure_app_logger(app)
#################### blueprint registration and rest_plus namespace additions ###############
from api_1_0 import api as api_1_0_blueprint
from api_1_0.restplus import api_restplus
# ************************************************** #
api_restplus.init_app(api_1_0_blueprint)
api_restplus.add_namespace(application_namespace)
api_restplus.add_namespace(pipeline_template_namespace)
api_restplus.add_namespace(loadbalancer_namespace)
api_restplus.add_namespace(servergroup_namespace)
api_restplus.add_namespace(task_namespace)
# ************************************************** #
app.register_blueprint(api_1_0_blueprint)
##############################################################
return app
I want to access flask config variables defined in config.py in some other files which are outside application context. The app configuration depends on which profile it is started with (dev,stage or production) which is being passed from command line as an arg.
The only way that I can think of accessing config variables outside app context is to set profile (dev,stage or prod) as an environment variable and
then import directly from config file.
The second way that I tried was to move creation of flask app in app/init.py outside method.
This is how I am trying to access config variables in another class.
import requests
class Client(object):
def __init__(self):
from app import app
print "fjaijflkajsf" + app.config['SPINNAKER_BASE_URL']
pass
Is there a way better of doing this in flask ?
From the docs:
Rather than passing the application around to each function, the current_app and g proxies are accessed instead.
The Flask application object has attributes, such as config, that are useful to access within views and CLI commands. However, importing the app instance within the modules in your project is prone to circular import issues.
Flask solves this issue with the application context. Rather than referring to an app directly, you use the the current_app proxy, which points to the application handling the current activity.
You import current_app like this:
from flask import current_app
and then access the config or other attributes like this:
config = current_app.config
Example:
src/application.py (where config is set in the context)
create_app():
app = Flask('app')
app.config.from_object(some_class)
return app
src/module/another_module.py
from flask import current_app
def function_that_requires_config():
config = current_app.config
Alternative:
src/application.py (where config is set in the context)
APP = create_app(os.environ.get('FLASK_ENV'))
src/module/another_module.py
from src.application import APP
def function_that_requires_config():
config_value = APP.config.get(config_key, default_value)
Not sure if it is good to put it here as it may not respond to the question directly, but here is the cleanest way i've figured to use config values outside of requests, without having to pass config as a param.
The solution is actually pretty simple, juste consider the part of your code as a flask_extension.
my exemple will be the use of external api, with ROOT_URL in the config file, and i don't want to make api call from within my routes, so the api is in its own module.
in my create_app fuction:
from flask import Flask
from .api import api
from .configmodule import Config
from .model import db
def create_app(environment):
app = Flask(__name__)
app.config.from_object(Config.get_config(environment))
db.init_app(app)
api.init_app(app) # here i use api.init_app the same way i do for sqlalchemy
and in api/init.py
class Api:
def init_app(self, app):
self.config = app.config
api = Api()
and in any files in my api modude i can now write
from . import api
def foo():
print(api.config.get("API_ROOT_URL"))
this can even be improved if you feel the need to access some other global app vars from your module.
Ok, i'm a bit new to Flask, so I'm not sure if i should be using Flask Script or Flask's built-in CLI took but I'm going with the latter for now.
However, I'm facing the problem that others have faced when it comes to bootstrapping the app context before running a command and having current_app context and whatnot.
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.cli.add_command(seed_db)
return app
#click.command
#with_appcontext
def seed_db():
# Register SqlAlchemy
from .models import db, User, Role
db.init_app(app)
# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
# Some initial models (if they don't exist)
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='end-user', description='End user')
return app
So in this case I have a factory method initializing the app. And then I have my custom command that I want to run to seed the database.
I've already been able to run flask db migrate to set up my initial tables, but if I try to run flask seed_db, I get something like this:
File "/usr/local/lib/python3.6/site-packages/click/core.py", line
1150, in add_command
name = name or cmd.name AttributeError: 'function' object has no attribute 'name'
Can someone enlighten me on how to properly bootstrap the app with commands outside the create_app method?
There is an error when you are using #click.command
I think the error message gives you a clear clue that you forget to give a name to your command.
You need to init a name to the command like this #click.command("seed_db") or just default function name like this #click.command()
#click.command("seed_db")
#with_appcontext
def seed_db():
# Register SqlAlchemy
from .models import db, User, Role
db.init_app(app)
Hope it helps.
These two lines are in the wrong place:
app.cli.add_command(seed_db)
return app
Your seed_db() function is adding itself to the app, which is a bit confusing - then returning the app. I think you meant for these two actions to occur your app factory method:
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.cli.add_command(seed_db)
return app
Your app factory is the function with the responsibility of setting up the app object and returning it, no other function should be trying to do this.
I am creating a flask instance using:
application/init.py:
def create_app(config_name):
app = Flask(__name__)
mongo = PyMongo(app)
...
return app
manage.py
app = create_app(Config.FLASK_CONFIG)
manager = Manager(app)
with application started using:
python manage.py runserver
In views I would like to access the mongo instance created in app. Any idea how I can access the PyMongo wrapper around app?
In __init__.py, initialize app and mongo to None.
app = None
mongo = None
Create a separate setup.py module in application and move create_app factory function there. The reason for this approach is so as to bind Flask application instance and mongo instance to your application package.
Also, refactor the app factory separating the database factory from it since we need to keep the database instance around.
import application
def create_app(config_name):
app = Flask(__name__)
....
return app
def create_mongo(app):
return PyMongo(app)
app = create_app(Config.FLASK_CONFIG)
# bind to application package
application.app = app
application.mongo = create_mongo(app)
In manage.py, rewrite this to use the app instance created in setup.py module.
from application.setup import app
manager = Manager(app)
Mongo instance can be imported directly from the application package because the instance was bound earlier to the package in setup.
from application import mongo
NOTE:
You may care to know that initializing a PyMongo instance registers pymongo as an extension in your flask application.
The connection and database are bound to a config_prefix key whose default value is 'MONGO' in the registered extension. They can both be retrieved like so:
cx, db = app.extensions['pymongo']['MONGO']
Reference
Registering PyMongo extension in Flask application