added support for rpm packaging and basic support for deb
This commit is contained in:
		
							parent
							
								
									ce758e8129
								
							
						
					
					
						commit
						783e7e6d0d
					
				
							
								
								
									
										10
									
								
								deb/control
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								deb/control
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					Package: tis-tisbackup
 | 
				
			||||||
 | 
					Version: VERSION
 | 
				
			||||||
 | 
					Section: base
 | 
				
			||||||
 | 
					Priority: optional
 | 
				
			||||||
 | 
					Architecture: all
 | 
				
			||||||
 | 
					Depends:  unzip ssh rsync python-paramiko python-pyvmomi python-pexpect
 | 
				
			||||||
 | 
					Maintainer: Tranquil-IT-Systems <admin@tranquil-it-systems.fr>
 | 
				
			||||||
 | 
					Description: TISBackup backup management 
 | 
				
			||||||
 | 
					Homepage: http://www.tranquil-it-systems.fr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										34
									
								
								deb/createdeb.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								deb/createdeb.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env bash
 | 
				
			||||||
 | 
					#svn --username svnuser up
 | 
				
			||||||
 | 
					#VERSION=$(svn info |awk '/Revi/{print $2}')
 | 
				
			||||||
 | 
					VERSION=0.1
 | 
				
			||||||
 | 
					VERSION=$VERSION-$(git rev-parse --short HEAD)
 | 
				
			||||||
 | 
					rm -f *.deb
 | 
				
			||||||
 | 
					rm -Rf builddir
 | 
				
			||||||
 | 
					mkdir builddir
 | 
				
			||||||
 | 
					mkdir builddir/DEBIAN
 | 
				
			||||||
 | 
					cp ./control ./builddir/DEBIAN
 | 
				
			||||||
 | 
					#cp ./files/postinst ./builddir/DEBIAN
 | 
				
			||||||
 | 
					#cp ./files/prerm ./builddir/DEBIAN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sed "s/VERSION/$VERSION/" -i ./builddir/DEBIAN/control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p builddir/opt/tisbackup/
 | 
				
			||||||
 | 
					mkdir -p ./builddir/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#cp ../scripts/tisbackup_gui.service ./builddir/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					rsync -aP --exclude=deb ../ ./builddir/opt/tisbackup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#tis-arpwatch
 | 
				
			||||||
 | 
					#chmod 755 ./builddir/opt/tis-nagios/*.py
 | 
				
			||||||
 | 
					#chmod 755 ./builddir/etc/init.d/tis-arpwatch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dpkg-deb --build builddir tis-tisbackup-${VERSION}.deb
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#echo "== Copie du .deb sur le serveur tisdeb =="
 | 
				
			||||||
 | 
					#scp *.deb root@srvinstallation:/var/www/srvinstallation/tisdeb/binary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#echo "== Scan du répertoire =="
 | 
				
			||||||
 | 
					#ssh root@srvinstallation /var/www/srvinstallation/tisdeb/updateRepo.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										62
									
								
								lib/huey/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/huey/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					__author__ = 'Charles Leifer'
 | 
				
			||||||
 | 
					__license__ = 'MIT'
 | 
				
			||||||
 | 
					__version__ = '0.4.9'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.api import Huey, crontab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    import redis
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisBlockingQueue
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisDataStore
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisEventEmitter
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisSchedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class RedisHuey(Huey):
 | 
				
			||||||
 | 
					        def __init__(self, name='huey', store_none=False, always_eager=False,
 | 
				
			||||||
 | 
					                     read_timeout=None, **conn_kwargs):
 | 
				
			||||||
 | 
					            queue = RedisBlockingQueue(
 | 
				
			||||||
 | 
					                name,
 | 
				
			||||||
 | 
					                read_timeout=read_timeout,
 | 
				
			||||||
 | 
					                **conn_kwargs)
 | 
				
			||||||
 | 
					            result_store = RedisDataStore(name, **conn_kwargs)
 | 
				
			||||||
 | 
					            schedule = RedisSchedule(name, **conn_kwargs)
 | 
				
			||||||
 | 
					            events = RedisEventEmitter(name, **conn_kwargs)
 | 
				
			||||||
 | 
					            super(RedisHuey, self).__init__(
 | 
				
			||||||
 | 
					                queue=queue,
 | 
				
			||||||
 | 
					                result_store=result_store,
 | 
				
			||||||
 | 
					                schedule=schedule,
 | 
				
			||||||
 | 
					                events=events,
 | 
				
			||||||
 | 
					                store_none=store_none,
 | 
				
			||||||
 | 
					                always_eager=always_eager)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    class RedisHuey(object):
 | 
				
			||||||
 | 
					        def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					            raise RuntimeError('Error, "redis" is not installed. Install '
 | 
				
			||||||
 | 
					                               'using pip: "pip install redis"')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from huey.backends.sqlite_backend import SqliteQueue
 | 
				
			||||||
 | 
					    from huey.backends.sqlite_backend import SqliteDataStore
 | 
				
			||||||
 | 
					    from huey.backends.sqlite_backend import SqliteSchedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class SqliteHuey(Huey):
 | 
				
			||||||
 | 
					        def __init__(self, name='huey', store_none=False, always_eager=False,
 | 
				
			||||||
 | 
					                     location=None):
 | 
				
			||||||
 | 
					            if location is None:
 | 
				
			||||||
 | 
					                raise ValueError("Please specify a database file with the "
 | 
				
			||||||
 | 
					                                 "'location' parameter")
 | 
				
			||||||
 | 
					            queue = SqliteQueue(name, location)
 | 
				
			||||||
 | 
					            result_store = SqliteDataStore(name, location)
 | 
				
			||||||
 | 
					            schedule = SqliteSchedule(name, location)
 | 
				
			||||||
 | 
					            super(SqliteHuey, self).__init__(
 | 
				
			||||||
 | 
					                queue=queue,
 | 
				
			||||||
 | 
					                result_store=result_store,
 | 
				
			||||||
 | 
					                schedule=schedule,
 | 
				
			||||||
 | 
					                events=None,
 | 
				
			||||||
 | 
					                store_none=store_none,
 | 
				
			||||||
 | 
					                always_eager=always_eager)
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    class SqliteHuey(object):
 | 
				
			||||||
 | 
					        def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					            raise RuntimeError('Error, "sqlite" is not installed.')
 | 
				
			||||||
							
								
								
									
										513
									
								
								lib/huey/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								lib/huey/api.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,513 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import pickle
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummySchedule
 | 
				
			||||||
 | 
					from huey.exceptions import DataStoreGetException
 | 
				
			||||||
 | 
					from huey.exceptions import DataStorePutException
 | 
				
			||||||
 | 
					from huey.exceptions import DataStoreTimeout
 | 
				
			||||||
 | 
					from huey.exceptions import QueueException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueReadException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueRemoveException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueWriteException
 | 
				
			||||||
 | 
					from huey.exceptions import ScheduleAddException
 | 
				
			||||||
 | 
					from huey.exceptions import ScheduleReadException
 | 
				
			||||||
 | 
					from huey.registry import registry
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					from huey.utils import local_to_utc
 | 
				
			||||||
 | 
					from huey.utils import wrap_exception
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Huey(object):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Huey executes tasks by exposing function decorators that cause the function
 | 
				
			||||||
 | 
					    call to be enqueued for execution by the consumer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Typically your application will only need one Huey instance, but you can
 | 
				
			||||||
 | 
					    have as many as you like -- the only caveat is that one consumer process
 | 
				
			||||||
 | 
					    must be executed for each Huey instance.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param queue: a queue instance, e.g. ``RedisQueue()``
 | 
				
			||||||
 | 
					    :param result_store: a place to store results, e.g. ``RedisResultStore()``
 | 
				
			||||||
 | 
					    :param schedule: a place to store pending tasks, e.g. ``RedisSchedule()``
 | 
				
			||||||
 | 
					    :param events: channel to send events on, e.g. ``RedisEventEmitter()``
 | 
				
			||||||
 | 
					    :param store_none: Flag to indicate whether tasks that return ``None``
 | 
				
			||||||
 | 
					        should store their results in the result store.
 | 
				
			||||||
 | 
					    :param always_eager: Useful for testing, this will execute all tasks
 | 
				
			||||||
 | 
					        immediately, without enqueueing them.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example usage::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        from huey.api import Huey, crontab
 | 
				
			||||||
 | 
					        from huey.backends.redis_backend import RedisQueue, RedisDataStore, RedisSchedule
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        queue = RedisQueue('my-app')
 | 
				
			||||||
 | 
					        result_store = RedisDataStore('my-app')
 | 
				
			||||||
 | 
					        schedule = RedisSchedule('my-app')
 | 
				
			||||||
 | 
					        huey = Huey(queue, result_store, schedule)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # This is equivalent to the previous 4 lines:
 | 
				
			||||||
 | 
					        # huey = RedisHuey('my-app', {'host': 'localhost', 'port': 6379})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @huey.task()
 | 
				
			||||||
 | 
					        def slow_function(some_arg):
 | 
				
			||||||
 | 
					            # ... do something ...
 | 
				
			||||||
 | 
					            return some_arg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @huey.periodic_task(crontab(minute='0', hour='3'))
 | 
				
			||||||
 | 
					        def backup():
 | 
				
			||||||
 | 
					            # do a backup every day at 3am
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, queue, result_store=None, schedule=None, events=None,
 | 
				
			||||||
 | 
					                 store_none=False, always_eager=False):
 | 
				
			||||||
 | 
					        self.queue = queue
 | 
				
			||||||
 | 
					        self.result_store = result_store
 | 
				
			||||||
 | 
					        self.schedule = schedule or DummySchedule(self.queue.name)
 | 
				
			||||||
 | 
					        self.events = events
 | 
				
			||||||
 | 
					        self.blocking = self.queue.blocking
 | 
				
			||||||
 | 
					        self.store_none = store_none
 | 
				
			||||||
 | 
					        self.always_eager = always_eager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def task(self, retries=0, retry_delay=0, retries_as_argument=False,
 | 
				
			||||||
 | 
					             include_task=False, name=None):
 | 
				
			||||||
 | 
					        def decorator(func):
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					            Decorator to execute a function out-of-band via the consumer.
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					            klass = create_task(
 | 
				
			||||||
 | 
					                QueueTask,
 | 
				
			||||||
 | 
					                func,
 | 
				
			||||||
 | 
					                retries_as_argument,
 | 
				
			||||||
 | 
					                name,
 | 
				
			||||||
 | 
					                include_task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def schedule(args=None, kwargs=None, eta=None, delay=None,
 | 
				
			||||||
 | 
					                         convert_utc=True, task_id=None):
 | 
				
			||||||
 | 
					                if delay and eta:
 | 
				
			||||||
 | 
					                    raise ValueError('Both a delay and an eta cannot be '
 | 
				
			||||||
 | 
					                                     'specified at the same time')
 | 
				
			||||||
 | 
					                if delay:
 | 
				
			||||||
 | 
					                    eta = (datetime.datetime.now() +
 | 
				
			||||||
 | 
					                           datetime.timedelta(seconds=delay))
 | 
				
			||||||
 | 
					                if convert_utc and eta:
 | 
				
			||||||
 | 
					                    eta = local_to_utc(eta)
 | 
				
			||||||
 | 
					                cmd = klass(
 | 
				
			||||||
 | 
					                    (args or (), kwargs or {}),
 | 
				
			||||||
 | 
					                    execute_time=eta,
 | 
				
			||||||
 | 
					                    retries=retries,
 | 
				
			||||||
 | 
					                    retry_delay=retry_delay,
 | 
				
			||||||
 | 
					                    task_id=task_id)
 | 
				
			||||||
 | 
					                return self.enqueue(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            func.schedule = schedule
 | 
				
			||||||
 | 
					            func.task_class = klass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @wraps(func)
 | 
				
			||||||
 | 
					            def inner_run(*args, **kwargs):
 | 
				
			||||||
 | 
					                cmd = klass(
 | 
				
			||||||
 | 
					                    (args, kwargs),
 | 
				
			||||||
 | 
					                    retries=retries,
 | 
				
			||||||
 | 
					                    retry_delay=retry_delay)
 | 
				
			||||||
 | 
					                return self.enqueue(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            inner_run.call_local = func
 | 
				
			||||||
 | 
					            return inner_run
 | 
				
			||||||
 | 
					        return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def periodic_task(self, validate_datetime, name=None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Decorator to execute a function on a specific schedule.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        def decorator(func):
 | 
				
			||||||
 | 
					            def method_validate(self, dt):
 | 
				
			||||||
 | 
					                return validate_datetime(dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            klass = create_task(
 | 
				
			||||||
 | 
					                PeriodicQueueTask,
 | 
				
			||||||
 | 
					                func,
 | 
				
			||||||
 | 
					                task_name=name,
 | 
				
			||||||
 | 
					                validate_datetime=method_validate,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            func.task_class = klass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def _revoke(revoke_until=None, revoke_once=False):
 | 
				
			||||||
 | 
					                self.revoke(klass(), revoke_until, revoke_once)
 | 
				
			||||||
 | 
					            func.revoke = _revoke
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def _is_revoked(dt=None, peek=True):
 | 
				
			||||||
 | 
					                return self.is_revoked(klass(), dt, peek)
 | 
				
			||||||
 | 
					            func.is_revoked = _is_revoked
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def _restore():
 | 
				
			||||||
 | 
					                return self.restore(klass())
 | 
				
			||||||
 | 
					            func.restore = _restore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return func
 | 
				
			||||||
 | 
					        return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _wrapped_operation(exc_class):
 | 
				
			||||||
 | 
					        def decorator(fn):
 | 
				
			||||||
 | 
					            def inner(*args, **kwargs):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    return fn(*args, **kwargs)
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    wrap_exception(exc_class)
 | 
				
			||||||
 | 
					            return inner
 | 
				
			||||||
 | 
					        return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(QueueWriteException)
 | 
				
			||||||
 | 
					    def _write(self, msg):
 | 
				
			||||||
 | 
					        self.queue.write(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(QueueReadException)
 | 
				
			||||||
 | 
					    def _read(self):
 | 
				
			||||||
 | 
					        return self.queue.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(QueueRemoveException)
 | 
				
			||||||
 | 
					    def _remove(self, msg):
 | 
				
			||||||
 | 
					        return self.queue.remove(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(DataStoreGetException)
 | 
				
			||||||
 | 
					    def _get(self, key, peek=False):
 | 
				
			||||||
 | 
					        if peek:
 | 
				
			||||||
 | 
					            return self.result_store.peek(key)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.result_store.get(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(DataStorePutException)
 | 
				
			||||||
 | 
					    def _put(self, key, value):
 | 
				
			||||||
 | 
					        return self.result_store.put(key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(ScheduleAddException)
 | 
				
			||||||
 | 
					    def _add_schedule(self, data, ts):
 | 
				
			||||||
 | 
					        if self.schedule is None:
 | 
				
			||||||
 | 
					            raise AttributeError('Schedule not specified.')
 | 
				
			||||||
 | 
					        self.schedule.add(data, ts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @_wrapped_operation(ScheduleReadException)
 | 
				
			||||||
 | 
					    def _read_schedule(self, ts):
 | 
				
			||||||
 | 
					        if self.schedule is None:
 | 
				
			||||||
 | 
					            raise AttributeError('Schedule not specified.')
 | 
				
			||||||
 | 
					        return self.schedule.read(ts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, message):
 | 
				
			||||||
 | 
					        """Events should always fail silently."""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.events.emit(message)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def enqueue(self, task):
 | 
				
			||||||
 | 
					        if self.always_eager:
 | 
				
			||||||
 | 
					            return task.execute()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._write(registry.get_message_for_task(task))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.result_store:
 | 
				
			||||||
 | 
					            return AsyncData(self, task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dequeue(self):
 | 
				
			||||||
 | 
					        message = self._read()
 | 
				
			||||||
 | 
					        if message:
 | 
				
			||||||
 | 
					            return registry.get_task_for_message(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _format_time(self, dt):
 | 
				
			||||||
 | 
					        if dt is None:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        return time.mktime(dt.timetuple())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit_task(self, status, task, error=False):
 | 
				
			||||||
 | 
					        if self.events:
 | 
				
			||||||
 | 
					            message_data = {'status': status}
 | 
				
			||||||
 | 
					            message_data.update({
 | 
				
			||||||
 | 
					                'id': task.task_id,
 | 
				
			||||||
 | 
					                'task': type(task).__name__,
 | 
				
			||||||
 | 
					                'retries': task.retries,
 | 
				
			||||||
 | 
					                'retry_delay': task.retry_delay,
 | 
				
			||||||
 | 
					                'execute_time': self._format_time(task.execute_time),
 | 
				
			||||||
 | 
					                'error': error})
 | 
				
			||||||
 | 
					            if error:
 | 
				
			||||||
 | 
					                message_data['traceback'] = traceback.format_exc()
 | 
				
			||||||
 | 
					            self.emit(json.dumps(message_data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self, task):
 | 
				
			||||||
 | 
					        if not isinstance(task, QueueTask):
 | 
				
			||||||
 | 
					            raise TypeError('Unknown object: %s' % task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = task.execute()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if result is None and not self.store_none:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.result_store and not isinstance(task, PeriodicQueueTask):
 | 
				
			||||||
 | 
					            self._put(task.task_id, pickle.dumps(result))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def revoke(self, task, revoke_until=None, revoke_once=False):
 | 
				
			||||||
 | 
					        if not self.result_store:
 | 
				
			||||||
 | 
					            raise QueueException('A DataStore is required to revoke task')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serialized = pickle.dumps((revoke_until, revoke_once))
 | 
				
			||||||
 | 
					        self._put(task.revoke_id, serialized)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def restore(self, task):
 | 
				
			||||||
 | 
					        self._get(task.revoke_id)  # simply get and delete if there
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_revoked(self, task, dt=None, peek=True):
 | 
				
			||||||
 | 
					        if not self.result_store:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        res = self._get(task.revoke_id, peek=True)
 | 
				
			||||||
 | 
					        if res is EmptyData:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        revoke_until, revoke_once = pickle.loads(res)
 | 
				
			||||||
 | 
					        if revoke_once:
 | 
				
			||||||
 | 
					            # This task *was* revoked for one run, but now it should be
 | 
				
			||||||
 | 
					            # restored to normal execution.
 | 
				
			||||||
 | 
					            if not peek:
 | 
				
			||||||
 | 
					                self.restore(task)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        return revoke_until is None or revoke_until > dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_schedule(self, task):
 | 
				
			||||||
 | 
					        msg = registry.get_message_for_task(task)
 | 
				
			||||||
 | 
					        ex_time = task.execute_time or datetime.datetime.fromtimestamp(0)
 | 
				
			||||||
 | 
					        self._add_schedule(msg, ex_time)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_schedule(self, ts):
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            registry.get_task_for_message(m) for m in self._read_schedule(ts)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self.queue.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def ready_to_run(self, cmd, dt=None):
 | 
				
			||||||
 | 
					        dt = dt or datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					        return cmd.execute_time is None or cmd.execute_time <= dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AsyncData(object):
 | 
				
			||||||
 | 
					    def __init__(self, huey, task):
 | 
				
			||||||
 | 
					        self.huey = huey
 | 
				
			||||||
 | 
					        self.task = task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._result = EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get(self):
 | 
				
			||||||
 | 
					        task_id = self.task.task_id
 | 
				
			||||||
 | 
					        if self._result is EmptyData:
 | 
				
			||||||
 | 
					            res = self.huey._get(task_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if res is not EmptyData:
 | 
				
			||||||
 | 
					                self._result = pickle.loads(res)
 | 
				
			||||||
 | 
					                return self._result
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return res
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self._result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, blocking=False, timeout=None, backoff=1.15, max_delay=1.0,
 | 
				
			||||||
 | 
					            revoke_on_timeout=False):
 | 
				
			||||||
 | 
					        if not blocking:
 | 
				
			||||||
 | 
					            res = self._get()
 | 
				
			||||||
 | 
					            if res is not EmptyData:
 | 
				
			||||||
 | 
					                return res
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            start = time.time()
 | 
				
			||||||
 | 
					            delay = .1
 | 
				
			||||||
 | 
					            while self._result is EmptyData:
 | 
				
			||||||
 | 
					                if timeout and time.time() - start >= timeout:
 | 
				
			||||||
 | 
					                    if revoke_on_timeout:
 | 
				
			||||||
 | 
					                        self.revoke()
 | 
				
			||||||
 | 
					                    raise DataStoreTimeout
 | 
				
			||||||
 | 
					                if delay > max_delay:
 | 
				
			||||||
 | 
					                    delay = max_delay
 | 
				
			||||||
 | 
					                if self._get() is EmptyData:
 | 
				
			||||||
 | 
					                    time.sleep(delay)
 | 
				
			||||||
 | 
					                    delay *= backoff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return self._result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def revoke(self):
 | 
				
			||||||
 | 
					        self.huey.revoke(self.task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def restore(self):
 | 
				
			||||||
 | 
					        self.huey.restore(self.task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def with_metaclass(meta, base=object):
 | 
				
			||||||
 | 
					    return meta("NewBase", (base,), {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueTaskMetaClass(type):
 | 
				
			||||||
 | 
					    def __init__(cls, name, bases, attrs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Metaclass to ensure that all task classes are registered
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        registry.register(cls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueTask(with_metaclass(QueueTaskMetaClass)):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A class that encapsulates the logic necessary to 'do something' given some
 | 
				
			||||||
 | 
					    arbitrary data.  When enqueued with the :class:`Huey`, it will be
 | 
				
			||||||
 | 
					    stored in a queue for out-of-band execution via the consumer.  See also
 | 
				
			||||||
 | 
					    the :meth:`task` decorator, which can be used to automatically
 | 
				
			||||||
 | 
					    execute any function out-of-band.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Example::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class SendEmailTask(QueueTask):
 | 
				
			||||||
 | 
					        def execute(self):
 | 
				
			||||||
 | 
					            data = self.get_data()
 | 
				
			||||||
 | 
					            send_email(data['recipient'], data['subject'], data['body'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    huey.enqueue(
 | 
				
			||||||
 | 
					        SendEmailTask({
 | 
				
			||||||
 | 
					            'recipient': 'somebody@spam.com',
 | 
				
			||||||
 | 
					            'subject': 'look at this awesome website',
 | 
				
			||||||
 | 
					            'body': 'http://youtube.com'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, data=None, task_id=None, execute_time=None, retries=0,
 | 
				
			||||||
 | 
					                 retry_delay=0):
 | 
				
			||||||
 | 
					        self.set_data(data)
 | 
				
			||||||
 | 
					        self.task_id = task_id or self.create_id()
 | 
				
			||||||
 | 
					        self.revoke_id = 'r:%s' % self.task_id
 | 
				
			||||||
 | 
					        self.execute_time = execute_time
 | 
				
			||||||
 | 
					        self.retries = retries
 | 
				
			||||||
 | 
					        self.retry_delay = retry_delay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_id(self):
 | 
				
			||||||
 | 
					        return str(uuid.uuid4())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_data(self):
 | 
				
			||||||
 | 
					        return self.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_data(self, data):
 | 
				
			||||||
 | 
					        self.data = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self):
 | 
				
			||||||
 | 
					        """Execute any arbitary code here"""
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __eq__(self, rhs):
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            self.task_id == rhs.task_id and
 | 
				
			||||||
 | 
					            self.execute_time == rhs.execute_time and
 | 
				
			||||||
 | 
					            type(self) == type(rhs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PeriodicQueueTask(QueueTask):
 | 
				
			||||||
 | 
					    def create_id(self):
 | 
				
			||||||
 | 
					        return registry.task_to_string(type(self))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_datetime(self, dt):
 | 
				
			||||||
 | 
					        """Validate that the task should execute at the given datetime"""
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_task(task_class, func, retries_as_argument=False, task_name=None,
 | 
				
			||||||
 | 
					                include_task=False, **kwargs):
 | 
				
			||||||
 | 
					    def execute(self):
 | 
				
			||||||
 | 
					        args, kwargs = self.data or ((), {})
 | 
				
			||||||
 | 
					        if retries_as_argument:
 | 
				
			||||||
 | 
					            kwargs['retries'] = self.retries
 | 
				
			||||||
 | 
					        if include_task:
 | 
				
			||||||
 | 
					            kwargs['task'] = self
 | 
				
			||||||
 | 
					        return func(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    attrs = {
 | 
				
			||||||
 | 
					        'execute': execute,
 | 
				
			||||||
 | 
					        '__module__': func.__module__,
 | 
				
			||||||
 | 
					        '__doc__': func.__doc__
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    attrs.update(kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    klass = type(
 | 
				
			||||||
 | 
					        task_name or 'queuecmd_%s' % (func.__name__),
 | 
				
			||||||
 | 
					        (task_class,),
 | 
				
			||||||
 | 
					        attrs
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return klass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dash_re = re.compile('(\d+)-(\d+)')
 | 
				
			||||||
 | 
					every_re = re.compile('\*\/(\d+)')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def crontab(month='*', day='*', day_of_week='*', hour='*', minute='*'):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Convert a "crontab"-style set of parameters into a test function that will
 | 
				
			||||||
 | 
					    return True when the given datetime matches the parameters set forth in
 | 
				
			||||||
 | 
					    the crontab.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Acceptable inputs:
 | 
				
			||||||
 | 
					    * = every distinct value
 | 
				
			||||||
 | 
					    */n = run every "n" times, i.e. hours='*/4' == 0, 4, 8, 12, 16, 20
 | 
				
			||||||
 | 
					    m-n = run every time m..n
 | 
				
			||||||
 | 
					    m,n = run on m and n
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    validation = (
 | 
				
			||||||
 | 
					        ('m', month, range(1, 13)),
 | 
				
			||||||
 | 
					        ('d', day, range(1, 32)),
 | 
				
			||||||
 | 
					        ('w', day_of_week, range(7)),
 | 
				
			||||||
 | 
					        ('H', hour, range(24)),
 | 
				
			||||||
 | 
					        ('M', minute, range(60))
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cron_settings = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (date_str, value, acceptable) in validation:
 | 
				
			||||||
 | 
					        settings = set([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isinstance(value, int):
 | 
				
			||||||
 | 
					            value = str(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for piece in value.split(','):
 | 
				
			||||||
 | 
					            if piece == '*':
 | 
				
			||||||
 | 
					                settings.update(acceptable)
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if piece.isdigit():
 | 
				
			||||||
 | 
					                piece = int(piece)
 | 
				
			||||||
 | 
					                if piece not in acceptable:
 | 
				
			||||||
 | 
					                    raise ValueError('%d is not a valid input' % piece)
 | 
				
			||||||
 | 
					                settings.add(piece)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                dash_match = dash_re.match(piece)
 | 
				
			||||||
 | 
					                if dash_match:
 | 
				
			||||||
 | 
					                    lhs, rhs = map(int, dash_match.groups())
 | 
				
			||||||
 | 
					                    if lhs not in acceptable or rhs not in acceptable:
 | 
				
			||||||
 | 
					                        raise ValueError('%s is not a valid input' % piece)
 | 
				
			||||||
 | 
					                    settings.update(range(lhs, rhs+1))
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                every_match = every_re.match(piece)
 | 
				
			||||||
 | 
					                if every_match:
 | 
				
			||||||
 | 
					                    interval = int(every_match.groups()[0])
 | 
				
			||||||
 | 
					                    settings.update(acceptable[::interval])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cron_settings.append(sorted(list(settings)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate_date(dt):
 | 
				
			||||||
 | 
					        _, m, d, H, M, _, w, _, _ = dt.timetuple()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fix the weekday to be sunday=0
 | 
				
			||||||
 | 
					        w = (w + 1) % 7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (date_piece, selection) in zip([m, d, w, H, M], cron_settings):
 | 
				
			||||||
 | 
					            if date_piece not in selection:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return validate_date
 | 
				
			||||||
							
								
								
									
										0
									
								
								lib/huey/backends/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/huey/backends/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										113
									
								
								lib/huey/backends/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								lib/huey/backends/base.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					class BaseQueue(object):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base implementation for a Queue, all backends should subclass
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # whether this backend blocks while waiting for new results or should be
 | 
				
			||||||
 | 
					    # polled by the consumer
 | 
				
			||||||
 | 
					    blocking = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize the Queue - this happens once when the module is loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param name: A string representation of the name for this queue
 | 
				
			||||||
 | 
					        :param connection: Connection parameters for the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.connection = connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Push 'data' onto the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Pop 'data' from the queue, returning None if no data is available --
 | 
				
			||||||
 | 
					        an empty queue should not raise an Exception!
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, data):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Remove the given data from the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Delete everything from the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Used primarily in tests, but return the number of items in the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseSchedule(object):
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize the Queue - this happens once when the module is loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param name: A string representation of the name for this queue
 | 
				
			||||||
 | 
					        :param connection: Connection parameters for the queue
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.connection = connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, data, ts):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Add the timestamped data to the task schedule.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self, ts):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Read scheduled items for the given timestamp
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        """Delete all items in schedule."""
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseDataStore(object):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base implementation for a data store
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize the data store
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.name = name
 | 
				
			||||||
 | 
					        self.connection = connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, key, value):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def peek(self, key):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, key):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BaseEventEmitter(object):
 | 
				
			||||||
 | 
					    def __init__(self, channel, **connection):
 | 
				
			||||||
 | 
					        self.channel = channel
 | 
				
			||||||
 | 
					        self.connection = connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, message):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Components = (BaseQueue, BaseDataStore, BaseSchedule, BaseEventEmitter)
 | 
				
			||||||
							
								
								
									
										103
									
								
								lib/huey/backends/dummy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/huey/backends/dummy.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					Test-only implementations of Queue and DataStore.  These will not work for
 | 
				
			||||||
 | 
					real applications because they only store tasks/results in memory.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from collections import deque
 | 
				
			||||||
 | 
					import heapq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.backends.base import BaseDataStore
 | 
				
			||||||
 | 
					from huey.backends.base import BaseEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.base import BaseQueue
 | 
				
			||||||
 | 
					from huey.backends.base import BaseSchedule
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DummyQueue(BaseQueue):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super(DummyQueue, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self._queue = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, data):
 | 
				
			||||||
 | 
					        self._queue.insert(0, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self._queue.pop()
 | 
				
			||||||
 | 
					        except IndexError:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self._queue = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, data):
 | 
				
			||||||
 | 
					        clone = []
 | 
				
			||||||
 | 
					        ct = 0
 | 
				
			||||||
 | 
					        for elem in self._queue:
 | 
				
			||||||
 | 
					            if elem == data:
 | 
				
			||||||
 | 
					                ct += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                clone.append(elem)
 | 
				
			||||||
 | 
					        self._queue = clone
 | 
				
			||||||
 | 
					        return ct
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        return len(self._queue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DummySchedule(BaseSchedule):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super(DummySchedule, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self._schedule = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, data, ts):
 | 
				
			||||||
 | 
					        heapq.heappush(self._schedule, (ts, data))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self, ts):
 | 
				
			||||||
 | 
					        res = []
 | 
				
			||||||
 | 
					        while len(self._schedule):
 | 
				
			||||||
 | 
					            sts, data = heapq.heappop(self._schedule)
 | 
				
			||||||
 | 
					            if sts <= ts:
 | 
				
			||||||
 | 
					                res.append(data)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.add(data, sts)
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        return res
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self._schedule = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DummyDataStore(BaseDataStore):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super(DummyDataStore, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self._results = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, key, value):
 | 
				
			||||||
 | 
					        self._results[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def peek(self, key):
 | 
				
			||||||
 | 
					        return self._results.get(key, EmptyData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, key):
 | 
				
			||||||
 | 
					        return self._results.pop(key, EmptyData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self._results = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DummyEventEmitter(BaseEventEmitter):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        super(DummyEventEmitter, self).__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					        self._events = deque()
 | 
				
			||||||
 | 
					        self.__size = 100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, message):
 | 
				
			||||||
 | 
					        self._events.appendleft(message)
 | 
				
			||||||
 | 
					        num_events = len(self._events)
 | 
				
			||||||
 | 
					        if num_events > self.__size * 1.5:
 | 
				
			||||||
 | 
					            while num_events > self.__size:
 | 
				
			||||||
 | 
					                self._events.popright()
 | 
				
			||||||
 | 
					                num_events -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Components = (DummyQueue, DummyDataStore, DummySchedule, DummyEventEmitter)
 | 
				
			||||||
							
								
								
									
										153
									
								
								lib/huey/backends/rabbitmq_backend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								lib/huey/backends/rabbitmq_backend.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					__author__ = 'deathowl'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import pika
 | 
				
			||||||
 | 
					from pika.exceptions import AMQPConnectionError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.backends.base import BaseEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.base import BaseQueue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def clean_name(name):
 | 
				
			||||||
 | 
					    return re.sub('[^a-z0-9]', '', name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RabbitQueue(BaseQueue):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple Queue that uses the rabbit to store messages
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        connection = {
 | 
				
			||||||
 | 
					            'host': 'localhost',
 | 
				
			||||||
 | 
					            'port': 5672,
 | 
				
			||||||
 | 
					            'username': 'guest',
 | 
				
			||||||
 | 
					            'password': 'guest',
 | 
				
			||||||
 | 
					            'vhost': '/',
 | 
				
			||||||
 | 
					            'ssl': False
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        super(RabbitQueue, self).__init__(name, **connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.queue_name = 'huey.rabbit.%s' % clean_name(name)
 | 
				
			||||||
 | 
					        credentials = pika.PlainCredentials(
 | 
				
			||||||
 | 
					            connection.get('username', 'guest'),
 | 
				
			||||||
 | 
					            connection.get('password', 'guest'))
 | 
				
			||||||
 | 
					        connection_params = pika.ConnectionParameters(
 | 
				
			||||||
 | 
					            host=connection.get('host', 'localhost'),
 | 
				
			||||||
 | 
					            port=connection.get('port', 5672),
 | 
				
			||||||
 | 
					            credentials=credentials,
 | 
				
			||||||
 | 
					            virtual_host=connection.get('vhost', '/'),
 | 
				
			||||||
 | 
					            ssl=connection.get('ssl', False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.conn = pika.BlockingConnection(connection_params)
 | 
				
			||||||
 | 
					        self.channel = self.conn.channel()
 | 
				
			||||||
 | 
					        self.channel.queue_declare(self.queue_name, durable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, data):
 | 
				
			||||||
 | 
					        self.channel.basic_publish(
 | 
				
			||||||
 | 
					            exchange='',
 | 
				
			||||||
 | 
					            routing_key=self.queue_name,
 | 
				
			||||||
 | 
					            body=data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        return self.get_data_from_queue(self.queue_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, data):
 | 
				
			||||||
 | 
					        # This is not something you usually do in rabbit, this is the only
 | 
				
			||||||
 | 
					        # operation, which is not atomic, but this "hack" should do the trick.
 | 
				
			||||||
 | 
					        amount = 0
 | 
				
			||||||
 | 
					        idx = 0
 | 
				
			||||||
 | 
					        qlen = len(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for method_frame, _, body in self.channel.consume(self.queue_name):
 | 
				
			||||||
 | 
					            idx += 1
 | 
				
			||||||
 | 
					            if body == data:
 | 
				
			||||||
 | 
					                self.channel.basic_ack(method_frame.delivery_tag)
 | 
				
			||||||
 | 
					                amount += 1
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.channel.basic_nack(
 | 
				
			||||||
 | 
					                    method_frame.delivery_tag,
 | 
				
			||||||
 | 
					                    requeue=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if idx >= qlen:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.channel.cancel()
 | 
				
			||||||
 | 
					        return amount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self.channel.queue_purge(queue=self.queue_name)
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        queue = self.channel.queue_declare(self.queue_name, durable=True)
 | 
				
			||||||
 | 
					        return queue.method.message_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_data_from_queue(self, queue):
 | 
				
			||||||
 | 
					        data = None
 | 
				
			||||||
 | 
					        if len(self) == 0:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for method_frame, _, body in self.channel.consume(queue):
 | 
				
			||||||
 | 
					            data = body
 | 
				
			||||||
 | 
					            self.channel.basic_ack(method_frame.delivery_tag)
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.channel.cancel()
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RabbitBlockingQueue(RabbitQueue):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Use the blocking right pop, should result in messages getting
 | 
				
			||||||
 | 
					    executed close to immediately by the consumer as opposed to
 | 
				
			||||||
 | 
					    being polled for
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    blocking = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self.get_data_from_queue(self.queue_name)
 | 
				
			||||||
 | 
					        except AMQPConnectionError:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RabbitEventEmitter(BaseEventEmitter):
 | 
				
			||||||
 | 
					    def __init__(self, channel, **connection):
 | 
				
			||||||
 | 
					        super(RabbitEventEmitter, self).__init__(channel, **connection)
 | 
				
			||||||
 | 
					        credentials = pika.PlainCredentials(
 | 
				
			||||||
 | 
					            connection.get('username', 'guest'),
 | 
				
			||||||
 | 
					            connection.get('password', 'guest'))
 | 
				
			||||||
 | 
					        connection_params = pika.ConnectionParameters(
 | 
				
			||||||
 | 
					            host=connection.get('host', 'localhost'),
 | 
				
			||||||
 | 
					            port=connection.get('port', 5672),
 | 
				
			||||||
 | 
					            credentials=credentials,
 | 
				
			||||||
 | 
					            virtual_host=connection.get('vhost', '/'),
 | 
				
			||||||
 | 
					            ssl=connection.get('ssl', False))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.conn = pika.BlockingConnection(connection_params)
 | 
				
			||||||
 | 
					        self.channel = self.conn.channel()
 | 
				
			||||||
 | 
					        self.exchange_name = 'huey.events'
 | 
				
			||||||
 | 
					        self.channel.exchange_declare(
 | 
				
			||||||
 | 
					            exchange=self.exchange_name,
 | 
				
			||||||
 | 
					            type='fanout',
 | 
				
			||||||
 | 
					            auto_delete=False,
 | 
				
			||||||
 | 
					            durable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, message):
 | 
				
			||||||
 | 
					        properties = pika.BasicProperties(
 | 
				
			||||||
 | 
					            content_type="text/plain",
 | 
				
			||||||
 | 
					            delivery_mode=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.channel.basic_publish(
 | 
				
			||||||
 | 
					            exchange=self.exchange_name,
 | 
				
			||||||
 | 
					            routing_key='',
 | 
				
			||||||
 | 
					            body=message,
 | 
				
			||||||
 | 
					            properties=properties)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Components = (RabbitBlockingQueue, None, None, RabbitEventEmitter)
 | 
				
			||||||
							
								
								
									
										153
									
								
								lib/huey/backends/redis_backend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								lib/huey/backends/redis_backend.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					import re
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import redis
 | 
				
			||||||
 | 
					from redis.exceptions import ConnectionError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.backends.base import BaseDataStore
 | 
				
			||||||
 | 
					from huey.backends.base import BaseEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.base import BaseQueue
 | 
				
			||||||
 | 
					from huey.backends.base import BaseSchedule
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def clean_name(name):
 | 
				
			||||||
 | 
					    return re.sub('[^a-z0-9]', '', name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedisQueue(BaseQueue):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple Queue that uses the redis to store messages
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        connection = {
 | 
				
			||||||
 | 
					            'host': 'localhost',
 | 
				
			||||||
 | 
					            'port': 6379,
 | 
				
			||||||
 | 
					            'db': 0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        super(RedisQueue, self).__init__(name, **connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.queue_name = 'huey.redis.%s' % clean_name(name)
 | 
				
			||||||
 | 
					        self.conn = redis.Redis(**connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, data):
 | 
				
			||||||
 | 
					        self.conn.lpush(self.queue_name, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        return self.conn.rpop(self.queue_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, data):
 | 
				
			||||||
 | 
					        return self.conn.lrem(self.queue_name, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self.conn.delete(self.queue_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        return self.conn.llen(self.queue_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedisBlockingQueue(RedisQueue):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Use the blocking right pop, should result in messages getting
 | 
				
			||||||
 | 
					    executed close to immediately by the consumer as opposed to
 | 
				
			||||||
 | 
					    being polled for
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    blocking = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, read_timeout=None, **connection):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        connection = {
 | 
				
			||||||
 | 
					            'host': 'localhost',
 | 
				
			||||||
 | 
					            'port': 6379,
 | 
				
			||||||
 | 
					            'db': 0,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        super(RedisBlockingQueue, self).__init__(name, **connection)
 | 
				
			||||||
 | 
					        self.read_timeout = read_timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self.conn.brpop(
 | 
				
			||||||
 | 
					                self.queue_name,
 | 
				
			||||||
 | 
					                timeout=self.read_timeout)[1]
 | 
				
			||||||
 | 
					        except (ConnectionError, TypeError, IndexError):
 | 
				
			||||||
 | 
					            # unfortunately, there is no way to differentiate a socket timing
 | 
				
			||||||
 | 
					            # out and a host being unreachable
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# a custom lua script to pass to redis that will read tasks from the schedule
 | 
				
			||||||
 | 
					# and atomically pop them from the sorted set and return them.
 | 
				
			||||||
 | 
					# it won't return anything if it isn't able to remove the items it reads.
 | 
				
			||||||
 | 
					SCHEDULE_POP_LUA = """
 | 
				
			||||||
 | 
					local key = KEYS[1]
 | 
				
			||||||
 | 
					local unix_ts = ARGV[1]
 | 
				
			||||||
 | 
					local res = redis.call('zrangebyscore', key, '-inf', unix_ts)
 | 
				
			||||||
 | 
					if #res and redis.call('zremrangebyscore', key, '-inf', unix_ts) == #res then
 | 
				
			||||||
 | 
					    return res
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedisSchedule(BaseSchedule):
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        super(RedisSchedule, self).__init__(name, **connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.key = 'huey.schedule.%s' % clean_name(name)
 | 
				
			||||||
 | 
					        self.conn = redis.Redis(**connection)
 | 
				
			||||||
 | 
					        self._pop = self.conn.register_script(SCHEDULE_POP_LUA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def convert_ts(self, ts):
 | 
				
			||||||
 | 
					        return time.mktime(ts.timetuple())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, data, ts):
 | 
				
			||||||
 | 
					        self.conn.zadd(self.key, data, self.convert_ts(ts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self, ts):
 | 
				
			||||||
 | 
					        unix_ts = self.convert_ts(ts)
 | 
				
			||||||
 | 
					        # invoke the redis lua script that will atomically pop off
 | 
				
			||||||
 | 
					        # all the tasks older than the given timestamp
 | 
				
			||||||
 | 
					        tasks = self._pop(keys=[self.key], args=[unix_ts])
 | 
				
			||||||
 | 
					        return [] if tasks is None else tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self.conn.delete(self.key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedisDataStore(BaseDataStore):
 | 
				
			||||||
 | 
					    def __init__(self, name, **connection):
 | 
				
			||||||
 | 
					        super(RedisDataStore, self).__init__(name, **connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.storage_name = 'huey.results.%s' % clean_name(name)
 | 
				
			||||||
 | 
					        self.conn = redis.Redis(**connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, key, value):
 | 
				
			||||||
 | 
					        self.conn.hset(self.storage_name, key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def peek(self, key):
 | 
				
			||||||
 | 
					        if self.conn.hexists(self.storage_name, key):
 | 
				
			||||||
 | 
					            return self.conn.hget(self.storage_name, key)
 | 
				
			||||||
 | 
					        return EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, key):
 | 
				
			||||||
 | 
					        val = self.peek(key)
 | 
				
			||||||
 | 
					        if val is not EmptyData:
 | 
				
			||||||
 | 
					            self.conn.hdel(self.storage_name, key)
 | 
				
			||||||
 | 
					        return val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        self.conn.delete(self.storage_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedisEventEmitter(BaseEventEmitter):
 | 
				
			||||||
 | 
					    def __init__(self, channel, **connection):
 | 
				
			||||||
 | 
					        super(RedisEventEmitter, self).__init__(channel, **connection)
 | 
				
			||||||
 | 
					        self.conn = redis.Redis(**connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, message):
 | 
				
			||||||
 | 
					        self.conn.publish(self.channel, message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Components = (RedisBlockingQueue, RedisDataStore, RedisSchedule,
 | 
				
			||||||
 | 
					              RedisEventEmitter)
 | 
				
			||||||
							
								
								
									
										205
									
								
								lib/huey/backends/sqlite_backend.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								lib/huey/backends/sqlite_backend.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,205 @@
 | 
				
			|||||||
 | 
					""" SQLite backend for Huey.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Inspired from a snippet by Thiago Arruda [1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[1] http://flask.pocoo.org/snippets/88/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import sqlite3
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from thread import get_ident
 | 
				
			||||||
 | 
					except ImportError:  # Python 3
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from threading import get_ident
 | 
				
			||||||
 | 
					    except ImportError:
 | 
				
			||||||
 | 
					        from _thread import get_ident
 | 
				
			||||||
 | 
					    buffer = memoryview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.backends.base import BaseDataStore
 | 
				
			||||||
 | 
					from huey.backends.base import BaseEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.base import BaseQueue
 | 
				
			||||||
 | 
					from huey.backends.base import BaseSchedule
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _SqliteDatabase(object):
 | 
				
			||||||
 | 
					    def __init__(self, location):
 | 
				
			||||||
 | 
					        if location == ':memory:':
 | 
				
			||||||
 | 
					            raise ValueError("Database location has to be a file path, "
 | 
				
			||||||
 | 
					                             "in-memory databases are not supported.")
 | 
				
			||||||
 | 
					        self.location = location
 | 
				
			||||||
 | 
					        self._conn_cache = {}
 | 
				
			||||||
 | 
					        with self.get_connection() as conn:
 | 
				
			||||||
 | 
					            # Enable write-ahead logging
 | 
				
			||||||
 | 
					            conn.execute("PRAGMA journal_mode=WAL;")
 | 
				
			||||||
 | 
					            # Hand over syncing responsibility to OS
 | 
				
			||||||
 | 
					            conn.execute("PRAGMA synchronous=OFF;")
 | 
				
			||||||
 | 
					            # Store temporary tables and indices in memory
 | 
				
			||||||
 | 
					            conn.execute("PRAGMA temp_store=MEMORY;")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_connection(self, immediate=False):
 | 
				
			||||||
 | 
					        """ Obtain a sqlite3.Connection instance for the database.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Connections are cached on a by-thread basis, i.e. every calling thread
 | 
				
			||||||
 | 
					        will always get the same Connection object back.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if immediate:
 | 
				
			||||||
 | 
					            return sqlite3.Connection(self.location, timeout=60,
 | 
				
			||||||
 | 
					                                      isolation_level="IMMEDIATE")
 | 
				
			||||||
 | 
					        id = get_ident()
 | 
				
			||||||
 | 
					        if id not in self._conn_cache:
 | 
				
			||||||
 | 
					            self._conn_cache[id] = sqlite3.Connection(
 | 
				
			||||||
 | 
					                self.location, timeout=60)
 | 
				
			||||||
 | 
					        return self._conn_cache[id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SqliteQueue(BaseQueue):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple Queue that uses SQLite to store messages
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _create = """
 | 
				
			||||||
 | 
					        CREATE TABLE IF NOT EXISTS {0}
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					          id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					          item BLOB
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _count = "SELECT COUNT(*) FROM {0}"
 | 
				
			||||||
 | 
					    _append = "INSERT INTO {0} (item) VALUES (?)"
 | 
				
			||||||
 | 
					    _get = "SELECT id, item FROM {0} ORDER BY id LIMIT 1"
 | 
				
			||||||
 | 
					    _remove_by_value = "DELETE FROM {0} WHERE item = ?"
 | 
				
			||||||
 | 
					    _remove_by_id = "DELETE FROM {0} WHERE id = ?"
 | 
				
			||||||
 | 
					    _flush = "DELETE FROM {0}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, location):
 | 
				
			||||||
 | 
					        super(SqliteQueue, self).__init__(name, location=location)
 | 
				
			||||||
 | 
					        self.queue_name = 'huey_queue_{0}'.format(name)
 | 
				
			||||||
 | 
					        self._db = _SqliteDatabase(location)
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._create.format(self.queue_name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, data):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._append.format(self.queue_name), (data,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self):
 | 
				
			||||||
 | 
					        with self._db.get_connection(immediate=True) as conn:
 | 
				
			||||||
 | 
					            cursor = conn.execute(self._get.format(self.queue_name))
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                id, data = next(cursor)
 | 
				
			||||||
 | 
					            except StopIteration:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					            if id:
 | 
				
			||||||
 | 
					                conn.execute(self._remove_by_id.format(self.queue_name), (id,))
 | 
				
			||||||
 | 
					                return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, data):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            return conn.execute(self._remove_by_value.format(self.queue_name),
 | 
				
			||||||
 | 
					                                (data,)).rowcount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._flush.format(self.queue_name,))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __len__(self):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            return next(conn.execute(self._count.format(self.queue_name)))[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SqliteSchedule(BaseSchedule):
 | 
				
			||||||
 | 
					    _create = """
 | 
				
			||||||
 | 
					        CREATE TABLE IF NOT EXISTS {0}
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					          id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					          item BLOB,
 | 
				
			||||||
 | 
					          timestamp INTEGER
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _read_items = """
 | 
				
			||||||
 | 
					        SELECT item, timestamp FROM {0} WHERE timestamp <= ?
 | 
				
			||||||
 | 
					        ORDER BY timestamp
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _delete_items = "DELETE FROM {0} WHERE timestamp <= ?"
 | 
				
			||||||
 | 
					    _add_item = "INSERT INTO {0} (item, timestamp) VALUES (?, ?)"
 | 
				
			||||||
 | 
					    _flush = "DELETE FROM {0}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, location):
 | 
				
			||||||
 | 
					        super(SqliteSchedule, self).__init__(name, location=location)
 | 
				
			||||||
 | 
					        self._db = _SqliteDatabase(location)
 | 
				
			||||||
 | 
					        self.name = 'huey_schedule_{0}'.format(name)
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._create.format(self.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def convert_ts(self, ts):
 | 
				
			||||||
 | 
					        return time.mktime(ts.timetuple())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add(self, data, ts):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._add_item.format(self.name),
 | 
				
			||||||
 | 
					                         (data, self.convert_ts(ts)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read(self, ts):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            results = conn.execute(self._read_items.format(self.name),
 | 
				
			||||||
 | 
					                                   (self.convert_ts(ts),)).fetchall()
 | 
				
			||||||
 | 
					            conn.execute(self._delete_items.format(self.name),
 | 
				
			||||||
 | 
					                         (self.convert_ts(ts),))
 | 
				
			||||||
 | 
					        return [data for data, _ in results]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._flush.format(self.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SqliteDataStore(BaseDataStore):
 | 
				
			||||||
 | 
					    _create = """
 | 
				
			||||||
 | 
					        CREATE TABLE IF NOT EXISTS {0}
 | 
				
			||||||
 | 
					        (
 | 
				
			||||||
 | 
					          id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
				
			||||||
 | 
					          key TEXT,
 | 
				
			||||||
 | 
					          result BLOB
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _put = "INSERT INTO {0} (key, result) VALUES (?, ?)"
 | 
				
			||||||
 | 
					    _peek = "SELECT result FROM {0} WHERE key = ?"
 | 
				
			||||||
 | 
					    _remove = "DELETE FROM {0} WHERE key = ?"
 | 
				
			||||||
 | 
					    _flush = "DELETE FROM {0}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, name, location):
 | 
				
			||||||
 | 
					        super(SqliteDataStore, self).__init__(name, location=location)
 | 
				
			||||||
 | 
					        self._db = _SqliteDatabase(location)
 | 
				
			||||||
 | 
					        self.name = 'huey_results_{0}'.format(name)
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._create.format(self.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def put(self, key, value):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._remove.format(self.name), (key,))
 | 
				
			||||||
 | 
					            conn.execute(self._put.format(self.name), (key, value))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def peek(self, key):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                return next(conn.execute(self._peek.format(self.name),
 | 
				
			||||||
 | 
					                                         (key,)))[0]
 | 
				
			||||||
 | 
					            except StopIteration:
 | 
				
			||||||
 | 
					                return EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, key):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                data = next(conn.execute(self._peek.format(self.name),
 | 
				
			||||||
 | 
					                                         (key,)))[0]
 | 
				
			||||||
 | 
					                conn.execute(self._remove.format(self.name), (key,))
 | 
				
			||||||
 | 
					                return data
 | 
				
			||||||
 | 
					            except StopIteration:
 | 
				
			||||||
 | 
					                return EmptyData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        with self._db.get_connection() as conn:
 | 
				
			||||||
 | 
					            conn.execute(self._flush.format(self.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Components = (SqliteQueue, SqliteDataStore, SqliteSchedule, None)
 | 
				
			||||||
							
								
								
									
										0
									
								
								lib/huey/bin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/huey/bin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										121
									
								
								lib/huey/bin/huey_consumer.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										121
									
								
								lib/huey/bin/huey_consumer.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import optparse
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					from logging.handlers import RotatingFileHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.consumer import Consumer
 | 
				
			||||||
 | 
					from huey.utils import load_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def err(s):
 | 
				
			||||||
 | 
					    sys.stderr.write('\033[91m%s\033[0m\n' % s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_loglevel(verbose=None):
 | 
				
			||||||
 | 
					    if verbose is None:
 | 
				
			||||||
 | 
					        return logging.INFO
 | 
				
			||||||
 | 
					    elif verbose:
 | 
				
			||||||
 | 
					        return logging.DEBUG
 | 
				
			||||||
 | 
					    return logging.ERROR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_logger(loglevel, logfile):
 | 
				
			||||||
 | 
					    log_format = ('%(threadName)s %(asctime)s %(name)s '
 | 
				
			||||||
 | 
					                  '%(levelname)s %(message)s')
 | 
				
			||||||
 | 
					    logging.basicConfig(level=loglevel, format=log_format)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if logfile:
 | 
				
			||||||
 | 
					        handler = RotatingFileHandler(
 | 
				
			||||||
 | 
					            logfile, maxBytes=1024*1024, backupCount=3)
 | 
				
			||||||
 | 
					        handler.setFormatter(logging.Formatter(log_format))
 | 
				
			||||||
 | 
					        logging.getLogger().addHandler(handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_option_parser():
 | 
				
			||||||
 | 
					    parser = optparse.OptionParser(
 | 
				
			||||||
 | 
					        'Usage: %prog [options] path.to.huey_instance')
 | 
				
			||||||
 | 
					    parser.add_option('-l', '--logfile', dest='logfile',
 | 
				
			||||||
 | 
					                      help='write logs to FILE', metavar='FILE')
 | 
				
			||||||
 | 
					    parser.add_option('-v', '--verbose', dest='verbose',
 | 
				
			||||||
 | 
					                      help='verbose logging', action='store_true')
 | 
				
			||||||
 | 
					    parser.add_option('-q', '--quiet', dest='verbose',
 | 
				
			||||||
 | 
					                      help='log exceptions only', action='store_false')
 | 
				
			||||||
 | 
					    parser.add_option('-w', '--workers', dest='workers', type='int',
 | 
				
			||||||
 | 
					                      help='worker threads (default=1)', default=1)
 | 
				
			||||||
 | 
					    parser.add_option('-t', '--threads', dest='workers', type='int',
 | 
				
			||||||
 | 
					                      help='same as "workers"', default=1)
 | 
				
			||||||
 | 
					    parser.add_option('-p', '--periodic', dest='periodic', default=True,
 | 
				
			||||||
 | 
					                      help='execute periodic tasks (default=True)',
 | 
				
			||||||
 | 
					                      action='store_true')
 | 
				
			||||||
 | 
					    parser.add_option('-n', '--no-periodic', dest='periodic',
 | 
				
			||||||
 | 
					                      help='do NOT execute periodic tasks',
 | 
				
			||||||
 | 
					                      action='store_false')
 | 
				
			||||||
 | 
					    parser.add_option('-d', '--delay', dest='initial_delay', type='float',
 | 
				
			||||||
 | 
					                      help='initial delay in seconds (default=0.1)',
 | 
				
			||||||
 | 
					                      default=0.1)
 | 
				
			||||||
 | 
					    parser.add_option('-m', '--max-delay', dest='max_delay', type='float',
 | 
				
			||||||
 | 
					                      help='maximum time to wait between polling the queue '
 | 
				
			||||||
 | 
					                           '(default=10)',
 | 
				
			||||||
 | 
					                      default=10)
 | 
				
			||||||
 | 
					    parser.add_option('-b', '--backoff', dest='backoff', type='float',
 | 
				
			||||||
 | 
					                      help='amount to backoff delay when no results present '
 | 
				
			||||||
 | 
					                           '(default=1.15)',
 | 
				
			||||||
 | 
					                      default=1.15)
 | 
				
			||||||
 | 
					    parser.add_option('-P', '--periodic-task-interval',
 | 
				
			||||||
 | 
					                      dest='periodic_task_interval',
 | 
				
			||||||
 | 
					                      type='float', help='Granularity of periodic tasks.',
 | 
				
			||||||
 | 
					                      default=60.0)
 | 
				
			||||||
 | 
					    parser.add_option('-S', '--scheduler-interval', dest='scheduler_interval',
 | 
				
			||||||
 | 
					                      type='float', help='Granularity of scheduler.',
 | 
				
			||||||
 | 
					                      default=1.0)
 | 
				
			||||||
 | 
					    parser.add_option('-u', '--utc', dest='utc', action='store_true',
 | 
				
			||||||
 | 
					                      help='use UTC time for all tasks (default=True)',
 | 
				
			||||||
 | 
					                      default=True)
 | 
				
			||||||
 | 
					    parser.add_option('--localtime', dest='utc', action='store_false',
 | 
				
			||||||
 | 
					                      help='use local time for all tasks')
 | 
				
			||||||
 | 
					    return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_huey(path):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return load_class(path)
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        cur_dir = os.getcwd()
 | 
				
			||||||
 | 
					        if cur_dir not in sys.path:
 | 
				
			||||||
 | 
					            sys.path.insert(0, cur_dir)
 | 
				
			||||||
 | 
					            return load_huey(path)
 | 
				
			||||||
 | 
					        err('Error importing %s' % path)
 | 
				
			||||||
 | 
					        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def consumer_main():
 | 
				
			||||||
 | 
					    parser = get_option_parser()
 | 
				
			||||||
 | 
					    options, args = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setup_logger(get_loglevel(options.verbose), options.logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if len(args) == 0:
 | 
				
			||||||
 | 
					        err('Error:   missing import path to `Huey` instance')
 | 
				
			||||||
 | 
					        err('Example: huey_consumer.py app.queue.huey_instance')
 | 
				
			||||||
 | 
					        sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    huey_instance = load_huey(args[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    consumer = Consumer(
 | 
				
			||||||
 | 
					        huey_instance,
 | 
				
			||||||
 | 
					        options.workers,
 | 
				
			||||||
 | 
					        options.periodic,
 | 
				
			||||||
 | 
					        options.initial_delay,
 | 
				
			||||||
 | 
					        options.backoff,
 | 
				
			||||||
 | 
					        options.max_delay,
 | 
				
			||||||
 | 
					        options.utc,
 | 
				
			||||||
 | 
					        options.scheduler_interval,
 | 
				
			||||||
 | 
					        options.periodic_task_interval)
 | 
				
			||||||
 | 
					    consumer.run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    consumer_main()
 | 
				
			||||||
							
								
								
									
										279
									
								
								lib/huey/consumer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								lib/huey/consumer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,279 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.exceptions import DataStoreGetException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueReadException
 | 
				
			||||||
 | 
					from huey.exceptions import DataStorePutException
 | 
				
			||||||
 | 
					from huey.exceptions import QueueWriteException
 | 
				
			||||||
 | 
					from huey.exceptions import ScheduleAddException
 | 
				
			||||||
 | 
					from huey.exceptions import ScheduleReadException
 | 
				
			||||||
 | 
					from huey.registry import registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConsumerThread(threading.Thread):
 | 
				
			||||||
 | 
					    def __init__(self, huey, utc, shutdown, interval=60):
 | 
				
			||||||
 | 
					        self.huey = huey
 | 
				
			||||||
 | 
					        self.utc = utc
 | 
				
			||||||
 | 
					        self.shutdown = shutdown
 | 
				
			||||||
 | 
					        self.interval = interval
 | 
				
			||||||
 | 
					        self._logger = logging.getLogger('huey.consumer.ConsumerThread')
 | 
				
			||||||
 | 
					        super(ConsumerThread, self).__init__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_now(self):
 | 
				
			||||||
 | 
					        if self.utc:
 | 
				
			||||||
 | 
					            return datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					        return datetime.datetime.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def on_shutdown(self):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def loop(self, now):
 | 
				
			||||||
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        while not self.shutdown.is_set():
 | 
				
			||||||
 | 
					            self.loop()
 | 
				
			||||||
 | 
					        self._logger.debug('Thread shutting down')
 | 
				
			||||||
 | 
					        self.on_shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def enqueue(self, task):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.huey.enqueue(task)
 | 
				
			||||||
 | 
					            self.huey.emit_task('enqueued', task)
 | 
				
			||||||
 | 
					        except QueueWriteException:
 | 
				
			||||||
 | 
					            self._logger.error('Error enqueueing task: %s' % task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_schedule(self, task):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.huey.add_schedule(task)
 | 
				
			||||||
 | 
					            self.huey.emit_task('scheduled', task)
 | 
				
			||||||
 | 
					        except ScheduleAddException:
 | 
				
			||||||
 | 
					            self._logger.error('Error adding task to schedule: %s' % task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_revoked(self, task, ts):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if self.huey.is_revoked(task, ts, peek=False):
 | 
				
			||||||
 | 
					                self.huey.emit_task('revoked', task)
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        except DataStoreGetException:
 | 
				
			||||||
 | 
					            self._logger.error('Error checking if task is revoked: %s' % task)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sleep_for_interval(self, start_ts):
 | 
				
			||||||
 | 
					        delta = time.time() - start_ts
 | 
				
			||||||
 | 
					        if delta < self.interval:
 | 
				
			||||||
 | 
					            time.sleep(self.interval - (time.time() - start_ts))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PeriodicTaskThread(ConsumerThread):
 | 
				
			||||||
 | 
					    def loop(self, now=None):
 | 
				
			||||||
 | 
					        now = now or self.get_now()
 | 
				
			||||||
 | 
					        self._logger.debug('Checking periodic command registry')
 | 
				
			||||||
 | 
					        start = time.time()
 | 
				
			||||||
 | 
					        for task in registry.get_periodic_tasks():
 | 
				
			||||||
 | 
					            if task.validate_datetime(now):
 | 
				
			||||||
 | 
					                self._logger.info('Scheduling %s for execution' % task)
 | 
				
			||||||
 | 
					                self.enqueue(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sleep_for_interval(start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SchedulerThread(ConsumerThread):
 | 
				
			||||||
 | 
					    def read_schedule(self, ts):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self.huey.read_schedule(ts)
 | 
				
			||||||
 | 
					        except ScheduleReadException:
 | 
				
			||||||
 | 
					            self._logger.error('Error reading schedule', exc_info=1)
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def loop(self, now=None):
 | 
				
			||||||
 | 
					        now = now or self.get_now()
 | 
				
			||||||
 | 
					        start = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for task in self.read_schedule(now):
 | 
				
			||||||
 | 
					            self._logger.info('Scheduling %s for execution' % task)
 | 
				
			||||||
 | 
					            self.enqueue(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.sleep_for_interval(start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WorkerThread(ConsumerThread):
 | 
				
			||||||
 | 
					    def __init__(self, huey, default_delay, max_delay, backoff, utc,
 | 
				
			||||||
 | 
					                 shutdown):
 | 
				
			||||||
 | 
					        self.delay = self.default_delay = default_delay
 | 
				
			||||||
 | 
					        self.max_delay = max_delay
 | 
				
			||||||
 | 
					        self.backoff = backoff
 | 
				
			||||||
 | 
					        self._logger = logging.getLogger('huey.consumer.WorkerThread')
 | 
				
			||||||
 | 
					        super(WorkerThread, self).__init__(huey, utc, shutdown)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def loop(self):
 | 
				
			||||||
 | 
					        self.check_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def check_message(self):
 | 
				
			||||||
 | 
					        self._logger.debug('Checking for message')
 | 
				
			||||||
 | 
					        task = exc_raised = None
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            task = self.huey.dequeue()
 | 
				
			||||||
 | 
					        except QueueReadException:
 | 
				
			||||||
 | 
					            self._logger.error('Error reading from queue', exc_info=1)
 | 
				
			||||||
 | 
					            exc_raised = True
 | 
				
			||||||
 | 
					        except QueueException:
 | 
				
			||||||
 | 
					            self._logger.error('Queue exception', exc_info=1)
 | 
				
			||||||
 | 
					            exc_raised = True
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self._logger.error('Unknown exception', exc_info=1)
 | 
				
			||||||
 | 
					            exc_raised = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if task:
 | 
				
			||||||
 | 
					            self.delay = self.default_delay
 | 
				
			||||||
 | 
					            self.handle_task(task, self.get_now())
 | 
				
			||||||
 | 
					        elif exc_raised or not self.huey.blocking:
 | 
				
			||||||
 | 
					            self.sleep()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sleep(self):
 | 
				
			||||||
 | 
					        if self.delay > self.max_delay:
 | 
				
			||||||
 | 
					            self.delay = self.max_delay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._logger.debug('No messages, sleeping for: %s' % self.delay)
 | 
				
			||||||
 | 
					        time.sleep(self.delay)
 | 
				
			||||||
 | 
					        self.delay *= self.backoff
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle_task(self, task, ts):
 | 
				
			||||||
 | 
					        if not self.huey.ready_to_run(task, ts):
 | 
				
			||||||
 | 
					            self._logger.info('Adding %s to schedule' % task)
 | 
				
			||||||
 | 
					            self.add_schedule(task)
 | 
				
			||||||
 | 
					        elif not self.is_revoked(task, ts):
 | 
				
			||||||
 | 
					            self.process_task(task, ts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process_task(self, task, ts):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self._logger.info('Executing %s' % task)
 | 
				
			||||||
 | 
					            self.huey.emit_task('started', task)
 | 
				
			||||||
 | 
					            self.huey.execute(task)
 | 
				
			||||||
 | 
					            self.huey.emit_task('finished', task)
 | 
				
			||||||
 | 
					        except DataStorePutException:
 | 
				
			||||||
 | 
					            self._logger.warn('Error storing result', exc_info=1)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self._logger.error('Unhandled exception in worker thread',
 | 
				
			||||||
 | 
					                               exc_info=1)
 | 
				
			||||||
 | 
					            self.huey.emit_task('error', task, error=True)
 | 
				
			||||||
 | 
					            if task.retries:
 | 
				
			||||||
 | 
					                self.huey.emit_task('retrying', task)
 | 
				
			||||||
 | 
					                self.requeue_task(task, self.get_now())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def requeue_task(self, task, ts):
 | 
				
			||||||
 | 
					        task.retries -= 1
 | 
				
			||||||
 | 
					        self._logger.info('Re-enqueueing task %s, %s tries left' %
 | 
				
			||||||
 | 
					                          (task.task_id, task.retries))
 | 
				
			||||||
 | 
					        if task.retry_delay:
 | 
				
			||||||
 | 
					            delay = datetime.timedelta(seconds=task.retry_delay)
 | 
				
			||||||
 | 
					            task.execute_time = ts + delay
 | 
				
			||||||
 | 
					            self._logger.debug('Execute %s at: %s' % (task, task.execute_time))
 | 
				
			||||||
 | 
					            self.add_schedule(task)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.enqueue(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Consumer(object):
 | 
				
			||||||
 | 
					    def __init__(self, huey, workers=1, periodic=True, initial_delay=0.1,
 | 
				
			||||||
 | 
					                 backoff=1.15, max_delay=10.0, utc=True, scheduler_interval=1,
 | 
				
			||||||
 | 
					                 periodic_task_interval=60):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._logger = logging.getLogger('huey.consumer.ConsumerThread')
 | 
				
			||||||
 | 
					        self.huey = huey
 | 
				
			||||||
 | 
					        self.workers = workers
 | 
				
			||||||
 | 
					        self.periodic = periodic
 | 
				
			||||||
 | 
					        self.default_delay = initial_delay
 | 
				
			||||||
 | 
					        self.backoff = backoff
 | 
				
			||||||
 | 
					        self.max_delay = max_delay
 | 
				
			||||||
 | 
					        self.utc = utc
 | 
				
			||||||
 | 
					        self.scheduler_interval = scheduler_interval
 | 
				
			||||||
 | 
					        self.periodic_task_interval = periodic_task_interval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.delay = self.default_delay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._shutdown = threading.Event()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.start()
 | 
				
			||||||
 | 
					            # it seems that calling self._shutdown.wait() here prevents the
 | 
				
			||||||
 | 
					            # signal handler from executing
 | 
				
			||||||
 | 
					            while not self._shutdown.is_set():
 | 
				
			||||||
 | 
					                self._shutdown.wait(.1)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self._logger.error('Error', exc_info=1)
 | 
				
			||||||
 | 
					            self.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def start(self):
 | 
				
			||||||
 | 
					        self._logger.info('%d worker threads' % self.workers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._set_signal_handler()
 | 
				
			||||||
 | 
					        self._log_registered_commands()
 | 
				
			||||||
 | 
					        self._create_threads()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._logger.info('Starting scheduler thread')
 | 
				
			||||||
 | 
					        self.scheduler_t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._logger.info('Starting worker threads')
 | 
				
			||||||
 | 
					        for worker in self.worker_threads:
 | 
				
			||||||
 | 
					            worker.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.periodic:
 | 
				
			||||||
 | 
					            self._logger.info('Starting periodic task scheduler thread')
 | 
				
			||||||
 | 
					            self.periodic_t.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def shutdown(self):
 | 
				
			||||||
 | 
					        self._logger.info('Shutdown initiated')
 | 
				
			||||||
 | 
					        self._shutdown.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handle_signal(self, sig_num, frame):
 | 
				
			||||||
 | 
					        self._logger.info('Received SIGTERM')
 | 
				
			||||||
 | 
					        self.shutdown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_signal_handler(self):
 | 
				
			||||||
 | 
					        self._logger.info('Setting signal handler')
 | 
				
			||||||
 | 
					        signal.signal(signal.SIGTERM, self._handle_signal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _log_registered_commands(self):
 | 
				
			||||||
 | 
					        msg = ['Huey consumer initialized with following commands']
 | 
				
			||||||
 | 
					        for command in registry._registry:
 | 
				
			||||||
 | 
					            msg.append('+ %s' % command.replace('queuecmd_', ''))
 | 
				
			||||||
 | 
					        self._logger.info('\n'.join(msg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _create_threads(self):
 | 
				
			||||||
 | 
					        self.scheduler_t = SchedulerThread(
 | 
				
			||||||
 | 
					            self.huey,
 | 
				
			||||||
 | 
					            self.utc,
 | 
				
			||||||
 | 
					            self._shutdown,
 | 
				
			||||||
 | 
					            self.scheduler_interval)
 | 
				
			||||||
 | 
					        self.scheduler_t.name = 'Scheduler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.worker_threads = []
 | 
				
			||||||
 | 
					        for i in range(self.workers):
 | 
				
			||||||
 | 
					            worker_t = WorkerThread(
 | 
				
			||||||
 | 
					                self.huey,
 | 
				
			||||||
 | 
					                self.default_delay,
 | 
				
			||||||
 | 
					                self.max_delay,
 | 
				
			||||||
 | 
					                self.backoff,
 | 
				
			||||||
 | 
					                self.utc,
 | 
				
			||||||
 | 
					                self._shutdown)
 | 
				
			||||||
 | 
					            worker_t.daemon = True
 | 
				
			||||||
 | 
					            worker_t.name = 'Worker %d' % (i + 1)
 | 
				
			||||||
 | 
					            self.worker_threads.append(worker_t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.periodic:
 | 
				
			||||||
 | 
					            self.periodic_t = PeriodicTaskThread(
 | 
				
			||||||
 | 
					                self.huey,
 | 
				
			||||||
 | 
					                self.utc,
 | 
				
			||||||
 | 
					                self._shutdown,
 | 
				
			||||||
 | 
					                self.periodic_task_interval)
 | 
				
			||||||
 | 
					            self.periodic_t.daemon = True
 | 
				
			||||||
 | 
					            self.periodic_t.name = 'Periodic Task'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.periodic_t = None
 | 
				
			||||||
							
								
								
									
										119
									
								
								lib/huey/djhuey/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								lib/huey/djhuey/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					This module contains a lot of cruft to handle instantiating a "Huey" object
 | 
				
			||||||
 | 
					using Django settings.  Unlike more flexible python apps, the huey django
 | 
				
			||||||
 | 
					integration consists of a single global Huey instance configured via the
 | 
				
			||||||
 | 
					settings module.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.db import connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey import crontab
 | 
				
			||||||
 | 
					from huey import Huey
 | 
				
			||||||
 | 
					from huey.utils import load_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					configuration_message = """
 | 
				
			||||||
 | 
					Configuring Huey for use with Django
 | 
				
			||||||
 | 
					====================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Huey was designed to be simple to configure in the general case.  For that
 | 
				
			||||||
 | 
					reason, huey will "just work" with no configuration at all provided you have
 | 
				
			||||||
 | 
					Redis installed and running locally.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					On the other hand, you can configure huey manually using the following
 | 
				
			||||||
 | 
					setting structure.  The following example uses Redis on localhost:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Simply point to a backend:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HUEY = {
 | 
				
			||||||
 | 
					    'backend': 'huey.backends.redis_backend',
 | 
				
			||||||
 | 
					    'name': 'unique name',
 | 
				
			||||||
 | 
					    'connection': {'host': 'localhost', 'port': 6379}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    'consumer_options': {'workers': 4},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you would like to configure Huey's logger using Django's integrated logging
 | 
				
			||||||
 | 
					settings, the logger used by consumer is named "huey.consumer".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more granular control, you can assign HUEY programmatically:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					HUEY = Huey(RedisBlockingQueue('my-queue'))
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def default_queue_name():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return settings.DATABASE_NAME
 | 
				
			||||||
 | 
					    except AttributeError:
 | 
				
			||||||
 | 
					        return settings.DATABASES['default']['NAME']
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        return 'huey'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def config_error(msg):
 | 
				
			||||||
 | 
					    print(configuration_message)
 | 
				
			||||||
 | 
					    print('\n\n')
 | 
				
			||||||
 | 
					    print(msg)
 | 
				
			||||||
 | 
					    sys.exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def dynamic_import(obj, key, required=False):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        path = obj[key]
 | 
				
			||||||
 | 
					    except KeyError:
 | 
				
			||||||
 | 
					        if required:
 | 
				
			||||||
 | 
					            config_error('Missing required configuration: "%s"' % key)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return load_class(path + '.Components')
 | 
				
			||||||
 | 
					    except ImportError:
 | 
				
			||||||
 | 
					        config_error('Unable to import %s: "%s"' % (key, path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    HUEY = getattr(settings, 'HUEY', None)
 | 
				
			||||||
 | 
					except:
 | 
				
			||||||
 | 
					    config_error('Error encountered reading settings.HUEY')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if HUEY is None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from huey import RedisHuey
 | 
				
			||||||
 | 
					    except ImportError:
 | 
				
			||||||
 | 
					        config_error('Error: Huey could not import the redis backend. '
 | 
				
			||||||
 | 
					                     'Install `redis-py`.')
 | 
				
			||||||
 | 
					    HUEY = RedisHuey(default_queue_name())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not isinstance(HUEY, Huey):
 | 
				
			||||||
 | 
					    Queue, DataStore, Schedule, Events = dynamic_import(HUEY, 'backend')
 | 
				
			||||||
 | 
					    name = HUEY.get('name') or default_queue_name()
 | 
				
			||||||
 | 
					    conn = HUEY.get('connection', {})
 | 
				
			||||||
 | 
					    always_eager = HUEY.get('always_eager', False)
 | 
				
			||||||
 | 
					    HUEY = Huey(
 | 
				
			||||||
 | 
					        Queue(name, **conn),
 | 
				
			||||||
 | 
					        DataStore and DataStore(name, **conn) or None,
 | 
				
			||||||
 | 
					        Schedule and Schedule(name, **conn) or None,
 | 
				
			||||||
 | 
					        Events and Events(name, **conn) or None,
 | 
				
			||||||
 | 
					        always_eager=always_eager)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					task = HUEY.task
 | 
				
			||||||
 | 
					periodic_task = HUEY.periodic_task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def close_db(fn):
 | 
				
			||||||
 | 
					    """Decorator to be used with tasks that may operate on the database."""
 | 
				
			||||||
 | 
					    @wraps(fn)
 | 
				
			||||||
 | 
					    def inner(*args, **kwargs):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return fn(*args, **kwargs)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            connection.close()
 | 
				
			||||||
 | 
					    return inner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_task(*args, **kwargs):
 | 
				
			||||||
 | 
					    def decorator(fn):
 | 
				
			||||||
 | 
					        return task(*args, **kwargs)(close_db(fn))
 | 
				
			||||||
 | 
					    return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_periodic_task(*args, **kwargs):
 | 
				
			||||||
 | 
					    def decorator(fn):
 | 
				
			||||||
 | 
					        return periodic_task(*args, **kwargs)(close_db(fn))
 | 
				
			||||||
 | 
					    return decorator
 | 
				
			||||||
							
								
								
									
										0
									
								
								lib/huey/djhuey/management/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/huey/djhuey/management/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								lib/huey/djhuey/management/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/huey/djhuey/management/commands/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										126
									
								
								lib/huey/djhuey/management/commands/run_huey.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								lib/huey/djhuey/management/commands/run_huey.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					import imp
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					from optparse import make_option
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf import settings
 | 
				
			||||||
 | 
					from django.core.management.base import BaseCommand
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from importlib import import_module
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    from django.utils.importlib import import_module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from django.apps import apps as django_apps
 | 
				
			||||||
 | 
					    HAS_DJANGO_APPS = True
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    # Django 1.6
 | 
				
			||||||
 | 
					    HAS_DJANGO_APPS = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.consumer import Consumer
 | 
				
			||||||
 | 
					from huey.bin.huey_consumer import get_loglevel
 | 
				
			||||||
 | 
					from huey.bin.huey_consumer import setup_logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Command(BaseCommand):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Queue consumer.  Example usage::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    To start the consumer (note you must export the settings module):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    django-admin.py run_huey
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    help = "Run the queue consumer"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    option_list = BaseCommand.option_list + (
 | 
				
			||||||
 | 
					        make_option('--periodic', '-p',
 | 
				
			||||||
 | 
					            dest='periodic',
 | 
				
			||||||
 | 
					            action='store_true',
 | 
				
			||||||
 | 
					            help='Enqueue periodic commands'
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        make_option('--no-periodic', '-n',
 | 
				
			||||||
 | 
					            dest='periodic',
 | 
				
			||||||
 | 
					            action='store_false',
 | 
				
			||||||
 | 
					            help='Do not enqueue periodic commands'
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        make_option('--workers', '-w',
 | 
				
			||||||
 | 
					            dest='workers',
 | 
				
			||||||
 | 
					            type='int',
 | 
				
			||||||
 | 
					            help='Number of worker threads'
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        make_option('--delay', '-d',
 | 
				
			||||||
 | 
					            dest='initial_delay',
 | 
				
			||||||
 | 
					            type='float',
 | 
				
			||||||
 | 
					            help='Delay between polling requests'
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        make_option('--max_delay', '-m',
 | 
				
			||||||
 | 
					            dest='max_delay',
 | 
				
			||||||
 | 
					            type='float',
 | 
				
			||||||
 | 
					            help='Maximum delay between polling requests'
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def autodiscover_appconfigs(self):
 | 
				
			||||||
 | 
					        """Use Django app registry to pull out potential apps with tasks.py module."""
 | 
				
			||||||
 | 
					        module_name = 'tasks'
 | 
				
			||||||
 | 
					        for config in django_apps.get_app_configs():
 | 
				
			||||||
 | 
					            app_path = config.module.__path__
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                fp, path, description = imp.find_module(module_name, app_path)
 | 
				
			||||||
 | 
					            except ImportError:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                import_path = '%s.%s' % (config.name, module_name)
 | 
				
			||||||
 | 
					                imp.load_module(import_path, fp, path, description)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def autodiscover_old(self):
 | 
				
			||||||
 | 
					        # this is to find modules named <commands.py> in a django project's
 | 
				
			||||||
 | 
					        # installed apps directories
 | 
				
			||||||
 | 
					        module_name = 'tasks'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for app in settings.INSTALLED_APPS:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                import_module(app)
 | 
				
			||||||
 | 
					                app_path = sys.modules[app].__path__
 | 
				
			||||||
 | 
					            except AttributeError:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                imp.find_module(module_name, app_path)
 | 
				
			||||||
 | 
					            except ImportError:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            import_module('%s.%s' % (app, module_name))
 | 
				
			||||||
 | 
					            app_path = sys.modules['%s.%s' % (app, module_name)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def autodiscover(self):
 | 
				
			||||||
 | 
					        """Switch between Django 1.7 style and old style app importing."""
 | 
				
			||||||
 | 
					        if HAS_DJANGO_APPS:
 | 
				
			||||||
 | 
					            self.autodiscover_appconfigs()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.autodiscover_old()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle(self, *args, **options):
 | 
				
			||||||
 | 
					        from huey.djhuey import HUEY
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            consumer_options = settings.HUEY['consumer_options']
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            consumer_options = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if options['workers'] is not None:
 | 
				
			||||||
 | 
					            consumer_options['workers'] = options['workers']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if options['periodic'] is not None:
 | 
				
			||||||
 | 
					            consumer_options['periodic'] = options['periodic']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if options['initial_delay'] is not None:
 | 
				
			||||||
 | 
					            consumer_options['initial_delay'] = options['initial_delay']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if options['max_delay'] is not None:
 | 
				
			||||||
 | 
					            consumer_options['max_delay'] = options['max_delay']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.autodiscover()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loglevel = get_loglevel(consumer_options.pop('loglevel', None))
 | 
				
			||||||
 | 
					        logfile = consumer_options.pop('logfile', None)
 | 
				
			||||||
 | 
					        setup_logger(loglevel, logfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        consumer = Consumer(HUEY, **consumer_options)
 | 
				
			||||||
 | 
					        consumer.run()
 | 
				
			||||||
							
								
								
									
										0
									
								
								lib/huey/djhuey/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								lib/huey/djhuey/models.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										26
									
								
								lib/huey/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								lib/huey/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					class QueueException(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueWriteException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueReadException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueueRemoveException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DataStoreGetException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DataStorePutException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DataStoreTimeout(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScheduleAddException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScheduleReadException(QueueException):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
							
								
								
									
										20
									
								
								lib/huey/peewee_helpers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/huey/peewee_helpers/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					from functools import wraps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _transaction(db, fn):
 | 
				
			||||||
 | 
					    @wraps(fn)
 | 
				
			||||||
 | 
					    def inner(*args, **kwargs):
 | 
				
			||||||
 | 
					        # Execute function in its own connection, in a transaction.
 | 
				
			||||||
 | 
					        with db.execution_context(with_transaction=True):
 | 
				
			||||||
 | 
					            return fn(*args, **kwargs)
 | 
				
			||||||
 | 
					    return inner
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_task(huey, db, *args, **kwargs):
 | 
				
			||||||
 | 
					    def decorator(fn):
 | 
				
			||||||
 | 
					        return huey.task(*args, **kwargs)(_transaction(db, fn))
 | 
				
			||||||
 | 
					    return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def db_periodic_task(huey, db, *args, **kwargs):
 | 
				
			||||||
 | 
					    def decorator(fn):
 | 
				
			||||||
 | 
					        return huey.periodic_task(*args, **kwargs)(_transaction(db, fn))
 | 
				
			||||||
 | 
					    return decorator
 | 
				
			||||||
							
								
								
									
										77
									
								
								lib/huey/registry.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								lib/huey/registry.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					import pickle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.exceptions import QueueException
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TaskRegistry(object):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A simple Registry used to track subclasses of :class:`QueueTask` - the
 | 
				
			||||||
 | 
					    purpose of this registry is to allow translation from queue messages to
 | 
				
			||||||
 | 
					    task classes, and vice-versa.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _ignore = ['QueueTask', 'PeriodicQueueTask']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _registry = {}
 | 
				
			||||||
 | 
					    _periodic_tasks = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def task_to_string(self, task):
 | 
				
			||||||
 | 
					        return '%s' % (task.__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def register(self, task_class):
 | 
				
			||||||
 | 
					        klass_str = self.task_to_string(task_class)
 | 
				
			||||||
 | 
					        if klass_str in self._ignore:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if klass_str not in self._registry:
 | 
				
			||||||
 | 
					            self._registry[klass_str] = task_class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # store an instance in a separate list of periodic tasks
 | 
				
			||||||
 | 
					            if hasattr(task_class, 'validate_datetime'):
 | 
				
			||||||
 | 
					                self._periodic_tasks.append(task_class())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def unregister(self, task_class):
 | 
				
			||||||
 | 
					        klass_str = self.task_to_string(task_class)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if klass_str in self._registry:
 | 
				
			||||||
 | 
					            del(self._registry[klass_str])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for task in self._periodic_tasks:
 | 
				
			||||||
 | 
					                if isinstance(task, task_class):
 | 
				
			||||||
 | 
					                    self._periodic_tasks.remove(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __contains__(self, klass_str):
 | 
				
			||||||
 | 
					        return klass_str in self._registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_message_for_task(self, task):
 | 
				
			||||||
 | 
					        """Convert a task object to a message for storage in the queue"""
 | 
				
			||||||
 | 
					        return pickle.dumps((
 | 
				
			||||||
 | 
					            task.task_id,
 | 
				
			||||||
 | 
					            self.task_to_string(type(task)),
 | 
				
			||||||
 | 
					            task.execute_time,
 | 
				
			||||||
 | 
					            task.retries,
 | 
				
			||||||
 | 
					            task.retry_delay,
 | 
				
			||||||
 | 
					            task.get_data(),
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_task_class(self, klass_str):
 | 
				
			||||||
 | 
					        klass = self._registry.get(klass_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not klass:
 | 
				
			||||||
 | 
					            raise QueueException('%s not found in TaskRegistry' % klass_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return klass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_task_for_message(self, msg):
 | 
				
			||||||
 | 
					        """Convert a message from the queue into a task"""
 | 
				
			||||||
 | 
					        # parse out the pieces from the enqueued message
 | 
				
			||||||
 | 
					        raw = pickle.loads(msg)
 | 
				
			||||||
 | 
					        task_id, klass_str, execute_time, retries, delay, data = raw
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        klass = self.get_task_class(klass_str)
 | 
				
			||||||
 | 
					        return klass(data, task_id, execute_time, retries, delay)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_periodic_tasks(self):
 | 
				
			||||||
 | 
					        return self._periodic_tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					registry = TaskRegistry()
 | 
				
			||||||
							
								
								
									
										10
									
								
								lib/huey/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								lib/huey/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					from huey.tests.backends import *
 | 
				
			||||||
 | 
					from huey.tests.consumer import *
 | 
				
			||||||
 | 
					from huey.tests.crontab import *
 | 
				
			||||||
 | 
					from huey.tests.queue import *
 | 
				
			||||||
 | 
					from huey.tests.utils import *
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    import peewee
 | 
				
			||||||
 | 
					    from huey.tests.peewee_tests import *
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
							
								
								
									
										170
									
								
								lib/huey/tests/backends.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								lib/huey/tests/backends.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					from collections import deque
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import tempfile
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.api import Huey
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyDataStore
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyQueue
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummySchedule
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					from huey.backends.sqlite_backend import SqliteDataStore
 | 
				
			||||||
 | 
					from huey.backends.sqlite_backend import SqliteQueue
 | 
				
			||||||
 | 
					from huey.backends.sqlite_backend import SqliteSchedule
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisDataStore
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisEventEmitter
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisQueue
 | 
				
			||||||
 | 
					    from huey.backends.redis_backend import RedisSchedule
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    RedisQueue = RedisDataStore = RedisSchedule = RedisEventEmitter = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					try:
 | 
				
			||||||
 | 
					    from huey.backends.rabbitmq_backend import RabbitQueue, RabbitEventEmitter
 | 
				
			||||||
 | 
					except ImportError:
 | 
				
			||||||
 | 
					    RabbitQueue = RabbitEventEmitter = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if sys.version_info[0] == 2:
 | 
				
			||||||
 | 
					    redis_kwargs = {}
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    redis_kwargs = {'decode_responses': True}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QUEUES = (DummyQueue, RedisQueue, SqliteQueue, RabbitQueue)
 | 
				
			||||||
 | 
					DATA_STORES = (DummyDataStore, RedisDataStore, SqliteDataStore, None)
 | 
				
			||||||
 | 
					SCHEDULES = (DummySchedule, RedisSchedule, SqliteSchedule, None)
 | 
				
			||||||
 | 
					EVENTS = (DummyEventEmitter, RedisEventEmitter, None, RabbitEventEmitter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HueyBackendTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.sqlite_location = tempfile.mkstemp(prefix='hueytest.')[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        os.unlink(self.sqlite_location)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_queues(self):
 | 
				
			||||||
 | 
					        result_store = DummyDataStore('dummy')
 | 
				
			||||||
 | 
					        for q in QUEUES:
 | 
				
			||||||
 | 
					            if not q:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if issubclass(q, SqliteQueue):
 | 
				
			||||||
 | 
					                queue = q('test', location=self.sqlite_location)
 | 
				
			||||||
 | 
					            elif issubclass(q, RedisQueue):
 | 
				
			||||||
 | 
					                queue = q('test', **redis_kwargs)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                queue = q('test')
 | 
				
			||||||
 | 
					            queue.flush()
 | 
				
			||||||
 | 
					            queue.write('a')
 | 
				
			||||||
 | 
					            queue.write('b')
 | 
				
			||||||
 | 
					            self.assertEqual(len(queue), 2)
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), 'a')
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), 'b')
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            queue.write('c')
 | 
				
			||||||
 | 
					            queue.write('d')
 | 
				
			||||||
 | 
					            queue.write('c')
 | 
				
			||||||
 | 
					            queue.write('x')
 | 
				
			||||||
 | 
					            queue.write('d')
 | 
				
			||||||
 | 
					            self.assertEqual(len(queue), 5)
 | 
				
			||||||
 | 
					            self.assertEqual(queue.remove('c'), 2)
 | 
				
			||||||
 | 
					            self.assertEqual(len(queue), 3)
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), 'd')
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), 'x')
 | 
				
			||||||
 | 
					            self.assertEqual(queue.read(), 'd')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            queue.flush()
 | 
				
			||||||
 | 
					            test_huey = Huey(queue, result_store)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @test_huey.task()
 | 
				
			||||||
 | 
					            def test_queues_add(k, v):
 | 
				
			||||||
 | 
					                return k + v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            res = test_queues_add('k', 'v')
 | 
				
			||||||
 | 
					            self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					            task = test_huey.dequeue()
 | 
				
			||||||
 | 
					            test_huey.execute(task)
 | 
				
			||||||
 | 
					            self.assertEqual(res.get(), 'kv')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            res = test_queues_add('\xce', '\xcf')
 | 
				
			||||||
 | 
					            task = test_huey.dequeue()
 | 
				
			||||||
 | 
					            test_huey.execute(task)
 | 
				
			||||||
 | 
					            self.assertEqual(res.get(), '\xce\xcf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_data_stores(self):
 | 
				
			||||||
 | 
					        for d in DATA_STORES:
 | 
				
			||||||
 | 
					            if not d:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if issubclass(d, SqliteDataStore):
 | 
				
			||||||
 | 
					                data_store = d('test', location=self.sqlite_location)
 | 
				
			||||||
 | 
					            elif issubclass(d, RedisDataStore):
 | 
				
			||||||
 | 
					                data_store = d('test', **redis_kwargs)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                data_store = d('test')
 | 
				
			||||||
 | 
					            data_store.put('k1', 'v1')
 | 
				
			||||||
 | 
					            data_store.put('k2', 'v2')
 | 
				
			||||||
 | 
					            data_store.put('k3', 'v3')
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.peek('k2'), 'v2')
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.get('k2'), 'v2')
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.peek('k2'), EmptyData)
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.get('k2'), EmptyData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.peek('k3'), 'v3')
 | 
				
			||||||
 | 
					            data_store.put('k3', 'v3-2')
 | 
				
			||||||
 | 
					            self.assertEqual(data_store.peek('k3'), 'v3-2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_schedules(self):
 | 
				
			||||||
 | 
					        for s in SCHEDULES:
 | 
				
			||||||
 | 
					            if not s:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            if issubclass(s, SqliteSchedule):
 | 
				
			||||||
 | 
					                schedule = s('test', location=self.sqlite_location)
 | 
				
			||||||
 | 
					            elif issubclass(s, RedisSchedule):
 | 
				
			||||||
 | 
					                schedule = s('test', **redis_kwargs)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                schedule = s('test')
 | 
				
			||||||
 | 
					            dt1 = datetime.datetime(2013, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					            dt2 = datetime.datetime(2013, 1, 2, 0, 0)
 | 
				
			||||||
 | 
					            dt3 = datetime.datetime(2013, 1, 3, 0, 0)
 | 
				
			||||||
 | 
					            dt4 = datetime.datetime(2013, 1, 4, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add to schedule out-of-order to ensure sorting is performed by
 | 
				
			||||||
 | 
					            # the schedule.
 | 
				
			||||||
 | 
					            schedule.add('s2', dt2)
 | 
				
			||||||
 | 
					            schedule.add('s1', dt1)
 | 
				
			||||||
 | 
					            schedule.add('s4', dt4)
 | 
				
			||||||
 | 
					            schedule.add('s3', dt3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Ensure that asking for a timestamp previous to any item in the
 | 
				
			||||||
 | 
					            # schedule returns empty list.
 | 
				
			||||||
 | 
					            self.assertEqual(
 | 
				
			||||||
 | 
					                schedule.read(dt1 - datetime.timedelta(days=1)),
 | 
				
			||||||
 | 
					                [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Ensure the upper boundary is inclusive of whatever timestamp
 | 
				
			||||||
 | 
					            # is passed in.
 | 
				
			||||||
 | 
					            self.assertEqual(schedule.read(dt3), ['s1', 's2', 's3'])
 | 
				
			||||||
 | 
					            self.assertEqual(schedule.read(dt3), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Ensure the schedule is flushed and an empty schedule returns an
 | 
				
			||||||
 | 
					            # empty list.
 | 
				
			||||||
 | 
					            self.assertEqual(schedule.read(dt4), ['s4'])
 | 
				
			||||||
 | 
					            self.assertEqual(schedule.read(dt4), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_events(self):
 | 
				
			||||||
 | 
					        for e in EVENTS:
 | 
				
			||||||
 | 
					            if not e:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            e = e('test')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            messages = ['a', 'b', 'c', 'd']
 | 
				
			||||||
 | 
					            for message in messages:
 | 
				
			||||||
 | 
					                e.emit(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if hasattr(e, '_events'):
 | 
				
			||||||
 | 
					                self.assertEqual(e._events, deque(['d', 'c', 'b', 'a']))
 | 
				
			||||||
							
								
								
									
										441
									
								
								lib/huey/tests/consumer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										441
									
								
								lib/huey/tests/consumer.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,441 @@
 | 
				
			|||||||
 | 
					from collections import deque
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey import crontab
 | 
				
			||||||
 | 
					from huey import Huey
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyDataStore
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyEventEmitter
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyQueue
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummySchedule
 | 
				
			||||||
 | 
					from huey.consumer import Consumer
 | 
				
			||||||
 | 
					from huey.consumer import WorkerThread
 | 
				
			||||||
 | 
					from huey.registry import registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Logger used by the consumer.
 | 
				
			||||||
 | 
					logger = logging.getLogger('huey.consumer')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Store some global state.
 | 
				
			||||||
 | 
					state = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create a queue, result store, schedule and event emitter, then attach them
 | 
				
			||||||
 | 
					# to a test-only Huey instance.
 | 
				
			||||||
 | 
					test_queue = DummyQueue('test-queue')
 | 
				
			||||||
 | 
					test_result_store = DummyDataStore('test-queue')
 | 
				
			||||||
 | 
					test_schedule = DummySchedule('test-queue')
 | 
				
			||||||
 | 
					test_events = DummyEventEmitter('test-queue')
 | 
				
			||||||
 | 
					test_huey = Huey(test_queue, test_result_store, test_schedule, test_events)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create some test tasks.
 | 
				
			||||||
 | 
					@test_huey.task()
 | 
				
			||||||
 | 
					def modify_state(k, v):
 | 
				
			||||||
 | 
					    state[k] = v
 | 
				
			||||||
 | 
					    return v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@test_huey.task()
 | 
				
			||||||
 | 
					def blow_up():
 | 
				
			||||||
 | 
					    raise Exception('blowed up')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@test_huey.task(retries=3)
 | 
				
			||||||
 | 
					def retry_command(k, always_fail=True):
 | 
				
			||||||
 | 
					    if k not in state:
 | 
				
			||||||
 | 
					        if not always_fail:
 | 
				
			||||||
 | 
					            state[k] = 'fixed'
 | 
				
			||||||
 | 
					        raise Exception('fappsk')
 | 
				
			||||||
 | 
					    return state[k]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@test_huey.task(retries=3, retry_delay=10)
 | 
				
			||||||
 | 
					def retry_command_slow(k, always_fail=True):
 | 
				
			||||||
 | 
					    if k not in state:
 | 
				
			||||||
 | 
					        if not always_fail:
 | 
				
			||||||
 | 
					            state[k] = 'fixed'
 | 
				
			||||||
 | 
					        raise Exception('fappsk')
 | 
				
			||||||
 | 
					    return state[k]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@test_huey.periodic_task(crontab(minute='0'))
 | 
				
			||||||
 | 
					def every_hour():
 | 
				
			||||||
 | 
					    state['p'] = 'y'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create a log handler that will track messages generated by the consumer.
 | 
				
			||||||
 | 
					class TestLogHandler(logging.Handler):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.messages = []
 | 
				
			||||||
 | 
					        logging.Handler.__init__(self, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def emit(self, record):
 | 
				
			||||||
 | 
					        self.messages.append(record.getMessage())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConsumerTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        global state
 | 
				
			||||||
 | 
					        state = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.orig_pc = registry._periodic_tasks
 | 
				
			||||||
 | 
					        registry._periodic_commands = [every_hour.task_class()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.orig_sleep = time.sleep
 | 
				
			||||||
 | 
					        time.sleep = lambda x: None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_huey.queue.flush()
 | 
				
			||||||
 | 
					        test_huey.result_store.flush()
 | 
				
			||||||
 | 
					        test_huey.schedule.flush()
 | 
				
			||||||
 | 
					        test_events._events = deque()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.consumer = Consumer(test_huey, workers=2)
 | 
				
			||||||
 | 
					        self.consumer._create_threads()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.handler = TestLogHandler()
 | 
				
			||||||
 | 
					        logger.addHandler(self.handler)
 | 
				
			||||||
 | 
					        logger.setLevel(logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        self.consumer.shutdown()
 | 
				
			||||||
 | 
					        logger.removeHandler(self.handler)
 | 
				
			||||||
 | 
					        registry._periodic_tasks = self.orig_pc
 | 
				
			||||||
 | 
					        time.sleep = self.orig_sleep
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def assertStatusTask(self, status_task):
 | 
				
			||||||
 | 
					        parsed = []
 | 
				
			||||||
 | 
					        i = 0
 | 
				
			||||||
 | 
					        while i < len(status_task):
 | 
				
			||||||
 | 
					            event = json.loads(test_events._events[i])
 | 
				
			||||||
 | 
					            status, task, extra = status_task[i]
 | 
				
			||||||
 | 
					            self.assertEqual(event['status'], status)
 | 
				
			||||||
 | 
					            self.assertEqual(event['id'], task.task_id)
 | 
				
			||||||
 | 
					            for k, v in extra.items():
 | 
				
			||||||
 | 
					                self.assertEqual(event[k], v)
 | 
				
			||||||
 | 
					            i += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def spawn(self, func, *args, **kwargs):
 | 
				
			||||||
 | 
					        t = threading.Thread(target=func, args=args, kwargs=kwargs)
 | 
				
			||||||
 | 
					        t.start()
 | 
				
			||||||
 | 
					        return t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run_worker(self, task, ts=None):
 | 
				
			||||||
 | 
					        worker_t = WorkerThread(
 | 
				
			||||||
 | 
					            test_huey,
 | 
				
			||||||
 | 
					            self.consumer.default_delay,
 | 
				
			||||||
 | 
					            self.consumer.max_delay,
 | 
				
			||||||
 | 
					            self.consumer.backoff,
 | 
				
			||||||
 | 
					            self.consumer.utc,
 | 
				
			||||||
 | 
					            self.consumer._shutdown)
 | 
				
			||||||
 | 
					        ts = ts or datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					        worker_t.handle_task(task, ts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_message_processing(self):
 | 
				
			||||||
 | 
					        self.consumer.worker_threads[0].start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res = modify_state('k', 'v')
 | 
				
			||||||
 | 
					        res.get(blocking=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue('k' in state)
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), 'v')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_events._events), 2)
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('finished', res.task, {}),
 | 
				
			||||||
 | 
					            ('started', res.task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_worker(self):
 | 
				
			||||||
 | 
					        modify_state('k', 'w')
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'k': 'w'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_worker_exception(self):
 | 
				
			||||||
 | 
					        blow_up()
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					        self.assertTrue(
 | 
				
			||||||
 | 
					            'Unhandled exception in worker thread' in self.handler.messages)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_events._events), 2)
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('error', task, {'error': True}),
 | 
				
			||||||
 | 
					            ('started', task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_retries_and_logging(self):
 | 
				
			||||||
 | 
					        # this will continually fail
 | 
				
			||||||
 | 
					        retry_command('blampf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in reversed(range(4)):
 | 
				
			||||||
 | 
					            task = test_huey.dequeue()
 | 
				
			||||||
 | 
					            self.assertEqual(task.retries, i)
 | 
				
			||||||
 | 
					            self.run_worker(task)
 | 
				
			||||||
 | 
					            if i > 0:
 | 
				
			||||||
 | 
					                self.assertEqual(
 | 
				
			||||||
 | 
					                    self.handler.messages[-1],
 | 
				
			||||||
 | 
					                    'Re-enqueueing task %s, %s tries left' % (
 | 
				
			||||||
 | 
					                        task.task_id, i - 1))
 | 
				
			||||||
 | 
					                self.assertStatusTask([
 | 
				
			||||||
 | 
					                    ('enqueued', task, {}),
 | 
				
			||||||
 | 
					                    ('retrying', task, {}),
 | 
				
			||||||
 | 
					                    ('error', task,{}),
 | 
				
			||||||
 | 
					                    ('started', task, {}),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                last_idx = -2
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.assertStatusTask([
 | 
				
			||||||
 | 
					                    ('error', task,{}),
 | 
				
			||||||
 | 
					                    ('started', task, {}),
 | 
				
			||||||
 | 
					                ])
 | 
				
			||||||
 | 
					                last_idx = -1
 | 
				
			||||||
 | 
					            self.assertEqual(self.handler.messages[last_idx],
 | 
				
			||||||
 | 
					                             'Unhandled exception in worker thread')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(test_huey.dequeue(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_retries_with_success(self):
 | 
				
			||||||
 | 
					        # this will fail once, then succeed
 | 
				
			||||||
 | 
					        retry_command('blampf', False)
 | 
				
			||||||
 | 
					        self.assertFalse('blampf' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					        self.assertEqual(self.handler.messages, [
 | 
				
			||||||
 | 
					            'Executing %s' % task,
 | 
				
			||||||
 | 
					            'Unhandled exception in worker thread',
 | 
				
			||||||
 | 
					            'Re-enqueueing task %s, 2 tries left' % task.task_id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.assertEqual(task.retries, 2)
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(state['blampf'], 'fixed')
 | 
				
			||||||
 | 
					        self.assertEqual(test_huey.dequeue(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('finished', task, {}),
 | 
				
			||||||
 | 
					            ('started', task, {}),
 | 
				
			||||||
 | 
					            ('enqueued', task, {'retries': 2}),
 | 
				
			||||||
 | 
					            ('retrying', task, {'retries': 3}),
 | 
				
			||||||
 | 
					            ('error', task, {'error': True}),
 | 
				
			||||||
 | 
					            ('started', task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_scheduling(self):
 | 
				
			||||||
 | 
					        dt = datetime.datetime(2011, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					        dt2 = datetime.datetime(2037, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					        ad1 = modify_state.schedule(args=('k', 'v'), eta=dt, convert_utc=False)
 | 
				
			||||||
 | 
					        ad2 = modify_state.schedule(args=('k2', 'v2'), eta=dt2, convert_utc=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # dequeue the past-timestamped task and run it.
 | 
				
			||||||
 | 
					        worker = self.consumer.worker_threads[0]
 | 
				
			||||||
 | 
					        worker.check_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # dequeue the future-timestamped task.
 | 
				
			||||||
 | 
					        worker.check_message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # verify the task got stored in the schedule instead of executing
 | 
				
			||||||
 | 
					        self.assertFalse('k2' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('scheduled', ad2.task, {}),
 | 
				
			||||||
 | 
					            ('finished', ad1.task, {}),
 | 
				
			||||||
 | 
					            ('started', ad1.task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # run through an iteration of the scheduler
 | 
				
			||||||
 | 
					        self.consumer.scheduler_t.loop(dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # our command was not enqueued and no events were emitted.
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_queue._queue), 0)
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_events._events), 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # run through an iteration of the scheduler
 | 
				
			||||||
 | 
					        self.consumer.scheduler_t.loop(dt2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # our command was enqueued
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_queue._queue), 1)
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_events._events), 4)
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('enqueued', ad2.task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_retry_scheduling(self):
 | 
				
			||||||
 | 
					        # this will continually fail
 | 
				
			||||||
 | 
					        retry_command_slow('blampf')
 | 
				
			||||||
 | 
					        cur_time = datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.run_worker(task, ts=cur_time)
 | 
				
			||||||
 | 
					        self.assertEqual(self.handler.messages, [
 | 
				
			||||||
 | 
					            'Executing %s' % task,
 | 
				
			||||||
 | 
					            'Unhandled exception in worker thread',
 | 
				
			||||||
 | 
					            'Re-enqueueing task %s, 2 tries left' % task.task_id,
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        in_11 = cur_time + datetime.timedelta(seconds=11)
 | 
				
			||||||
 | 
					        tasks_from_sched = test_huey.read_schedule(in_11)
 | 
				
			||||||
 | 
					        self.assertEqual(tasks_from_sched, [task])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = tasks_from_sched[0]
 | 
				
			||||||
 | 
					        self.assertEqual(task.retries, 2)
 | 
				
			||||||
 | 
					        exec_time = task.execute_time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual((exec_time - cur_time).seconds, 10)
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('scheduled', task, {
 | 
				
			||||||
 | 
					                'retries': 2,
 | 
				
			||||||
 | 
					                'retry_delay': 10,
 | 
				
			||||||
 | 
					                'execute_time': time.mktime(exec_time.timetuple())}),
 | 
				
			||||||
 | 
					            ('retrying', task, {
 | 
				
			||||||
 | 
					                'retries': 3,
 | 
				
			||||||
 | 
					                'retry_delay': 10,
 | 
				
			||||||
 | 
					                'execute_time': None}),
 | 
				
			||||||
 | 
					            ('error', task, {}),
 | 
				
			||||||
 | 
					            ('started', task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_revoking_normal(self):
 | 
				
			||||||
 | 
					        # enqueue 2 normal commands
 | 
				
			||||||
 | 
					        r1 = modify_state('k', 'v')
 | 
				
			||||||
 | 
					        r2 = modify_state('k2', 'v2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # revoke the first *before it has been checked*
 | 
				
			||||||
 | 
					        r1.revoke()
 | 
				
			||||||
 | 
					        self.assertTrue(test_huey.is_revoked(r1.task))
 | 
				
			||||||
 | 
					        self.assertFalse(test_huey.is_revoked(r2.task))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # dequeue a *single* message (r1)
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_events._events), 1)
 | 
				
			||||||
 | 
					        self.assertStatusTask([
 | 
				
			||||||
 | 
					            ('revoked', r1.task, {}),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # no changes and the task was not added to the schedule
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # dequeue a *single* message
 | 
				
			||||||
 | 
					        task = test_huey.dequeue()
 | 
				
			||||||
 | 
					        self.run_worker(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue('k2' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_revoking_schedule(self):
 | 
				
			||||||
 | 
					        global state
 | 
				
			||||||
 | 
					        dt = datetime.datetime(2011, 1, 1)
 | 
				
			||||||
 | 
					        dt2 = datetime.datetime(2037, 1, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        r1 = modify_state.schedule(args=('k', 'v'), eta=dt, convert_utc=False)
 | 
				
			||||||
 | 
					        r2 = modify_state.schedule(args=('k2', 'v2'), eta=dt, convert_utc=False)
 | 
				
			||||||
 | 
					        r3 = modify_state.schedule(args=('k3', 'v3'), eta=dt2, convert_utc=False)
 | 
				
			||||||
 | 
					        r4 = modify_state.schedule(args=('k4', 'v4'), eta=dt2, convert_utc=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # revoke r1 and r3
 | 
				
			||||||
 | 
					        r1.revoke()
 | 
				
			||||||
 | 
					        r3.revoke()
 | 
				
			||||||
 | 
					        self.assertTrue(test_huey.is_revoked(r1.task))
 | 
				
			||||||
 | 
					        self.assertFalse(test_huey.is_revoked(r2.task))
 | 
				
			||||||
 | 
					        self.assertTrue(test_huey.is_revoked(r3.task))
 | 
				
			||||||
 | 
					        self.assertFalse(test_huey.is_revoked(r4.task))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expected = [
 | 
				
			||||||
 | 
					            #state,        schedule
 | 
				
			||||||
 | 
					            ({},           0),
 | 
				
			||||||
 | 
					            ({'k2': 'v2'}, 0),
 | 
				
			||||||
 | 
					            ({'k2': 'v2'}, 1),
 | 
				
			||||||
 | 
					            ({'k2': 'v2'}, 2),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(4):
 | 
				
			||||||
 | 
					            estate, esc = expected[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # dequeue a *single* message
 | 
				
			||||||
 | 
					            task = test_huey.dequeue()
 | 
				
			||||||
 | 
					            self.run_worker(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(state, estate)
 | 
				
			||||||
 | 
					            self.assertEqual(len(test_huey.schedule._schedule), esc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # lets pretend its 2037
 | 
				
			||||||
 | 
					        future = dt2 + datetime.timedelta(seconds=1)
 | 
				
			||||||
 | 
					        self.consumer.scheduler_t.loop(future)
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_huey.schedule._schedule), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # There are two tasks in the queue now (r3 and r4) -- process both.
 | 
				
			||||||
 | 
					        for i in range(2):
 | 
				
			||||||
 | 
					            task = test_huey.dequeue()
 | 
				
			||||||
 | 
					            self.run_worker(task, future)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'k2': 'v2', 'k4': 'v4'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_revoking_periodic(self):
 | 
				
			||||||
 | 
					        global state
 | 
				
			||||||
 | 
					        def loop_periodic(ts):
 | 
				
			||||||
 | 
					            self.consumer.periodic_t.loop(ts)
 | 
				
			||||||
 | 
					            for i in range(len(test_queue._queue)):
 | 
				
			||||||
 | 
					                task = test_huey.dequeue()
 | 
				
			||||||
 | 
					                self.run_worker(task, ts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # revoke the command once
 | 
				
			||||||
 | 
					        every_hour.revoke(revoke_once=True)
 | 
				
			||||||
 | 
					        self.assertTrue(every_hour.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it will be skipped the first go-round
 | 
				
			||||||
 | 
					        dt = datetime.datetime(2011, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it has not been run
 | 
				
			||||||
 | 
					        self.assertEqual(state, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # the next go-round it will be enqueued
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # our command was run
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'p': 'y'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # reset state
 | 
				
			||||||
 | 
					        state = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # revoke the command
 | 
				
			||||||
 | 
					        every_hour.revoke()
 | 
				
			||||||
 | 
					        self.assertTrue(every_hour.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it will no longer be enqueued
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # restore
 | 
				
			||||||
 | 
					        every_hour.restore()
 | 
				
			||||||
 | 
					        self.assertFalse(every_hour.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it will now be enqueued
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'p': 'y'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # reset
 | 
				
			||||||
 | 
					        state = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # revoke for an hour
 | 
				
			||||||
 | 
					        td = datetime.timedelta(seconds=3600)
 | 
				
			||||||
 | 
					        every_hour.revoke(revoke_until=dt + td)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loop_periodic(dt)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # after an hour it is back
 | 
				
			||||||
 | 
					        loop_periodic(dt + td)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'p': 'y'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # our data store should reflect the delay
 | 
				
			||||||
 | 
					        task_obj = every_hour.task_class()
 | 
				
			||||||
 | 
					        self.assertEqual(len(test_huey.result_store._results), 1)
 | 
				
			||||||
 | 
					        self.assertTrue(task_obj.revoke_id in test_huey.result_store._results)
 | 
				
			||||||
							
								
								
									
										91
									
								
								lib/huey/tests/crontab.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								lib/huey/tests/crontab.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey import crontab
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CrontabTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					    def test_crontab_month(self):
 | 
				
			||||||
 | 
					        # validates the following months, 1, 4, 7, 8, 9
 | 
				
			||||||
 | 
					        valids = [1, 4, 7, 8, 9]
 | 
				
			||||||
 | 
					        validate_m = crontab(month='1,4,*/6,8-9')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for x in range(1, 13):
 | 
				
			||||||
 | 
					            res = validate_m(datetime.datetime(2011, x, 1))
 | 
				
			||||||
 | 
					            self.assertEqual(res, x in valids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_crontab_day(self):
 | 
				
			||||||
 | 
					        # validates the following days
 | 
				
			||||||
 | 
					        valids = [1, 4, 7, 8, 9, 13, 19, 25, 31]
 | 
				
			||||||
 | 
					        validate_d = crontab(day='*/6,1,4,8-9')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for x in range(1, 32):
 | 
				
			||||||
 | 
					            res = validate_d(datetime.datetime(2011, 1, x))
 | 
				
			||||||
 | 
					            self.assertEqual(res, x in valids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_crontab_hour(self):
 | 
				
			||||||
 | 
					        # validates the following hours
 | 
				
			||||||
 | 
					        valids = [0, 1, 4, 6, 8, 9, 12, 18]
 | 
				
			||||||
 | 
					        validate_h = crontab(hour='8-9,*/6,1,4')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for x in range(24):
 | 
				
			||||||
 | 
					            res = validate_h(datetime.datetime(2011, 1, 1, x))
 | 
				
			||||||
 | 
					            self.assertEqual(res, x in valids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        edge = crontab(hour=0)
 | 
				
			||||||
 | 
					        self.assertTrue(edge(datetime.datetime(2011, 1, 1, 0, 0)))
 | 
				
			||||||
 | 
					        self.assertFalse(edge(datetime.datetime(2011, 1, 1, 12, 0)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_crontab_minute(self):
 | 
				
			||||||
 | 
					        # validates the following minutes
 | 
				
			||||||
 | 
					        valids = [0, 1, 4, 6, 8, 9, 12, 18, 24, 30, 36, 42, 48, 54]
 | 
				
			||||||
 | 
					        validate_m = crontab(minute='4,8-9,*/6,1')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for x in range(60):
 | 
				
			||||||
 | 
					            res = validate_m(datetime.datetime(2011, 1, 1, 1, x))
 | 
				
			||||||
 | 
					            self.assertEqual(res, x in valids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_crontab_day_of_week(self):
 | 
				
			||||||
 | 
					        # validates the following days of week
 | 
				
			||||||
 | 
					        # jan, 1, 2011 is a saturday
 | 
				
			||||||
 | 
					        valids = [2, 4, 9, 11, 16, 18, 23, 25, 30]
 | 
				
			||||||
 | 
					        validate_dow = crontab(day_of_week='0,2')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for x in range(1, 32):
 | 
				
			||||||
 | 
					            res = validate_dow(datetime.datetime(2011, 1, x))
 | 
				
			||||||
 | 
					            self.assertEqual(res, x in valids)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_crontab_all_together(self):
 | 
				
			||||||
 | 
					        # jan 1, 2011 is a saturday
 | 
				
			||||||
 | 
					        # may 1, 2011 is a sunday
 | 
				
			||||||
 | 
					        validate = crontab(
 | 
				
			||||||
 | 
					            month='1,5',
 | 
				
			||||||
 | 
					            day='1,4,7',
 | 
				
			||||||
 | 
					            day_of_week='0,6',
 | 
				
			||||||
 | 
					            hour='*/4',
 | 
				
			||||||
 | 
					            minute='1-5,10-15,50'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(validate(datetime.datetime(2011, 5, 1, 4, 11)))
 | 
				
			||||||
 | 
					        self.assertTrue(validate(datetime.datetime(2011, 5, 7, 20, 50)))
 | 
				
			||||||
 | 
					        self.assertTrue(validate(datetime.datetime(2011, 1, 1, 0, 1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fails validation on month
 | 
				
			||||||
 | 
					        self.assertFalse(validate(datetime.datetime(2011, 6, 4, 4, 11)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fails validation on day
 | 
				
			||||||
 | 
					        self.assertFalse(validate(datetime.datetime(2011, 1, 6, 4, 11)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fails validation on day_of_week
 | 
				
			||||||
 | 
					        self.assertFalse(validate(datetime.datetime(2011, 1, 4, 4, 11)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fails validation on hour
 | 
				
			||||||
 | 
					        self.assertFalse(validate(datetime.datetime(2011, 1, 1, 1, 11)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # fails validation on minute
 | 
				
			||||||
 | 
					        self.assertFalse(validate(datetime.datetime(2011, 1, 1, 4, 6)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_invalid_crontabs(self):
 | 
				
			||||||
 | 
					        # check invalid configurations are detected and reported
 | 
				
			||||||
 | 
					        self.assertRaises(ValueError, crontab, minute='61')
 | 
				
			||||||
 | 
					        self.assertRaises(ValueError, crontab, minute='0-61')
 | 
				
			||||||
							
								
								
									
										62
									
								
								lib/huey/tests/peewee_tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/huey/tests/peewee_tests.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					from contextlib import contextmanager
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey import Huey
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyDataStore
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyQueue
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummySchedule
 | 
				
			||||||
 | 
					from huey.peewee_helpers import db_periodic_task
 | 
				
			||||||
 | 
					from huey.peewee_helpers import db_task
 | 
				
			||||||
 | 
					from peewee import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					queue = DummyQueue('test-queue')
 | 
				
			||||||
 | 
					schedule = DummySchedule('test-queue')
 | 
				
			||||||
 | 
					data_store = DummyDataStore('test-queue')
 | 
				
			||||||
 | 
					huey = Huey(queue, data_store, schedule=schedule)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					STATE = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MockSqliteDatabase(SqliteDatabase):
 | 
				
			||||||
 | 
					    def record_call(fn):
 | 
				
			||||||
 | 
					        def inner(*args, **kwargs):
 | 
				
			||||||
 | 
					            STATE.append(fn.__name__)
 | 
				
			||||||
 | 
					            return fn(*args, **kwargs)
 | 
				
			||||||
 | 
					        return inner
 | 
				
			||||||
 | 
					    connect = record_call(SqliteDatabase.connect)
 | 
				
			||||||
 | 
					    _close = record_call(SqliteDatabase._close)
 | 
				
			||||||
 | 
					    transaction = record_call(SqliteDatabase.transaction)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					db = MockSqliteDatabase('test.huey.db')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Value(Model):
 | 
				
			||||||
 | 
					    data = CharField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        database = db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def create(cls, *args, **kwargs):
 | 
				
			||||||
 | 
					        STATE.append('create')
 | 
				
			||||||
 | 
					        return super(Value, cls).create(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@db_task(huey, db)
 | 
				
			||||||
 | 
					def test_db_task(val):
 | 
				
			||||||
 | 
					    return Value.create(data=val)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestPeeweeHelpers(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        global STATE
 | 
				
			||||||
 | 
					        STATE = []
 | 
				
			||||||
 | 
					        queue.flush()
 | 
				
			||||||
 | 
					        data_store.flush()
 | 
				
			||||||
 | 
					        schedule.flush()
 | 
				
			||||||
 | 
					        Value.drop_table(True)
 | 
				
			||||||
 | 
					        Value.create_table()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_helper(self):
 | 
				
			||||||
 | 
					        test_db_task('foo')
 | 
				
			||||||
 | 
					        self.assertEqual(STATE, ['connect'])
 | 
				
			||||||
 | 
					        huey.execute(huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(STATE, ['connect', 'transaction', 'create', '_close'])
 | 
				
			||||||
 | 
					        self.assertEqual(Value.select().count(), 1)
 | 
				
			||||||
							
								
								
									
										438
									
								
								lib/huey/tests/queue.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								lib/huey/tests/queue.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,438 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey import crontab
 | 
				
			||||||
 | 
					from huey import exceptions as huey_exceptions
 | 
				
			||||||
 | 
					from huey import Huey
 | 
				
			||||||
 | 
					from huey.api import QueueTask
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyDataStore
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummyQueue
 | 
				
			||||||
 | 
					from huey.backends.dummy import DummySchedule
 | 
				
			||||||
 | 
					from huey.registry import registry
 | 
				
			||||||
 | 
					from huey.utils import EmptyData
 | 
				
			||||||
 | 
					from huey.utils import local_to_utc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					queue_name = 'test-queue'
 | 
				
			||||||
 | 
					queue = DummyQueue(queue_name)
 | 
				
			||||||
 | 
					schedule = DummySchedule(queue_name)
 | 
				
			||||||
 | 
					huey = Huey(queue, schedule=schedule)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					res_queue_name = 'test-queue-2'
 | 
				
			||||||
 | 
					res_queue = DummyQueue(res_queue_name)
 | 
				
			||||||
 | 
					res_store = DummyDataStore(res_queue_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					res_huey = Huey(res_queue, res_store, schedule)
 | 
				
			||||||
 | 
					res_huey_nones = Huey(res_queue, res_store, store_none=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# store some global state
 | 
				
			||||||
 | 
					state = {}
 | 
				
			||||||
 | 
					last_executed_task_class = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# create a decorated queue command
 | 
				
			||||||
 | 
					@huey.task()
 | 
				
			||||||
 | 
					def add(key, value):
 | 
				
			||||||
 | 
					    state[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@huey.task(include_task=True)
 | 
				
			||||||
 | 
					def self_aware(key, value, task=None):
 | 
				
			||||||
 | 
					    last_executed_task_class.append(task.__class__.__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# create a periodic queue command
 | 
				
			||||||
 | 
					@huey.periodic_task(crontab(minute='0'))
 | 
				
			||||||
 | 
					def add_on_the_hour():
 | 
				
			||||||
 | 
					    state['periodic'] = 'x'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# define a command using the class
 | 
				
			||||||
 | 
					class AddTask(QueueTask):
 | 
				
			||||||
 | 
					    def execute(self):
 | 
				
			||||||
 | 
					        k, v = self.data
 | 
				
			||||||
 | 
					        state[k] = v
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# create a command that raises an exception
 | 
				
			||||||
 | 
					class BampfException(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@huey.task()
 | 
				
			||||||
 | 
					def throw_error():
 | 
				
			||||||
 | 
					    raise BampfException('bampf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@res_huey.task()
 | 
				
			||||||
 | 
					def add2(a, b):
 | 
				
			||||||
 | 
					    return a + b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@res_huey.periodic_task(crontab(minute='0'))
 | 
				
			||||||
 | 
					def add_on_the_hour2():
 | 
				
			||||||
 | 
					    state['periodic'] = 'x'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@res_huey.task()
 | 
				
			||||||
 | 
					def returns_none():
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@res_huey_nones.task()
 | 
				
			||||||
 | 
					def returns_none2():
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HueyTestCase(unittest.TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        global state
 | 
				
			||||||
 | 
					        global last_executed_task_class
 | 
				
			||||||
 | 
					        queue.flush()
 | 
				
			||||||
 | 
					        res_queue.flush()
 | 
				
			||||||
 | 
					        schedule.flush()
 | 
				
			||||||
 | 
					        state = {}
 | 
				
			||||||
 | 
					        last_executed_task_class = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_registration(self):
 | 
				
			||||||
 | 
					        self.assertTrue('queuecmd_add' in registry)
 | 
				
			||||||
 | 
					        self.assertTrue('queuecmd_add_on_the_hour' in registry)
 | 
				
			||||||
 | 
					        self.assertTrue('AddTask' in registry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_enqueue(self):
 | 
				
			||||||
 | 
					        # sanity check
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # initializing the command does not enqueue it
 | 
				
			||||||
 | 
					        ac = AddTask(('k', 'v'))
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # ok, enqueue it, then check that it was enqueued
 | 
				
			||||||
 | 
					        huey.enqueue(ac)
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it can be enqueued multiple times
 | 
				
			||||||
 | 
					        huey.enqueue(ac)
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # no changes to state
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_enqueue_decorator(self):
 | 
				
			||||||
 | 
					        # sanity check
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add('k', 'v')
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add('k', 'v')
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # no changes to state
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_schedule(self):
 | 
				
			||||||
 | 
					        dt = datetime.datetime(2011, 1, 1, 0, 1)
 | 
				
			||||||
 | 
					        add('k', 'v')
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					        self.assertEqual(task.execute_time, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add.schedule(args=('k2', 'v2'), eta=dt)
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					        self.assertEqual(task.execute_time, local_to_utc(dt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add.schedule(args=('k3', 'v3'), eta=dt, convert_utc=False)
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 1)
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					        self.assertEqual(task.execute_time, dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_error_raised(self):
 | 
				
			||||||
 | 
					        throw_error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # no error
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # error
 | 
				
			||||||
 | 
					        self.assertRaises(BampfException, huey.execute, task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_internal_error(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Verify that exceptions are wrapped with the special "huey"
 | 
				
			||||||
 | 
					        exception classes.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        class SpecialException(Exception):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class BrokenQueue(DummyQueue):
 | 
				
			||||||
 | 
					            def read(self):
 | 
				
			||||||
 | 
					                raise SpecialException('read error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def write(self, data):
 | 
				
			||||||
 | 
					                raise SpecialException('write error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class BrokenDataStore(DummyDataStore):
 | 
				
			||||||
 | 
					            def get(self, key):
 | 
				
			||||||
 | 
					                raise SpecialException('get error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def put(self, key, value):
 | 
				
			||||||
 | 
					                raise SpecialException('put error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class BrokenSchedule(DummySchedule):
 | 
				
			||||||
 | 
					            def add(self, data, ts):
 | 
				
			||||||
 | 
					                raise SpecialException('add error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def read(self, ts):
 | 
				
			||||||
 | 
					                raise SpecialException('read error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = AddTask()
 | 
				
			||||||
 | 
					        huey = Huey(
 | 
				
			||||||
 | 
					            BrokenQueue('q'),
 | 
				
			||||||
 | 
					            BrokenDataStore('q'),
 | 
				
			||||||
 | 
					            BrokenSchedule('q'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.QueueWriteException,
 | 
				
			||||||
 | 
					            huey.enqueue,
 | 
				
			||||||
 | 
					            AddTask())
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.QueueReadException,
 | 
				
			||||||
 | 
					            huey.dequeue)
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.DataStorePutException,
 | 
				
			||||||
 | 
					            huey.revoke,
 | 
				
			||||||
 | 
					            task)
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.DataStoreGetException,
 | 
				
			||||||
 | 
					            huey.restore,
 | 
				
			||||||
 | 
					            task)
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.ScheduleAddException,
 | 
				
			||||||
 | 
					            huey.add_schedule,
 | 
				
			||||||
 | 
					            task)
 | 
				
			||||||
 | 
					        self.assertRaises(
 | 
				
			||||||
 | 
					            huey_exceptions.ScheduleReadException,
 | 
				
			||||||
 | 
					            huey.read_schedule,
 | 
				
			||||||
 | 
					            1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_dequeueing(self):
 | 
				
			||||||
 | 
					        res = huey.dequeue() # no error raised if queue is empty
 | 
				
			||||||
 | 
					        self.assertEqual(res, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add('k', 'v')
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(isinstance(task, QueueTask))
 | 
				
			||||||
 | 
					        self.assertEqual(task.get_data(), (('k', 'v'), {}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_execution(self):
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					        add('k', 'v')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					        self.assertFalse('k' in state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        huey.execute(task)
 | 
				
			||||||
 | 
					        self.assertEqual(state['k'], 'v')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add('k', 'X')
 | 
				
			||||||
 | 
					        self.assertEqual(state['k'], 'v')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        huey.execute(huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(state['k'], 'X')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(TypeError, huey.execute, huey.dequeue())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_self_awareness(self):
 | 
				
			||||||
 | 
					        self_aware('k', 'v')
 | 
				
			||||||
 | 
					        task = huey.dequeue()
 | 
				
			||||||
 | 
					        huey.execute(task)
 | 
				
			||||||
 | 
					        self.assertEqual(last_executed_task_class.pop(), "queuecmd_self_aware")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self_aware('k', 'v')
 | 
				
			||||||
 | 
					        huey.execute(huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(last_executed_task_class.pop(), "queuecmd_self_aware")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add('k', 'x')
 | 
				
			||||||
 | 
					        huey.execute(huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(len(last_executed_task_class), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_call_local(self):
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 0)
 | 
				
			||||||
 | 
					        self.assertEqual(state, {})
 | 
				
			||||||
 | 
					        add.call_local('nugget', 'green')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(queue), 0)
 | 
				
			||||||
 | 
					        self.assertEqual(state['nugget'], 'green')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_revoke(self):
 | 
				
			||||||
 | 
					        ac = AddTask(('k', 'v'))
 | 
				
			||||||
 | 
					        ac2 = AddTask(('k2', 'v2'))
 | 
				
			||||||
 | 
					        ac3 = AddTask(('k3', 'v3'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res_huey.enqueue(ac)
 | 
				
			||||||
 | 
					        res_huey.enqueue(ac2)
 | 
				
			||||||
 | 
					        res_huey.enqueue(ac3)
 | 
				
			||||||
 | 
					        res_huey.enqueue(ac2)
 | 
				
			||||||
 | 
					        res_huey.enqueue(ac)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(len(res_queue), 5)
 | 
				
			||||||
 | 
					        res_huey.revoke(ac2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while res_queue:
 | 
				
			||||||
 | 
					            task = res_huey.dequeue()
 | 
				
			||||||
 | 
					            if not res_huey.is_revoked(task):
 | 
				
			||||||
 | 
					                res_huey.execute(task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(state, {'k': 'v', 'k3': 'v3'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_revoke_periodic(self):
 | 
				
			||||||
 | 
					        add_on_the_hour2.revoke()
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it is still revoked
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_on_the_hour2.restore()
 | 
				
			||||||
 | 
					        self.assertFalse(add_on_the_hour2.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_on_the_hour2.revoke(revoke_once=True)
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked()) # it is revoked once, but we are preserving that state
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked(peek=False)) # is revoked once, but clear state
 | 
				
			||||||
 | 
					        self.assertFalse(add_on_the_hour2.is_revoked()) # no longer revoked
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        d = datetime.datetime
 | 
				
			||||||
 | 
					        add_on_the_hour2.revoke(revoke_until=d(2011, 1, 1, 11, 0))
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked(dt=d(2011, 1, 1, 10, 0)))
 | 
				
			||||||
 | 
					        self.assertTrue(add_on_the_hour2.is_revoked(dt=d(2011, 1, 1, 10, 59)))
 | 
				
			||||||
 | 
					        self.assertFalse(add_on_the_hour2.is_revoked(dt=d(2011, 1, 1, 11, 0)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_on_the_hour2.restore()
 | 
				
			||||||
 | 
					        self.assertFalse(add_on_the_hour2.is_revoked())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_result_store(self):
 | 
				
			||||||
 | 
					        res = add2(1, 2)
 | 
				
			||||||
 | 
					        res2 = add2(4, 5)
 | 
				
			||||||
 | 
					        res3 = add2(0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # none have been executed as yet
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), None)
 | 
				
			||||||
 | 
					        self.assertEqual(res2.get(), None)
 | 
				
			||||||
 | 
					        self.assertEqual(res3.get(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # execute the first task
 | 
				
			||||||
 | 
					        res_huey.execute(res_huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), 3)
 | 
				
			||||||
 | 
					        self.assertEqual(res2.get(), None)
 | 
				
			||||||
 | 
					        self.assertEqual(res3.get(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # execute the second task
 | 
				
			||||||
 | 
					        res_huey.execute(res_huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), 3)
 | 
				
			||||||
 | 
					        self.assertEqual(res2.get(), 9)
 | 
				
			||||||
 | 
					        self.assertEqual(res3.get(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # execute the 3rd, which returns a zero value
 | 
				
			||||||
 | 
					        res_huey.execute(res_huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), 3)
 | 
				
			||||||
 | 
					        self.assertEqual(res2.get(), 9)
 | 
				
			||||||
 | 
					        self.assertEqual(res3.get(), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check that it returns None when nothing is present
 | 
				
			||||||
 | 
					        res = returns_none()
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # execute, it will still return None, but underneath it is an EmptyResult
 | 
				
			||||||
 | 
					        # indicating its actual result was not persisted
 | 
				
			||||||
 | 
					        res_huey.execute(res_huey.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), None)
 | 
				
			||||||
 | 
					        self.assertEqual(res._result, EmptyData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # execute again, this time note that we're pointing at the invoker
 | 
				
			||||||
 | 
					        # that *does* accept None as a store-able result
 | 
				
			||||||
 | 
					        res = returns_none2()
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # it stores None
 | 
				
			||||||
 | 
					        res_huey_nones.execute(res_huey_nones.dequeue())
 | 
				
			||||||
 | 
					        self.assertEqual(res.get(), None)
 | 
				
			||||||
 | 
					        self.assertEqual(res._result, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_task_store(self):
 | 
				
			||||||
 | 
					        dt1 = datetime.datetime(2011, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					        dt2 = datetime.datetime(2035, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k', 'v'), eta=dt1, convert_utc=False)
 | 
				
			||||||
 | 
					        task1 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k2', 'v2'), eta=dt2, convert_utc=False)
 | 
				
			||||||
 | 
					        task2 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2('k3', 'v3')
 | 
				
			||||||
 | 
					        task3 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # add the command to the schedule
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task1)
 | 
				
			||||||
 | 
					        self.assertEqual(len(res_huey.schedule._schedule), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # add a future-dated command
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task2)
 | 
				
			||||||
 | 
					        self.assertEqual(len(res_huey.schedule._schedule), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tasks = res_huey.read_schedule(dt1)
 | 
				
			||||||
 | 
					        self.assertEqual(tasks, [task3, task1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tasks = res_huey.read_schedule(dt1)
 | 
				
			||||||
 | 
					        self.assertEqual(tasks, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tasks = res_huey.read_schedule(dt2)
 | 
				
			||||||
 | 
					        self.assertEqual(tasks, [task2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_ready_to_run_method(self):
 | 
				
			||||||
 | 
					        dt1 = datetime.datetime(2011, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					        dt2 = datetime.datetime(2035, 1, 1, 0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k', 'v'), eta=dt1)
 | 
				
			||||||
 | 
					        task1 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k2', 'v2'), eta=dt2)
 | 
				
			||||||
 | 
					        task2 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2('k3', 'v3')
 | 
				
			||||||
 | 
					        task3 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k4', 'v4'), task_id='test_task_id')
 | 
				
			||||||
 | 
					        task4 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # sanity check what should be run
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task1))
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task2))
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task3))
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task4))
 | 
				
			||||||
 | 
					        self.assertEqual('test_task_id', task4.task_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_task_delay(self):
 | 
				
			||||||
 | 
					        curr = datetime.datetime.utcnow()
 | 
				
			||||||
 | 
					        curr50 = curr + datetime.timedelta(seconds=50)
 | 
				
			||||||
 | 
					        curr70 = curr + datetime.timedelta(seconds=70)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k', 'v'), delay=60)
 | 
				
			||||||
 | 
					        task1 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2.schedule(args=('k2', 'v2'), delay=600)
 | 
				
			||||||
 | 
					        task2 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add2('k3', 'v3')
 | 
				
			||||||
 | 
					        task3 = res_huey.dequeue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # add the command to the schedule
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task1)
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task2)
 | 
				
			||||||
 | 
					        res_huey.add_schedule(task3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # sanity check what should be run
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task1))
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task2))
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task3))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task1, curr50))
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task2, curr50))
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task3, curr50))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task1, curr70))
 | 
				
			||||||
 | 
					        self.assertFalse(res_huey.ready_to_run(task2, curr70))
 | 
				
			||||||
 | 
					        self.assertTrue(res_huey.ready_to_run(task3, curr70))
 | 
				
			||||||
							
								
								
									
										24
									
								
								lib/huey/tests/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								lib/huey/tests/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from huey.utils import wrap_exception
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MyException(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestWrapException(unittest.TestCase):
 | 
				
			||||||
 | 
					    def test_wrap_exception(self):
 | 
				
			||||||
 | 
					        def raise_keyerror():
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                {}['huey']
 | 
				
			||||||
 | 
					            except KeyError as exc:
 | 
				
			||||||
 | 
					                raise wrap_exception(MyException)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(MyException, raise_keyerror)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            raise_keyerror()
 | 
				
			||||||
 | 
					        except MyException as exc:
 | 
				
			||||||
 | 
					            self.assertEqual(str(exc), "KeyError: 'huey'")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            assert False
 | 
				
			||||||
							
								
								
									
										21
									
								
								lib/huey/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/huey/utils.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EmptyData(object):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_class(s):
 | 
				
			||||||
 | 
					    path, klass = s.rsplit('.', 1)
 | 
				
			||||||
 | 
					    __import__(path)
 | 
				
			||||||
 | 
					    mod = sys.modules[path]
 | 
				
			||||||
 | 
					    return getattr(mod, klass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wrap_exception(new_exc_class):
 | 
				
			||||||
 | 
					    exc_class, exc, tb = sys.exc_info()
 | 
				
			||||||
 | 
					    raise new_exc_class('%s: %s' % (exc_class.__name__, exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def local_to_utc(dt):
 | 
				
			||||||
 | 
					    return datetime.datetime(*time.gmtime(time.mktime(dt.timetuple()))[:6])
 | 
				
			||||||
							
								
								
									
										12
									
								
								rpm/build.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										12
									
								
								rpm/build.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -ex
 | 
				
			||||||
 | 
					rm -rf ./tis-tisbackup/ ./BUILD  *.rpm ./RPMS
 | 
				
			||||||
 | 
					mkdir -p BUILD RPMS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VERSION=`git rev-list HEAD --count` 
 | 
				
			||||||
 | 
					echo $VERSION > __VERSION__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpmbuild -bb --buildroot $PWD/builddir -v --clean tis-tisbackup.spec
 | 
				
			||||||
 | 
					cp RPMS/*/*.rpm .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										54
									
								
								rpm/tis-tisbackup.spec
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								rpm/tis-tisbackup.spec
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					%define _topdir   .
 | 
				
			||||||
 | 
					%define buildroot ./builddir
 | 
				
			||||||
 | 
					%define VERSION %(cat __VERSION__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Name:	        tis-tisbackup
 | 
				
			||||||
 | 
					Version:        %{VERSION}
 | 
				
			||||||
 | 
					Release:	1%{?dist}
 | 
				
			||||||
 | 
					Summary:	TisBackup backup manager
 | 
				
			||||||
 | 
					BuildArch:	x86_64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Group:          System Environment/Daemons
 | 
				
			||||||
 | 
					License:	GPL
 | 
				
			||||||
 | 
					URL:		http://dev.tranquil.it
 | 
				
			||||||
 | 
					Source0:	../
 | 
				
			||||||
 | 
					Prefix:		/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Requires:       unzip rsync python-paramiko python-pyvmomi python-pip nfs-utils  python-flask python-setuptools python-simplejson autofs pexpect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Turn off the brp-python-bytecompile script
 | 
				
			||||||
 | 
					#%global __os_install_post %(echo '%{__os_install_post}' | sed -e 's!/usr/lib[^[:space:]]*/brp-python-bytecompile[[:space:]].*$!!g')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%install
 | 
				
			||||||
 | 
					set -ex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p %{buildroot}/opt/tisbackup/
 | 
				
			||||||
 | 
					mkdir -p %{buildroot}/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					mkdir -p %{buildroot}/etc/cron.d/
 | 
				
			||||||
 | 
					mkdir -p %{buildroot}/etc/tis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rsync --exclude="rpm" --exclude=".git" -aP ../../../tisbackup/  %{buildroot}/opt/tisbackup/
 | 
				
			||||||
 | 
					rsync -aP ../../../tisbackup/scripts/tisbackup_gui.service  %{buildroot}/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					rsync -aP ../../../tisbackup/scripts/tisbackup_huey.service  %{buildroot}/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					rsync -aP ../../../tisbackup/samples/tisbackup.cron  %{buildroot}/etc/cron.d/
 | 
				
			||||||
 | 
					rsync -aP ../../../tisbackup/samples/tisbackup_gui.ini  %{buildroot}/etc/tis
 | 
				
			||||||
 | 
					rsync -aP ../../../tisbackup/samples/config.ini.sample  %{buildroot}/etc/tis/tisbackup-config.ini.sample
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%files
 | 
				
			||||||
 | 
					%defattr(-,root,root)
 | 
				
			||||||
 | 
					%attr(-,root,root)/opt/tisbackup/
 | 
				
			||||||
 | 
					%attr(-,root,root)/usr/lib/systemd/system/
 | 
				
			||||||
 | 
					%attr(-,root,root)/etc/tis
 | 
				
			||||||
 | 
					%attr(-,root,root)/etc/cron.d/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%pre
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%post
 | 
				
			||||||
 | 
					#[ -f /etc/dhcp/reservations.conf ] || touch /etc/dhcp/reservations.conf
 | 
				
			||||||
 | 
					#[ -f /etc/dhcp/reservations.conf.disabled ] || touch /etc/dhcp/reservations.conf.disabled
 | 
				
			||||||
 | 
					#[ -f /opt/tis-dhcpmanager/config.ini ] || cp /opt/tis-dhcpmanager/config.ini.sample /opt/tis-dhcpmanager/config.ini
 | 
				
			||||||
 | 
					#[ -f /etc/dhcp/reservations.conf ] && sed -i 's/{/{\n/;s/;/;\n/g;'  /etc/dhcp/reservations.conf &&  sed -i '/^$/d'  /etc/dhcp/reservations.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								scripts/tisbackup_gui.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/tisbackup_gui.service
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=tisbackup
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=simple
 | 
				
			||||||
 | 
					ExecStart=/usr/bin/python2 /opt/tisbackup/tisbackup_gui.py
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
							
								
								
									
										9
									
								
								scripts/tisbackup_huey.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								scripts/tisbackup_huey.service
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=tisbackup
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					Type=simple
 | 
				
			||||||
 | 
					ExecStart=/opt/tisbackup/huey_consumer.py -n tisbackup_gui.huey
 | 
				
			||||||
 | 
					WorkingDirectory=/opt/tisbackup 
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user