Flask test setup with Flask-Babel - python

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

Related

How to have separate routers module in Flask?

How can I decouple this class? I would like to put the paths in another file, is it possible to move the routes in another file?
#api.route('/home', '/api/email')
class Server(Resource):
def create_server(app, oauth=None):
if not oauth:
oauth = default_provider(app)
app = prepare_app(app)
#app.before_request
def load_current_user():
user = User.query.get(1)
g.user = user
#app.route('/home')
def home():
return 'home'
#app.route('/oauth/authorize', methods=['GET', 'POST'])
#oauth.authorize_handler
def authorize(*args, **kwargs):
return True
Those
#app.route('/home') # and
#app.route('/oauth/authorize', methods=['GET', 'POST'])
have to be in another file.
My attempt was this, I tried to create a file for routers:
class Router():
def __init__(self, app, oauth):
self.app = app
self.oauth = oauth
#app.route('/home')
def home():
return 'home'
I'm getting this error:
NameError: name 'app' is not defined
Well, I see a package you can use for flask projects called Flask-Via [pypi], inspired by the Django URL configuration system and designed to add similar functionality to Flask applications that have grown beyond a simple single file application. The following example is given from the docs of this project:
from flask import Flask
from flask.ext.via import Via
from flask.ext.via.routers.default import Functional
app = Flask(__name__)
def foo(bar=None):
return 'Foo View!'
routes = [
Functional('/foo', foo),
Functional('/foo/<bar>', foo, endpoint='foo2'),
]
via = Via()
via.init_app(app, route_module='flask_via.examples.basic')
if __name__ == "__main__":
app.run(debug=True)
I think this is exactly what you want :) and you can move it to another python module for example called routers.py.

Generate URLs for Flask test client with url_for function

I'm trying to write unit tests for a Flask app using pytest. I have an app factory:
def create_app():
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
import os
app.secret_key = os.urandom(24)
from models import db
db.init_app(app)
return app
And a test class:
class TestViews(object):
#classmethod
def setup_class(cls):
cls.app = create_app()
cls.app.testing = True
cls.client = cls.app.test_client()
#classmethod
def teardown_class(cls):
cls.app_context.pop()
def test_create_user(self):
"""
Tests the creation of a new user.
"""
view = TestViews.client.get(url_for('create_users')).status_code == 200
but when I run my tests I get the following error:
RuntimeError: Attempted to generate a URL without the application context being pushed. This has to be executed when application context is available.
Googling this tells me (I think) that using the test client should create an automatic application context. What am I missing?
Making requests with the test client does indeed push an app context (indirectly). However, you're confusing the fact that url_for is visually inside the test request call with the idea that it is actually called inside. The url_for call is evaluated first, the result is passed to client.get.
url_for is typically for generating URLs within the app, unit tests are external. Typically, you just write exactly the URL you're trying to test in the request instead of generating it.
self.client.get('/users/create')
If you really want to use url_for here, you must do it in an app context. Note that when you're in an app context but not a request context, you must set the SERVER_NAME config and also pass _external=False. But again, you should probably just write out the URL you're trying to test.
app.config['SERVER_NAME'] = 'localhost'
with self.app.app_context():
url = url_for(..., _external=False)
self.client.get(url, ...)
You can call url_for() in test request context that created with app.test_request_context() method. There are three methods to achieve this.
With setup and teardown
Since you have created the setup and teardown method, just like what I normally do with unittest, you can just push a test request context in setup method then pop it in teardown method:
class TestViews(object):
#classmethod
def setup_class(cls):
cls.app = create_app()
cls.app.testing = True
cls.client = cls.app.test_client()
cls.context = cls.app.test_request_context() # create the context object
cls.context.push() # push the context
#classmethod
def teardown_class(cls):
cls.context.pop() # pop the context
def test_create_user(self):
"""
Tests the creation of a new user.
"""
view = TestViews.client.get(url_for('create_users')).status_code == 200
With pytest-flask
Besides, you can also just use pytest-flask. With pytest-flask, you can access to context bound objects (url_for, request, session) without context managers:
def test_app(client):
assert client.get(url_for('myview')).status_code == 200
With autouse fixture
If you don't want to install the plugin, you can just use the following fixtures to do similar things (stolen from the source of pytest-flask):
#pytest.fixture
def app():
app = create_app('testing')
return app
#pytest.fixture(autouse=True)
def _push_request_context(request, app):
ctx = app.test_request_context() # create context
ctx.push() # push
def teardown():
ctx.pop() # pop
request.addfinalizer(teardown) # set teardown

Leveraging the Flask application context in a custom URL converter

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.

Flask returns 404 in views

I am running unittests in a Flask app and I keep getting 404 when views.py file is not imported even though it is not used. I have such tests.py package:
import unittest
from presence_analyzer import main, utils
from presence_analyzer import views
class PresenceAnalyzerViewsTestCase(unittest.TestCase):
def setUp(self):
self.client = main.app.test_client()
def test_mainpage(self):
resp = self.client.get('/')
self.assertEqual(resp.status_code, 302)
When I delete views import the described problem occurs. Views are organized in a similar way to this:
from presence_analyzer.main import app
#app.route('/')
def mainpage():
return redirect('/static/presence_weekday.html')
And the main.py file:
import os.path
from flask import Flask
app = Flask(__name__) # pylint: disable=invalid-name
app.config.update(
DEBUG=True,
)
I guess it's something similar to what happened in this case, so I'm trying to change the application so that I don't have to make this dumb imports while testing. I've been trying to make use of the answer from above, but still can't make it work and these docs don't seem helpful. What am I doing wrong? main.py:
from flask.blueprints import Blueprint
PROJECT_NAME = 'presence_analyzer'
blue_print = Blueprint(PROJECT_NAME, __name__)
def create_app():
app_to_create = Flask(__name__) # pylint: disable=invalid-name
app_to_create.register_blueprint(blue_print)
return app_to_create
app = create_app()
views.py:
from presence_analyzer.main import app, blue_print
#blue_print.route('/')
def mainpage():
return redirect('/static/presence_weekday.html')
And tests.py has remained unchanged.
You must import views, or the route will not be registered. No, you are not executing the views directly, but importing executes code all module-level code. Executing code calls route. route registers the view function. You cannot get around needing to import a module in order to use the module.

404 Response when running FlaskClient test method

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()

Categories

Resources