|  | @@ -0,0 +1,152 @@
 | 
	
		
			
				|  |  | +from celery import registry
 | 
	
		
			
				|  |  | +from datetime import datetime
 | 
	
		
			
				|  |  | +from UserDict import UserDict
 | 
	
		
			
				|  |  | +from celery.serialization import pickle
 | 
	
		
			
				|  |  | +import atexit
 | 
	
		
			
				|  |  | +import errno
 | 
	
		
			
				|  |  | +import time
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +schedule = PersistentDict(save_at_exit=True)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class ScheduleEntry(object):
 | 
	
		
			
				|  |  | +    """An entry in the scheduler.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    :param task: The task class.
 | 
	
		
			
				|  |  | +    :keyword last_run_at: The time and date when this task was last run.
 | 
	
		
			
				|  |  | +    :keyword total_run_count: Total number of times this periodic task has
 | 
	
		
			
				|  |  | +        been executed.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, task, last_run_at=None, total_run_count=None)
 | 
	
		
			
				|  |  | +        self.task = task
 | 
	
		
			
				|  |  | +        self.last_run_at = None
 | 
	
		
			
				|  |  | +        self.total_run_count = None
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def execute(self):
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            result = self.task.apply_async()
 | 
	
		
			
				|  |  | +        except Exception, exc:
 | 
	
		
			
				|  |  | +            print("Couldn't apply scheduled task %s: %s" % (
 | 
	
		
			
				|  |  | +                self.task.name, exc))
 | 
	
		
			
				|  |  | +            result = None
 | 
	
		
			
				|  |  | +        self.last_run_at = datetime.now()
 | 
	
		
			
				|  |  | +        self.total_run_count += 1
 | 
	
		
			
				|  |  | +        return result
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def is_due(self):
 | 
	
		
			
				|  |  | +        run_at = self.last_run_at + self.task.run_every
 | 
	
		
			
				|  |  | +        return True if datetime.now() > self.last_run_at else return False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class Scheduler(object):
 | 
	
		
			
				|  |  | +    """Scheduler for periodic tasks.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    :keyword registry: The task registry to use.
 | 
	
		
			
				|  |  | +    :keyword schedule: The schedule dictionary. Default is the global
 | 
	
		
			
				|  |  | +        persistent schedule ``celery.beat.schedule``.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    registry = registry.tasks
 | 
	
		
			
				|  |  | +    data = schedule
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, registry=None, schedule=None):
 | 
	
		
			
				|  |  | +        self.registry = registry or self.registry
 | 
	
		
			
				|  |  | +        self.data = schedule or self.data
 | 
	
		
			
				|  |  | +        self.schedule_registry()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def run(self):
 | 
	
		
			
				|  |  | +        """Run the scheduler.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        This runs :meth:`tick` every second in a never-exit loop."""
 | 
	
		
			
				|  |  | +        while True:
 | 
	
		
			
				|  |  | +            self.tick() 
 | 
	
		
			
				|  |  | +            time.sleep(1)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def tick(self):
 | 
	
		
			
				|  |  | +        """Run a tick, that is one iteration of the scheduler.
 | 
	
		
			
				|  |  | +        Executes all due tasks."""
 | 
	
		
			
				|  |  | +        return [(entry.task, entry.execute())
 | 
	
		
			
				|  |  | +                    for entry in self.get_due_tasks()]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def get_due_tasks(self):
 | 
	
		
			
				|  |  | +        """Get all the schedule entries that are due to execution."""
 | 
	
		
			
				|  |  | +        return filter(lambda entry: entry.is_due(), self.schedule.values())
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def schedule_registry(self):
 | 
	
		
			
				|  |  | +        """Add the current contents of the registry to the schedule."""
 | 
	
		
			
				|  |  | +        periodic_tasks = self.registry.get_all_periodic()
 | 
	
		
			
				|  |  | +        schedule = dict((name, tasktimetuple(task))
 | 
	
		
			
				|  |  | +                            for name, task in periodic_tasks.items())
 | 
	
		
			
				|  |  | +        self.schedule = dict(schedule, **self.schedule)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @property
 | 
	
		
			
				|  |  | +    def schedule(self):
 | 
	
		
			
				|  |  | +        return self.data
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class PersistentDict(UserDict):
 | 
	
		
			
				|  |  | +    """Dictionary that can be stored to disk.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    :param filename: Name of the file to save to.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    :keyword initial_data: Initial dict to start with.
 | 
	
		
			
				|  |  | +    :keyword save_at_exit: Register an atexit handler to automatically save
 | 
	
		
			
				|  |  | +        the data when the program exits (not safe, but as an extra precaution)
 | 
	
		
			
				|  |  | +    :keyword encoding: The encoding to write the file with, default is
 | 
	
		
			
				|  |  | +        ``"zlib"``.
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    """
 | 
	
		
			
				|  |  | +    encoding = "zlib"
 | 
	
		
			
				|  |  | +    save_at_exit = False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __init__(self, filename, initial_data=None, save_at_exit=None,
 | 
	
		
			
				|  |  | +            encoding=None):
 | 
	
		
			
				|  |  | +        self.data = initial_data or {}
 | 
	
		
			
				|  |  | +        self.filename = filename
 | 
	
		
			
				|  |  | +        self.encoding = encoding
 | 
	
		
			
				|  |  | +        if save_at_exit is not None:
 | 
	
		
			
				|  |  | +            self.save_at_exit = save_at_exit
 | 
	
		
			
				|  |  | +        self.reload()
 | 
	
		
			
				|  |  | +        self._saved = False
 | 
	
		
			
				|  |  | +        self.save_at_exit and self.register_atexit()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def reload(self):
 | 
	
		
			
				|  |  | +        """Reload data from disk."""
 | 
	
		
			
				|  |  | +        persisted_data = self._read_file(self.filename, self.encoding)
 | 
	
		
			
				|  |  | +        self.data = dict(self.data, **persisted_data)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def save(self):
 | 
	
		
			
				|  |  | +        """Save data to disk."""
 | 
	
		
			
				|  |  | +        self._saved = True
 | 
	
		
			
				|  |  | +        encoded = pickle.dump(fh, self.data).encode(self.encoding)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        fh = open(self.filename, "w")
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            fh.write(encoded)
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            fh.close()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def register_atexit(self):
 | 
	
		
			
				|  |  | +        """Register an atexit handler to save data to disk when the
 | 
	
		
			
				|  |  | +        program terminates."""
 | 
	
		
			
				|  |  | +        atexit.register(self._save_atexit)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _save_atexit(self):
 | 
	
		
			
				|  |  | +        self._saved or self.save()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _read_file(self, filename, encoding):
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            fh = open(filename)
 | 
	
		
			
				|  |  | +        except IOError, exc:
 | 
	
		
			
				|  |  | +            if exc.errno == errno.ENOENT:
 | 
	
		
			
				|  |  | +                return
 | 
	
		
			
				|  |  | +            raise
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        try:
 | 
	
		
			
				|  |  | +            return pickle.loads(fh.read().decode(encoding))
 | 
	
		
			
				|  |  | +        finally:
 | 
	
		
			
				|  |  | +            fh.close()
 | 
	
		
			
				|  |  | +
 |