web-dev-qa-db-ja.com

Flask create_app、SQLAlchemyおよびCelery

Flask、SQLAlchemy、Celeryの適切な設定を取得するのに本当に苦労しています。私は広範囲を検索し、さまざまなアプローチを試しましたが、実際には何も機能しないようです。アプリケーションコンテキストを逃したか、ワーカーを実行できないか、その他の問題があります。より大きなアプリケーションを構築できるように、構造は非常に一般的です。

私は使用しています:Flask 0.10.1、SQLAlchemy 1.0、Celery 3.1.13、私の現在の設定は次のとおりです:

app/__ init__.py

#Empty

app/config.py

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:

    @staticmethod
    def init_app(app):
        pass

class LocalConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = r"sqlite:///" + os.path.join(basedir, 
                                 "data-dev.sqlite")
    CELERY_BROKER_URL = 'amqp://guest:guest@localhost:5672//'


config = {
    "local": LocalConfig}

app/exstensions.py

from flask.ext.sqlalchemy import SQLAlchemy
from celery import Celery

db = SQLAlchemy()
celery = Celery()

app/factory.py

from extensions import db, celery
from flask import Flask
from flask import g
from config import config

def create_before_request(app):
    def before_request():
        g.db = db
    return before_request


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    db.init_app(app)
    celery.config_from_object(config)

    # Register the blueprints

    # Add the before request handler
    app.before_request(create_before_request(app))
    return app

app/manage.py

from factory import create_app

app = create_app("local")

from flask import render_template
from flask import request

@app.route('/test', methods=['POST'])
def task_simple():
    import tasks
    tasks.do_some_stuff.delay()
    return ""

if __name__ == "__main__":
    app.run()

app/models.py

from extensions import db

class User(db.Model):
    __tablename__ = "user"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(128), unique=True, nullable=False)

app/tasks.py

from extensions import celery
from celery.signals import task_prerun
from flask import g, current_app


@task_prerun.connect
def close_session(*args, **kwargs):
    with current_app.app_context():
       # use g.db
       print g

@celery.task()
def do_some_stuff():
    with current_app.app_context():
       # use g.db
       print g

フォルダーアプリ:

  • 開発用ウェブサーバーをpython.exe manage.pyで開始します
  • ワーカーの開始:celery.exe worker -A tasks

意味のないインポートエラーが発生します。アプリケーションの構造を変える必要がありますか?最後に、私は非常に基本的なセットアップが必要だと思います。 Flaskをファクトリパターンで使用して、Flask-SQLAlchmey拡張機能を使用し、データベースにアクセスする必要があるワーカーを用意することができます。

どんな助けでも大歓迎です。

トレースバックは、セロリワーカーの起動時に実行されます。

Traceback (most recent call last):

  File "[PATH]\scripts\celery-script.py", line 9, in <module>
    load_entry_point('celery==3.1.13', 'console_scripts', 'celery')()

  File "[PATH]\lib\site-packages\celery\__main__.py", line 30, in main
    main()

  File "[PATH]\lib\site-packages\celery\bin\celery.py", line 81, in main
    cmd.execute_from_commandline(argv)

  File "[PATH]\lib\site-packages\celery\bin\celery.py", line 769, in execute_from_commandline
    super(CeleryCommand, self).execute_from_commandline(argv)))

  File "[PATH]\lib\site-packages\celery\bin\base.py", line 305, in execute_from_commandline
    argv = self.setup_app_from_commandline(argv)

  File "[PATH]\lib\site-packages\celery\bin\base.py", line 473, in setup_app_from_commandline
    user_preload = Tuple(self.app.user_options['preload'] or ())
AttributeError: 'Flask' object has no attribute 'user_options'

[〜#〜] update [〜#〜]コメントの提案に従ってコードを変更します。ワーカーが起動しますが、http://127.0.0.1:5000/testへのgetリクエストでテストすると、次のトレースバックが表示されます。

Traceback (most recent call last):
  File "[PATH]\lib\site-packages\celery\app\trace.py", line 230, in trace_task
    args=args, kwargs=kwargs)

  File "[PATH]\lib\site-packages\celery\utils\dispatch\signal.py", line 166, in send
    response = receiver(signal=self, sender=sender, \**named)

  File "[PATH]\app\stackoverflow\tasks.py", line 7, in close_session
    with current_app.app_context():

  File "[PATH]\lib\site-packages\werkzeug\local.py", line 338, in __getattr__
    return getattr(self._get_current_object(), name)

  File "[PATH]\lib\site-packages\werkzeug\local.py", line 297, in _get_current_object
    return self.__local()

  File "[PATH]\lib\site-packages\flask\globals.py", line 34, in _find_app
    raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context exc, exc_info.traceback)))

[〜#〜] update [〜#〜]Marteenからのコメントに基づいて、コードを変更しました。現在の作業バージョンは以下です https://Gist.github.com/anonymous/fa47834db2f4f3b8b257 。さらなる改善や提案は大歓迎です。

20
foobar

私はcurrent_appのアドバイスに不満を感じていました。

セロリオブジェクトは、アプリケーションコンテキストにアクセスする必要があります。ファクトリー関数を使用したCeleryオブジェクトの作成に関する情報をオンラインで見つけました。以下の例は、メッセージブローカーなしでテストされています。

#factory.py
from celery import Celery
from config import config

def create_celery_app(app=None):
    app = app or create_app(config)
    celery = Celery(__name__, broker=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
    return celery

そしてtasks.pyで:

#tasks.py
from factory import create_celery_app
from celery.signals import task_prerun
from flask import g

celery = create_celery_app()

@task_prerun.connect
def celery_prerun(*args, **kwargs):
    #print g
    with celery.app.app_context():
    #   # use g.db
       print g

@celery.task()
def do_some_stuff():
    with celery.app.app_context():
        # use g.db
        g.user = "test"
        print g.user

いくつかのリンク:

ファクトリ関数でCeleryインスタンスを作成するためのフラスコパターン

アプリケーションファクトリとセロリの両方を使用するアプリケーション

上記のアプリケーションのfactory.pyのソース

アプリケーションtasks.pyのソース

18
Maarten

flaskアプリケーションファクトリパターンで動作し、タスクでapp.app_context()を明示的に使用する必要なく、コンテキストでセロリタスクを作成するソリューションを次に示します。私のアプリでは、循環インポートを回避しながらそのアプリオブジェクトを取得するのは非常に難しいですが、これはそれを解決します。これは、執筆時の最新のセロリバージョン4.2にも適しています。

構造:

_repo_name/
    manage.py
    base/
    base/__init__.py
    base/app.py
    base/runcelery.py
    base/celeryconfig.py
    base/utility/celery_util.py
    base/tasks/workers.py
_

したがって、この例ではbaseがメインアプリケーションパッケージです。 _base/__init__.py_では、次のようにセロリインスタンスを作成します。

_from celery import Celery
celery = Celery('base', config_source='base.celeryconfig')
_

_base/app.py_ファイルには、flask app factory _create_app_が含まれ、init_celery(app, celery)が含まれていることに注意してください。

_from base import celery
from base.utility.celery_util import init_celery

def create_app(config_obj):
    """An application factory, as explained here:
    http://flask.pocoo.org/docs/patterns/appfactories/.
    :param config_object: The configuration object to use.
    """
    app = Flask('base')
    app.config.from_object(config_obj)
    init_celery(app, celery=celery)
    register_extensions(app)
    register_blueprints(app)
    register_errorhandlers(app)
    register_app_context_processors(app)
    return app
_

_base/runcelery.py_のコンテンツに移ります:

_from flask.helpers import get_debug_flag
from base.settings import DevConfig, ProdConfig
from base import celery
from base.app import create_app
from base.utility.celery_util import init_celery
CONFIG = DevConfig if get_debug_flag() else ProdConfig
app = create_app(CONFIG)
init_celery(app, celery)
_

次に、_base/celeryconfig.py_ファイル(例):

_# -*- coding: utf-8 -*-
"""
Configure Celery. See the configuration guide at ->
http://docs.celeryproject.org/en/master/userguide/configuration.html#configuration
"""

## Broker settings.
broker_url = 'pyamqp://guest:guest@localhost:5672//'
broker_heartbeat=0

# List of modules to import when the Celery worker starts.
imports = ('base.tasks.workers',)

## Using the database to store task state and results.
result_backend = 'rpc'
#result_persistent = False

accept_content = ['json', 'application/text']

result_serializer = 'json'
timezone = "UTC"

# define periodic tasks / cron here
# beat_schedule = {
#    'add-every-10-seconds': {
#        'task': 'workers.add_together',
#        'schedule': 10.0,
#        'args': (16, 16)
#    },
# }
_

次に、_base/utility/celery_util.py_ファイルでinit_celeryを定義します。

_# -*- coding: utf-8 -*-

def init_celery(app, celery):
    """Add flask app context to celery.Task"""
    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
_

_base/tasks/workers.py_の労働者向け:

_from base import celery as celery_app
from flask_security.utils import config_value, send_mail
from base.bp.users.models.user_models import User

@celery_app.task
def send_welcome_email(email, user_id, confirmation_link):
    """Background task to send a welcome email with flask-security's mail.
    You don't need to use with app.app_context() as Task has app context.
    """
    user = User.query.filter_by(id=user_id).first()
    print(f'sending user {user} a welcome email')
    send_mail(config_value('EMAIL_SUBJECT_REGISTER'),
              email,
              'welcome', user=user,
              confirmation_link=confirmation_link) 

@celery_app.task
def do_some_stuff():
    print(g)
_

次に、セロリビートとセロリワーカーを_repo_name_フォルダ内から2つの異なるコマンドプロンプトで開始する必要があります。

1つのコマンドプロンプトで_celery -A base.runcelery:celery beat_を実行し、もう1つの_celery -A base.runcelery:celery worker_を実行します。

次に、flaskコンテキストが必要なタスクを実行します。動作するはずです。

3
Bob Jordan