from inspect import isclass, ismodule
from .compat import PY2
from .colls import walk_values
from .funcs import iffy
from .strings import cut_prefix
__all__ = ['cached_property', 'cached_readonly', 'wrap_prop', 'monkey', 'namespace', 'LazyObject']
class cached_property(object):
"""
Decorator that converts a method with a single self argument into
a property cached on the instance.
"""
# NOTE: implementation borrowed from Django.
# NOTE: we use fget, fset and fdel attributes to mimic @property.
fset = fdel = None
def __init__(self, fget):
self.fget = fget
self.__doc__ = getattr(fget, '__doc__')
def __get__(self, instance, type=None):
if instance is None:
return self
res = instance.__dict__[self.fget.__name__] = self.fget(instance)
return res
class cached_readonly(cached_property):
"""Same as @cached_property, but protected against rewrites."""
def __set__(self, instance, value):
raise AttributeError("property is read-only")
def wrap_prop(ctx):
"""Wrap a property accessors with a context manager"""
def decorator(prop):
class WrapperProp(object):
def __repr__(self):
return repr(prop)
def __get__(self, instance, type=None):
if instance is None:
return self
with ctx:
return prop.__get__(instance, type)
if hasattr(prop, '__set__'):
def __set__(self, name, value):
with ctx:
return prop.__set__(name, value)
if hasattr(prop, '__del__'):
def __del__(self, name):
with ctx:
return prop.__del__(name)
return WrapperProp()
return decorator
def monkey(cls, name=None):
"""
Monkey patches class or module by adding to it decorated function.
Anything overwritten could be accessed via .original attribute of decorated object.
"""
assert isclass(cls) or ismodule(cls), "Attempting to monkey patch non-class and non-module"
def decorator(value):
func = getattr(value, 'fget', value) # Support properties
func_name = name or cut_prefix(func.__name__, '%s__' % cls.__name__)
func.__name__ = func_name
func.original = getattr(cls, func_name, None)
setattr(cls, func_name, value)
return value
return decorator
# TODO: monkey_mix()?
class namespace_meta(type):
def __new__(cls, name, bases, attrs):
attrs = walk_values(iffy(callable, staticmethod), attrs)
return super(namespace_meta, cls).__new__(cls, name, bases, attrs)
class namespace(object):
"""A base class that prevents its member functions turning into methods."""
if PY2:
__metaclass__ = namespace_meta
class LazyObject(object):
"""
A simplistic lazy init object.
Rewrites itself when any attribute is accessed.
"""
# NOTE: we can add lots of magic methods here to intercept on more events,
# this is postponed. As well as metaclass to support isinstance() check.
def __init__(self, init):
self.__dict__['_init'] = init
def _setup(self):
obj = self._init()
object.__setattr__(self, '__class__', obj.__class__)
object.__setattr__(self, '__dict__', obj.__dict__)
def __getattr__(self, name):
self._setup()
return getattr(self, name)
def __setattr__(self, name, value):
self._setup()
return setattr(self, name, value)