Last active
May 30, 2016 09:05
-
-
Save flisboac/bfd81543f553c154ff90bb0b9aab4c1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#/usr/bin/env python | |
default_impl = object() | |
no_impl = object() | |
def _empty_impl(): | |
pass | |
def _is_empty_impl(fn, pfn = None): | |
return _empty_impl.__code__.co_code == fn.__code__.co_code | |
class AttrValue(object): | |
def __init__(self, value = None, assigned = None): | |
if isinstance(value, type(self)): | |
other = value | |
value = other.value | |
if assigned is None: assigned = other.assigned | |
self.value = value | |
self.assigned = assigned if assigned is not None else True | |
AttrValue.UNASSIGNED = AttrValue(assigned = False) | |
AttrValue.NONE = AttrValue() | |
class Attr(object): | |
default_impl = default_impl | |
no_impl = no_impl | |
def __init__(self, | |
_cls = None, | |
class_field_name = None, | |
doc = None): | |
self._class_field_name = class_field_name or 'attr' | |
self.__doc__ = doc | |
self._cls = None | |
self._properties = [] | |
if _cls: | |
self(_cls) | |
def __call__(self, _cls = None): | |
if _cls is None: | |
return lambda fn: self(fn) | |
pending_attr = getattr(_cls, self._class_field_name, None) | |
if pending_attr: | |
if not pending_attr.__doc__ and self.__doc__: | |
pending_attr.__doc__ = self.__doc__ | |
else: | |
pending_attr = self | |
pending_attr._apply(_cls, self._class_field_name) | |
return _cls | |
def _apply(self, cls, class_field_name): | |
self._cls = cls | |
self._class_field_name = class_field_name | |
if not self.__doc__ and self._cls.__doc__: | |
self.__doc__ = self._cls.__doc__ | |
initializer = self._prepare_initializer(cls = self._cls) | |
setattr(self._cls, self._class_field_name, self) | |
setattr(self._cls, '__init__', initializer) | |
def _prepare_initializer(self, | |
method = None, | |
cls = None, | |
doc = None, | |
initialize_properties = True): | |
initializer = self._prepare_method(method, cls, '__init__', doc, | |
initialize_properties) | |
return initializer | |
def _prepare_method(self, | |
method = None, | |
cls = None, | |
name = None, | |
doc = None, | |
initialize_properties = False): | |
if not method: | |
method = getattr(cls, name, None) | |
def modified_method(this, *args, **kwargs): | |
if initialize_properties: | |
self.initialize_properties(this) | |
if method: | |
method(this, *args, **kwargs) | |
modified_method._attr = self | |
if not doc and method: | |
doc = method.__doc__ | |
modified_method.__doc__ = doc # Could do better | |
if method: | |
modified_method.__name__ = method.__name__ | |
return modified_method | |
def _add_property(self, property): | |
self._properties.append(property) | |
def property(self, | |
_fget=None, | |
fset=None, | |
fdel=None, | |
doc=None, | |
name = None, | |
internal_field_name = None, | |
value = AttrValue.UNASSIGNED): | |
if _fget is None: | |
return lambda fn: self.property(fn, fset, fdel, doc, name, | |
internal_field_name, value) | |
property = _Property(_fget, fset, fdel, self, doc, | |
name, internal_field_name, value) | |
return property | |
def getter(self, | |
_fget=None, | |
fdel=None, | |
doc=None, | |
writable = None, | |
deletable = None, | |
name = None, | |
internal_field_name = None, | |
value = AttrValue.UNASSIGNED): | |
if _fget is None: | |
return lambda fn: self.getter(fn, fdel, doc, writable, deletable, | |
name, internal_field_name, value) | |
fset = no_impl if not writable else default_impl | |
if not fdel: | |
fdel = no_impl if not deletable else default_impl | |
return self.property(_fget, fset, fdel, doc, | |
name, internal_field_name, value) | |
def setter(self, | |
_fset=None, | |
fdel=None, | |
doc=None, | |
readable = None, | |
deletable = None, | |
name = None, | |
internal_field_name = None, | |
value = AttrValue.UNASSIGNED): | |
if _fset is None: | |
return lambda fn: self.setter(fn, fdel, doc, readable, deletable, | |
name, internal_field_name, value) | |
fget = no_impl if not readable else default_impl | |
if not fdel: | |
fdel = no_impl if not deletable else default_impl | |
return self.property(fget, _fset, fdel, doc, | |
name, internal_field_name, value) | |
def accessor(self, | |
_faccessor=None, | |
fdel=None, | |
doc=None, | |
deletable = None, | |
name = None, | |
internal_field_name = None, | |
value = AttrValue.UNASSIGNED): | |
if _faccessor is None: | |
return lambda fn: self.accessor(fn, fdel, doc, deletable, | |
name, internal_field_name, value) | |
if not fdel: | |
fdel = no_impl if not deletable else default_impl | |
return self.property(_faccessor, _faccessor, fdel, doc, | |
name, internal_field_name, value) | |
def initialize_properties(self, obj): | |
for property in self._properties: | |
property.initialize_field(obj) | |
class _Property(object): | |
def __init__(self, | |
_fget=None, | |
fset=None, | |
fdel=None, | |
attr = None, | |
doc=None, | |
name = None, | |
internal_field_name = None, | |
initial_value = AttrValue.UNASSIGNED): | |
self._parent = attr | |
self._internal_field_name = internal_field_name | |
self._initial_value = AttrValue(initial_value) | |
self.__doc__ = doc | |
self._property_name = name | |
self.getter(_fget).setter(fset).deleter(fdel) | |
if self._parent: | |
self._parent._add_property(self) | |
def __get__(self, obj, obj_type=None): | |
if obj is None: | |
return self | |
if self._get is None: | |
raise AttributeError("Property '" | |
+ self.get_property_name() + "' from class '" | |
+ _full_class_name(obj) + "' is not readable.") | |
return self._get(obj) | |
def __set__(self, obj, value): | |
if self._set is None: | |
raise AttributeError("Property '" | |
+ self.get_property_name() + "' from class '" | |
+ _full_class_name(obj) + "' is not writable.") | |
self._set(obj, value) | |
def __delete__(self, obj): | |
if self._del is None: | |
raise AttributeError("Property '" | |
+ self.get_property_name() + "' from class '" | |
+ _full_class_name(obj) + "' is not deletable.") | |
self._del(obj) | |
def is_getter(self): | |
return self._get is not None | |
def is_setter(self): | |
return self._set is not None | |
def is_accessor(self): | |
return self.is_getter() and self.is_setter() | |
def is_single_accessor(self): | |
return self.is_accessor() and self._get == self._set | |
def initialize_field(self, obj): | |
if self._initial_value.assigned: | |
self.raw_set(obj, self._initial_value) | |
def raw_get(self, obj, default = AttrValue.UNASSIGNED, fail_if_not_assigned = False): | |
value = default | |
internal_field_name = self.get_internal_field_name() | |
if internal_field_name: | |
value = AttrValue(getattr(obj, internal_field_name)) | |
if not value.assigned and fail_if_not_assigned: | |
raise AttributeError("Value not assigned for property '" | |
+ self.get_property_name() + "' (internal field name: '" | |
+ internal_field_name + "').") | |
return value | |
def raw_set(self, obj, value, fail_if_not_assigned = False): | |
success = False | |
internal_field_name = self.get_internal_field_name() | |
if internal_field_name: | |
raw_value = value | |
assigned = True | |
if isinstance(value, AttrValue): | |
raw_value = value.value | |
assigned = value.assigned | |
if assigned: | |
setattr(obj, internal_field_name, value) | |
success = True | |
elif hasattr(obj, internal_field_name): | |
delattr(obj, internal_field_name) | |
success = True | |
if not success and fail_if_not_assigned: | |
raise AttributeError("Could not assign value to property '" | |
+ self.get_property_name() | |
+ "' (internal field name: '" | |
+ internal_field_name + "').") | |
return success | |
def raw_del(self, obj, fail_if_not_assigned = False): | |
return self.raw_set(obj, AttrValue.UNASSIGNED, fail_if_not_assigned) | |
def any_get(self, obj, objType = None, default = AttrValue.UNASSIGNED): | |
value = None | |
if self.is_getter(): | |
value = AttrValue(self.__get__(obj, objType)) | |
if not value: | |
value = self.raw_get(obj, default) | |
return value | |
def any_set(self, obj, value): | |
success = False | |
if self.is_setter(): | |
success = True | |
self.__set__(obj, value) | |
else: | |
value = self.raw_set(obj, value) | |
return value | |
def get_property_name(self): | |
return (self._property_name | |
or (self.is_getter() and self._get.__name__) | |
or (self.is_setter() and self._set.__name__) | |
or None | |
) | |
def get_internal_field_name(self): | |
internal_field_name = self._internal_field_name | |
if not internal_field_name: | |
property_name = self.get_property_name() | |
if property_name: | |
internal_field_name = "_%s" % property_name | |
return internal_field_name | |
def _set_fn(self, fname, fn, impl): | |
implement_method = False | |
empty_impl = None | |
impl.__name__ = '' | |
if fn is default_impl: | |
implement_method = True | |
elif fn is no_impl or fn is None: | |
setattr(self, fname, None) | |
else: | |
empty_impl = _is_empty_impl(fn) | |
if not empty_impl: | |
setattr(self, fname, fn) | |
else: | |
implement_method = True | |
if implement_method: | |
setattr(self, fname, impl) | |
if empty_impl: | |
getattr(self, fname).__name__ = fn.__name__ | |
if not self.__doc__ and getattr(self, fname) and getattr(self, fname).__doc__: | |
self.__doc__ = getattr(self, fname).__doc__ | |
return self | |
def getter(self, _fget): | |
return self._set_fn('_get', _fget, lambda this: self.raw_get(this, fail_if_not_assigned = True).value) | |
def setter(self, _fset): | |
return self._set_fn('_set', _fset, lambda this, value: self.raw_set(this, value, fail_if_not_assigned = True)) | |
def deleter(self, _fdel): | |
return self._set_fn('_del', _fdel, lambda this: self.raw_del(this, fail_if_not_assigned = True)) | |
def initialize_field(self, obj): | |
if self._initial_value.assigned: | |
self.raw_set(obj, self._initial_value.value) | |
def attr(_cls = None, *args, **kargs): | |
if _cls is None: | |
return Attr(None, *args, **kargs) | |
Attr(_cls, *args, **kargs) | |
return _cls | |
if __name__ == '__main__': | |
# Examples below | |
@attr | |
class A(object): | |
# This is just a helper object | |
attr = attr() | |
# A single method implementing both get and set | |
@attr.accessor(value = 0) | |
def x(self, v = None): | |
if v is not None: | |
self._x = v | |
else: | |
return self._x | |
# Separate methods declared together, where the setter is not trivial | |
@attr.setter(value = 0, readable = True) | |
def y(self, v = None): | |
if v is not None: | |
self._y = v | |
else: | |
return self._y | |
# Separate methods declared together, where the getter is not trivial | |
@attr.getter(value = None, writable = True) | |
def z(self): | |
if self._z is None: | |
return "oops" | |
return self._z | |
@attr.getter(value = 0, writable = True) | |
def w(self): pass | |
a = A() | |
print(a.x) | |
print(a.y) | |
print(a.z) | |
print(a.w) | |
a.x = 10 | |
print(a.x) | |
a.x = None | |
print(a.x) | |
a.y = a.x * 65 | |
print(a.y) | |
a.z = 101 | |
print(a.z) | |
a.z = 2**10 + 0.5 | |
print(a.z) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment