Skip to content

Commit

Permalink
Merge 60db6e3 into 4448776
Browse files Browse the repository at this point in the history
  • Loading branch information
hephex committed Oct 30, 2018
2 parents 4448776 + 60db6e3 commit f23bcec
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 58 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ python:
- 3.4
- 3.5
- 3.6
- 3.7-dev
- pypy3.5

install:
Expand Down
140 changes: 84 additions & 56 deletions cachetools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import absolute_import

import functools
import sys

from . import keys
from .cache import Cache
Expand All @@ -11,6 +12,17 @@
from .rr import RRCache
from .ttl import TTLCache

if sys.version_info >= (3, 5):
from inspect import iscoroutinefunction
from . import _async

else:
def iscoroutinefunction(*_, **__):
return False

_async = None


__all__ = (
'Cache', 'LFUCache', 'LRUCache', 'RRCache', 'TTLCache',
'cached', 'cachedmethod'
Expand All @@ -37,33 +49,41 @@ def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
elif lock is None:
def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
return cache[k]
except KeyError:
pass # key not found
v = func(*args, **kwargs)
try:
cache[k] = v
except ValueError:
pass # value too large
return v
else:
def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
with lock:
if iscoroutinefunction(func):
wrapper = _async.func_wrapper(func, cache, key)

else:
def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
return cache[k]
except KeyError:
pass # key not found
v = func(*args, **kwargs)
try:
with lock:
except KeyError:
pass # key not found
v = func(*args, **kwargs)
try:
cache[k] = v
except ValueError:
pass # value too large
return v
except ValueError:
pass # value too large
return v
else:
if iscoroutinefunction(func):
wrapper = _async.func_wrapper_lock(func, cache, key, lock)

else:
def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
with lock:
return cache[k]
except KeyError:
pass # key not found
v = func(*args, **kwargs)
try:
with lock:
cache[k] = v
except ValueError:
pass # value too large
return v
return _update_wrapper(wrapper, func)
return decorator

Expand All @@ -75,38 +95,46 @@ def cachedmethod(cache, key=keys.hashkey, lock=None):
"""
def decorator(method):
if lock is None:
def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
return method(self, *args, **kwargs)
k = key(self, *args, **kwargs)
try:
return c[k]
except KeyError:
pass # key not found
v = method(self, *args, **kwargs)
try:
c[k] = v
except ValueError:
pass # value too large
return v
else:
def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
return method(self, *args, **kwargs)
k = key(self, *args, **kwargs)
try:
with lock(self):
if iscoroutinefunction(method):
wrapper = _async.method_wrapper(method, cache, key)

else:
def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
return method(self, *args, **kwargs)
k = key(self, *args, **kwargs)
try:
return c[k]
except KeyError:
pass # key not found
v = method(self, *args, **kwargs)
try:
with lock(self):
except KeyError:
pass # key not found
v = method(self, *args, **kwargs)
try:
c[k] = v
except ValueError:
pass # value too large
return v
except ValueError:
pass # value too large
return v
else:
if iscoroutinefunction(method):
wrapper = _async.method_wrapper_lock(method, cache, key, lock)

else:
def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
return method(self, *args, **kwargs)
k = key(self, *args, **kwargs)
try:
with lock(self):
return c[k]
except KeyError:
pass # key not found
v = method(self, *args, **kwargs)
try:
with lock(self):
c[k] = v
except ValueError:
pass # value too large
return v
return _update_wrapper(wrapper, method)
return decorator
97 changes: 97 additions & 0 deletions cachetools/_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
This module contains all the code that uses `async`/`await`
and gets imported by other modules only if the running Python version
is greater or equal to 3.5.
"""


def func_wrapper(func, cache, key):
"""
Creates an async wrapper for `cachetools.cached`.
"""
async def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
return cache[k]
except KeyError:
pass # key not found
v = await func(*args, **kwargs)
try:
cache[k] = v
except ValueError:
pass # value too large
return v

return wrapper


def func_wrapper_lock(func, cache, key, lock):
"""
Creates an async wrapper with locking for `cachetools.cached`.
"""
async def wrapper(*args, **kwargs):
k = key(*args, **kwargs)
try:
with lock:
return cache[k]
except KeyError:
pass # key not found
v = await func(*args, **kwargs)
try:
with lock:
cache[k] = v
except ValueError:
pass # value too large
return v

return wrapper


def method_wrapper(method, cache, key):
"""
Creates an async wrapper for `cachetools.cachedmethod`.
"""
async def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
v = await method(self, *args, **kwargs)
return v
k = key(self, *args, **kwargs)
try:
return c[k]
except KeyError:
pass # key not found
v = await method(self, *args, **kwargs)
try:
c[k] = v
except ValueError:
pass # value too large
return v

return wrapper


def method_wrapper_lock(method, cache, key, lock):
"""
Creates an async wrapper with locking for `cachetools.cachedmethod`.
"""
async def wrapper(self, *args, **kwargs):
c = cache(self)
if c is None:
v = await method(self, *args, **kwargs)
return v
k = key(self, *args, **kwargs)
try:
with lock(self):
return c[k]
except KeyError:
pass # key not found
v = await method(self, *args, **kwargs)
try:
with lock(self):
c[k] = v
except ValueError:
pass # value too large
return v

return wrapper
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os.path
import sys


def pytest_ignore_collect(path):
if sys.version_info >= (3, 5):
# do not skip any file when running tests with 3.5+
return False

else:
# skip async tests when running tests with <3.5
return 'async' in os.path.basename(str(path))

0 comments on commit f23bcec

Please sign in to comment.