@ Django Bulgaria Autumn Meetup
Martin Angelov
Take a look at these works by RadoRado:
Go to the docs:
from .models import AsyncActionReport
class BaseErrorHandlerMixin:
def on_failure(self, exc, task_id, args, kwargs, einfo):
AsyncActionReport.objects.filter(id=kwargs['async_action_report_id'])\
.update(status=AsyncActionReport.FAILED,
error_message=str(exc),
error_traceback=einfo)
def on_success(self, retval, task_id, args, kwargs):
AsyncActionReport.objects.filter(id=kwargs['async_action_report_id'])\
.update(status=AsyncActionReport.OK)
Why do we use a mixin?
Here is how the model looks like ->
class AsyncActionReport(models.Model):
PENDING = 'pending'
OK = 'ok'
FAILED = 'failed'
STATUS_CHOICES = (
(PENDING, 'pending'),
(OK, 'ok'),
(FAILED, 'failed')
)
status = models.CharField(max_length=7, choices=STATUS_CHOICES, default=PENDING)
action = models.CharField(max_length=255)
error_message = models.TextField(null=True, blank=True)
error_traceback = models.TextField(null=True, blank=True)
def __str__(self):
return self.status
How do we use it? ->
def call_some_task(arg1, arg2):
action = 'Proper message here.'
async_action_report = AsyncActionReport.objects.create(action=action)
return some_task.delay(arg1, arg2, async_action_report_id=async_action_report.id)
class SomeBaseTask(BaseErrorHandlerMixin, Task):
pass
@shared_task(task=SomeBaseTask)
def some_task(arg1, arg2, **kwargs):
# do some slow and complicated logic
return
What to think about:
I've written a blog post about that in our blog. You can check it out for more details
https://www.hacksoft.io/blog/handle-third-party-errors-with-celery-and-django/
@shared_task(base=InvoicesPlusBaseTask)
def __fetch_data(**kwargs):
client = InvoicesPlusClient(api_key=settings.THIRD_PARTY_API_KEY)
fetched_data = client.fetch_data_method()
return fetched_data
@shared_task
@transaction.atomic
def __store_data(fetched_data):
container = ThirdPartyDataStorage(**fetched_data)
container.save()
return container.id
@shared_task(base=InvoicesPlusBaseTask)
def fetch_data_and_store_it(**kwargs):
async_action_report = AsyncActionReport.objects.create(action='Fetching data.')
t1 = __fetch_data.s(async_action_report_id=async_action_report.id)
t2 = __store_data.s()
return chain(t1, t2).delay()
Let's take a look at the following example ->
@shared_task
def inner_chain():
t1 = some_task.s()
t2 = other_task.s()
return chain(t1, t2).delay()
@shared_task
def mother():
t1 = inner_chain.s()
t2 = regular_task.s()
return chain(t1, t2).delay()
def chainable(chain_wannabe):
@wraps(chain_wannabe)
def wrapper(*args, **kwargs):
signatures = chain_wannabe(*args, **kwargs)
error_msg = 'Functions decorated with `chainable` must return Signature instances.'
if not isinstance(signatures, Iterable):
raise ValueError(error_msg)
for task_sig in chain_wannabe(*args, **kwargs):
if not isinstance(task_sig, Signature):
raise ValueError(error_msg)
return signatures
return wrapper
from path.to.decorators import chainable
@chainable
def inner_chain():
t1 = some_task.s()
t2 = other_task.s()
return t1, t2
@shared_task
def mother():
inner_tasks = inner_chain()
t2 = regular_task.s()
return chain(*inner_tasks, t2).delay()