"""
..
Copyright (c) 2014-2017, Magni developers.
All rights reserved.
See LICENSE.rst for further information.
Module providing custom data types.
Routine listings
----------------
ClassProperty(property)
Class property.
ReadOnlyDict(collections.OrderedDict)
Read-only ordered dict.
"""
from __future__ import division
from collections import OrderedDict as _OrderedDict
[docs]class ClassProperty(property):
"""
Class property.
The present class is a combination of the built-in property type and the
built-in classmethod type. That is, it is a property which is invoked on a
class rather than an instance but is available to both the class and its
instances.
Parameters
----------
fget : function, optional
The getter function of the property. (the default is None, which
implies that the property cannot be read)
fset : function, optional
The setter function of the property. (the default is None, which
implies that the property cannot be written)
fdel : function, optional
The deleter function of the property. (the default is None, which
implies that the property cannot be deleted)
doc : string, optional
The docstring of the property. (the default is None, which implies that
the docstring of the getter function, if any, is used)
See Also
--------
property : Superclass from which all behaviour is inherited or extended.
Examples
--------
The following example illustrates the difference between regular properties
and class properties. First, the example class is defined and instantiated:
>>> from magni.utils.types import ClassProperty
>>> class Example(object):
... _x_class = 0
... def __init__(self, x):
... self._x_instance = x
... @ClassProperty
... def x_class(class_):
... return class_._x_class
... @property
... def x_instance(self):
... return self._x_instance
>>> example = Example(1)
The regular read-only property works on the instance:
>>> print('{!r}'.format(example.x_instance))
1
>>> print('Is property? {!r}'.format(isinstance(Example.x_instance,
... property)))
Is property? True
The class property, on the other hand, works on the class:
>>> print('{!r}'.format(example.x_class))
0
>>> print('Is property? {!r}'.format(isinstance(Example.x_class,
... property)))
Is property? False
>>> print('{!r}'.format(Example.x_class))
0
"""
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
property.__init__(self, fget, fset, fdel, doc)
[docs] def __get__(self, obj, class_=None):
"""
The get method of the property.
Parameters
----------
obj : object
The instance on which the property is requested.
class_ : type, optional
The class on which the property is requested. (the default is None,
which implies that the class is retrieved from `obj`)
Returns
-------
value : None
The value of the property.
See Also
--------
property.__get__ : The method being extended.
"""
if class_ is None:
class_ = type(obj)
return property.__get__(self, class_)
[docs] def __set__(self, obj, value):
"""
The set method of the property.
Parameters
----------
obj : object
The instance on which the property is requested.
value : None
The value which should be assigned to the property.
See Also
--------
property.__set__ : The method being extended.
"""
return property.__set__(self, type(obj), value)
[docs] def __delete__(self, obj):
"""
The delete method of the property.
Parameters
----------
obj : object
The instance on which the property is requested.
See Also
--------
property.__delete__ : The method being extended.
"""
return property.__delete__(self, type(obj))
[docs]class ReadOnlyDict(_OrderedDict):
"""
Read-only ordered dict.
The present ordered dict subclass has its non-read-only methods disabled.
See Also
--------
collections.OrderedDict : Superclass from which all read-only behaviour is
inherited.
Examples
--------
This ordered dict subclass exposes the same interface as the OrderedDict
class when not using methods that alter the dict.
>>> from magni.utils.types import ReadOnlyDict
>>> d = ReadOnlyDict(key='value')
>>> for item in d.items():
... print('{!r}'.format(item))
('key', 'value')
However, any attempt to assign another value to the property raises an
exception:
>>> try:
... d['key'] = 'new value'
... except Exception:
... print('An exception occured.')
An exception occured.
"""
def __init__(self, *args, **kwargs):
self._disabled = True
_OrderedDict.__init__(self, *args, **kwargs)
self._disabled = False
[docs] def __delitem__(self, name):
"""
Prevent deletion of items.
Parameters
----------
name : str
The name of the item to be deleted.
"""
if self._disabled:
_OrderedDict.__delitem__(self, name)
else:
raise AttributeError('{!r} objects are read-only.'
.format(self.__class__))
[docs] def __getattribute__(self, name):
"""
Return the requested attribute unless it is a non-read-only method.
The purpose of this overwrite is to disable access to the following
non-read-only dict methods: `clear`, `pop`, `popitem`, `setdefault`,
and `update`. The first two methods are disabled otherwise.
Parameters
----------
name : str
The name of the requested attribute.
Returns
-------
attribute : None
The requested attribute.
Notes
-----
`__getattribute__` is implicitly called, when the attribute of an
object is accessed as `object.attribute`.
"""
if name in ('clear', 'pop', 'popitem', 'setdefault', 'update'):
if self._disabled:
value = _OrderedDict.__getattribute__(self, name)
else:
raise TypeError('{!r} objects are read-only.'
.format(self.__class__))
else:
value = _OrderedDict.__getattribute__(self, name)
return value
[docs] def __setitem__(self, name, value):
"""
Prevent overwrite of items.
Parameters
----------
name : str
The name of the item to be overwritten.
value : None
The value to be written.
"""
if self._disabled:
_OrderedDict.__setitem__(self, name, value)
else:
raise AttributeError('{!r} objects are read-only.'
.format(self.__class__))