diff --git a/rq/exceptions.py b/rq/exceptions.py index ebb6b3b..e9f58e0 100644 --- a/rq/exceptions.py +++ b/rq/exceptions.py @@ -29,3 +29,7 @@ class ShutDownImminentException(Exception): def __init__(self, msg, extra_info): self.extra_info = extra_info super(ShutDownImminentException, self).__init__(msg) + + +class TimeoutFormatError(Exception): + pass diff --git a/rq/queue.py b/rq/queue.py index 9e4e2b5..090997e 100644 --- a/rq/queue.py +++ b/rq/queue.py @@ -12,7 +12,7 @@ from .defaults import DEFAULT_RESULT_TTL from .exceptions import (DequeueTimeout, InvalidJobDependency, InvalidJobOperationError, NoSuchJobError, UnpickleError) from .job import Job, JobStatus -from .utils import backend_class, import_attribute, utcnow +from .utils import backend_class, import_attribute, utcnow, parse_timeout def get_failed_queue(connection=None, job_class=None): @@ -63,7 +63,7 @@ class Queue(object): prefix = self.redis_queue_namespace_prefix self.name = name self._key = '{0}{1}'.format(prefix, name) - self._default_timeout = default_timeout + self._default_timeout = parse_timeout(default_timeout) self._async = async # override class attribute job_class if one was passed @@ -191,7 +191,7 @@ class Queue(object): and kwargs as explicit arguments. Any kwargs passed to this function contain options for RQ itself. """ - timeout = timeout or self._default_timeout + timeout = parse_timeout(timeout) or self._default_timeout job = self.job_class.create( func, args=args, kwargs=kwargs, connection=self.connection, diff --git a/rq/utils.py b/rq/utils.py index 7558100..d7ec53c 100644 --- a/rq/utils.py +++ b/rq/utils.py @@ -12,10 +12,12 @@ import calendar import datetime import importlib import logging +import numbers import sys from collections import Iterable from .compat import as_text, is_python_version, string_types +from .exceptions import TimeoutFormatError class _Colorizer(object): @@ -242,3 +244,21 @@ def backend_class(holder, default_name, override=None): return import_attribute(override) else: return override + + +def parse_timeout(timeout): + """Transfer all kinds of timeout format to an integer representing seconds""" + if not isinstance(timeout, numbers.Integral) and timeout is not None: + try: + timeout = int(timeout) + except ValueError: + digit, unit = timeout[:-1], (timeout[-1:]).lower() + unit_second = {'h': 3600, 'm': 60, 's': 1} + try: + timeout = int(digit) * unit_second[unit] + except (ValueError, KeyError): + raise TimeoutFormatError('Timeout must be an integer or a string representing an integer, or ' + 'a string with format: digits + unit, unit can be "h", "m", "s", ' + 'such as "1h", "23m".') + + return timeout diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..d3d9ef9 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import (absolute_import, division, print_function, + unicode_literals) + +from tests import RQTestCase +from rq.utils import parse_timeout + + +class TestUtils(RQTestCase): + def test_parse_timeout(self): + """Ensure function parse_timeout works correctly""" + self.assertEqual(12, parse_timeout(12)) + self.assertEqual(12, parse_timeout('12')) + self.assertEqual(12, parse_timeout('12s')) + self.assertEqual(720, parse_timeout('12m')) + self.assertEqual(3600, parse_timeout('1h')) + self.assertEqual(3600, parse_timeout('1H'))