From 4447aa1a8294d60ab4c156add50087702cbc2370 Mon Sep 17 00:00:00 2001 From: Theo Date: Fri, 8 Sep 2017 10:01:38 +0100 Subject: [PATCH] Merge latest master. Code reviews. # Conflicts: # setup.py --- .travis.yml | 2 -- CHANGES.md | 4 ++- py26-requirements.txt | 3 --- rq/logutils.py | 7 ----- rq/queue.py | 6 +++++ rq/version.py | 2 +- setup.py | 20 ++++----------- tests/fixtures.py | 4 +++ tests/test_connection.py | 30 +++++++++++++++++++++- tests/test_utils.py | 55 +++++++++++++++++++++++++++++++++++++--- tests/test_worker.py | 6 ++--- 11 files changed, 102 insertions(+), 37 deletions(-) delete mode 100644 py26-requirements.txt diff --git a/.travis.yml b/.travis.yml index 88e5a2c..77c8bdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: python services: - redis python: - - "2.6" - "2.7" - "3.3" - "3.4" @@ -12,7 +11,6 @@ python: - "3.6-dev" - "pypy" install: - - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install -r py26-requirements.txt; fi - pip install -e . - pip install pytest-cov - pip install coveralls diff --git a/CHANGES.md b/CHANGES.md index 9223c21..6a36e37 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +### 0.8.2 +- Fixed an issue where `job.save()` may fail with unpickleable return value. + ### 0.8.1 - Replace `job.id` with `Job` instance in local `_job_stack `. Thanks @katichev! @@ -8,7 +11,6 @@ - RQ cli commands now accept `--path` parameter. Thanks @kirill and @sjtbham! - Make `job.dependency` slightly more efficient. Thanks @liangsijian! - `FailedQueue` now returns jobs with the correct class. Thanks @amjith! -- Fixed an issue where `job.save()` may fail with unpickleable return value. ### 0.8.0 diff --git a/py26-requirements.txt b/py26-requirements.txt deleted file mode 100644 index 222f989..0000000 --- a/py26-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -unittest2 -importlib -argparse diff --git a/rq/logutils.py b/rq/logutils.py index aeb2c8f..ed351fa 100644 --- a/rq/logutils.py +++ b/rq/logutils.py @@ -11,13 +11,6 @@ def setup_loghandlers(level): logger = logging.getLogger('rq.worker') if not _has_effective_handler(logger): logger.setLevel(level) - # This statement doesn't set level properly in Python-2.6 - # Following is an additional check to see if level has been set to - # appropriate(int) value - if logger.getEffectiveLevel() == level: - # Python-2.6. Set again by using logging.INFO etc. - level_int = getattr(logging, level) - logger.setLevel(level_int) formatter = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S') handler = ColorizingStreamHandler() diff --git a/rq/queue.py b/rq/queue.py index e4b6a47..9d2357f 100644 --- a/rq/queue.py +++ b/rq/queue.py @@ -75,6 +75,12 @@ class Queue(object): def __len__(self): return self.count + def __nonzero__(self): + return True + + def __bool__(self): + return True + def __iter__(self): yield self diff --git a/rq/version.py b/rq/version.py index 396d9c3..eb80bc6 100644 --- a/rq/version.py +++ b/rq/version.py @@ -2,4 +2,4 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -VERSION = '0.8.1' +VERSION = '0.8.2' diff --git a/setup.py b/setup.py index 54c9bf7..f22355a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,6 @@ rq is a simple, lightweight, library for creating background jobs, and processing them. """ -import sys import os from setuptools import setup, find_packages @@ -18,16 +17,6 @@ def get_version(): raise RuntimeError('No version info found.') -def get_dependencies(): - deps = ['redis >= 2.7.0', 'click >= 5.0'] - if sys.version_info < (2, 7) or \ - ((3, 0) <= sys.version_info < (3, 1)): - deps += ['importlib'] - if sys.version_info < (2, 7) or \ - ((3, 0) <= sys.version_info < (3, 2)): - deps += ['argparse'] - return deps - setup( name='rq', version=get_version(), @@ -42,7 +31,11 @@ setup( include_package_data=True, zip_safe=False, platforms='any', - install_requires=get_dependencies(), + install_requires=[ + 'redis >= 2.7.0', + 'click >= 5.0' + ], + python_requires='>=2.7', entry_points={ 'console_scripts': [ 'rq = rq.cli:main', @@ -53,9 +46,6 @@ setup( 'rqworker = rq.cli:worker', ], }, - extras_require={ - ':python_version=="2.6"': ['argparse', 'importlib'], - }, classifiers=[ # As from http://pypi.python.org/pypi?%3Aaction=list_classifiers # 'Development Status :: 1 - Planning', diff --git a/tests/fixtures.py b/tests/fixtures.py index 9173989..f9d1eb1 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -146,3 +146,7 @@ def run_dummy_heroku_worker(sandbox, _imminent_shutdown_delay): w = TestHerokuWorker(Queue('dummy')) w.main_work_horse(None, None) + + +class DummyQueue(object): + pass diff --git a/tests/test_connection.py b/tests/test_connection.py index 505f129..c04358b 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -2,7 +2,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -from rq import Connection, Queue +from rq import Connection, Queue, use_connection, get_current_connection, pop_connection +from rq.connections import NoRedisConnectionException from tests import find_empty_redis_database, RQTestCase from tests.fixtures import do_nothing @@ -38,3 +39,30 @@ class TestConnectionInheritance(RQTestCase): job2 = q2.enqueue(do_nothing) self.assertEqual(q1.connection, job1.connection) self.assertEqual(q2.connection, job2.connection) + + +class TestConnectionHelpers(RQTestCase): + def test_use_connection(self): + """Test function use_connection works as expected.""" + conn = new_connection() + use_connection(conn) + + self.assertEqual(conn, get_current_connection()) + + use_connection() + + self.assertNotEqual(conn, get_current_connection()) + + use_connection(self.testconn) # Restore RQTestCase connection + + with self.assertRaises(AssertionError): + with Connection(new_connection()): + use_connection() + with Connection(new_connection()): + use_connection() + + def test_resolve_connection_raises_on_no_connection(self): + """Test function resolve_connection raises if there is no connection.""" + pop_connection() + with self.assertRaises(NoRedisConnectionException): + Queue() diff --git a/tests/test_utils.py b/tests/test_utils.py index d3d9ef9..529c61e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import (absolute_import, division, print_function, unicode_literals) - -from tests import RQTestCase -from rq.utils import parse_timeout +import re +import datetime +from tests import RQTestCase, fixtures +from rq.utils import parse_timeout, first, is_nonstring_iterable, ensure_list, utcparse, backend_class +from rq.exceptions import TimeoutFormatError class TestUtils(RQTestCase): @@ -15,3 +17,50 @@ class TestUtils(RQTestCase): self.assertEqual(720, parse_timeout('12m')) self.assertEqual(3600, parse_timeout('1h')) self.assertEqual(3600, parse_timeout('1H')) + + def test_parse_timeout_coverage_scenarios(self): + """Test parse_timeout edge cases for coverage""" + timeouts = ['h12', 'h', 'm', 's', '10k'] + + self.assertEqual(None, parse_timeout(None)) + with self.assertRaises(TimeoutFormatError): + for timeout in timeouts: + parse_timeout(timeout) + + def test_first(self): + """Ensure function first works correctly""" + self.assertEqual(42, first([0, False, None, [], (), 42])) + self.assertEqual(None, first([0, False, None, [], ()])) + self.assertEqual('ohai', first([0, False, None, [], ()], default='ohai')) + self.assertEqual('bc', first(re.match(regex, 'abc') for regex in ['b.*', 'a(.*)']).group(1)) + self.assertEqual(4, first([1, 1, 3, 4, 5], key=lambda x: x % 2 == 0)) + + def test_is_nonstring_iterable(self): + """Ensure function is_nonstring_iterable works correctly""" + self.assertEqual(True, is_nonstring_iterable([])) + self.assertEqual(False, is_nonstring_iterable('test')) + self.assertEqual(True, is_nonstring_iterable({})) + self.assertEqual(True, is_nonstring_iterable(())) + + def test_ensure_list(self): + """Ensure function ensure_list works correctly""" + self.assertEqual([], ensure_list([])) + self.assertEqual(['test'], ensure_list('test')) + self.assertEqual({}, ensure_list({})) + self.assertEqual((), ensure_list(())) + + def test_utcparse(self): + """Ensure function utcparse works correctly""" + utc_formated_time = '2017-08-31T10:14:02Z' + utc_compat_formated_time = '2017-08-31T10:20:56.226733+00:00' + + self.assertEqual(datetime.datetime(2017, 8, 31, 10, 14, 2), utcparse(utc_formated_time)) + self.assertEqual(datetime.datetime(2017, 8, 31, 10, 20, 56, 226733), utcparse(utc_compat_formated_time)) + + def test_backend_class(self): + """Ensure function backend_class works correctly""" + self.assertEqual(fixtures.DummyQueue, backend_class(fixtures, 'DummyQueue')) + self.assertNotEqual(fixtures.say_pid, backend_class(fixtures, 'DummyQueue')) + self.assertEqual(fixtures.DummyQueue, backend_class(fixtures, 'DummyQueue', override=fixtures.DummyQueue)) + self.assertEqual(fixtures.DummyQueue, + backend_class(fixtures, 'DummyQueue', override='tests.fixtures.DummyQueue')) diff --git a/tests/test_worker.py b/tests/test_worker.py index 19d44a8..e29e371 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -11,6 +11,7 @@ import time from multiprocessing import Process import subprocess import sys +from unittest import skipIf import pytest import mock @@ -817,12 +818,9 @@ class TestWorkerSubprocess(RQTestCase): assert get_failed_queue().count == 0 assert q.count == 0 - # @skipIf('pypy' in sys.version.lower(), 'often times out with pypy') + @skipIf('pypy' in sys.version.lower(), 'often times out with pypy') def test_run_scheduled_access_self(self): """Schedule a job that schedules a job, then run the worker as subprocess""" - if 'pypy' in sys.version.lower(): - # horrible bodge until we drop 2.6 support and can use skipIf - return q = Queue() q.enqueue(schedule_access_self) subprocess.check_call(['rqworker', '-u', self.redis_url, '-b'])