reorganise code
[tiramisu.git] / tiramisu / setting.py
index b491d47..d77ef5a 100644 (file)
@@ -1,31 +1,27 @@
 # -*- coding: utf-8 -*-
 "sets the options of the configuration objects Config object itself"
-# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
+# Copyright (C) 2012-2017 Team tiramisu (see AUTHORS for all contributors)
 #
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from
-# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ____________________________________________________________
 from time import time
 from copy import copy
+from logging import getLogger
 import weakref
-from tiramisu.error import (RequirementError, PropertiesOptionError,
-                            ConstError, ConfigError)
-from tiramisu.i18n import _
+from .error import (RequirementError, PropertiesOptionError,
+                    ConstError, ConfigError, display_list)
+from .i18n import _
 
 
 "Default encoding for display a Config if raise UnicodeEncodeError"
@@ -79,11 +75,17 @@ everything_frozen
     whole option in config are frozen (even if option have not frozen
     property)
 
+empty
+    raise mandatory PropertiesOptionError if multi or master have empty value
+
 validator
     launch validator set by user in option (this property has no effect
     for internal validator)
+
+warnings
+    display warnings during validation
 """
-default_properties = ('cache', 'expire', 'validator')
+default_properties = ('cache', 'expire', 'validator', 'warnings')
 
 """Config can be in two defaut mode:
 
@@ -98,10 +100,21 @@ read_write
     you can set all variables not frozen
 """
 ro_append = set(['frozen', 'disabled', 'validator', 'everything_frozen',
-                'mandatory'])
+                'mandatory', 'empty'])
 ro_remove = set(['permissive', 'hidden'])
 rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
-rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
+rw_remove = set(['permissive', 'everything_frozen', 'mandatory', 'empty'])
+
+
+forbidden_set_properties = set(['force_store_value'])
+
+
+log = getLogger('tiramisu')
+#FIXME
+#import logging
+#logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
+debug = False
+static_set = frozenset()
 
 
 # ____________________________________________________________
@@ -112,11 +125,11 @@ class _NameSpace(object):
     """
 
     def __setattr__(self, name, value):
-        if name in self.__dict__:
+        if name in self.__dict__:  # pragma: optional cover
             raise ConstError(_("can't rebind {0}").format(name))
         self.__dict__[name] = value
 
-    def __delattr__(self, name):
+    def __delattr__(self, name):  # pragma: optional cover
         if name in self.__dict__:
             raise ConstError(_("can't unbind {0}").format(name))
         raise ValueError(name)
@@ -203,6 +216,7 @@ def populate_owners():
     """
     setattr(owners, 'default', owners.DefaultOwner('default'))
     setattr(owners, 'user', owners.Owner('user'))
+    setattr(owners, 'forced', owners.Owner('forced'))
 
     def addowner(name):
         """
@@ -211,39 +225,16 @@ def populate_owners():
         setattr(owners, name, owners.Owner(name))
     setattr(owners, 'addowner', addowner)
 
-
-def populate_multitypes():
-    """all multi option should have a type, this type is automaticly set do
-    not touch this
-
-    default
-        default's multi option set if not master or slave
-
-    master
-        master's option in a group with master's type, name of this option
-        should be the same name of the optiondescription
-
-    slave
-        slave's option in a group with master's type
-
-    """
-    setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
-    setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
-    setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
-
-
 # ____________________________________________________________
-# populate groups, owners and multitypes with default attributes
+# populate groups and owners with default attributes
 groups = GroupModule()
 populate_groups()
 owners = OwnerModule()
 populate_owners()
-multitypes = MultiTypeModule()
-populate_multitypes()
 
 
 # ____________________________________________________________
-class Undefined():
+class Undefined(object):
     pass
 
 
@@ -267,13 +258,19 @@ class Property(object):
         :param propname: a predefined or user defined property name
         :type propname: string
         """
+        self._append(propname)
+
+    def _append(self, propname, save=True):
         if self._opt is not None and self._opt.impl_getrequires() is not None \
-                and propname in self._opt.impl_getrequires():
+                and propname in getattr(self._opt, '_calc_properties', static_set):  # pragma: optional cover
             raise ValueError(_('cannot append {0} property for option {1}: '
                                'this property is calculated').format(
                                    propname, self._opt.impl_getname()))
+        if propname in forbidden_set_properties:
+            raise ConfigError(_('cannot add those properties: {0}').format(propname))
         self._properties.add(propname)
-        self._setting._setproperties(self._properties, self._opt, self._path)
+        if save:
+            self._setting._setproperties(self._properties, self._opt, self._path, force=True)
 
     def remove(self, propname):
         """Removes a property named propname
@@ -283,8 +280,7 @@ class Property(object):
         """
         if propname in self._properties:
             self._properties.remove(propname)
-            self._setting._setproperties(self._properties, self._opt,
-                                         self._path)
+            self._setting._setproperties(self._properties, self._opt, self._path)
 
     def extend(self, propnames):
         """Extends properties to the existing properties
@@ -293,7 +289,8 @@ class Property(object):
         :type propnames: iterable of string
         """
         for propname in propnames:
-            self.append(propname)
+            self._append(propname, save=False)
+        self._setting._setproperties(self._properties, self._opt, self._path)
 
     def reset(self):
         """resets the properties (does not **clear** the properties,
@@ -307,13 +304,16 @@ class Property(object):
     def __repr__(self):
         return str(list(self._properties))
 
+    def get(self):
+        return tuple(self._properties)
+
 
 #____________________________________________________________
 class Settings(object):
     "``config.Config()``'s configuration options settings"
-    __slots__ = ('context', '_owner', '_p_', '__weakref__')
+    __slots__ = ('context', '_owner', '_p_', '_pp_', '__weakref__')
 
-    def __init__(self, context, storage):
+    def __init__(self, context, properties, permissives):
         """
         initializer
 
@@ -326,7 +326,8 @@ class Settings(object):
         # generic owner
         self._owner = owners.user
         self.context = weakref.ref(context)
-        self._p_ = storage
+        self._p_ = properties
+        self._pp_ = permissives
 
     def _getcontext(self):
         """context could be None, we need to test it
@@ -335,7 +336,7 @@ class Settings(object):
         old `SubConfig`, `Values`, `Multi` or `Settings`)
         """
         context = self.context()
-        if context is None:
+        if context is None:  # pragma: optional cover
             raise ConfigError(_('the context does not exist anymore'))
         return context
 
@@ -343,64 +344,98 @@ class Settings(object):
     # properties methods
     def __contains__(self, propname):
         "enables the pythonic 'in' syntaxic sugar"
-        return propname in self._getproperties()
+        return propname in self._getproperties(read_write=False)
 
     def __repr__(self):
-        return str(list(self._getproperties()))
+        return str(list(self._getproperties(read_write=False)))
 
     def __getitem__(self, opt):
-        path = self._get_path_by_opt(opt)
-        return self._getitem(opt, path)
+        path = opt.impl_getpath(self._getcontext())
+        return self.getproperties(opt, path)
 
-    def _getitem(self, opt, path):
-        return Property(self, self._getproperties(opt, path), opt, path)
+    def getproperties(self, opt, path, setting_properties=undefined):
+        return Property(self,
+                        self._getproperties(opt, path,
+                                            setting_properties=setting_properties),
+                        opt, path)
 
-    def __setitem__(self, opt, value):
-        raise ValueError('you should only append/remove properties')
+    def __setitem__(self, opt, value):  # pragma: optional cover
+        raise ValueError(_('you should only append/remove properties'))
 
     def reset(self, opt=None, _path=None, all_properties=False):
-        if all_properties and (_path or opt):
+        if all_properties and (_path or opt):  # pragma: optional cover
             raise ValueError(_('opt and all_properties must not be set '
                                'together in reset'))
         if all_properties:
             self._p_.reset_all_properties()
         else:
             if opt is not None and _path is None:
-                _path = self._get_path_by_opt(opt)
-            self._p_.reset_properties(_path)
-        self._getcontext().cfgimpl_reset_cache()
+                _path = opt.impl_getpath(self._getcontext())
+            self._p_.delproperties(_path)
+        self._getcontext().cfgimpl_reset_cache(opt=opt, path=_path, only=('settings',))
 
-    def _getproperties(self, opt=None, path=None, is_apply_req=True):
+    def _getproperties(self, opt=None, path=None,
+                       setting_properties=undefined, read_write=True,
+                       apply_requires=True, index=None):
+        """
+        """
         if opt is None:
-            props = self._p_.getproperties(path, default_properties)
+            ntime = int(time())
+            if self._p_.hascache(path, index):
+                is_cached, props = self._p_.getcache(path, ntime, index)
+            else:
+                is_cached = False
+            if not is_cached or 'cache' not in props:
+                meta = self._getcontext().cfgimpl_get_meta()
+                if meta is None:
+                    props = self._p_.getproperties(path, default_properties)
+                else:
+                    props = meta.cfgimpl_get_settings()._getproperties()
+            if 'cache' in props:
+                if 'expire' in props:
+                    ntime = ntime + expires_time
+                else:
+                    ntime = None
+                self._p_.setcache(path, props, ntime, index)
         else:
-            if path is None:
+            if path is None:  # pragma: optional cover
                 raise ValueError(_('if opt is not None, path should not be'
                                    ' None in _getproperties'))
-            ntime = None
-            if 'cache' in self and self._p_.hascache(path):
-                if 'expire' in self:
+            if setting_properties is undefined:
+                setting_properties = self._getproperties(read_write=False)
+            is_cached = False
+
+            if apply_requires:
+                if 'cache' in setting_properties and 'expire' in setting_properties:
                     ntime = int(time())
-                is_cached, props = self._p_.getcache(path, ntime)
-                if is_cached:
-                    return props
-            #FIXME
-            props = self._p_.getproperties(path, opt.impl_getproperties())
-            if is_apply_req:
-                props |= self.apply_requires(opt, path)
-            if 'cache' in self:
-                if 'expire' in self:
-                    if  ntime is None:
-                        ntime = int(time())
-                    ntime = ntime + expires_time
-                self._p_.setcache(path, props, ntime)
+                else:
+                    ntime = None
+                if 'cache' in setting_properties and self._p_.hascache(path, index):
+                    is_cached, props = self._p_.getcache(path, ntime, index)
+            if not is_cached:
+                props = self._p_.getproperties(path, opt.impl_getproperties())
+                if not opt.impl_is_optiondescription() and opt.impl_is_multi() and \
+                        not opt.impl_is_master_slaves('slave'):
+                    props.add('empty')
+                if apply_requires:
+                    requires = self.apply_requires(opt, path, setting_properties, index, False)
+                    if requires != set([]):
+                        props = copy(props)
+                        props |= requires
+                    if 'cache' in setting_properties:
+                        if 'expire' in setting_properties:
+                            ntime = ntime + expires_time
+                        self._p_.setcache(path, props, ntime, index)
+        if read_write:
+            props = copy(props)
         return props
 
     def append(self, propname):
         "puts property propname in the Config's properties attribute"
         props = self._p_.getproperties(None, default_properties)
-        props.add(propname)
-        self._setproperties(props, None, None)
+        if propname not in props:
+            props.add(propname)
+            self._setproperties(props, None, None)
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
@@ -409,89 +444,132 @@ class Settings(object):
             props.remove(propname)
             self._setproperties(props, None, None)
 
-    def _setproperties(self, properties, opt, path):
-        """save properties for specified opt
+    def extend(self, propnames):
+        for propname in propnames:
+            self.append(propname)
+
+    def _setproperties(self, properties, opt, path, force=False):
+        """save properties for specified path
         (never save properties if same has option properties)
         """
-        if opt is None:
-            self._p_.setproperties(None, properties)
+        if self._getcontext().cfgimpl_get_meta() is not None:
+            raise ConfigError(_('cannot change global property with metaconfig'))
+        if not force:
+            forbidden_properties = forbidden_set_properties & properties
+            if forbidden_properties:
+                raise ConfigError(_('cannot add those properties: {0}').format(
+                    ' '.join(forbidden_properties)))
+        self._p_.setproperties(path, properties)
+        self._getcontext().cfgimpl_reset_cache(opt=opt, path=path)
+
+    def getpermissive(self, setting_properties, path=None):
+        if 'cache' in setting_properties and 'expire' in setting_properties:
+            ntime = int(time())
         else:
-            #if opt._calc_properties is not None:
-            #    properties -= opt._calc_properties
-            #if set(opt._properties) == properties:
-            #    self._p_.reset_properties(path)
-            #else:
-            #    self._p_.setproperties(path, properties)
-            self._p_.setproperties(path, properties)
-        self._getcontext().cfgimpl_reset_cache()
+            ntime = None
+        if 'cache' in setting_properties and self._pp_.hascache(path, None):
+            is_cached, perm = self._pp_.getcache(path, ntime, None)
+        else:
+            is_cached = False
+        if not is_cached:
+            if path is not None:
+                perm = self._pp_.getpermissive(path)
+            else:
+                perm = self._pp_.getpermissive()
+            if 'cache' in setting_properties:
+                if 'expire' in setting_properties:
+                    ntime = ntime + expires_time
+                self._pp_.setcache(path, perm, ntime, None)
+        return perm
 
     #____________________________________________________________
-    def validate_properties(self, opt_or_descr, is_descr, is_write, path,
+    def validate_properties(self, opt_or_descr, is_descr, check_frozen, path,
                             value=None, force_permissive=False,
-                            force_properties=None, force_permissives=None):
+                            setting_properties=undefined,
+                            self_properties=undefined,
+                            index=None, debug=False):
         """
         validation upon the properties related to `opt_or_descr`
 
         :param opt_or_descr: an option or an option description object
         :param force_permissive: behaves as if the permissive property
                                  was present
-        :param force_properties: set() with properties that is force to add
-                                 in global properties
-        :param force_permissives: set() with permissives that is force to add
-                                 in global permissives
         :param is_descr: we have to know if we are in an option description,
                          just because the mandatory property
                          doesn't exist here
 
-        :param is_write: in the validation process, an option is to be modified,
+        :param check_frozen: in the validation process, an option is to be modified,
                          the behavior can be different
                          (typically with the `frozen` property)
         """
         # opt properties
-        properties = copy(self._getproperties(opt_or_descr, path))
-        self_properties = copy(self._getproperties())
-        # remove opt permissive
-        # permissive affect option's permission with or without permissive
-        # global property
-        properties -= self._p_.getpermissive(path)
-        # remove global permissive if need
-        if force_permissive is True or 'permissive' in self_properties:
-            properties -= self._p_.getpermissive()
-        if force_permissives is not None:
-            properties -= force_permissives
-
-        # global properties
-        if force_properties is not None:
-            self_properties.update(force_properties)
-
-        # calc properties
-        properties &= self_properties
-        # mandatory and frozen are special properties
-        if is_descr:
-            properties -= frozenset(('mandatory', 'frozen'))
+        if setting_properties is undefined:
+            setting_properties = self._getproperties(read_write=False)
+        if self_properties is not undefined:
+            properties = copy(self_properties)
         else:
+            properties = self._getproperties(opt_or_descr, path,
+                                             setting_properties=setting_properties,
+                                             index=index)
+        # calc properties
+        properties &= setting_properties
+        if not is_descr:
+            #mandatory
             if 'mandatory' in properties and \
                     not self._getcontext().cfgimpl_get_values()._isempty(
-                        opt_or_descr, value):
+                        opt_or_descr, value, index=index):
                 properties.remove('mandatory')
-            if is_write and 'everything_frozen' in self_properties:
+            elif 'empty' in properties and \
+                    'empty' in setting_properties and \
+                    self._getcontext().cfgimpl_get_values()._isempty(
+                        opt_or_descr, value, force_allow_empty_list=True, index=index):
+                properties.add('mandatory')
+            # should return 'frozen' only when tried to modify a value
+            if check_frozen and 'everything_frozen' in setting_properties:
                 properties.add('frozen')
-            elif 'frozen' in properties and not is_write:
+            elif 'frozen' in properties and not check_frozen:
                 properties.remove('frozen')
+            if 'empty' in properties:
+                properties.remove('empty')
+
+        # remove permissive properties
+        if properties != frozenset():
+            # remove opt permissive
+            # permissive affect option's permission with or without permissive
+            # global property
+            properties -= self.getpermissive(setting_properties, path)
+            # remove global permissive if need
+            if force_permissive is True or 'permissive' in setting_properties:
+                properties -= self.getpermissive(setting_properties)
+
         # at this point an option should not remain in properties
         if properties != frozenset():
             props = list(properties)
+            datas = {'opt': opt_or_descr, 'path': path, 'setting_properties': setting_properties,
+                     'index': index, 'debug': True}
+            if is_descr:
+                opt_type = 'optiondescription'
+            else:
+                opt_type = 'option'
             if 'frozen' in properties:
-                raise PropertiesOptionError(_('cannot change the value for '
-                                              'option {0} this option is'
-                                              ' frozen').format(
-                                                  opt_or_descr.impl_getname()),
-                                            props)
+                return PropertiesOptionError(_('cannot change the value for '
+                                               'option "{0}" this option is'
+                                               ' frozen').format(
+                                                   opt_or_descr.impl_getname()),
+                                             props, self, datas, opt_type)
             else:
-                raise PropertiesOptionError(_("trying to access to an option "
-                                              "named: {0} with properties {1}"
-                                              "").format(opt_or_descr.impl_getname(),
-                                                         str(props)), props)
+                if len(props) == 1:
+                    prop_msg = _('property')
+                else:
+                    prop_msg = _('properties')
+                return PropertiesOptionError(_('cannot access to {0} "{1}" '
+                                               'because has {2} {3}'
+                                               '').format(opt_type,
+                                                          opt_or_descr.impl_get_display_name(),
+                                                          prop_msg,
+                                                          display_list(props)),
+                                               props,
+                                               self, datas, opt_type)
 
     def setpermissive(self, permissive, opt=None, path=None):
         """
@@ -504,27 +582,42 @@ class Settings(object):
                     instead of passing a :class:`tiramisu.option.Option()` object.
         """
         if opt is not None and path is None:
-            path = self._get_path_by_opt(opt)
-        if not isinstance(permissive, tuple):
+            path = opt.impl_getpath(self._getcontext())
+        if not isinstance(permissive, tuple):  # pragma: optional cover
             raise TypeError(_('permissive must be a tuple'))
-        self._p_.setpermissive(path, permissive)
+        self._pp_.setpermissive(path, permissive)
+        setting_properties = self._getproperties(read_write=False)
+        self._getcontext().cfgimpl_reset_cache(opt=opt, path=path, only=('values',))
+        if 'cache' in setting_properties:
+            if 'expire' in setting_properties:
+                ntime = int(time()) + expires_time
+            else:
+                ntime = None
+            self._pp_.setcache(path, set(permissive), ntime, None)
 
     #____________________________________________________________
     def setowner(self, owner):
         ":param owner: sets the default value for owner at the Config level"
-        if not isinstance(owner, owners.Owner):
+        if not isinstance(owner, owners.Owner):  # pragma: optional cover
             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
         self._owner = owner
+        #FIXME qu'est ce qui se passe si pas de owner ??
 
     def getowner(self):
         return self._owner
 
     #____________________________________________________________
     def _read(self, remove, append):
-        for prop in remove:
-            self.remove(prop)
-        for prop in append:
-            self.append(prop)
+        props = self._p_.getproperties(None, default_properties)
+        modified = False
+        if remove & props != set([]):
+            props = props - remove
+            modified = True
+        if append & props != append:
+            props = props | append
+            modified = True
+        if modified:
+            self._setproperties(props, None, None)
 
     def read_only(self):
         "convenience method to freeze, hide and disable"
@@ -534,18 +627,7 @@ class Settings(object):
         "convenience method to freeze, hide and disable"
         self._read(rw_remove, rw_append)
 
-    def reset_cache(self, only_expired):
-        """reset all settings in cache
-
-        :param only_expired: if True reset only expired cached values
-        :type only_expired: boolean
-        """
-        if only_expired:
-            self._p_.reset_expired_cache(int(time()))
-        else:
-            self._p_.reset_all_cache()
-
-    def apply_requires(self, opt, path):
+    def apply_requires(self, opt, path, setting_properties, index, debug):
         """carries out the jit (just in time) requirements between options
 
         a requirement is a tuple of this form that comes from the option's
@@ -582,77 +664,111 @@ class Settings(object):
           exception :exc:`~error.RequirementError` is raised.
 
         And at last, if no target option matches the expected values, the
-        action must be removed from the option's properties list.
+        action will not add to the option's properties list.
 
         :param opt: the option on wich the requirement occurs
         :type opt: `option.Option()`
         :param path: the option's path in the config
         :type path: str
         """
-        if opt.impl_getrequires() is None:
-            return frozenset()
+        current_requires = opt.impl_getrequires()
 
         # filters the callbacks
-        calc_properties = set()
-        context = self._getcontext()
-        for require in opt.impl_getrequires():
-            expected = tuple(require.get_expected())
-            inverse = require.inverse
-            option = require.option
-            reqpath = self._get_path_by_opt(option)
-            if reqpath == path or reqpath.startswith(path + '.'):
-                raise RequirementError(_("malformed requirements "
-                                         "imbrication detected for option:"
-                                         " '{0}' with requirement on: "
-                                         "'{1}'").format(path, reqpath))
-            try:
-                value = context._getattr(reqpath,
-                                                force_permissive=True)
-            except PropertiesOptionError as err:
-                if not require.transitive:
-                    continue
-                properties = err.proptype
-                if require.same_action and require.action not in properties:
-                    raise RequirementError(_("option '{0}' has "
-                                             "requirement's property "
-                                             "error: "
-                                             "{1} {2}").format(opt.impl_getname(),
-                                                               reqpath,
-                                                               properties))
-                # transitive action, force expected
-                value = expected[0]
-                inverse = False
-            if not inverse and value in expected or \
-                    inverse and value not in expected:
-                calc_properties.add(require.action)
-                # the calculation cannot be carried out
-                #break
-        return calc_properties
+        if debug:
+            calc_properties = {}
+        else:
+            calc_properties = set()
 
-    def _get_path_by_opt(self, opt):
-        """just a wrapper to get path in optiondescription's cache
+        if not current_requires:
+            return calc_properties
 
-        :param opt: `Option`'s object
-        :returns: path
-        """
-        return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt)
+        context = self._getcontext()
+        all_properties = None
+        for requires in current_requires:
+            for require in requires:
+                exps, action, inverse, \
+                    transitive, same_action, operator = require
+                breaked = False
+                for exp in exps:
+                    option, expected = exp
+                    reqpath = option.impl_getpath(context)
+                    if reqpath == path or reqpath.startswith(path + '.'):  # pragma: optional cover
+                        raise RequirementError(_("malformed requirements "
+                                                 "imbrication detected for option:"
+                                                 " '{0}' with requirement on: "
+                                                 "'{1}'").format(path, reqpath))
+                    if option.impl_is_multi():
+                        if index is None:
+                            # multi is allowed only for slaves
+                            # so do not calculated requires if no index
+                            continue
+                        idx = index
+                    else:
+                        idx = None
+                    value = context.getattr(reqpath, force_permissive=True,
+                                            _setting_properties=setting_properties,
+                                            index=idx, returns_raise=True)
+                    if isinstance(value, Exception):
+                        if isinstance(value, PropertiesOptionError):
+                            if not transitive:
+                                if all_properties is None:
+                                    all_properties = []
+                                    for requires in opt.impl_getrequires():
+                                        for require in requires:
+                                            all_properties.append(require[1])
+                                if not set(value.proptype) - set(all_properties):
+                                    continue
+                            properties = value.proptype
+                            if same_action and action not in properties:  # pragma: optional cover
+                                if len(properties) == 1:
+                                    prop_msg = _('property')
+                                else:
+                                    prop_msg = _('properties')
+                                raise RequirementError(_('cannot access to option "{0}" because '
+                                                         'required option "{1}" has {2} {3}'
+                                                         '').format(opt.impl_get_display_name(),
+                                                                    option.impl_get_display_name(),
+                                                                    prop_msg,
+                                                                    display_list(properties)))
+                            orig_value = value
+                            # transitive action, force expected
+                            value = expected[0]
+                            inverse = False
+                        else:  # pragma: no cover
+                            raise value
+                    else:
+                        orig_value = value
+                    if (not inverse and value in expected or
+                            inverse and value not in expected):
+                        if operator != 'and':
+                            if debug:
+                                if isinstance(orig_value, PropertiesOptionError):
+                                    for msg in orig_value._settings.apply_requires(**orig_value._datas).values():
+                                        calc_properties.setdefault(action, []).extend(msg)
+                                else:
+                                    if not inverse:
+                                        msg = _('the value of "{0}" is "{1}"')
+                                    else:
+                                        msg = _('the value of "{0}" is not "{1}"')
+                                    calc_properties.setdefault(action, []).append(
+                                        msg.format(option.impl_get_display_name(),
+                                                   display_list(expected, 'or')))
+                            else:
+                                calc_properties.add(action)
+                                breaked = True
+                                break
+                    elif operator == 'and':
+                        break
+                else:
+                    if operator == 'and':
+                        calc_properties.add(action)
+                    continue  # pragma: no cover
+                if breaked:
+                    break
+        return calc_properties
 
     def get_modified_properties(self):
         return self._p_.get_modified_properties()
 
     def get_modified_permissives(self):
-        return self._p_.get_modified_permissives()
-
-    def __getstate__(self):
-        return {'_p_': self._p_, '_owner': str(self._owner)}
-
-    def _impl_setstate(self, storage):
-        self._p_._storage = storage
-
-    def __setstate__(self, states):
-        self._p_ = states['_p_']
-        try:
-            self._owner = getattr(owners, states['_owner'])
-        except AttributeError:
-            owners.addowner(states['_owner'])
-            self._owner = getattr(owners, states['_owner'])
+        return self._pp_.get_modified_permissives()