Skip to content
This repository has been archived by the owner on Jul 22, 2019. It is now read-only.

Commit

Permalink
celery: Flask integration
Browse files Browse the repository at this point in the history
* Adds support for creating Celery applications with Flask-AppFactory.

* Increases test coverage to 100%.

Signed-off-by: Lars Holm Nielsen <lars.holm.nielsen@cern.ch>
  • Loading branch information
lnielsen committed Aug 17, 2015
1 parent f903b3d commit 183a4c0
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 7 deletions.
6 changes: 6 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ CLI factory
:members:
:undoc-members:

Celery factory
--------------
.. automodule:: flask_appfactory.celery
:members:
:undoc-members:

Extensions
----------

Expand Down
4 changes: 3 additions & 1 deletion docs/tut_app.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ translations etc. is loaded according to this order.
The ``EXTENSIONS`` defines the list of extensions that Flask-AppFactory
should load. You'll see that in addition to ``myexts.sqlalchemy`` we also
load ``flask_appfactory.ext.jinja2``. This extension is needed in order to make
the template loader aware of the order of packages.
the template loader aware of the order of packages. We also load
``flask_celeryext:FlaskCeleryExt``, which will be explained in detail in
Step 4 together with ``BROKER_URL``.


Application factory
Expand Down
89 changes: 89 additions & 0 deletions docs/tut_celery.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
Step 4: Celery based background tasks
=====================================

Flask-AppFactory includes optional support for Celery integration via the
`Flask-CeleryExt <http://flask-celeryext.readthedocs.org>`_ extension. If you
wish to use it, be sure to install Flask-AppFactory like this::

pip install Flask-AppFactory[celery]

To enable Celery support we add one file to our application package, and one
file to our reusable package::

myapp/celery.py
mymodule/tasks.py


Configuration
-------------
In our configuration we first have to add the Flask-CeleryExt extension
(line 5) as well as define the Celery broker via the ``BROKER_URL`` variable
(line 13). Note in this example we use a local Redis installation. For other
options see the Celery documentation.

.. literalinclude:: ../examples/myapp/config.py
:language: python
:linenos:
:emphasize-lines: 5, 13

The Flask-CeleryExt takes care of creating a minimal Celery application with
the correct configuration so Celery knows e.g. which broker to use. In addition
the minimal Celery application doesn't load any tasks to ensure faster startup
time.

See the `Celery documentation
<http://celery.readthedocs.org/en/latest/configuration.html>`_ for all the
possible configuration variables.

Celery application
------------------
Next, we create a small Celery application which is later used to start the
Celery worker from.

.. literalinclude:: ../examples/myapp/celery.py
:language: python
:linenos:

Reusable tasks
--------------
The reusable packages can easily define Celery tasks by adding a ``tasks.py``
file like this:

.. literalinclude:: ../examples/mymodule/tasks.py
:language: python
:linenos:
:emphasize-lines: 8, 13


Notice the use of ``@shared_task`` decorator (line 8). This ensures that the
task can be reused by many different Celery applications. The Celery
application created above takes care of register the tasks.

Each task is executed within a Flask application context (notice the use of
e.g. ``current_app``). If you need to have a task executed in a request context
(e.g. if you need to ensure before first request functions have been run), you
just have to change the base class to use the ``RequestContextTask`` (line 13).

Running the worker
------------------

Next, you can start a Celery worker by simply pointing Celery to your new
Celery application in ``myapp.celery``:

.. code-block:: console
$ celery worker -A myapp.celery
Sending tasks
-------------

Executing tasks requires you to have your Flask application initialized, hence
simply start a Python shell using your management script:

.. code-block:: console
$ myapp shell
...
>>> from mymodule.tasks import appctx
>>> appctx.delay()
1 change: 1 addition & 0 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ The full source code by viewed in the ``examples`` folder in the package or on
tut_package
tut_ext
tut_app
tut_celery
6 changes: 6 additions & 0 deletions examples/myapp/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# myapp/celery.py

from flask_appfactory.celery import celeryfactory
from .app import create_app

celery = celeryfactory(create_app())
3 changes: 3 additions & 0 deletions examples/myapp/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

EXTENSIONS = [
"flask_appfactory.ext.jinja2",
"flask_celeryext:FlaskCeleryExt",
"myexts.sqlalchemy",
]

PACKAGES = [
"mymodule",
]

BROKER_URL = "redis://localhost:6379/0"
15 changes: 15 additions & 0 deletions examples/mymodule/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# mymodule/tasks.py

from celery import shared_task
from flask import current_app, request
from flask_celeryext import RequestContextTask


@shared_task
def appctx():
current_app.logger.info(current_app.config['BROKER_URL'])


@shared_task(base=RequestContextTask)
def reqctx():
current_app.logger.info(request.method)
2 changes: 1 addition & 1 deletion examples/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
install_requires=[
'Flask>=0.10',
'Flask-AppFactory',
'Flask-AppFactory[celery]',
'Flask-SQLAlchemy',
],
)
2 changes: 1 addition & 1 deletion flask_appfactory/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def base_app(app_name, instance_path=None, static_folder=None,
try:
if not os.path.exists(instance_path):
os.makedirs(instance_path)
except Exception:
except Exception: # pragma: no cover
pass

# Create the Flask application instance
Expand Down
36 changes: 36 additions & 0 deletions flask_appfactory/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
#
# This file is part of Flask-AppFactory
# Copyright (C) 2015 CERN.
#
# Flask-AppFactory is free software; you can redistribute it and/or
# modify it under the terms of the Revised BSD License; see LICENSE
# file for more details.

"""Celery application factory."""

from __future__ import absolute_import, print_function, unicode_literals

from flask_celeryext import create_celery_app
from flask_registry import ModuleAutoDiscoveryRegistry


def load_tasks(app):
"""Load Celery tasks from installed packages."""
app.extensions['registry']['tasks'] = ModuleAutoDiscoveryRegistry(
module_name='tasks', app=app)


def celeryfactory(app):
"""Create a Celery application based on Flask application.
:param app: Flask application instance.
"""
try:
# Check if celery application has already been created.
celery = app.extensions['flask-celeryext'].celery
except KeyError:
celery = create_celery_app(app)
load_tasks(app)

return celery
6 changes: 3 additions & 3 deletions flask_appfactory/ext/jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
try:
# Deprecated in Flask commit 817b72d484d353800d907b3580c899314bf7f3c6
from flask.templating import blueprint_is_module
except ImportError:
except ImportError: # pragma: no cover
def blueprint_is_module(blueprint):
"""Dummy function for Flask 1.0."""
return False
Expand All @@ -54,11 +54,11 @@ class OrderAwareDispatchingJinjaLoader(DispatchingJinjaLoader):
def _iter_loaders(self, template):
for blueprint in self.app.extensions['registry']['blueprints']:
if blueprint_is_module(blueprint):
continue
continue # pragma: no cover
loader = blueprint.jinja_loader
if loader is not None:
if IS_FLASK_1_0:
yield blueprint, loader
yield blueprint, loader # pragma: no cover
else:
yield loader, template

Expand Down
1 change: 1 addition & 0 deletions requirements.devel.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-e git+https://github.com/mitsuhiko/flask.git#egg=Flask
-e git+https://github.com/inveniosoftware/flask-registry.git#egg=Flask-Registry
-e git+https://github.com/inveniosoftware/flask-cli.git#egg=Flask-CLI
-e git+https://github.com/inveniosoftware/flask-celeryext.git#egg=Flask-CeleryExt
1 change: 1 addition & 0 deletions requirements.latest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Flask
click
Flask-Registry
Flask-CLI
Flask-CeleryExt
1 change: 1 addition & 0 deletions requirements.lowest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Flask==0.10
click==2.0
Flask-Registry==0.2.0
Flask-CLI==0.2.1
Flask-CeleryExt==0.1.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def run_tests(self):
'Flask-CLI>=0.2.1',
],
extras_require={
'docs': ['sphinx_rtd_theme'],
'celery': ['Flask-CeleryExt>=0.1.0'],
},
cmdclass={'test': PyTest},
classifiers=[
Expand Down
16 changes: 16 additions & 0 deletions tests/simplemodule/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
# This file is part of Flask-AppFactory
# Copyright (C) 2015 CERN.
#
# Flask-AppFactory is free software; you can redistribute it and/or
# modify it under the terms of the Revised BSD License; see LICENSE
# file for more details.

from celery import shared_task


@shared_task
def simpletask():
"""Simple test task."""
return "simpletask"
16 changes: 16 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
from __future__ import absolute_import, print_function, unicode_literals

import os
import shutil
import tempfile

from flask_appfactory import appfactory
from flask_appfactory.app import base_app


def test_dummy_app():
Expand Down Expand Up @@ -95,6 +99,18 @@ class conf:
assert rv.data == 'TEST'.encode('utf-8')


def test_instance_path_creation():
"""Test instance path creation."""
path = tempfile.mkdtemp()
try:
instance_path = os.path.join(path, "myinstance")
assert not os.path.exists(instance_path)
base_app('instance', instance_path=instance_path)
assert os.path.exists(instance_path)
finally:
shutil.rmtree(path)


def test_simple_app_noload():
"""."""
class conf:
Expand Down
36 changes: 36 additions & 0 deletions tests/test_celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
#
# This file is part of Flask-AppFactory
# Copyright (C) 2015 CERN.
#
# Flask-AppFactory is free software; you can redistribute it and/or
# modify it under the terms of the Revised BSD License; see LICENSE
# file for more details.

"""Test celery factory."""

from __future__ import absolute_import, print_function, unicode_literals

from flask_appfactory import appfactory
from flask_appfactory.celery import celeryfactory


def test_dummy_app():
""""Test celery app creation."""
class conf:
EXTENSIONS = ['flask_celeryext:FlaskCeleryExt']

app = appfactory("app2", conf)
celery = celeryfactory(app)
assert celery
assert celery.flask_app == app
assert app.extensions['flask-celeryext'].celery == celery


def test_dummy_app_noext():
""""Test celery app creation without extension."""
app = appfactory("app3", None)
celery = celeryfactory(app)
assert celery
assert celery.flask_app == app
assert 'flask-celeryext' not in app.extensions

0 comments on commit 183a4c0

Please sign in to comment.