Browse Source

Only old Task is now bound by class, new are bound by instance

Ask Solem 13 years ago
parent
commit
cd4a4fd61a
5 changed files with 65 additions and 77 deletions
  1. 5 0
      celery/__compat__.py
  2. 4 3
      celery/app/base.py
  3. 34 70
      celery/app/task/__init__.py
  4. 2 2
      celery/task/__init__.py
  5. 20 2
      celery/task/base.py

+ 5 - 0
celery/__compat__.py

@@ -5,6 +5,7 @@ import sys
 from types import ModuleType
 from types import ModuleType
 
 
 from .local import Proxy
 from .local import Proxy
+from .utils.compat import fun_of_method
 
 
 MODULE_DEPRECATED = """
 MODULE_DEPRECATED = """
 The module %s is deprecated and will be removed in a future version.
 The module %s is deprecated and will be removed in a future version.
@@ -95,3 +96,7 @@ class class_property(object):
         if obj is None:
         if obj is None:
             return self
             return self
         return self.__set.__get__(obj)(value)
         return self.__set.__get__(obj)(value)
+
+
+def reclassmethod(method):
+    return classmethod(fun_of_method(method))

+ 4 - 3
celery/app/base.py

@@ -110,11 +110,10 @@ class App(object):
         from .task import BaseTask
         from .task import BaseTask
 
 
         class Task(BaseTask):
         class Task(BaseTask):
-            app = self
+            _app = self
             abstract = True
             abstract = True
 
 
         Task.__doc__ = BaseTask.__doc__
         Task.__doc__ = BaseTask.__doc__
-        Task.bind(self)
 
 
         return Task
         return Task
 
 
@@ -203,7 +202,9 @@ class App(object):
                 "run": staticmethod(fun),
                 "run": staticmethod(fun),
                 "__doc__": fun.__doc__,
                 "__doc__": fun.__doc__,
                 "__module__": fun.__module__}, **options))()
                 "__module__": fun.__module__}, **options))()
-        return self._tasks[T.name]  # return global instance.
+        task = self._tasks[T.name]  # return global instance.
+        task.bind(self)
+        return task
 
 
     def annotate_task(self, task):
     def annotate_task(self, task):
         if self.annotations:
         if self.annotations:

+ 34 - 70
celery/app/task/__init__.py

@@ -17,7 +17,6 @@ import threading
 
 
 from ... import current_app
 from ... import current_app
 from ... import states
 from ... import states
-from ...__compat__ import class_property
 from ...datastructures import ExceptionInfo
 from ...datastructures import ExceptionInfo
 from ...exceptions import MaxRetriesExceededError, RetryTaskError
 from ...exceptions import MaxRetriesExceededError, RetryTaskError
 from ...result import EagerResult
 from ...result import EagerResult
@@ -25,7 +24,6 @@ from ...utils import fun_takes_kwargs, uuid, maybe_reraise
 from ...utils.functional import mattrgetter, maybe_list
 from ...utils.functional import mattrgetter, maybe_list
 from ...utils.imports import instantiate
 from ...utils.imports import instantiate
 from ...utils.mail import ErrorMail
 from ...utils.mail import ErrorMail
-from ...utils.compat import fun_of_method
 
 
 from ..state import current_task
 from ..state import current_task
 from ..registry import _unpickle_task
 from ..registry import _unpickle_task
@@ -38,14 +36,6 @@ extract_exec_options = mattrgetter("queue", "routing_key",
                                    "compression", "expires")
                                    "compression", "expires")
 
 
 
 
-#: list of methods that should be classmethods in
-#: backward compatibility mode.
-_COMPAT_CLASSMETHODS = ("get_logger", "establish_connection",
-                        "get_publisher", "get_consumer",
-                        "delay", "apply_async", "retry",
-                        "apply", "AsyncResult", "subtask")
-
-
 class Context(threading.local):
 class Context(threading.local):
     # Default context
     # Default context
     logfile = None
     logfile = None
@@ -102,19 +92,6 @@ class TaskType(type):
         new = super(TaskType, cls).__new__
         new = super(TaskType, cls).__new__
         task_module = attrs.get("__module__") or "__main__"
         task_module = attrs.get("__module__") or "__main__"
 
 
-        # In old Celery the @task decorator didn't exist, so one would create
-        # classes instead and use them directly (e.g. MyTask.apply_async()).
-        # the use of classmethods was a hack so that it was not necessary
-        # to instantiate the class before using it, but it has only
-        # given us pain (like all magic).
-        #
-        # This must be removed for 3.0.
-        if attrs.pop("compat", False):
-            for fun_name in _COMPAT_CLASSMETHODS:
-                if fun_name not in attrs:
-                    fun = fun_of_method(getattr(bases[0], fun_name))
-                    attrs[fun_name] = classmethod(fun)
-
         # - Abstract class: abstract attribute should not be inherited.
         # - Abstract class: abstract attribute should not be inherited.
         if attrs.pop("abstract", None) or not attrs.get("autoregister", True):
         if attrs.pop("abstract", None) or not attrs.get("autoregister", True):
             return new(cls, name, bases, attrs)
             return new(cls, name, bases, attrs)
@@ -122,9 +99,8 @@ class TaskType(type):
         # The 'app' attribute is now a property, with the real app located
         # The 'app' attribute is now a property, with the real app located
         # in the '_app' attribute.  Previously this was a regular attribute,
         # in the '_app' attribute.  Previously this was a regular attribute,
         # so we should support classes defining it.
         # so we should support classes defining it.
-        app = attrs["_app"] = (attrs.pop("_app", None) or
-                               attrs.pop("app", None) or
-                               current_app)
+        _app1, _app2 = attrs.pop("_app", None), attrs.pop("app", None)
+        app = attrs["_app"] =  _app1 or _app2 or current_app
 
 
         # - Automatically generate missing/empty name.
         # - Automatically generate missing/empty name.
         autoname = False
         autoname = False
@@ -143,18 +119,15 @@ class TaskType(type):
         # with the framework.  There should only be one class for each task
         # with the framework.  There should only be one class for each task
         # name, so we always return the registered version.
         # name, so we always return the registered version.
         tasks = app._tasks
         tasks = app._tasks
+        if autoname and task_module == "__main__" and app.main:
+            attrs["name"] = '.'.join([app.main, name])
+
         task_name = attrs["name"]
         task_name = attrs["name"]
         if task_name not in tasks:
         if task_name not in tasks:
-            task_cls = new(cls, name, bases, attrs)
-            if autoname and task_module == "__main__" and app.main:
-                task_name = task_cls.name = '.'.join([app.main, name])
-            tasks.register(task_cls)
-            task_cls.bind(app)
-        task = tasks[task_name].__class__
-
-        # decorate with annotations from config.
-        app.annotate_task(task)
-        return task
+            tasks.register(new(cls, name, bases, attrs))
+        instance = tasks[task_name]
+        instance.bind(app)
+        return instance.__class__
 
 
     def __repr__(cls):
     def __repr__(cls):
         if cls._app:
         if cls._app:
@@ -320,54 +293,45 @@ class BaseTask(object):
             "CELERY_STORE_ERRORS_EVEN_IF_IGNORED"),
             "CELERY_STORE_ERRORS_EVEN_IF_IGNORED"),
     )
     )
 
 
+    __bound__ = False
+
     # - Tasks are lazily bound, so that configuration is not set
     # - Tasks are lazily bound, so that configuration is not set
     # - until the task is actually used
     # - until the task is actually used
 
 
-    @classmethod
-    def _maybe_bind(cls, app):
-        if not cls.__bound__:
-            cls.__bound__ = True
-            return cls.bind(app)
-
-    @classmethod
-    def bind(cls, app):
-        cls._app = app
+    def bind(self, app):
+        self.__bound__ = True
+        self._app = app
         conf = app.conf
         conf = app.conf
 
 
-        for attr_name, config_name in cls.from_config:
-            if getattr(cls, attr_name, None) is None:
-                setattr(cls, attr_name, conf[config_name])
-        cls.accept_magic_kwargs = app.accept_magic_kwargs
-        if cls.accept_magic_kwargs is None:
-            cls.accept_magic_kwargs = app.accept_magic_kwargs
-        if cls.backend is None:
-            cls.backend = app.backend
+        for attr_name, config_name in self.from_config:
+            if getattr(self, attr_name, None) is None:
+                setattr(self, attr_name, conf[config_name])
+        self.accept_magic_kwargs = app.accept_magic_kwargs
+        if self.accept_magic_kwargs is None:
+            self.accept_magic_kwargs = app.accept_magic_kwargs
+        if self.backend is None:
+            self.backend = app.backend
+
+        # decorate with annotations from config.
+        app.annotate_task(self)
 
 
-        # e.g. PeriodicTask uses this to add itself to the PeriodicTask
-        # schedule.
-        cls.on_bound(app)
+        # PeriodicTask uses this to add itself to the PeriodicTask schedule.
+        self.on_bound(app)
 
 
         return app
         return app
 
 
-    @classmethod
     def on_bound(self, app):
     def on_bound(self, app):
         """This method can be defined to do additional actions when the
         """This method can be defined to do additional actions when the
         task class is bound to an app."""
         task class is bound to an app."""
         pass
         pass
 
 
-    @classmethod
-    def _get_app(cls):
-        if cls._app is None or not cls.__bound__:
-            # if app is set on the class, then the app descriptors
-            # __set__  method is not called, and the cls must
-            # be bound later.
-            cls._maybe_bind(current_app)
-        return cls._app
-
-    @classmethod
-    def _set_app(cls, app):
-        cls.bind(app)
-    app = class_property(_get_app, _set_app)
+    def _get_app(self):
+        if not self.__bound__ or self._app is None:
+            # The app property's __set__  method is not called
+            # if Task.app is set (on the class), so must bind on use.
+            self.bind(current_app)
+        return self._app
+    app = property(_get_app, bind)
 
 
     # - tasks are pickled into the name of the task only, and the reciever
     # - tasks are pickled into the name of the task only, and the reciever
     # - simply grabs it from the local registry.
     # - simply grabs it from the local registry.

+ 2 - 2
celery/task/__init__.py

@@ -53,8 +53,8 @@ def task(*args, **kwargs):
             >>> refresh_feed.delay("http://example.com/rss") # Async
             >>> refresh_feed.delay("http://example.com/rss") # Async
             <AsyncResult: 8998d0f4-da0b-4669-ba03-d5ab5ac6ad5d>
             <AsyncResult: 8998d0f4-da0b-4669-ba03-d5ab5ac6ad5d>
     """
     """
-    kwargs.setdefault("accept_magic_kwargs", False)
-    return app_or_default().task(*args, **kwargs)
+    return current_app.task(*args, **dict({"accept_magic_kwargs": False,
+                                           "base": Task}, **kwargs))
 
 
 
 
 def periodic_task(*args, **options):
 def periodic_task(*args, **options):

+ 20 - 2
celery/task/base.py

@@ -5,22 +5,40 @@
 
 
     The task implementation has been moved to :mod:`celery.app.task`.
     The task implementation has been moved to :mod:`celery.app.task`.
 
 
+    This contains the backward compatible Task class used in the old API.
+
     :copyright: (c) 2009 - 2012 by Ask Solem.
     :copyright: (c) 2009 - 2012 by Ask Solem.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 
 
 """
 """
 from __future__ import absolute_import
 from __future__ import absolute_import
 
 
+from ..__compat__ import class_property, reclassmethod
 from ..app.task import Context, TaskType, BaseTask  # noqa
 from ..app.task import Context, TaskType, BaseTask  # noqa
 from ..schedules import maybe_schedule
 from ..schedules import maybe_schedule
 
 
+#: list of methods that are classmethods in the old API.
+_COMPAT_CLASSMETHODS = (
+    "get_logger", "establish_connection", "get_publisher", "get_consumer",
+    "delay", "apply_async", "retry", "apply", "AsyncResult", "subtask",
+    "bind", "on_bound", "_get_app")
+
 
 
 class Task(BaseTask):
 class Task(BaseTask):
     abstract = True
     abstract = True
-    compat = True       # see note in TaskType.
+    __bound__ = False
+
+    # In old Celery the @task decorator didn't exist, so one would create
+    # classes instead and use them directly (e.g. MyTask.apply_async()).
+    # the use of classmethods was a hack so that it was not necessary
+    # to instantiate the class before using it, but it has only
+    # given us pain (like all magic).
+    for name in _COMPAT_CLASSMETHODS:
+        locals()[name] = reclassmethod(getattr(BaseTask, name))
+    app = class_property(_get_app, bind)  # noqa
 
 
 
 
-class PeriodicTask(BaseTask):
+class PeriodicTask(Task):
     """A periodic task is a task that adds itself to the
     """A periodic task is a task that adds itself to the
     :setting:`CELERYBEAT_SCHEDULE` setting."""
     :setting:`CELERYBEAT_SCHEDULE` setting."""
     abstract = True
     abstract = True