From 979eaf8acf2d96f6fdb80f77874341e952487a60 Mon Sep 17 00:00:00 2001 From: Felipe Bidu Date: Fri, 17 May 2024 16:40:35 -0300 Subject: [PATCH 1/4] Use qualname to avoid key collisions --- dogpile/cache/util.py | 7 +++---- tests/cache/test_util.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tests/cache/test_util.py diff --git a/dogpile/cache/util.py b/dogpile/cache/util.py index 7fddaa5..3eee032 100644 --- a/dogpile/cache/util.py +++ b/dogpile/cache/util.py @@ -1,7 +1,6 @@ from hashlib import sha1 -from ..util import compat -from ..util import langhelpers +from ..util import compat, langhelpers def function_key_generator(namespace, fn, to_str=str): @@ -24,9 +23,9 @@ def function_key_generator(namespace, fn, to_str=str): """ if namespace is None: - namespace = "%s:%s" % (fn.__module__, fn.__name__) + namespace = "%s:%s" % (fn.__module__, fn.__qualname__) else: - namespace = "%s:%s|%s" % (fn.__module__, fn.__name__, namespace) + namespace = "%s:%s|%s" % (fn.__module__, fn.__qualname__, namespace) args = compat.inspect_getargspec(fn) has_self = args[0] and args[0][0] in ("self", "cls") diff --git a/tests/cache/test_util.py b/tests/cache/test_util.py new file mode 100644 index 0000000..0feeb22 --- /dev/null +++ b/tests/cache/test_util.py @@ -0,0 +1,28 @@ +from dogpile.cache import util + + +class A: + @classmethod + def class_method(cls): + pass + + @staticmethod + def static_method(): + pass + + def instance_method(self): + pass + + +def test_function_key_generator(): + key_generator = util.function_key_generator(None, A.class_method) + assert key_generator() == "tests.cache.test_util:A.class_method|" + + key_generator = util.function_key_generator(None, A.static_method) + assert key_generator() == "tests.cache.test_util:A.static_method|" + + key_generator = util.function_key_generator(None, A.instance_method) + assert key_generator() == "tests.cache.test_util:A.instance_method|" + + key_generator = util.function_key_generator("namespace", A.class_method) + assert key_generator() == "tests.cache.test_util:A.class_method|namespace|" From dde14520861b12515863aefd9d8476c4425f2da9 Mon Sep 17 00:00:00 2001 From: Felipe Bidu Date: Fri, 17 May 2024 16:47:21 -0300 Subject: [PATCH 2/4] Make the qualname usage optional --- dogpile/cache/util.py | 16 +++++++++++++--- tests/cache/test_util.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/dogpile/cache/util.py b/dogpile/cache/util.py index 3eee032..81ba2e7 100644 --- a/dogpile/cache/util.py +++ b/dogpile/cache/util.py @@ -3,7 +3,7 @@ from ..util import compat, langhelpers -def function_key_generator(namespace, fn, to_str=str): +def function_key_generator(namespace, fn, to_str=str, use_qual_name=False): """Return a function that generates a string key, based on a given function as well as arguments to the returned function itself. @@ -15,17 +15,27 @@ def function_key_generator(namespace, fn, to_str=str): the :paramref:`.CacheRegion.function_key_generator` argument for :class:`.CacheRegion`. + :param namespace: A string namespace to be used in the key. + :param fn: The function which will be called. + :param to_str: A function that will convert arguments to strings. + :param use_qual_name: If True, use the qualified name of the function + instead of just the name. This is useful to avoid collision in modules + that defines more than one class containing methods with the same name. + .. seealso:: :func:`.kwarg_function_key_generator` - similar function that also takes keyword arguments into account """ + fn_name = fn.__name__ + if use_qual_name: + fn_name = fn.__qualname__ if namespace is None: - namespace = "%s:%s" % (fn.__module__, fn.__qualname__) + namespace = "%s:%s" % (fn.__module__, fn_name) else: - namespace = "%s:%s|%s" % (fn.__module__, fn.__qualname__, namespace) + namespace = "%s:%s|%s" % (fn.__module__, fn_name, namespace) args = compat.inspect_getargspec(fn) has_self = args[0] and args[0][0] in ("self", "cls") diff --git a/tests/cache/test_util.py b/tests/cache/test_util.py index 0feeb22..1e8767a 100644 --- a/tests/cache/test_util.py +++ b/tests/cache/test_util.py @@ -14,15 +14,37 @@ def instance_method(self): pass +def test_function_key_generator_qualname(): + key_generator = util.function_key_generator( + None, A.class_method, use_qual_name=True + ) + assert key_generator() == "tests.cache.test_util:A.class_method|" + + key_generator = util.function_key_generator( + None, A.static_method, use_qual_name=True + ) + assert key_generator() == "tests.cache.test_util:A.static_method|" + + key_generator = util.function_key_generator( + None, A.instance_method, use_qual_name=True + ) + assert key_generator() == "tests.cache.test_util:A.instance_method|" + + key_generator = util.function_key_generator( + "namespace", A.class_method, use_qual_name=True + ) + assert key_generator() == "tests.cache.test_util:A.class_method|namespace|" + + def test_function_key_generator(): key_generator = util.function_key_generator(None, A.class_method) - assert key_generator() == "tests.cache.test_util:A.class_method|" + assert key_generator() == "tests.cache.test_util:class_method|" key_generator = util.function_key_generator(None, A.static_method) - assert key_generator() == "tests.cache.test_util:A.static_method|" + assert key_generator() == "tests.cache.test_util:static_method|" key_generator = util.function_key_generator(None, A.instance_method) - assert key_generator() == "tests.cache.test_util:A.instance_method|" + assert key_generator() == "tests.cache.test_util:instance_method|" key_generator = util.function_key_generator("namespace", A.class_method) - assert key_generator() == "tests.cache.test_util:A.class_method|namespace|" + assert key_generator() == "tests.cache.test_util:class_method|namespace|" From 3718c14f3dfb34f4c7fba60cab4b327c8a6605c0 Mon Sep 17 00:00:00 2001 From: Felipe Bidu Date: Fri, 17 May 2024 16:49:54 -0300 Subject: [PATCH 3/4] Add test for nested function --- dogpile/cache/util.py | 3 ++- tests/cache/test_util.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dogpile/cache/util.py b/dogpile/cache/util.py index 81ba2e7..74d9c06 100644 --- a/dogpile/cache/util.py +++ b/dogpile/cache/util.py @@ -1,6 +1,7 @@ from hashlib import sha1 -from ..util import compat, langhelpers +from ..util import compat +from ..util import langhelpers def function_key_generator(namespace, fn, to_str=str, use_qual_name=False): diff --git a/tests/cache/test_util.py b/tests/cache/test_util.py index 1e8767a..d75065d 100644 --- a/tests/cache/test_util.py +++ b/tests/cache/test_util.py @@ -13,6 +13,12 @@ def static_method(): def instance_method(self): pass + def nested_method(self): + def nested(): + pass + + return nested + def test_function_key_generator_qualname(): key_generator = util.function_key_generator( @@ -35,6 +41,15 @@ def test_function_key_generator_qualname(): ) assert key_generator() == "tests.cache.test_util:A.class_method|namespace|" + nested = A().nested_method() + key_generator = util.function_key_generator( + None, nested, use_qual_name=True + ) + assert ( + key_generator() + == "tests.cache.test_util:A.nested_method..nested|" + ) + def test_function_key_generator(): key_generator = util.function_key_generator(None, A.class_method) @@ -48,3 +63,7 @@ def test_function_key_generator(): key_generator = util.function_key_generator("namespace", A.class_method) assert key_generator() == "tests.cache.test_util:class_method|namespace|" + + nested = A().nested_method() + key_generator = util.function_key_generator(None, nested) + assert key_generator() == "tests.cache.test_util:nested|" From 7ac4fbc798646f4555f4bb2bbad986075b9e3af7 Mon Sep 17 00:00:00 2001 From: Felipe Bidu Date: Mon, 20 May 2024 17:44:03 -0300 Subject: [PATCH 4/4] Touches up docs and design of qualname usage --- dogpile/cache/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dogpile/cache/util.py b/dogpile/cache/util.py index 74d9c06..148653f 100644 --- a/dogpile/cache/util.py +++ b/dogpile/cache/util.py @@ -23,15 +23,18 @@ def function_key_generator(namespace, fn, to_str=str, use_qual_name=False): instead of just the name. This is useful to avoid collision in modules that defines more than one class containing methods with the same name. + .. versionadded:: 1.3.4 + .. seealso:: :func:`.kwarg_function_key_generator` - similar function that also takes keyword arguments into account """ - fn_name = fn.__name__ if use_qual_name: fn_name = fn.__qualname__ + else: + fn_name = fn.__name__ if namespace is None: namespace = "%s:%s" % (fn.__module__, fn_name)