Je me bats vraiment pour obtenir la bonne configuration pour Flask, SQLAlchemy et Celery. J'ai longuement cherché et essayé différentes approches, rien ne semble vraiment fonctionner. Soit j'ai raté le contexte de l'application, soit je ne peux pas exécuter les travailleurs, soit il y a d'autres problèmes. La structure est très générale afin que je puisse construire une application plus grande.
J'utilise: Flacon 0.10.1, SQLAlchemy 1.0, Celery 3.1.13, ma configuration actuelle est la suivante:
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 __== "__main__":
app.run()
app/models.py
from extensions import db
class User(db.Model):
__table= "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
Dans l'application de dossier:
python.exe manage.py
celery.exe worker -A tasks
Je reçois une erreur d'importation qui n'a aucun sens pour moi. Devrais-je structurer l'application différemment? À la fin, je pense que je veux une configuration assez basique, par exemple. En utilisant Flask avec le modèle d'usine, vous pouvez utiliser l'extension Flask-SQLAlchmey et disposer d'un ouvrier ayant besoin d'accéder à la base de données.
Toute aide est grandement appréciée.
Le traçage est exécuté lors du démarrage du céleri.
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'
UPDATEJe change le code en fonction de la suggestion dans le commentaire. L'agent s'ouvre maintenant mais testez-le avec une requête get à http://127.0.0.1:5000/test
. Je reçois le suivi suivant:
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)))
UPDATEEn fonction des commentaires de Marteen, j'ai changé le code. La version de travail actuelle se trouve sous: https://Gist.github.com/anonymous/fa47834db2f4f3b8b257 . Toute autre amélioration ou suggestion est la bienvenue.
J'étais parti avec le conseil current_app.
Votre objet céleri doit avoir accès au contexte de l'application. J'ai trouvé des informations en ligne sur la création d'un objet Celery avec une fonction factory. L'exemple ci-dessous est testé sans courtier de messages.
#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
et dans 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
Quelques liens:
Modèle de flacon pour la création d'une instance de céleri avec fonction d'usine
Application utilisant à la fois une fabrique d'applications et du céleri
Voici une solution qui fonctionne avec le modèle de fabrique d’applications de flacon et crée également une tâche de céleri avec un contexte, sans avoir à utiliser explicitement app.app_context()
dans les tâches. Dans mon application, il est vraiment difficile d'obtenir cet objet d'application tout en évitant les importations circulaires, mais cela résout le problème. Cela convient également à la dernière version 4.2 du céleri au moment de la rédaction.
Structure:
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
Donc base
est le package d'application principal dans cet exemple. Dans le base/__init__.py
, nous créons l'instance de céleri comme suit:
from celery import Celery
celery = Celery('base', config_source='base.celeryconfig')
Le fichier base/app.py
contient la fabrique d’applications de flacon create_app
et notez la init_celery(app, celery)
qu’il contient:
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
Passer au contenu de 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)
Ensuite, le fichier base/celeryconfig.py
(à titre d'exemple):
# -*- 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)
# },
# }
Définissez maintenant init_celery dans le fichier base/utility/celery_util.py
:
# -*- 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
Pour les travailleurs dans 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)
Ensuite, vous devez démarrer le batteur de céleri et le travailleur de céleri dans deux invites de commande différentes à partir de dans le dossier repo_name
.
Dans une invite de commande, faites un celery -A base.runcelery:celery beat
et l'autre celery -A base.runcelery:celery worker
.
Ensuite, exécutez votre tâche qui nécessitait le contexte de la bouteille. Devrait marcher.