mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-01-26 21:02:59 -08:00
6bb4ae56bd
Dependencies: * backports.functools-lru-cache 1.2.1 * jaraco.classes 1.3 * jaraco.collections 1.3.2 * jaraco.functools 1.11 * jaraco.structures 1.0 * jaraco.text 1.7 * jaraco.ui 1.4 * jaraco.windows 3.6 * more-itertools 2.2 * path.py 8.2.1 * six 1.10.0
269 lines
6.6 KiB
Python
269 lines
6.6 KiB
Python
from __future__ import absolute_import, unicode_literals, print_function, division
|
|
|
|
import functools
|
|
import time
|
|
import warnings
|
|
|
|
try:
|
|
from functools import lru_cache
|
|
except ImportError:
|
|
try:
|
|
from backports.functools_lru_cache import lru_cache
|
|
except ImportError:
|
|
try:
|
|
from functools32 import lru_cache
|
|
except ImportError:
|
|
warnings.warn("No lru_cache available")
|
|
|
|
|
|
def compose(*funcs):
|
|
"""
|
|
Compose any number of unary functions into a single unary function.
|
|
|
|
>>> import textwrap
|
|
>>> from six import text_type
|
|
>>> text_type.strip(textwrap.dedent(compose.__doc__)) == compose(text_type.strip, textwrap.dedent)(compose.__doc__)
|
|
True
|
|
|
|
Compose also allows the innermost function to take arbitrary arguments.
|
|
|
|
>>> round_three = lambda x: round(x, ndigits=3)
|
|
>>> f = compose(round_three, int.__truediv__)
|
|
>>> [f(3*x, x+1) for x in range(1,10)]
|
|
[1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
|
|
"""
|
|
|
|
compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs))
|
|
return functools.reduce(compose_two, funcs)
|
|
|
|
|
|
def method_caller(method_name, *args, **kwargs):
|
|
"""
|
|
Return a function that will call a named method on the
|
|
target object with optional positional and keyword
|
|
arguments.
|
|
|
|
>>> lower = method_caller('lower')
|
|
>>> lower('MyString')
|
|
'mystring'
|
|
"""
|
|
def call_method(target):
|
|
func = getattr(target, method_name)
|
|
return func(*args, **kwargs)
|
|
return call_method
|
|
|
|
|
|
def once(func):
|
|
"""
|
|
Decorate func so it's only ever called the first time.
|
|
|
|
This decorator can ensure that an expensive or non-idempotent function
|
|
will not be expensive on subsequent calls and is idempotent.
|
|
|
|
>>> func = once(lambda a: a+3)
|
|
>>> func(3)
|
|
6
|
|
>>> func(9)
|
|
6
|
|
>>> func('12')
|
|
6
|
|
"""
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
if not hasattr(func, 'always_returns'):
|
|
func.always_returns = func(*args, **kwargs)
|
|
return func.always_returns
|
|
return wrapper
|
|
|
|
|
|
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()
|
|
|
|
Another cache wrapper may be supplied:
|
|
|
|
>>> cache = lru_cache(maxsize=2)
|
|
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
|
|
>>> a = MyClass()
|
|
>>> a.method2()
|
|
3
|
|
|
|
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 lru_cache()
|
|
def wrapper(self, *args, **kwargs):
|
|
# it's the first call, replace the method with a cached, bound method
|
|
bound_method = functools.partial(method, self)
|
|
cached_method = cache_wrapper(bound_method)
|
|
setattr(self, method.__name__, cached_method)
|
|
return cached_method(*args, **kwargs)
|
|
return _special_method_cache(method, cache_wrapper) or wrapper
|
|
|
|
|
|
def _special_method_cache(method, cache_wrapper):
|
|
"""
|
|
Because Python treats special methods differently, it's not
|
|
possible to use instance attributes to implement the cached
|
|
methods.
|
|
|
|
Instead, install the wrapper method under a different name
|
|
and return a simple proxy to that wrapper.
|
|
|
|
https://github.com/jaraco/jaraco.functools/issues/5
|
|
"""
|
|
name = method.__name__
|
|
special_names = '__getattr__', '__getitem__'
|
|
if name not in special_names:
|
|
return
|
|
|
|
wrapper_name = '__cached' + name
|
|
|
|
def proxy(self, *args, **kwargs):
|
|
if wrapper_name not in vars(self):
|
|
bound = functools.partial(method, self)
|
|
cache = cache_wrapper(bound)
|
|
setattr(self, wrapper_name, cache)
|
|
else:
|
|
cache = getattr(self, wrapper_name)
|
|
return cache(*args, **kwargs)
|
|
|
|
return proxy
|
|
|
|
|
|
def apply(transform):
|
|
"""
|
|
Decorate a function with a transform function that is
|
|
invoked on results returned from the decorated function.
|
|
|
|
>>> @apply(reversed)
|
|
... def get_numbers(start):
|
|
... return range(start, start+3)
|
|
>>> list(get_numbers(4))
|
|
[6, 5, 4]
|
|
"""
|
|
def wrap(func):
|
|
return compose(transform, func)
|
|
return wrap
|
|
|
|
|
|
def call_aside(f, *args, **kwargs):
|
|
"""
|
|
Call a function for its side effect after initialization.
|
|
|
|
>>> @call_aside
|
|
... def func(): print("called")
|
|
called
|
|
>>> func()
|
|
called
|
|
|
|
Use functools.partial to pass parameters to the initial call
|
|
|
|
>>> @functools.partial(call_aside, name='bingo')
|
|
... def func(name): print("called with", name)
|
|
called with bingo
|
|
"""
|
|
f(*args, **kwargs)
|
|
return f
|
|
|
|
|
|
class Throttler(object):
|
|
"""
|
|
Rate-limit a function (or other callable)
|
|
"""
|
|
def __init__(self, func, max_rate=float('Inf')):
|
|
if isinstance(func, Throttler):
|
|
func = func.func
|
|
self.func = func
|
|
self.max_rate = max_rate
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.last_called = 0
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
self._wait()
|
|
return self.func(*args, **kwargs)
|
|
|
|
def _wait(self):
|
|
"ensure at least 1/max_rate seconds from last call"
|
|
elapsed = time.time() - self.last_called
|
|
must_wait = 1 / self.max_rate - elapsed
|
|
time.sleep(max(0, must_wait))
|
|
self.last_called = time.time()
|
|
|
|
def __get__(self, obj, type=None):
|
|
return first_invoke(self._wait, functools.partial(self.func, obj))
|
|
|
|
|
|
def first_invoke(func1, func2):
|
|
"""
|
|
Return a function that when invoked will invoke func1 without
|
|
any parameters (for its side-effect) and then invoke func2
|
|
with whatever parameters were passed, returning its result.
|
|
"""
|
|
def wrapper(*args, **kwargs):
|
|
func1()
|
|
return func2(*args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def retry_call(func, cleanup=lambda: None, retries=0, trap=()):
|
|
"""
|
|
Given a callable func, trap the indicated exceptions
|
|
for up to 'retries' times, invoking cleanup on the
|
|
exception. On the final attempt, allow any exceptions
|
|
to propagate.
|
|
"""
|
|
for attempt in range(retries):
|
|
try:
|
|
return func()
|
|
except trap:
|
|
cleanup()
|
|
|
|
return func()
|