Browse Source

Updated Changelog with crontab feature

Ask Solem 15 years ago
parent
commit
e09fc1af51
2 changed files with 43 additions and 99 deletions
  1. 35 0
      Changelog
  2. 8 99
      celery/task/base.py

+ 35 - 0
Changelog

@@ -55,6 +55,41 @@ News
 	crashes in the middle of their execution. Not acceptable for most
 	crashes in the middle of their execution. Not acceptable for most
 	applications, but desirable for others.
 	applications, but desirable for others.
 
 
+* Added crontab-like scheduling to periodic tasks.
+
+	Like a cron job, you can specify units of time of when
+	you would like the task to execute. While not a full implementation
+	of cron's features, it should provide a fair degree of common scheduling
+	needs.
+    
+    You can specify a minute (0-59), an hour (0-23), and/or a day of the
+    week (0-6 where 0 is Sunday, or by names: sun, mon, tue, wed, thu, fri,
+    sat).
+
+	Examples:
+
+	.. code-block:: python
+
+		from celery.task import crontab
+		from celery.decorators import periodic_task
+
+		@periodic_task(run_every=crontab(hour=7, minute=30))
+		def every_morning():
+			print("Runs every morning at 7:30a.m")
+
+		@periodic_task(run_every=crontab(hour=7, minute=30,
+									     day_of_week="monday"))
+		def every_monday_morning():
+			print("Run every monday morning at 7:30a.m")
+
+		@periodic_task(run_every=crontab(minutes=30))
+		def every_hour():
+			print("Runs every hour on the clock. e.g. 1:30, 2:30, 3:30 etc.")
+
+	Note that this a late addition. While we have unittests, due to the
+	nature of this feature we haven't been able to completely test this
+	in practice, so consider this experimental.
+
 * ``TaskPool.apply_async``: Now supports the ``accept_callback`` argument.
 * ``TaskPool.apply_async``: Now supports the ``accept_callback`` argument.
 
 
 * ``apply_async``: Now raises :exc:`ValueError` if task args is not a list,
 * ``apply_async``: Now raises :exc:`ValueError` if task args is not a list,

+ 8 - 99
celery/task/base.py

@@ -1,14 +1,13 @@
 import sys
 import sys
 import warnings
 import warnings
-from datetime import datetime, timedelta
+from datetime import timedelta
 
 
 from billiard.serialization import pickle
 from billiard.serialization import pickle
 
 
 from celery import conf
 from celery import conf
 from celery.log import setup_task_logger
 from celery.log import setup_task_logger
 from celery.utils import gen_unique_id, padlist
 from celery.utils import gen_unique_id, padlist
-from celery.utils.timeutils import timedelta_seconds, weekday
-from celery.utils.timeutils import delta_resolution
+from celery.utils.timeutils import timedelta_seconds
 from celery.result import BaseAsyncResult, TaskSetResult, EagerResult
 from celery.result import BaseAsyncResult, TaskSetResult, EagerResult
 from celery.execute import apply_async, apply
 from celery.execute import apply_async, apply
 from celery.registry import tasks
 from celery.registry import tasks
@@ -17,6 +16,8 @@ from celery.messaging import TaskPublisher, TaskConsumer
 from celery.messaging import establish_connection as _establish_connection
 from celery.messaging import establish_connection as _establish_connection
 from celery.exceptions import MaxRetriesExceededError, RetryTaskError
 from celery.exceptions import MaxRetriesExceededError, RetryTaskError
 
 
+from celery.task.schedules import schedule
+
 
 
 class TaskType(type):
 class TaskType(type):
     """Metaclass for tasks.
     """Metaclass for tasks.
@@ -654,100 +655,6 @@ class TaskSet(object):
         return AsynchronousMapTask.delay(serfunc, args, timeout=timeout)
         return AsynchronousMapTask.delay(serfunc, args, timeout=timeout)
 
 
 
 
-class schedule(object):
-    relative = False
-
-    def __init__(self, run_every=None, relative=False):
-        self.run_every = run_every
-        self.relative = relative
-
-    def remaining_estimate(self, last_run_at):
-        """Returns when the periodic task should run next as a timedelta."""
-        next_run_at = last_run_at + self.run_every
-        if not self.relative:
-            next_run_at = delta_resolution(next_run_at, self.run_every)
-        return next_run_at - datetime.now()
-
-    def is_due(self, last_run_at):
-        """Returns tuple of two items ``(is_due, next_time_to_run)``,
-        where next time to run is in seconds.
-
-        See :meth:`PeriodicTask.is_due` for more information.
-
-        """
-        rem_delta = self.remaining_estimate(last_run_at)
-        rem = timedelta_seconds(rem_delta)
-        if rem == 0:
-            return True, timedelta_seconds(self.run_every)
-        return False, rem
-
-
-class crontab(schedule):
-    """A crontab can be used as the ``run_every`` value of a
-    :class:`PeriodicTask` to add cron-like scheduling.
-
-    Like a :manpage:`cron` job, you can specify units of time of when
-    you would like the task to execute. While not a full implementation
-    of cron's features, it should provide a fair degree of common scheduling
-    needs.
-
-    You can specify a minute, an hour, and/or a day of the week.
-
-    .. attribute:: minute
-
-        An integer from 0-59 that represents the minute of an hour of when
-        execution should occur.
-
-    .. attribute:: hour
-
-        An integer from 0-23 that represents the hour of a day of when
-        execution should occur.
-
-    .. attribute:: day_of_week
-
-        An integer from 0-6, where Sunday = 0 and Saturday = 6, that
-        represents the day of week that execution should occur.
-
-    """
-
-    def __init__(self, minute=None, hour=None, day_of_week=None,
-            nowfun=datetime.now):
-        self.hour = hour                  # (0 - 23)
-        self.minute = minute              # (0 - 59)
-        self.day_of_week = day_of_week    # (0 - 6) (Sunday=0)
-        self.nowfun = nowfun
-
-        if isinstance(self.day_of_week, basestring):
-            self.day_of_week = weekday(self.day_of_week)
-
-
-    def remaining_estimate(self, last_run_at):
-        # remaining_estimate controls the frequency of scheduler
-        # ticks. The scheduler needs to wake up every second in this case.
-        return 1
-
-    def is_due(self, last_run_at):
-        now = self.nowfun()
-        last = now - last_run_at
-        due, when = False, 1
-        if last.days > 0 or last.seconds > 60:
-            if self.day_of_week in (None, now.isoweekday()):
-                due, when = self._check_hour_minute(now)
-        return due, when
-
-    def _check_hour_minute(self, now):
-        due, when = False, 1
-        if self.hour is None and self.minute is None:
-            due, when = True, 1
-        if self.hour is None and self.minute == now.minute:
-            due, when = True, 1
-        if self.hour == now.hour and self.minute is None:
-            due, when = True, 1
-        if self.hour == now.hour and self.minute == now.minute:
-            due, when = True, 1
-        return due, when
-
-
 class PeriodicTask(Task):
 class PeriodicTask(Task):
     """A periodic task is a task that behaves like a :manpage:`cron` job.
     """A periodic task is a task that behaves like a :manpage:`cron` job.
 
 
@@ -779,7 +686,9 @@ class PeriodicTask(Task):
         ...         logger = self.get_logger(**kwargs)
         ...         logger = self.get_logger(**kwargs)
         ...         logger.info("Execute every 30 seconds")
         ...         logger.info("Execute every 30 seconds")
 
 
-        >>> from celery.task import PeriodicTask, crontab
+        >>> from celery.task import PeriodicTask
+        >>> from celery.task.schedules import crontab
+
         >>> class EveryMondayMorningTask(PeriodicTask):
         >>> class EveryMondayMorningTask(PeriodicTask):
         ...     run_every = crontab(hour=7, minute=30, day_of_week=1)
         ...     run_every = crontab(hour=7, minute=30, day_of_week=1)
         ...
         ...
@@ -796,7 +705,7 @@ class PeriodicTask(Task):
 
 
         >>> class EveryQuarterPastTheHourTask(PeriodicTask):
         >>> class EveryQuarterPastTheHourTask(PeriodicTask):
         ...     run_every = crontab(minute=15)
         ...     run_every = crontab(minute=15)
-
+        ...
         ...     def run(self, **kwargs):
         ...     def run(self, **kwargs):
         ...         logger = self.get_logger(**kwargs)
         ...         logger = self.get_logger(**kwargs)
         ...         logger.info("Execute every 0:15 past the hour every day.")
         ...         logger.info("Execute every 0:15 past the hour every day.")