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