import collections
import functools
import inspect
import logging
import re
import six
from .fetchable import *
from .compose import compose
from .currying import curry
from .misc import *
log = logging.getLogger(__name__)
[docs]def makelist(value):
if not isinstance(value, list):
if isinstance(value, collections.Iterable):
if isinstance(value, six.string_types):
return [value]
return list(value)
else:
return [value]
return value
[docs]def makelist_decorator(function):
@functools.wraps(function)
def wrapped(arg):
return function(makelist(arg))
return wrapped
[docs]class cached_property(object):
"""Descriptor that caches the result of the first call to resolve its
contents.
"""
def __init__(self, func):
self.__doc__ = getattr(func, '__doc__')
self.func = func
def __get__(self, obj, cls):
if obj is None:
return self
value = self.func(obj)
setattr(obj, self.func.__name__, value)
return value
[docs] def bust_self(self, obj):
"""Remove the value that is being stored on `obj` for this
:class:`.cached_property`
object.
:param obj: The instance on which to bust the cache.
"""
if self.func.__name__ in obj.__dict__:
delattr(obj, self.func.__name__)
@classmethod
[docs] def bust_caches(cls, obj, excludes=()):
"""Bust the cache for all :class:`.cached_property` objects on `obj`
:param obj: The instance on which to bust the caches.
"""
for name, _ in cls.get_cached_properties(obj):
if name in obj.__dict__ and not name in excludes:
delattr(obj, name)
@classmethod
[docs] def get_cached_properties(cls, obj):
return inspect.getmembers(type(obj), lambda x: isinstance(x, cls))
[docs]class CallableMap(object):
def __init__(self, func_value_pairs=()):
if isinstance(func_value_pairs, dict):
func_value_pairs = func_value_pairs.items()
self.func_value_pairs = list(func_value_pairs)
def __getitem__(self, item):
for func, value in self.func_value_pairs:
if func(item):
return value
raise KeyError('{0} did not match any of this objects functions.'.format(item))
def __setitem__(self, function, value):
self.add(func, value)
[docs] def add(func, value):
self.func_value_pairs.append((func, value))
[docs]class REMap(object):
"""A mapping object that matches regular expressions to values."""
NO_DEFAULT = object()
@classmethod
[docs] def from_string_pairs(cls, string_value_pairs, **kwargs):
"""Build an :class:`~.REMap` from str, value pairs by applying
`re.compile` to each string and calling the __init__ of :class:`~.REMap`
"""
return cls(re_value_pairs=[(re.compile(s), v)
for s, v in string_value_pairs],
**kwargs)
def __init__(self, re_value_pairs=(), default=NO_DEFAULT):
self.default = default
if isinstance(re_value_pairs, dict):
re_value_pairs = re_value_pairs.items()
self.re_value_pairs = list(re_value_pairs)
def __getitem__(self, item):
if item is None:
if self.default is self.NO_DEFAULT:
raise KeyError("None does not match any expression")
else:
return self.default
for matcher, value in self.re_value_pairs:
if matcher.search(item):
return value
if self.default is not self.NO_DEFAULT:
if item is not None and len(item) > 1:
log.warning("Returning default from REMAP for {0}.".format(
repr(item)
))
return self.default
else:
raise KeyError('{0} did not match any of this objects regular'
'expressions.'.format(repr(item)))
def __setitem__(self, re, value):
self.add(re, value)
[docs] def add(re, value):
self.re_value_pairs.append((re, value))
@property
[docs] def pattern_to_value(self):
return {expression.pattern: value
for expression, value in self.re_value_pairs}
[docs] def values(self):
return self.pattern_to_value.values()
[docs]class GetAttrGetItem(type):
def __getitem__(self, item):
return getattr(self, item)
def __call__(self, *args):
return self[args[0]](*args[1:])
[docs]def IndexedREMap(*re_strings, **kwargs):
"""Build a :class:`~.REMap` from the provided regular expression string.
Each string will be associated with the index corresponding to its position
in the argument list.
:param re_strings: The re_strings that will serve as keys in the map.
:param default: The value to return if none of the regular expressions match
:param offset: The offset at which to start indexing for regular expressions
defaults to 1.
"""
default = kwargs.get('default', 0)
offset = kwargs.get('offset', 1)
string_index_pairs = []
for index, string_or_tuple in enumerate(re_strings, offset):
if isinstance(string_or_tuple, six.string_types):
string_or_tuple = (string_or_tuple,)
for re_string in string_or_tuple:
string_index_pairs.append((re_string, index))
remap = REMap.from_string_pairs(string_index_pairs,
default=default)
return remap