I'm baffled by this. I'm using an application factory in a Flask application and under the test configuration my routes always return 404s.
However when I use Flask-Script and load the app from the interpreter everything works as expected, the response comes back as 200.
Navigating to the URL with the browser works fine
app/__init__.py
def create_app():
app = Flask(__name__)
return app
sever1.py
from flask import Flask
from flask_script import Manager
from app import create_app
app = create_app()
app_context = app.app_context()
app_context.push()
manager = Manager(app)
#app.route('/')
def index():
return '<h1>Hello World!</h1>'
#app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
#manager.command
def test():
"""Run the unit tests"""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
if __name__ == '__main__':
manager.run()
tests/test.py
#imports committed
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
self.client = self.app.test_client()
def test_app_exists(self):
response = self.client.get('/', follow_redirects=True)
print(response) #404 :(
self.assertTrue("Hello World!" in response.get_data()) #this is just an example of how it fails
You're not using the factory pattern correctly. You should use blueprints to collect routes and register them with the app in the factory. (Or use app.add_url_rule in the factory.) Nothing outside the factory should affect the app.
Right now you create an instance of the app and then use that instance to register routes. Then you create a different instance in your tests, which doesn't have the routes registered. Since that instance doesn't have any registered routes, it returns 404 for requests to those urls.
Instead, register your routes with a blueprint, then register the blueprint with the app in the factory. Use the factory to create an app during tests. Pass the factory to the Flask-Script manager. You should not need to push the app context manually.
from flask import Flask, Blueprint
from flask_script import Manager
from unittest import TestCase
bp = Blueprint('myapp', __name__)
#bp.route('/')
def index():
return 'Hello, World!'
def create_app(config='dev'):
app = Flask(__name__)
# config goes here
app.register_blueprint(bp)
return app
class SomeTest(TestCase):
def setUp(self):
self.app = create_app(config='test')
self.client = self.app.test_client()
def test_index(self):
rv = self.client.get('/')
self.assertEqual(rv.data, b'Hello, World!')
manager = Manager(create_app)
manager.add_option('-c', '--config', dest='config', required=False)
if __name__ == '__main__':
manager.run()
Related
I have a flask API that runs a set function in a thread. My goal is to unit test using pytest-mock.
flask app.py:
from flask import Flask, request, jsonify
from threading import Thread, Lock
def set():
while True:
# do some work
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__)
update_thread = Thread(target=set, daemon=True)
update_thread.start()
#app.route('/api', methods=['GET'])
def get_api(id):
...
app = create_app()
Unit app_test.py:
import pytest
from app import create_app
#pytest.fixture()
def app_test(mocker):
mocker.patch('app.set', return_value=True)
app = create_app()
app.config.update({
"testing": True,
})
# other setup can go here
yield app
# clean up / reset resources here
#pytest.fixture()
def client(app_test):
return app_test.test_client()
def test_set(client):
host = f'http://localhost:5000/api'
response = client.get(host)
assert ....
The problem is that function set is not mocked. It's called like the mock doesn't work.
I'm experiencing a similar problem to Passing application context to custom converter using the Application Factory pattern where I'm using a custom URL converter for converting a Neo4j graph database ID into a node object, i.e.,
import atexit
from flask import Flask
from neo4j.v1 import GraphDatabase
from werkzeug.routing import BaseConverter
class NodeConverter(BaseConverter):
def to_python(self, value):
with driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
app = Flask(__name__)
app.url_map.converters['node'] = NodeConverter
driver = GraphDatabase.driver('bolt://localhost')
atexit.register(lambda driver=driver: driver.close())
#app.route('/<node:node>')
def test(node):
print node
if __name__ == '__main__':
app.run()
Though this approach leverages a single database connection there are a couple of major drawbacks: i) the database connection cannot be configured via the Flask config, and ii) if the database fails so does the Flask app.
To counter this I created a local extension per http://flask.pocoo.org/docs/0.12/extensiondev/, i.e.,
from flask import _app_ctx_stack, Flask
from neo4j.v1 import GraphDatabase
from werkzeug.routing import BaseConverter
class MyGraphDatabase(object):
def __init__(self, app=None):
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
#app.teardown_appcontext
def teardown(exception):
ctx = _app_ctx_stack.top
if hasattr(ctx, 'driver'):
ctx.driver.close()
#property
def driver(self):
ctx = _app_ctx_stack.top
if ctx is not None and not hasattr(ctx, 'driver'):
ctx.driver = GraphDatabase.driver(app.config['NEO4J_URI'])
return ctx.driver
class NodeConverter(BaseConverter):
def to_python(self, value):
with app.app_context():
with db.driver.session() as session:
cursor = session.run('MATCH (n {id: $id}) RETURN n', id=value)
return cursor.single().values()[0]
db = MyGraphDatabase()
app = Flask(__name__)
app.config.from_pyfile('app.cfg')
app.url_map.converters['node'] = NodeConverter
db.init_app(app)
#app.route('/<node:node>')
def test(node):
print node
if __name__ == '__main__':
app.run()
This issue is given that the URL converter is outside of the app context I needed to include the following block,
with app.app_context():
...
where a temporary app context is created during URL parsing and then discarded immediately which seems suboptimal from a performance perspective. Is this the correct approach for this?
The other problem with this configuration is that one needs to be cognizant of potential circular references when the converter and application reside in different files since the NodeConverter requires the app and the app registers the NodeConverter.
I'd like to setUp with unittest module.
My Flask App is created using factory (create_app) uses Flask-Babel for i18n/
def create_app(config=None, app_name=None, blueprints=None):
# Create Flask App instance
app_name = app_name or __name__
app = Flask(app_name)
app.config.from_pyfile(config)
configure_hook(app)
configure_blueprints(app, blueprints)
configure_extensions(app)
configure_jinja_filters(app)
configure_logging(app)
configure_error_handlers(app)
configure_cli(app)
return app
create_app function calls configure_extensions(app) which is as follows:
def configure_extensions(app):
"""Initialize Flask Extensions."""
db.init_app(app)
babel.init_app(app)
csrf.init_app(app)
#babel.localeselector
def get_locale():
# If logged in, load user locale settings.
user = getattr(g, 'user', None)
if user is not None:
return user.locale
# Otherwise, choose the language from user browser.
return request.accept_languages.best_match(
app.config['BABEL_LANGUAGES'].keys())
#babel.timezoneselector
def get_timezone():
user = getattr(g, 'user', None)
if user is not None:
return user.timezone
It works fine when I run app, but I can't create a unittest properly because it asserts error like this:
File "C:\projects\rabiang\venv\lib\site-packages\flask_babel\__init__.py", line 127, in localeselector
'a localeselector function is already registered'
AssertionError: a localeselector function is already registered
Due to the message "a localeselector function is already registered", I thought that fact that my setUp method of unittest was invoked when each test method is called makes problem. Thus, I changed #classmethod setUpClass like this:
# -*- coding: utf-8 -*-
import unittest
from app import create_app, db
from app.blueprints.auth import auth
from app.blueprints.forum import forum
from app.blueprints.main import main
from app.blueprints.page import page
class BasicsTestCase(unittest.TestCase):
#classmethod
def setUpClass(cls):
blueprints = [main, page, auth, forum]
app = create_app(config='../test.cfg', blueprints=blueprints)
cls.app = app.test_client()
db.create_all()
#classmethod
def tearDownClass(cls):
db.session.remove()
db.drop_all()
def test_app_exists(self):
self.assertFalse(BasicsTestCase.app is None)
if __name__ == '__main__':
unittest.main()
However, #babel.localeselector and #babel.timezoneselector decorator doesn't work.
I fixed it by setting the app only once with the function setUpClass from unittest.
See also the answer Run setUp only once
I am building a simple extension for flask and have a problem with the url_for function not being able to build the urls within the extension.
Can somebody help me figure out what I am missing here?
I simplified the code to demonstrate the issue (all of the url_for calls raise a werkzeug BuildError exception):
import flask
import flask.views
import logging
logging.basicConfig(level=logging.DEBUG)
class MyFlaskExt(object):
def __init__(self, app=None):
if app is not None:
self.init_app(app)
def init_app(self, app):
self.blueprint = flask.Blueprint('myext', __name__, static_folder='static', template_folder='templates')
self.blueprint.add_url_rule('/', view_func=RootView.as_view('root'), endpoint='root')
self.blueprint.add_url_rule('/var/<somevar>', view_func=VarView.as_view('var'), endpoint='var')
self.blueprint.add_url_rule('/novar', view_func=NoVarView.as_view('novar'), endpoint='novar')
app.register_blueprint(self.blueprint)
class RootView(flask.views.View):
def dispatch_request(self):
logging.debug(flask.url_for('novar'))
logging.debug(flask.url_for('novar', _external=True))
return flask.redirect(flask.url_for('var', somevar='test'))
class VarView(flask.views.View):
def dispatch_request(self, somevar):
return "SUCCESS! ({})".format(somevar)
class NoVarView(flask.views.View):
def dispatch_request(self):
return "SUCCESS!"
if __name__ == '__main__':
app = flask.Flask(__name__)
app.debug = True
my_ext = MyFlaskExt()
my_ext.init_app(app)
logging.debug(app.url_map)
app.run()
Blueprint endpoints are registered under the name of the Blueprint. If I remember correctly you will either need to prepend your url_for calls with "." (if they will all be operating under the same blueprint) or use the full name:
def dispatch_request(self):
logging.debug(flask.url_for('.novar'))
logging.debug(flask.url_for('.novar', _external=True))
return flask.redirect(flask.url_for('.var', somevar='test'))
I am trying to add a function in the Jinja environment from a blueprint (a function that I will use into a template).
Main.py
app = Flask(__name__)
app.register_blueprint(heysyni)
MyBluePrint.py
heysyni = Blueprint('heysyni', __name__)
#heysyni.route('/heysyni'):
return render_template('heysyni.html', heysini=res_heysini)
Now in MyBluePrint.py, I would like to add something like :
def role_function():
return 'admin'
app.jinja_env.globals.update(role_function=role_function)
I will then be able to use this function in my template. I cannot figure out how I can access the application since
app = current_app._get_current_object()
returns the error:
working outside of request context
How can I implement such a pattern ?
The message error was actually pretty clear :
working outside of request context
In my blueprint, I was trying to get my application outside the 'request' function :
heysyni = Blueprint('heysyni', __name__)
app = current_app._get_current_object()
print(app)
#heysyni.route('/heysyni/')
def aheysyni():
return 'hello'
I simply had to move the current_app statement into the function. Finally it works that way :
Main.py
from flask import Flask
from Ablueprint import heysyni
app = Flask(__name__)
app.register_blueprint(heysyni)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run(debug=True)
Ablueprint.py
from flask import Blueprint, current_app
heysyni = Blueprint('heysyni', __name__)
#heysyni.route('/heysyni/')
def aheysyni():
# Got my app here
app = current_app._get_current_object()
return 'hello'