"""Assorted utility functions."""
import collections
from contextlib import contextmanager
import math
import signal
import sys
import types
import typing
import decorator
sqrt2 = math.sqrt(2)
[docs]def cached(oldMethod):
"""Decorator for making a method with no arguments cache its result"""
storageName = f'_cached_{oldMethod.__name__}'
@decorator.decorator
def wrapper(wrapped, *args, **kwargs):
self = args[0]
try:
# Use __getattribute__ for direct lookup in case self is a Distribution
return self.__getattribute__(storageName)
except AttributeError:
value = wrapped(self)
setattr(self, storageName, value)
return value
return wrapper(oldMethod)
def cached_property(oldMethod):
return property(cached(oldMethod))
def argsToString(args, kwargs={}):
args = ', '.join(repr(arg) for arg in args)
kwargs = ', '.join(f'{name}={value!r}' for name, value in kwargs.items())
parts = []
if args:
parts.append(args)
if kwargs:
parts.append(kwargs)
return ', '.join(parts)
@contextmanager
def alarm(seconds, handler=None, noNesting=False):
if seconds <= 0 or not hasattr(signal, 'SIGALRM'): # SIGALRM not supported on Windows
yield
return
if handler is None:
handler = signal.SIG_IGN
signal.signal(signal.SIGALRM, handler)
if noNesting:
assert oldHandler is signal.SIG_DFL, 'SIGALRM handler already installed'
length = int(math.ceil(seconds))
previous = signal.alarm(length)
if noNesting:
assert previous == 0, 'nested call to "alarm"'
try:
yield
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, signal.SIG_DFL)
[docs]class DefaultIdentityDict:
"""Dictionary which is the identity map by default.
The map works on all objects, even unhashable ones, but doesn't support all
of the standard mapping operations.
"""
def __init__(self):
self.storage = {}
def clear(self):
self.storage.clear()
def __getitem__(self, key):
return self.storage.get(id(key), key)
def __setitem__(self, key, value):
self.storage[id(key)] = value
def __contains__(self, key):
return id(key) in self.storage
def __repr__(self):
pairs = (f'{hex(key)}: {value!r}' for key, value in self.storage.items())
allPairs = ', '.join(pairs)
return f'<DefaultIdentityDict {{{allPairs}}}>'
# Generic type introspection functions backported to Python 3.7
# (code taken from their Python 3.8 implementations)
[docs]def get_type_origin(tp):
"""Version of `typing.get_origin` supporting Python 3.7."""
assert sys.version_info >= (3, 7)
if sys.version_info >= (3, 8):
return typing.get_origin(tp)
if isinstance(tp, typing._GenericAlias):
return tp.__origin__
if tp is typing.Generic:
return typing.Generic
return None
[docs]def get_type_args(tp):
"""Version of `typing.get_args` supporting Python 3.7."""
assert sys.version_info >= (3, 7)
if sys.version_info >= (3, 8):
return typing.get_args(tp)
if isinstance(tp, typing._GenericAlias) and not tp._special:
res = tp.__args__
if get_type_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
res = (list(res[:-1]), res[-1])
return res
return ()
# Patched version of typing.get_type_hints fixing bpo-37838
if (sys.version_info >= (3, 8, 1)
or (sys.version_info < (3, 8) and sys.version_info >= (3, 7, 6))):
get_type_hints = typing.get_type_hints
else:
def get_type_hints(obj, globalns=None, localns=None):
if not isinstance(obj, (type, types.ModuleType)) and globalns is None:
wrapped = obj
while hasattr(wrapped, '__wrapped__'):
wrapped = wrapped.__wrapped__
globalns = getattr(wrapped, '__globals__', {})
return typing.get_type_hints(obj, globalns, localns)