Skip to content

Commit

Permalink
Restore cache-clear behavior on a per-path basis.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Mar 27, 2021
1 parent 6e2740e commit db55bc6
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 1 deletion.
4 changes: 3 additions & 1 deletion importlib_metadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
Protocol,
)

from ._functools import method_cache
from ._itertools import unique_everseen

from configparser import ConfigParser
Expand Down Expand Up @@ -632,8 +633,9 @@ def search(self, name):
def mtime(self):
with contextlib.suppress(OSError):
return os.stat(self.root).st_mtime
self.lookup.cache_clear()

@functools.lru_cache()
@method_cache
def lookup(self, mtime):
return Lookup(self)

Expand Down
85 changes: 85 additions & 0 deletions importlib_metadata/_functools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import types
import functools


# from jaraco.functools 3.3
def method_cache(method, cache_wrapper=None):
"""
Wrap lru_cache to support storing the cache data in the object instances.
Abstracts the common paradigm where the method explicitly saves an
underscore-prefixed protected property on first call and returns that
subsequently.
>>> class MyClass:
... calls = 0
...
... @method_cache
... def method(self, value):
... self.calls += 1
... return value
>>> a = MyClass()
>>> a.method(3)
3
>>> for x in range(75):
... res = a.method(x)
>>> a.calls
75
Note that the apparent behavior will be exactly like that of lru_cache
except that the cache is stored on each instance, so values in one
instance will not flush values from another, and when an instance is
deleted, so are the cached values for that instance.
>>> b = MyClass()
>>> for x in range(35):
... res = b.method(x)
>>> b.calls
35
>>> a.method(0)
0
>>> a.calls
75
Note that if method had been decorated with ``functools.lru_cache()``,
a.calls would have been 76 (due to the cached value of 0 having been
flushed by the 'b' instance).
Clear the cache with ``.cache_clear()``
>>> a.method.cache_clear()
Same for a method that hasn't yet been called.
>>> c = MyClass()
>>> c.method.cache_clear()
Another cache wrapper may be supplied:
>>> cache = functools.lru_cache(maxsize=2)
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
>>> a = MyClass()
>>> a.method2()
3
Caution - do not subsequently wrap the method with another decorator, such
as ``@property``, which changes the semantics of the function.
See also
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
for another implementation and additional justification.
"""
cache_wrapper = cache_wrapper or functools.lru_cache()

def wrapper(self, *args, **kwargs):
# it's the first call, replace the method with a cached, bound method
bound_method = types.MethodType(method, self)
cached_method = cache_wrapper(bound_method)
setattr(self, method.__name__, cached_method)
return cached_method(*args, **kwargs)

# Support cache clear even before cache has been created.
wrapper.cache_clear = lambda: None

return wrapper

0 comments on commit db55bc6

Please sign in to comment.