According to celery docs:
Note that this example project layout is suitable for larger projects, for simple projects you may use a single contained module that defines both the app and tasks, like in the First Steps with Celery tutorial.
With that in mind, the fact that the celery setup file is inside of a folder in the path where we hold our apps is a little misleading. It is configured so it discovers tasks with the @shared_task decorator inside of any app, but it is inside of a folder named taskapp, again, misleading.
Another thing that i would like to point is that celery.py is a setup file, where we create the instance for the celery application. In that way it is very similar to the wsgi.py file, where we setup the wsgi app. Different things, i know, but with the same purpose.
Following that line of thought i would like to propose removing the taskapp and move the celery.py file (and the changes to init.py) to the config folder. Simpler structure and a more coherent setup in my opinion.
As I'm a huge fan of simplicity, I like your suggestion. That said, let's go query the best person in the world when it comes to Celery and see what they have to say.
Hey @ask, do you want to say anything about having Cookiecutter Django re-architect it's Celery implementation?
Here's some context on why we choose to implement this differently: https://github.com/pydanny/cookiecutter-django/pull/266
From what i see, the options are:
The proposed change would have the following consequences:
celery -A config, which is shorter :PAnyway, thanks for checking the suggestion.
Thanks for inviting me!
I think there are two very important things to consider here:
1) The @shared_task decorator means you don't need to import an explicit app instance
to define a task.
Good for reusable apps, for example. django-rest-framework could define tasks without knowing
where you define the app.
2) If @shared_task is used, the app needs to be created as early as possible when Django starts
The tutorial adds this to proj/__init__.py to make sure that happens:
# proj/__init__.py
from .celery import app as celery_app
If you always pass explicit app instances around, you don't have to worry about this part
but you also lose the ability for reusable apps to add tasks.
The reusable apps could provide with some setup code that you add:
app = Celery()
rest_framework.add_tasks(app)
That's definitely more correct, and robust, less magic and all that, but it's also less convenient.
Technically you could use @shared_task with both approaches: defining the app in the project, or in a self-contained reusable app, but you still need to make the app is created before you call tasks
in views and so on, which means you would need:
# proj/_init__.py
from tasks import app as celery_app
I was reading through how you are implementing this because, like @pydanny, I didn't like the idea of putting Celery code into __init__.py. Creating an app that can be added to INSTALLED_APPS is exactly how I was thinking to implement it, so it's great to see an example of that being done. Reading through this and the other related Issues/PRs, I had a couple questions and a comment:
@shared_task would be called in a view before the AppConfig would be loaded by django.setup()? I thought that the app registry would be fully set up sufficiently early to avoid that.{{cookiecutter.project_slug}}.taskapp.celery.CeleryConfig to INSTALLED_APPS. Is there a reason for that extended, explicit call rather than putting an apps.py file in taskapp and just putting a simple taskapp in the INSTALLED_APPS list?Comment: I noticed in the reddit post linked in #266 that inspired this method that it states that some of this code is put in the .ready() method of the AppConfig to "ensure that the app's setup will be called once, and only once, when Django starts." However, the Django docs state that there are edge cases where .ready() is called more than once. Is that a concern?
@matthewslaney Brings up some good points. Can anyone answer his questions?
This is how Django recommends to do it... It has nothing to do with the module name being apps.py. The short form is for Django version that pre-dates 1.7, and it can be mapped using the default_app_config attribute at the module level. It's not recommended though:
New applications should avoid default_app_config. Instead they should require the dotted path to the appropriate AppConfig subclass to be configured explicitly in INSTALLED_APPS.
To go back to the main topic of this issue, I'm in favor of building the Celery app outside of a Django app, I'm on the side of the "it's more like the wsgi.py" argument.
The tasks itself should be provided by each Django app, not by the Celery app (which is not a Django app IMO) and I don't think that centralising them is a good thing.
I've started a pull request to implement what has been discussed here: https://github.com/pydanny/cookiecutter-django/pull/2016
I've also added a dummy task to the user app, its main purpose is to:
It's pretty small, but any help in getting this reviewed & testing is welcome.
Most helpful comment
Thanks for inviting me!
I think there are two very important things to consider here:
1) The
@shared_taskdecorator means you don't need to import an explicit app instanceto define a task.
Good for reusable apps, for example. django-rest-framework could define tasks without knowing
where you define the app.
2) If @shared_task is used, the app needs to be created as early as possible when Django starts
The tutorial adds this to
proj/__init__.pyto make sure that happens:If you always pass explicit app instances around, you don't have to worry about this part
but you also lose the ability for reusable apps to add tasks.
The reusable apps could provide with some setup code that you add:
That's definitely more correct, and robust, less magic and all that, but it's also less convenient.
Technically you could use
@shared_taskwith both approaches: defining the app in the project, or in a self-contained reusable app, but you still need to make the app is created before you call tasksin views and so on, which means you would need: