Browse Source

Use celery to send emails asynchronously

sh4nks 9 years ago
parent
commit
4206653eef
8 changed files with 92 additions and 29 deletions
  1. 18 0
      celery_worker.py
  2. 25 9
      flaskbb/app.py
  3. 5 1
      flaskbb/configs/default.py
  4. 5 0
      flaskbb/configs/testing.py
  5. 7 2
      flaskbb/email.py
  6. 4 1
      flaskbb/extensions.py
  7. 5 0
      requirements.txt
  8. 23 16
      setup.py

+ 18 - 0
celery_worker.py

@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+"""
+    flaskbb.celery_worker
+    ~~~~~~~~~~~~~~~~~~~~~
+
+    Prepares the celery app for the celery worker.
+    To start celery, enter this in the console::
+
+        celery -A celery_worker.celery --loglevel=info worker
+
+    :copyright: (c) 2016 by the FlaskBB Team.
+    :license: BSD, see LICENSE for more details.
+"""
+from flaskbb.configs.development import DevelopmentConfig as Config
+from flaskbb.app import create_app
+from flaskbb.extensions import celery
+
+app = create_app(Config)

+ 25 - 9
flaskbb/app.py

@@ -16,27 +16,23 @@ from functools import partial
 
 
 from sqlalchemy import event
 from sqlalchemy import event
 from sqlalchemy.engine import Engine
 from sqlalchemy.engine import Engine
-
 from flask import Flask, request
 from flask import Flask, request
 from flask_login import current_user
 from flask_login import current_user
 from flask_whooshalchemy import whoosh_index
 from flask_whooshalchemy import whoosh_index
 
 
-# Import the user blueprint
+# views
 from flaskbb.user.views import user
 from flaskbb.user.views import user
-from flaskbb.user.models import User, Guest
-# Import the (private) message blueprint
 from flaskbb.message.views import message
 from flaskbb.message.views import message
-# Import the auth blueprint
 from flaskbb.auth.views import auth
 from flaskbb.auth.views import auth
-# Import the admin blueprint
 from flaskbb.management.views import management
 from flaskbb.management.views import management
-# Import the forum blueprint
 from flaskbb.forum.views import forum
 from flaskbb.forum.views import forum
+# models
+from flaskbb.user.models import User, Guest
 from flaskbb.forum.models import Post, Topic, Category, Forum
 from flaskbb.forum.models import Post, Topic, Category, Forum
 # extensions
 # extensions
 from flaskbb.extensions import (db, login_manager, mail, cache, redis_store,
 from flaskbb.extensions import (db, login_manager, mail, cache, redis_store,
                                 debugtoolbar, migrate, themes, plugin_manager,
                                 debugtoolbar, migrate, themes, plugin_manager,
-                                babel, csrf, allows, limiter)
+                                babel, csrf, allows, limiter, celery)
 # various helpers
 # various helpers
 from flaskbb.utils.helpers import (format_date, time_since, crop_title,
 from flaskbb.utils.helpers import (format_date, time_since, crop_title,
                                    is_online, render_markup, mark_online,
                                    is_online, render_markup, mark_online,
@@ -54,7 +50,10 @@ from flaskbb.utils.settings import flaskbb_config
 
 
 
 
 def create_app(config=None):
 def create_app(config=None):
-    """Creates the app."""
+    """Creates the app.
+
+    :param config: The configuration object.
+    """
 
 
     # Initialize the app
     # Initialize the app
     app = Flask("flaskbb")
     app = Flask("flaskbb")
@@ -66,6 +65,7 @@ def create_app(config=None):
     # try to update the config via the environment variable
     # try to update the config via the environment variable
     app.config.from_envvar("FLASKBB_SETTINGS", silent=True)
     app.config.from_envvar("FLASKBB_SETTINGS", silent=True)
 
 
+    configure_celery_app(app, celery)
     configure_blueprints(app)
     configure_blueprints(app)
     configure_extensions(app)
     configure_extensions(app)
     configure_template_filters(app)
     configure_template_filters(app)
@@ -77,6 +77,22 @@ def create_app(config=None):
     return app
     return app
 
 
 
 
+def configure_celery_app(app, celery):
+    """Configures the celery app."""
+    app.config.update({'BROKER_URL': app.config["CELERY_BROKER_URL"]})
+    celery.conf.update(app.config)
+
+    TaskBase = celery.Task
+
+    class ContextTask(TaskBase):
+        abstract = True
+
+        def __call__(self, *args, **kwargs):
+            with app.app_context():
+                return TaskBase.__call__(self, *args, **kwargs)
+    celery.Task = ContextTask
+
+
 def configure_blueprints(app):
 def configure_blueprints(app):
     app.register_blueprint(forum, url_prefix=app.config["FORUM_URL_PREFIX"])
     app.register_blueprint(forum, url_prefix=app.config["FORUM_URL_PREFIX"])
     app.register_blueprint(user, url_prefix=app.config["USER_URL_PREFIX"])
     app.register_blueprint(user, url_prefix=app.config["USER_URL_PREFIX"])

+ 5 - 1
flaskbb/configs/default.py

@@ -110,9 +110,13 @@ class DefaultConfig(object):
 
 
     # Flask-Redis
     # Flask-Redis
     REDIS_ENABLED = False
     REDIS_ENABLED = False
-    REDIS_URL = "redis://:password@localhost:6379"
+    REDIS_URL = "redis://localhost:6379"  # or with a password: "redis://:password@localhost:6379"
     REDIS_DATABASE = 0
     REDIS_DATABASE = 0
 
 
+    # Celery
+    CELERY_BROKER_URL = 'redis://localhost:6379'
+    CELERY_RESULT_BACKEND = 'redis://localhost:6379'
+
     # URL Prefixes
     # URL Prefixes
     FORUM_URL_PREFIX = ""
     FORUM_URL_PREFIX = ""
     USER_URL_PREFIX = "/user"
     USER_URL_PREFIX = "/user"

+ 5 - 0
flaskbb/configs/testing.py

@@ -60,5 +60,10 @@ class TestingConfig(DefaultConfig):
     MAIL_PASSWORD = "your_password"
     MAIL_PASSWORD = "your_password"
     MAIL_DEFAULT_SENDER = ("Your Name", "your_username@gmail.com")
     MAIL_DEFAULT_SENDER = ("Your Name", "your_username@gmail.com")
 
 
+    CELERY_ALWAYS_EAGER = True
+    CELERY_RESULT_BACKEND = "cache"
+    CELERY_CACHE_BACKEND = "memory"
+    CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
+
     # The user who should recieve the error logs
     # The user who should recieve the error logs
     ADMINS = ["your_admin_user@gmail.com"]
     ADMINS = ["your_admin_user@gmail.com"]

+ 7 - 2
flaskbb/email.py

@@ -12,7 +12,7 @@ from flask import render_template
 from flask_mail import Message
 from flask_mail import Message
 from flask_babelplus import lazy_gettext as _
 from flask_babelplus import lazy_gettext as _
 
 
-from flaskbb.extensions import mail
+from flaskbb.extensions import mail, celery
 from flaskbb.utils.tokens import make_token
 from flaskbb.utils.tokens import make_token
 
 
 
 
@@ -60,6 +60,11 @@ def send_activation_token(user):
     )
     )
 
 
 
 
+@celery.task
+def send_async_email(msg):
+    mail.send(msg)
+
+
 def send_email(subject, recipients, text_body, html_body, sender=None):
 def send_email(subject, recipients, text_body, html_body, sender=None):
     """Sends an email to the given recipients.
     """Sends an email to the given recipients.
 
 
@@ -74,4 +79,4 @@ def send_email(subject, recipients, text_body, html_body, sender=None):
     msg = Message(subject, recipients=recipients, sender=sender)
     msg = Message(subject, recipients=recipients, sender=sender)
     msg.body = text_body
     msg.body = text_body
     msg.html = html_body
     msg.html = html_body
-    mail.send(msg)
+    send_async_email.delay(msg)

+ 4 - 1
flaskbb/extensions.py

@@ -8,6 +8,7 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
+from celery import Celery
 from flask_allows import Allows
 from flask_allows import Allows
 from flask_sqlalchemy import SQLAlchemy
 from flask_sqlalchemy import SQLAlchemy
 from flask_login import LoginManager
 from flask_login import LoginManager
@@ -22,7 +23,6 @@ from flask_babelplus import Babel
 from flask_wtf.csrf import CsrfProtect
 from flask_wtf.csrf import CsrfProtect
 from flask_limiter import Limiter
 from flask_limiter import Limiter
 from flask_limiter.util import get_remote_address
 from flask_limiter.util import get_remote_address
-
 from flaskbb.exceptions import AuthorizationRequired
 from flaskbb.exceptions import AuthorizationRequired
 
 
 
 
@@ -64,3 +64,6 @@ csrf = CsrfProtect()
 
 
 # Rate Limiting
 # Rate Limiting
 limiter = Limiter(auto_check=False, key_func=get_remote_address)
 limiter = Limiter(auto_check=False, key_func=get_remote_address)
+
+# Celery
+celery = Celery("flaskbb")

+ 5 - 0
requirements.txt

@@ -1,6 +1,10 @@
 alembic==0.8.6
 alembic==0.8.6
+amqp==1.4.9
+anyjson==0.3.3
 Babel==2.3.4
 Babel==2.3.4
+billiard==3.3.0.23
 blinker==1.4
 blinker==1.4
+celery==3.1.23
 click==6.6
 click==6.6
 cov-core==1.15.0
 cov-core==1.15.0
 coverage==4.1
 coverage==4.1
@@ -21,6 +25,7 @@ Flask-Themes2==0.1.4
 Flask-WTF==0.12
 Flask-WTF==0.12
 itsdangerous==0.24
 itsdangerous==0.24
 Jinja2==2.8
 Jinja2==2.8
+kombu==3.0.35
 limits==1.1.1
 limits==1.1.1
 Mako==1.0.4
 Mako==1.0.4
 MarkupSafe==0.23
 MarkupSafe==0.23

+ 23 - 16
setup.py

@@ -60,9 +60,18 @@ setup(
     zip_safe=False,
     zip_safe=False,
     platforms='any',
     platforms='any',
     install_requires=[
     install_requires=[
+        'alembic',
+        'amqp',
+        'anyjson',
         'Babel',
         'Babel',
+        'billiard',
+        'blinker',
+        'celery',
+        'click',
+        'cov-core',
+        'coverage',
         'Flask',
         'Flask',
-        'Flask-Allows',
+        'flask-allows',
         'Flask-BabelPlus',
         'Flask-BabelPlus',
         'Flask-Cache',
         'Flask-Cache',
         'Flask-DebugToolbar',
         'Flask-DebugToolbar',
@@ -72,33 +81,31 @@ setup(
         'Flask-Migrate',
         'Flask-Migrate',
         'Flask-Plugins',
         'Flask-Plugins',
         'Flask-Redis',
         'Flask-Redis',
-        'Flask-SQLAlchemy',
         'Flask-Script',
         'Flask-Script',
+        'Flask-SQLAlchemy',
         'Flask-Themes2',
         'Flask-Themes2',
         'Flask-WTF',
         'Flask-WTF',
-        'Flask-WhooshAlchemy',
-        'Flask-BabelEx',
+        'itsdangerous',
         'Jinja2',
         'Jinja2',
+        'kombu',
+        'limits',
         'Mako',
         'Mako',
         'MarkupSafe',
         'MarkupSafe',
-        'Pygments',
-        'SQLAlchemy',
-        'Unidecode',
-        'WTForms',
-        'Werkzeug',
-        'Whoosh',
-        'alembic',
-        'blinker',
-        'cov-core',
-        'coverage',
-        'itsdangerous',
         'mistune',
         'mistune',
+        'Pygments',
+        'python-editor',
         'pytz',
         'pytz',
         'redis',
         'redis',
         'requests',
         'requests',
         'simplejson',
         'simplejson',
+        'six',
         'speaklater',
         'speaklater',
-        'sqlalchemy-utils'
+        'SQLAlchemy',
+        'SQLAlchemy-Utils',
+        'Unidecode',
+        'Werkzeug',
+        'Whoosh',
+        'WTForms'
     ],
     ],
     test_suite='tests',
     test_suite='tests',
     tests_require=[
     tests_require=[