diff --git a/rq/job.py b/rq/job.py index ac9fe97..2bf615a 100644 --- a/rq/job.py +++ b/rq/job.py @@ -22,7 +22,7 @@ from .exceptions import DeserializationError, NoSuchJobError from .local import LocalStack from .serializers import resolve_serializer from .utils import (get_version, import_attribute, parse_timeout, str_to_date, - utcformat, utcnow, ensure_list) + utcformat, utcnow, ensure_list, get_call_string) # Serialize pickle dumps using the highest pickle protocol (binary, default # uses ascii) @@ -45,12 +45,6 @@ class JobStatus(str, Enum): UNEVALUATED = object() -def truncate_long_string(data, maxlen=75): - """ Truncates strings longer than maxlen - """ - return (data[:maxlen] + '...') if len(data) > maxlen else data - - def cancel_job(job_id, connection=None): """Cancels the job with the given job ID, preventing execution. Discards any job info (i.e. it can't be requeued later). @@ -801,17 +795,7 @@ class Job: """Returns a string representation of the call, formatted as a regular Python function invocation statement. """ - if self.func_name is None: - return None - - arg_list = [as_text(truncate_long_string(repr(arg))) for arg in self.args] - - kwargs = ['{0}={1}'.format(k, as_text(truncate_long_string(repr(v)))) for k, v in self.kwargs.items()] - # Sort here because python 3.3 & 3.4 makes different call_string - arg_list += sorted(kwargs) - args = ', '.join(arg_list) - - return '{0}({1})'.format(self.func_name, args) + return get_call_string(self.func_name, self.args, self.kwargs, max_length=75) def cleanup(self, ttl=None, pipeline=None, remove_from_queue=True): """Prepare job for eventual deletion (if needed). This method is usually diff --git a/rq/utils.py b/rq/utils.py index 4eb86c0..e1cb412 100644 --- a/rq/utils.py +++ b/rq/utils.py @@ -297,3 +297,27 @@ def split_list(a_list, segment_size): """ for i in range(0, len(a_list), segment_size): yield a_list[i:i + segment_size] + + +def truncate_long_string(data, max_length=None): + """Truncate arguments with representation longer than max_length""" + if max_length is None: + return data + return (data[:max_length] + '...') if len(data) > max_length else data + + +def get_call_string(func_name, args, kwargs, max_length=None): + """Returns a string representation of the call, formatted as a regular + Python function invocation statement. If max_length is not None, truncate + arguments with representation longer than max_length. + """ + if func_name is None: + return None + + arg_list = [as_text(truncate_long_string(repr(arg), max_length)) for arg in args] + + kwargs = ['{0}={1}'.format(k, as_text(truncate_long_string(repr(v), max_length))) for k, v in kwargs.items()] + arg_list += sorted(kwargs) + args = ', '.join(arg_list) + + return '{0}({1})'.format(func_name, args) diff --git a/tests/test_utils.py b/tests/test_utils.py index 69db3bd..add2863 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,7 +11,7 @@ from redis import Redis from tests import RQTestCase, fixtures from rq.utils import backend_class, ensure_list, first, get_version, is_nonstring_iterable, parse_timeout, utcparse, \ - split_list, ceildiv + split_list, ceildiv, get_call_string, truncate_long_string from rq.exceptions import TimeoutFormatError @@ -113,3 +113,26 @@ class TestUtils(RQTestCase): expected_small_list_count = ceildiv(BIG_LIST_SIZE, SEGMENT_SIZE) self.assertEqual(len(small_lists), expected_small_list_count) + + def test_truncate_long_string(self): + """Ensure truncate_long_string works properly""" + assert truncate_long_string("12", max_length=3) == "12" + assert truncate_long_string("123", max_length=3) == "123" + assert truncate_long_string("1234", max_length=3) == "123..." + assert truncate_long_string("12345", max_length=3) == "123..." + + s = "long string but no max_length provided so no truncating should occur" * 10 + assert truncate_long_string(s) == s + + def test_get_call_string(self): + """Ensure a case, when func_name, args and kwargs are not None, works properly""" + cs = get_call_string("f", ('some', 'args', 42), {"key1": "value1", "key2": True}) + assert cs == "f('some', 'args', 42, key1='value1', key2=True)" + + def test_get_call_string_with_max_length(self): + """Ensure get_call_string works properly when max_length is provided""" + func_name = "f" + args = (1234, 12345, 123456) + kwargs = {"len4": 1234, "len5": 12345, "len6": 123456} + cs = get_call_string(func_name, args, kwargs, max_length=5) + assert cs == "f(1234, 12345, 12345..., len4=1234, len5=12345, len6=12345...)"