summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpwws <9303779+pwws@users.noreply.github.com>2021-05-07 06:07:31 +0200
committerGitHub <noreply@github.com>2021-05-07 11:07:31 +0700
commit40b90946a7ea04ee0ebd56c24529453c28baf56a (patch)
tree84a28a1c270d379c6ff75eeb7c540830a5484679
parent76ac0afbcd07c2a13b17ba92ddb63ca44e3b25aa (diff)
downloadrq-40b90946a7ea04ee0ebd56c24529453c28baf56a.tar.gz
bugfix: Allow using staticmethods as jobs (#1458)
-rw-r--r--rq/job.py2
-rw-r--r--rq/utils.py34
-rw-r--r--tests/fixtures.py6
-rw-r--r--tests/test_job.py8
4 files changed, 46 insertions, 4 deletions
diff --git a/rq/job.py b/rq/job.py
index 2296b9a..ea612c7 100644
--- a/rq/job.py
+++ b/rq/job.py
@@ -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)