Resolving circular imports in celery and django Resolving circular imports in celery and django python python

Resolving circular imports in celery and django


The solution posted by joshua is very good, but when I first tried it, I found that my @receiver decorators had no effect. That was because the tasks module wasn't imported anywhere, which was expected as I used task auto-discovery.

There is, however, another way to decouple tasks.py from modules.py. Namely, tasks can be sent by name and they don't have to be evaluated (imported) in the process that sends them:

from django.db import models#from tasks import my_taskimport celeryclass MyModel(models.Model):    field1 = models.IntegerField()    #more fields    my_field = models.FloatField(null=True)    @staticmethod    def load_from_file(file):        #parse file, set fields from file        #my_task.delay(id)        celery.current_app.send_task('myapp.tasks.my_task', (id,))

send_task() is a method on Celery app objects.

In this solution it is important to take care of correct, predictable names for your tasks.


In your models instead of importing the my_task at the beginning of the file, you can import it just before you use it. It will solve circular imports problem.

from django.db import modelsclass MyModel(models.Model):      field1 = models.IntegerField()      #more fields      my_field = models.FloatField(null=True)      @staticmethod      def load_from_file(file):          #parse file, set fields from file          from tasks import my_task   # import here instead of top          my_task.delay(id)

Alternatively, you can also do same thing in your tasks.py. You can import your models just before you use it instead of beginning.

Alternative:

You can use send_task method to call your task

from celery import current_appfrom django.db import modelsclass MyModel(models.Model):      field1 = models.IntegerField()      #more fields      my_field = models.FloatField(null=True)      @staticmethod      def load_from_file(file):          #parse file, set fields from file          current_app.send_task('myapp.tasks.my_task', (id,))


Just to toss one more not-great solution into this list, what I've ended up doing is relying on django's now-built-in app registry.

So in tasks.py, rather than importing from models, you use apps.get_model() to gain access to the model.

I do this with a helper method with a healthy bit of documentation just to express why this is painful:

from django.apps import appsdef _model(model_name):    """Generically retrieve a model object.    This is a hack around Django/Celery's inherent circular import    issues with tasks.py/models.py. In order to keep clean abstractions, we use    this to avoid importing from models, introducing a circular import.    No solutions for this are good so far (unnecessary signals, inline imports,    serializing the whole object, tasks forced to be in model, this), so we    use this because at least the annoyance is constrained to tasks.    """    return apps.get_model('my_app', model_name)

And then:

@shared_taskdef some_task(post_id):    post = _model('Post').objects.get(pk=post_id)

You could certainly just use apps.get_model() directly though.