update from Atlas

This commit is contained in:
Luciano Ramalho
2015-01-05 03:20:08 -02:00
parent 1edb0cd05b
commit 0618105a47
18 changed files with 822 additions and 123 deletions

View File

@@ -0,0 +1,64 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The check is also performed on instantiation::
>>> walnuts = LineItem('walnuts', 0, 10.00)
Traceback (most recent call last):
...
ValueError: value must be > 0
The proteced attribute can still be accessed if needed for some reason, such as
white box testing)::
>>> raisins._LineItem__weight
10
"""
# BEGIN LINEITEM_V2B
class LineItem:
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
def get_weight(self): # <1>
return self.__weight
def set_weight(self, value): # <2>
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')
weight = property(get_weight, set_weight) # <3>
# END LINEITEM_V2B

View File

@@ -0,0 +1,58 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
"""
# BEGIN LINEITEM_V2_PROP
def quantity(storage_name): # <1>
@property # <2>
def new_prop(self):
return self.__dict__[storage_name] # <3>
@new_prop.setter
def new_prop(self, value):
if value > 0:
self.__dict__[storage_name] = value # <4>
else:
raise ValueError('value must be > 0')
return new_prop # <5>
class LineItem:
weight = quantity('weight') # <6>
price = quantity('price') # <7>
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V2_PROP

View File

@@ -0,0 +1,76 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> sorted(dir(raisins)) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
[..., '_quantity_0', '_quantity_1', 'description',
'price', 'subtotal', 'weight']
>>> raisins._quantity_0
10
>>> raisins._quantity_1
6.95
"""
# BEGIN LINEITEM_V4_PROP
def quantity(): # <1>
try:
quantity.counter += 1 # <2>
except AttributeError:
quantity.counter = 0 # <3>
storage_name = '_{}_{}'.format('quantity', quantity.counter) # <4>
@property # <5>
def prop(self):
return getattr(self, storage_name)
@prop.setter
def prop(self, value):
if value > 0:
setattr(self, storage_name, value)
else:
raise ValueError('value must be > 0')
return prop # <6>
class LineItem:
weight = quantity() # <7>
price = quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V4_PROP

View File

@@ -0,0 +1,75 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
['_NonBlank_0', '_Quantity_0', '_Quantity_1', '__class__', ...
'description', 'price', 'subtotal', 'weight']
>>> raisins._Quantity_0
10
>>> raisins._Quantity_1
6.95
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.price # doctest: +ELLIPSIS
<model_v5.Quantity object at 0x...>
>>> br_nuts = LineItem('Brazil nuts', 10, 34.95)
>>> br_nuts.price
34.95
The `NonBlank` descriptor prevents empty or blank strings to be used
for the description:
>>> br_nuts.description = ' '
Traceback (most recent call last):
...
ValueError: value cannot be empty or blank
"""
# BEGIN LINEITEM_V5
import model_v5 as model # <1>
class LineItem:
description = model.NonBlank() # <2>
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V5

View File

@@ -0,0 +1,166 @@
"""
Data descriptor (a.k.a. overriding or enforced descriptor):
>>> o = Model()
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
>>> Model.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = None
owner = <class 'descriptorkinds.Model'>
A data descriptor cannot be shadowed by assigning to an instance:
>>> o.data = 7 # doctest: +ELLIPSIS
DataDescriptor.__set__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
Not even by poking the attribute into the instance ``__dict__``:
>>> o.__dict__['data'] = 8
>>> o.data # doctest: +ELLIPSIS
DataDescriptor.__get__() invoked with args:
self = <descriptorkinds.DataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
Data descriptor without ``__get__``:
>>> o.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
>>> Model.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
>>> o.data_no_get = 7 # doctest: +ELLIPSIS
DataDescriptorNoGet.__set__() invoked with args:
self = <descriptorkinds.DataDescriptorNoGet object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data_no_get # doctest: +ELLIPSIS
<descriptorkinds.DataDescriptorNoGet object at 0x...>
Poking the attribute into the instance ``__dict__`` means you can read the new
value for the attribute, but setting it still triggers ``__set__``:
>>> o.__dict__['data_no_get'] = 8
>>> o.data_no_get
8
>>> o.data_no_get = 7 # doctest: +ELLIPSIS
DataDescriptorNoGet.__set__() invoked with args:
self = <descriptorkinds.DataDescriptorNoGet object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
value = 7
>>> o.data_no_get # doctest: +ELLIPSIS
8
Non-data descriptor (a.k.a. non-overriding or shadowable descriptor):
>>> o = Model()
>>> o.non_data # doctest: +ELLIPSIS
NonDataDescriptor.__get__() invoked with args:
self = <descriptorkinds.NonDataDescriptor object at 0x...>
instance = <descriptorkinds.Model object at 0x...>
owner = <class 'descriptorkinds.Model'>
>>> Model.non_data # doctest: +ELLIPSIS
NonDataDescriptor.__get__() invoked with args:
self = <descriptorkinds.NonDataDescriptor object at 0x...>
instance = None
owner = <class 'descriptorkinds.Model'>
A non-data descriptor can be shadowed by assigning to an instance:
>>> o.non_data = 7
>>> o.non_data
7
Methods are non-data descriptors:
>>> o.spam # doctest: +ELLIPSIS
<bound method Model.spam of <descriptorkinds.Model object at 0x...>>
>>> Model.spam # doctest: +ELLIPSIS
<function Model.spam at 0x...>
>>> o.spam() # doctest: +ELLIPSIS
Model.spam() invoked with arg:
self = <descriptorkinds.Model object at 0x...>
>>> o.spam = 7
>>> o.spam
7
No descriptor type survives being overwritten on the class itself:
>>> Model.data = 1
>>> o.data
1
>>> Model.data_no_get = 2
>>> o.data_no_get
2
>>> Model.non_data = 3
>>> o.non_data
7
"""
class DataDescriptor:
"a.k.a. overriding or enforced descriptor"
def __get__(self, instance, owner):
print('DataDescriptor.__get__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' owner = ', owner)
def __set__(self, instance, value):
print('DataDescriptor.__set__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' value = ', value)
class DataDescriptorNoGet:
def __set__(self, instance, value):
print('DataDescriptorNoGet.__set__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' value = ', value)
class NonDataDescriptor:
"a.k.a. non-overriding or shadowable descriptor"
def __get__(self, instance, owner):
print('NonDataDescriptor.__get__() invoked with args:')
print(' self = ', self)
print(' instance = ', instance)
print(' owner = ', owner)
class Model:
data = DataDescriptor()
data_no_get = DataDescriptorNoGet()
non_data = NonDataDescriptor()
def spam(self):
print('Model.spam() invoked with arg:')
print(' self = ', self)

View File

@@ -0,0 +1,60 @@
"""
>>> f = Foo()
>>> f.bar = 77
>>> f.bar
77
>>> Foo.bar.__doc__
'The "bar" attribute'
>>> import pydoc
>>> pydoc.getdoc(Foo.bazz)
'The "bazz" attribute'
"""
def doc_descriptor_wrapper_factory(descriptor):
wrapper_cls_name = 'DocDescriptorWrapper'
wrapper_cls_attrs = descriptor.__dict__.copy()
wrapper_cls_attrs['__slots__'] = ['_wrapped']
def wrapped_getter(self):
"the wrapped descriptor instance"
return self._wrapped
def wrapper_repr(self):
return '<{} {!r}>'.format(wrapper_cls_name, self.__doc__)
wrapper_cls_attrs['wrapped'] = property(wrapped_getter)
wrapper_cls_attrs['__repr__'] = wrapper_repr
wrapper_cls = type(wrapper_cls_name, (), wrapper_cls_attrs)
wrapper = wrapper_cls()
wrapper._wrapped = descriptor
return wrapper
class DocDescriptor:
"""A documented descriptor"""
def __init__(self, documentation):
self.__doc__ = documentation
cls_name = self.__class__.__name__
self.storage_name = '_{}_{:x}'.format(cls_name, id(self))
def __get__(self, instance, owner):
"""The __get__ method"""
if instance is None:
return doc_descriptor_wrapper_factory(self)
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value)
class Foo:
"""The "Foo" class"""
bar = DocDescriptor('The "bar" attribute')
bazz = DocDescriptor('The "bazz" attribute')

52
descriptors/model_v5.py Normal file
View File

@@ -0,0 +1,52 @@
# BEGIN MODEL_V5
import abc
class AutoStorage: # <1>
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}_{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
setattr(instance, self.storage_name, value) # <2>
class Validated(abc.ABC, AutoStorage): # <3>
def __set__(self, instance, value):
value = self.validate(instance, value) # <4>
super().__set__(instance, value) # <5>
@abc.abstractmethod
def validate(self, instance, value): # <6>
"""return validated value or raise ValueError"""
class Quantity(Validated): # <7>
def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value
class NonBlank(Validated):
def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value # <8>
# END MODEL_V5