celery/celery — pull request #1897 by kalefranz (2014-02-28 18:55:30+00:00)
Title: ClassTask: a metaclass that turns any generic class into a celery task

I would like to submit the pull request for _feedback_only_ right now, with the possibility of merging into celery:master after further work. It is not currently ready to integrate into the celery code base (although as you'll see, all tests pass and I don't think it would create any issues).

My objective here was to create some type of class modifier (mixin, decorator, metaclass, etc) that turned the _entire_ class into a task. A small example might best illustrate what I want.

class WelcomeEmail(ClassTaskMixin, EmailBase):

def __init__(self, recipient_address, template_name, template_context):
self.recipient_address = recipient_address
self.template_name = template_name
self.template_context = template_context

def send(self):
self.render_templates()
self.construct_mime()
self.archive_to_db()
return self.send_smtp_email()

def run(self):
return self.send()

The `WelcomeEmail` class would then be used as

we = WelcomeEmail(recipient_address, template_name, template_context)
we.enqueue()

where the `enqueue` method is provided by `ClassTaskMixin` and is analogous to the celery `delay` or `apply_async`. Here, since the `run` method redirects to `send`, `enqueue` would simply send the email in the asynchronous process.

It's important to note that one of my goals was/is **delayed initialization of the instance object**. That is, the python standard call to `__init__` should be "lazy" and **not** called when the instance is created. This is important. With a _lazy_ `__init__`, the user need not worry about "heavy" and duplicitous work being done in `__init__` (duplicitous because without lazy `__init__` the work would be done in both the synchronous and worker processes). In the `WelcomeEmail` example, the coder may have for whatever reason put the calls to `render_templates` and `construct_mime` within `__init__`; and here, calling `render_templates` and `construct_mime` in the synchronous process is wasteful.

Now, I am aware of `celery.contrib.methods`, and after the above explanation, you can see that what I'm after is far more extensive.

I believe the pull request I am submitting here accomplishes my goals. Rather than a class mixin, it was necessary to create a metaclass to accomplish the lazy initialization. The two files in this pull request contain the `ClassTask` metaclass and a retrofitting of the first 12 of 32 tests for `celery.Task` (in `celery/tests/tasks/test_tasks.py`)

I am _very_ much interested in community input. This code will eventually be used in production for my employer. And if the community thinks this might be more broadly useful, I am happy to contribute it to the celery library.

Some questions to start with...
1) Are there better/more direct ways to test the `ClassTask` code than be retrofitting/duplicating what's in `celery/tests/tasks/test_tasks.py`?
2) For the methods in `ClassTaskBase` that are almost nearly just a call to `super` (_i.e._ `apply_async`, `apply`, `retry`, etc.), is there a better way to avoid the wrote code? My guess is there will be even more of these methods once I work through all of the applicable unit tests.
3) Any general comments/critiques/criticisms/questions/insights to help me make this code better?

* FINE PRINT: `nosetests` ran only with python 2.7 so far.