src/pyams_utils/inherit.py
branchdev-tf
changeset 408 cf2304af0fab
parent 367 2c95d34496f5
equal deleted inserted replaced
407:0037199881fb 408:cf2304af0fab
    13 """PyAMS_utils.inherit module
    13 """PyAMS_utils.inherit module
    14 
    14 
    15 This module is used to manage a generic inheritance between a content and
    15 This module is used to manage a generic inheritance between a content and
    16 it's parent container. It also defines a custom InheritedFieldProperty which
    16 it's parent container. It also defines a custom InheritedFieldProperty which
    17 allows to automatically manage inherited properties.
    17 allows to automatically manage inherited properties.
       
    18 
       
    19 This PyAMS module is used to handle inheritance between a parent object and a child which can
       
    20 "inherit" from some of it's properties, as long as they share the same "target" interface.
       
    21 
       
    22     >>> from zope.interface import implementer, Interface, Attribute
       
    23     >>> from zope.schema import TextLine
       
    24     >>> from zope.schema.fieldproperty import FieldProperty
       
    25 
       
    26     >>> from pyams_utils.adapter import adapter_config
       
    27     >>> from pyams_utils.interfaces.inherit import IInheritInfo
       
    28     >>> from pyams_utils.inherit import BaseInheritInfo, InheritedFieldProperty
       
    29     >>> from pyams_utils.registry import get_global_registry
       
    30 
       
    31 Let's start by creating a "content" interface, and a marker interface for objects for which we
       
    32 want to provide this interface:
       
    33 
       
    34     >>> class IMyInfoInterface(IInheritInfo):
       
    35     ...     '''Custom interface'''
       
    36     ...     value = TextLine(title="Custom attribute")
       
    37 
       
    38     >>> class IMyTargetInterface(Interface):
       
    39     ...     '''Target interface'''
       
    40 
       
    41     >>> @implementer(IMyInfoInterface)
       
    42     ... class MyInfo(BaseInheritInfo):
       
    43     ...     target_interface = IMyTargetInterface
       
    44     ...     adapted_interface = IMyInfoInterface
       
    45     ...
       
    46     ...     _value = FieldProperty(IMyInfoInterface['value'])
       
    47     ...     value = InheritedFieldProperty(IMyInfoInterface['value'])
       
    48 
       
    49 Please note that for each field of the interface which can be inherited, you must define to
       
    50 properties: one using "InheritedFieldProperty" with the name of the field, and one using a classic
       
    51 "FieldProperty" with the same name prefixed by "_"; this property is used to store the "local"
       
    52 property value, when inheritance is unset.
       
    53 
       
    54 The adapter is created to adapt an object providing IMyTargetInterface to IMyInfoInterface;
       
    55 please note that the adapter *must* attach the created object to it's parent by setting
       
    56 __parent__ attribute:
       
    57 
       
    58     >>> @adapter_config(context=IMyTargetInterface, provides=IMyInfoInterface)
       
    59     ... def my_info_factory(context):
       
    60     ...     info = getattr(context, '__info__', None)
       
    61     ...     if info is None:
       
    62     ...         info = context.__info__ = MyInfo()
       
    63     ...         info.__parent__ = context
       
    64     ...     return info
       
    65 
       
    66 Adapter registration is here only for testing; the "adapter_config" decorator may do the job in
       
    67 a normal application context:
       
    68 
       
    69     >>> registry = get_global_registry()
       
    70     >>> registry.registerAdapter(my_info_factory, (IMyTargetInterface, ), IMyInfoInterface)
       
    71 
       
    72 We can then create classes which will be adapted to support inheritance:
       
    73 
       
    74     >>> @implementer(IMyTargetInterface)
       
    75     ... class MyTarget:
       
    76     ...     '''Target class'''
       
    77     ...     __parent__ = None
       
    78     ...     __info__ = None
       
    79 
       
    80     >>> parent = MyTarget()
       
    81     >>> parent_info = IMyInfoInterface(parent)
       
    82     >>> parent.__info__
       
    83     <pyams_utils.tests.test_utils...MyInfo object at ...>
       
    84     >>> parent_info.value = 'parent'
       
    85     >>> parent_info.value
       
    86     'parent'
       
    87     >>> parent_info.can_inherit
       
    88     False
       
    89 
       
    90 As soon as a parent is defined, the child object can inherit from it's parent:
       
    91 
       
    92     >>> child = MyTarget()
       
    93     >>> child.__parent__ = parent
       
    94     >>> child_info = IMyInfoInterface(child)
       
    95     >>> child.__info__
       
    96     <pyams_utils.tests.test_utils...MyInfo object at ...>
       
    97 
       
    98     >>> child_info.can_inherit
       
    99     True
       
   100     >>> child_info.inherit
       
   101     True
       
   102     >>> child_info.value
       
   103     'parent'
       
   104 
       
   105 Setting child value while inheritance is enabled donesn't have any effect:
       
   106 
       
   107     >>> child_info.value = 'child'
       
   108     >>> child_info.value
       
   109     'parent'
       
   110     >>> child_info.inherit_from == parent
       
   111     True
       
   112 
       
   113 You can disable inheritance and define your own value:
       
   114 
       
   115     >>> child_info.inherit = False
       
   116     >>> child_info.value = 'child'
       
   117     >>> child_info.value
       
   118     'child'
       
   119     >>> child_info.inherit_from == child
       
   120     True
       
   121 
       
   122 Please note that parent and child in this example share the same class, but this is not a
       
   123 requirement; they just have to implement the same marker interface, to be adapted to the same
       
   124 content interface.
    18 """
   125 """
    19 
       
    20 __docformat__ = 'restructuredtext'
       
    21 
   126 
    22 from zope.interface import Interface, implementer
   127 from zope.interface import Interface, implementer
    23 from zope.location import Location
   128 from zope.location import Location
    24 from zope.schema.fieldproperty import FieldProperty
   129 from zope.schema.fieldproperty import FieldProperty
    25 
   130 
    26 from pyams_utils.interfaces.inherit import IInheritInfo
   131 from pyams_utils.interfaces.inherit import IInheritInfo
    27 from pyams_utils.traversing import get_parent
   132 from pyams_utils.traversing import get_parent
    28 from pyams_utils.zodb import volatile_property
   133 from pyams_utils.zodb import volatile_property
    29 
   134 
    30 
   135 
       
   136 __docformat__ = 'restructuredtext'
       
   137 
       
   138 
    31 @implementer(IInheritInfo)
   139 @implementer(IInheritInfo)
    32 class BaseInheritInfo(Location):
   140 class BaseInheritInfo(Location):
    33     """Base inherit class"""
   141     """Base inherit class
       
   142 
       
   143     Subclasses may generaly override target_interface and adapted_interface to
       
   144     correctly handle inheritance (see example in doctests).
       
   145     Please note also that adapters to this interface must correctly 'locate'
       
   146     """
    34 
   147 
    35     target_interface = Interface
   148     target_interface = Interface
    36     adapted_interface = Interface
   149     adapted_interface = Interface
    37 
   150 
    38     _inherit = FieldProperty(IInheritInfo['inherit'])
   151     _inherit = FieldProperty(IInheritInfo['inherit'])
    71 
   184 
    72     @property
   185     @property
    73     def inherit_from(self):
   186     def inherit_from(self):
    74         """Get current parent from which we inherit"""
   187         """Get current parent from which we inherit"""
    75         if not self.inherit:
   188         if not self.inherit:
    76             return self
   189             return self.__parent__
    77         parent = self.parent
   190         parent = self.parent
    78         while self.adapted_interface(parent).inherit:
   191         while self.adapted_interface(parent).inherit:
    79             parent = parent.parent
   192             parent = parent.parent  # pylint: disable=no-member
    80         return parent
   193         return parent
    81 
   194 
    82 
   195 
    83 class InheritedFieldProperty(object):
   196 class InheritedFieldProperty:
    84     """Inherited field property"""
   197     """Inherited field property"""
    85 
   198 
    86     def __init__(self, field, name=None):
   199     def __init__(self, field, name=None):
    87         if name is None:
   200         if name is None:
    88             name = field.__name__
   201             name = field.__name__
    92 
   205 
    93     def __get__(self, inst, klass):
   206     def __get__(self, inst, klass):
    94         if inst is None:
   207         if inst is None:
    95             return self
   208             return self
    96         inherit_info = IInheritInfo(inst)
   209         inherit_info = IInheritInfo(inst)
    97         if inherit_info.inherit:
   210         if inherit_info.inherit and (inherit_info.parent is not None):
       
   211             # pylint: disable=not-callable
    98             return getattr(inherit_info.adapted_interface(inherit_info.parent), self.__name)
   212             return getattr(inherit_info.adapted_interface(inherit_info.parent), self.__name)
    99         else:
   213         return getattr(inst, '_{0}'.format(self.__name))
   100             return getattr(inst, '_{0}'.format(self.__name))
       
   101 
   214 
   102     def __set__(self, inst, value):
   215     def __set__(self, inst, value):
   103         inherit_info = IInheritInfo(inst)
   216         inherit_info = IInheritInfo(inst)
   104         if not (inherit_info.can_inherit and inherit_info.inherit):
   217         if not (inherit_info.can_inherit and inherit_info.inherit):
   105             setattr(inst, '_{0}'.format(self.__name), value)
   218             setattr(inst, '_{0}'.format(self.__name), value)