diff options
author | pwws <9303779+pwws@users.noreply.github.com> | 2021-05-07 06:07:31 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-07 11:07:31 +0700 |
commit | 40b90946a7ea04ee0ebd56c24529453c28baf56a (patch) | |
tree | 84a28a1c270d379c6ff75eeb7c540830a5484679 | |
parent | 76ac0afbcd07c2a13b17ba92ddb63ca44e3b25aa (diff) | |
download | rq-40b90946a7ea04ee0ebd56c24529453c28baf56a.tar.gz |
bugfix: Allow using staticmethods as jobs (#1458)
-rw-r--r-- | rq/job.py | 2 | ||||
-rw-r--r-- | rq/utils.py | 34 | ||||
-rw-r--r-- | tests/fixtures.py | 6 | ||||
-rw-r--r-- | tests/test_job.py | 8 |
4 files changed, 46 insertions, 4 deletions
@@ -108,7 +108,7 @@ class Job(object): job._instance = func.__self__ job._func_name = func.__name__ elif inspect.isfunction(func) or inspect.isbuiltin(func): - job._func_name = '{0}.{1}'.format(func.__module__, func.__name__) + job._func_name = '{0}.{1}'.format(func.__module__, func.__qualname__) elif isinstance(func, string_types): job._func_name = as_text(func) elif not inspect.isclass(func) and hasattr(func, '__call__'): # a callable class instance diff --git a/rq/utils.py b/rq/utils.py index 573c4aa..2686f52 100644 --- a/rq/utils.py +++ b/rq/utils.py @@ -125,9 +125,37 @@ class ColorizingStreamHandler(logging.StreamHandler): def import_attribute(name): """Return an attribute from a dotted path name (e.g. "path.to.func").""" - module_name, attribute = name.rsplit('.', 1) - module = importlib.import_module(module_name) - return getattr(module, attribute) + name_bits = name.split('.') + module_name_bits, attribute_bits = name_bits[:-1], [name_bits[-1]] + module = None + # When the attribute we look for is a staticmethod, module name in its + # dotted path is not the last-before-end word + # E.g.: package_a.package_b.module_a.ClassA.my_static_method + # Thus we remove the bits from the end of the name until we can import it + while len(module_name_bits): + try: + module_name = '.'.join(module_name_bits) + module = importlib.import_module(module_name) + break + except ModuleNotFoundError: + attribute_bits.insert(0, module_name_bits.pop()) + + if module is None: + raise ValueError(f'Invalid attribute name: {name}') + + attribute_name = '.'.join(attribute_bits) + if hasattr(module, attribute_name): + return getattr(module, attribute_name) + + # staticmethods + attribute_name = attribute_bits.pop() + attribute_owner_name = '.'.join(attribute_bits) + attribute_owner = getattr(module, attribute_owner_name) + + if not hasattr(attribute_owner, attribute_name): + raise ValueError(f'Invalid attribute name: {name}') + + return getattr(attribute_owner, attribute_name) def utcnow(): diff --git a/tests/fixtures.py b/tests/fixtures.py index 82e98bc..dd2218a 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -143,6 +143,12 @@ class UnicodeStringObject(object): return u'é' +class ClassWithAStaticMethod(object): + @staticmethod + def static_method(): + return u"I'm a static method" + + with Connection(): @job(queue='default') def decorated_job(x, y): diff --git a/tests/test_job.py b/tests/test_job.py index a65ca86..f50b03e 100644 --- a/tests/test_job.py +++ b/tests/test_job.py @@ -772,6 +772,14 @@ class TestJob(RQTestCase): self.assertIsNotNone(job.get_call_string()) job.perform() + def test_create_job_from_static_method(self): + """test creating jobs with static method""" + queue = Queue(connection=self.testconn) + + job = queue.enqueue(fixtures.ClassWithAStaticMethod.static_method) + self.assertIsNotNone(job.get_call_string()) + job.perform() + def test_create_job_with_ttl_should_have_ttl_after_enqueued(self): """test creating jobs with ttl and checks if get_jobs returns it properly [issue502]""" queue = Queue(connection=self.testconn) |