tiramisu/tiramisu/setting.py

899 lines
28 KiB
Python
Raw Permalink Normal View History

2012-11-19 10:45:03 +01:00
# -*- coding: utf-8 -*-
"sets the options of the configuration objects Config object itself"
2025-09-11 22:07:53 +02:00
# Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
2012-11-19 10:45:03 +01:00
#
2013-09-22 22:33:09 +02:00
# 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.
2012-11-19 10:45:03 +01:00
#
2013-09-22 22:33:09 +02:00
# 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.
2012-11-19 10:45:03 +01:00
#
2013-09-22 22:33:09 +02:00
# 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/>.
2012-11-19 10:45:03 +01:00
# ____________________________________________________________
2023-05-11 15:44:48 +02:00
from typing import Union, Set
2019-11-19 18:39:44 +01:00
from itertools import chain
2024-10-31 08:53:58 +01:00
from .error import (
PropertiesOptionError,
ConstError,
ConfigError,
LeadershipError,
display_list,
)
from .i18n import _
2023-05-11 15:44:48 +02:00
# If cache and expire is enable, time before cache is expired.
# This delay start first time value/setting is set in cache, even if
# user access several time to value/setting
2019-02-24 10:36:42 +01:00
EXPIRATION_TIME = 5
2013-09-07 21:47:17 +02:00
2024-10-31 08:53:58 +01:00
# List of default properties (you can add new one if needed).
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# For common properties and personalise properties, if a propery is set for
# an Option and for the Config together, Setting raise a PropertiesOptionError
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# * Common properties:
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# hidden
2023-05-11 15:44:48 +02:00
# option with this property can only get value in read only mode. This
# option is not available in read write mode.
#
2024-10-31 08:53:58 +01:00
# disabled
2023-05-11 15:44:48 +02:00
# option with this property cannot be set/get
#
2024-10-31 08:53:58 +01:00
# frozen
2023-05-11 15:44:48 +02:00
# cannot set value for option with this properties if 'frozen' is set in
# config
#
2024-10-31 08:53:58 +01:00
# * Special property:
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# permissive
2023-05-11 15:44:48 +02:00
# option with 'permissive' cannot raise PropertiesOptionError for properties
# set in permissive
# config with 'permissive', whole option in this config cannot raise
# PropertiesOptionError for properties set in permissive
#
2024-10-31 08:53:58 +01:00
# mandatory
2023-05-11 15:44:48 +02:00
# should set value for option with this properties if 'mandatory' is set in
# config
# example: 'a', ['a'], [None] are valid
# None, [] are not valid
#
2024-10-31 08:53:58 +01:00
# empty
2023-05-11 15:44:48 +02:00
# raise mandatory PropertiesOptionError if multi or leader have empty value
# example: ['a'] is valid
# [None] is not valid
#
2024-10-31 08:53:58 +01:00
# unique
2023-05-11 15:44:48 +02:00
# raise ValueError if a value is set twice or more in a multi Option
#
2024-10-31 08:53:58 +01:00
# * Special Config properties:
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# cache
2023-05-11 15:44:48 +02:00
# if set, enable cache settings and values
#
2024-10-31 08:53:58 +01:00
# expire
2023-05-11 15:44:48 +02:00
# if set, settings and values in cache expire after ``expiration_time``
#
2024-10-31 08:53:58 +01:00
# everything_frozen
2023-05-11 15:44:48 +02:00
# whole option in config are frozen (even if option have not frozen
# property)
#
2024-10-31 08:53:58 +01:00
# validator
2023-05-11 15:44:48 +02:00
# launch validator set by user in option (this property has no effect
# for internal validator)
#
2024-10-31 08:53:58 +01:00
# warnings
2023-05-11 15:44:48 +02:00
# display warnings during validation
#
2024-10-31 08:53:58 +01:00
# demoting_error_warning
2023-05-11 15:44:48 +02:00
# all value errors are convert to warning (ValueErrorWarning)
2024-10-31 08:53:58 +01:00
DEFAULT_PROPERTIES = frozenset(["cache", "validator", "warnings"])
2025-02-13 22:11:26 +01:00
SPECIAL_PROPERTIES = {"frozen", "mandatory", "empty", "force_store_value", "validator"}
2013-09-07 21:47:17 +02:00
2024-10-31 08:53:58 +01:00
# Config can be in two defaut mode:
2023-05-11 15:44:48 +02:00
#
2024-10-31 08:53:58 +01:00
# read_only
2023-05-11 15:44:48 +02:00
# you can get all variables not disabled but you cannot set any variables
# if a value has a callback without any value, callback is launch and value
# of this variable can change
# you cannot access to mandatory variable without values
#
2024-10-31 08:53:58 +01:00
# read_write
2023-05-11 15:44:48 +02:00
# you can get all variables not disabled and not hidden
# you can set all variables not frozen
2024-10-31 08:53:58 +01:00
RO_APPEND = frozenset(
[
"frozen",
"disabled",
"validator",
"everything_frozen",
"mandatory",
"empty",
"force_store_value",
]
)
RO_REMOVE = frozenset(
[
"permissive",
"hidden",
]
)
RW_APPEND = frozenset(
[
"frozen",
"disabled",
"validator",
"hidden",
"force_store_value",
]
)
RW_REMOVE = frozenset(
[
"permissive",
"everything_frozen",
"mandatory",
"empty",
]
)
FORBIDDEN_SET_PROPERTIES = frozenset(["force_store_value"])
FORBIDDEN_SET_PERMISSIVES = frozenset(
[
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"force_store_value",
2025-02-13 22:11:26 +01:00
"validator",
2024-10-31 08:53:58 +01:00
]
)
ALLOWED_LEADER_PROPERTIES = {
"empty",
"notempty",
"notunique",
"unique",
"force_store_value",
"mandatory",
2025-02-13 22:11:26 +01:00
"validator",
"novalidator",
2024-10-31 08:53:58 +01:00
"force_default_on_freeze",
"force_metaconfig_on_freeze",
"frozen",
}
2014-07-06 15:31:57 +02:00
2017-07-22 16:26:06 +02:00
static_set = frozenset()
2014-04-03 22:15:41 +02:00
# ____________________________________________________________
2023-05-11 15:44:48 +02:00
class Undefined:
2024-10-31 08:53:58 +01:00
"""Object undefined, means that there is not value"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-few-public-methods
def __str__(self): # pragma: no cover
2024-10-31 08:53:58 +01:00
return "Undefined"
__repr__ = __str__
undefined = Undefined()
2018-08-18 10:03:08 +02:00
2018-08-01 08:37:58 +02:00
class ConfigBag:
2024-10-31 08:53:58 +01:00
"""Object to store information for context"""
__slots__ = (
"context", # link to the current context
"properties", # properties for current context
"true_properties", # properties for current context
"is_unrestraint",
"permissives", # permissives for current context
"expiration_time", # EXPIRATION_TIME
)
def __init__(self, context, properties: set, permissives: frozenset, **kwargs):
2018-08-02 22:35:40 +02:00
self.context = context
2019-12-24 15:24:20 +01:00
self.properties = properties
self.permissives = permissives
2017-12-19 23:11:45 +01:00
for key, value in kwargs.items():
2018-08-01 08:37:58 +02:00
setattr(self, key, value)
2017-12-19 23:11:45 +01:00
def __getattr__(self, key):
2024-10-31 08:53:58 +01:00
if key == "true_properties":
2018-12-24 09:30:58 +01:00
return self.properties
2024-10-31 08:53:58 +01:00
if key == "expiration_time":
self.expiration_time = (
EXPIRATION_TIME # pylint: disable=attribute-defined-outside-init
)
2019-02-24 10:36:42 +01:00
return self.expiration_time
2024-10-31 08:53:58 +01:00
if key == "is_unrestraint":
2019-07-04 20:43:47 +02:00
return False
2023-05-11 15:44:48 +02:00
raise KeyError(f'unknown key "{key}" for ConfigBag') # pragma: no cover
2018-08-17 23:11:25 +02:00
2023-05-11 15:44:48 +02:00
def nowarnings(self):
2024-10-31 08:53:58 +01:00
"""do not warnings"""
self.properties = frozenset(self.properties - {"warnings"})
2018-08-17 23:11:25 +02:00
def remove_validation(self):
2024-10-31 08:53:58 +01:00
"""do not validate option"""
self.properties = frozenset(self.properties - {"validator"})
2018-12-24 09:30:58 +01:00
def unrestraint(self):
2024-10-31 08:53:58 +01:00
"""do not restraint access to option"""
2023-05-11 15:44:48 +02:00
self.is_unrestraint = True # pylint: disable=attribute-defined-outside-init
2024-10-31 08:53:58 +01:00
self.true_properties = (
self.properties
) # pylint: disable=attribute-defined-outside-init
self.properties = frozenset(["cache"])
2018-12-24 09:30:58 +01:00
2018-08-18 08:06:29 +02:00
def set_permissive(self):
2024-10-31 08:53:58 +01:00
"""set permissive"""
self.properties = frozenset(self.properties | {"permissive"})
2018-08-18 08:06:29 +02:00
2018-08-01 08:37:58 +02:00
def copy(self):
2024-10-31 08:53:58 +01:00
"""copy the config"""
2017-12-19 23:11:45 +01:00
kwargs = {}
for key in self.__slots__:
kwargs[key] = getattr(self, key)
2017-12-19 23:11:45 +01:00
return ConfigBag(**kwargs)
2013-09-07 21:47:17 +02:00
# ____________________________________________________________
2023-05-11 15:44:48 +02:00
class _NameSpace:
2012-12-06 18:14:57 +01:00
"""convenient class that emulates a module
2013-09-07 21:47:17 +02:00
and builds constants (that is, unique names)
when attribute is added, we cannot delete it
"""
2012-12-06 18:14:57 +01:00
2024-10-31 08:53:58 +01:00
def __setattr__(
self,
name,
value,
):
2017-11-20 17:01:36 +01:00
if name in self.__dict__:
raise ConstError(_("can't rebind {0}").format(name))
2012-12-06 18:14:57 +01:00
self.__dict__[name] = value
2024-10-31 08:53:58 +01:00
def __delattr__(
self,
name,
):
2017-11-20 17:01:36 +01:00
raise ConstError(_("can't unbind {0}").format(name))
2013-04-03 12:20:26 +02:00
class GroupModule(_NameSpace):
2012-12-10 09:53:13 +01:00
"emulates a module to manage unique group (OptionDescription) names"
2024-10-31 08:53:58 +01:00
2023-05-11 15:44:48 +02:00
# pylint: disable=too-few-public-methods
2012-12-10 14:38:25 +01:00
class GroupType(str):
2012-12-06 18:14:57 +01:00
"""allowed normal group (OptionDescription) names
2019-02-23 19:06:23 +01:00
*normal* means : groups that are not leader
2012-12-06 18:14:57 +01:00
"""
2013-04-03 12:20:26 +02:00
2012-12-10 14:38:25 +01:00
class DefaultGroupType(GroupType):
2012-12-10 14:10:05 +01:00
"""groups that are default (typically 'default')"""
2019-02-23 19:06:23 +01:00
class LeadershipGroupType(GroupType):
2012-12-06 18:14:57 +01:00
"""allowed normal group (OptionDescription) names
2019-02-23 19:06:23 +01:00
*leadership* means : groups that have the 'leadership' attribute set
2012-12-06 18:14:57 +01:00
"""
2013-04-03 12:20:26 +02:00
class RootGroupType(GroupType):
2024-10-31 08:53:58 +01:00
"""root means this is the root optiondescription of whole config"""
2019-11-29 12:07:43 +01:00
def addgroup(self, name):
2024-10-31 08:53:58 +01:00
"""add a new group type"""
2019-11-29 12:07:43 +01:00
setattr(groups, name, groups.GroupType(name))
2013-04-03 12:20:26 +02:00
class OwnerModule(_NameSpace):
2012-12-10 14:10:05 +01:00
"""emulates a module to manage unique owner names.
owners are living in `Config._value_owners`
2012-12-10 14:10:05 +01:00
"""
2024-10-31 08:53:58 +01:00
2023-05-11 15:44:48 +02:00
# pylint: disable=too-few-public-methods
2012-12-10 14:10:05 +01:00
class Owner(str):
2024-10-31 08:53:58 +01:00
"""allowed owner names"""
2013-04-03 12:20:26 +02:00
2012-12-10 14:10:05 +01:00
class DefaultOwner(Owner):
"""groups that are default (typically 'default')"""
2013-09-07 21:47:17 +02:00
2017-11-20 17:01:36 +01:00
def addowner(self, name):
"""
:param name: the name of the new owner
"""
setattr(owners, name, owners.Owner(name))
2013-09-07 21:47:17 +02:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# populate groups
groups = GroupModule()
2013-09-07 21:47:17 +02:00
2023-05-11 15:44:48 +02:00
# groups.default: default group set when creating a new optiondescription
2024-10-31 08:53:58 +01:00
groups.default = groups.DefaultGroupType(
"default"
) # pylint: disable=attribute-defined-outside-init
2013-09-07 21:47:17 +02:00
2023-05-11 15:44:48 +02:00
# groups.leadership: leadership group is a special optiondescription, all suboptions should
# be multi option and all values should have same length, to find
# leader's option, the optiondescription's name should be same than de
# leader's option"""
2024-10-31 08:53:58 +01:00
groups.leadership = groups.LeadershipGroupType(
"leadership"
) # pylint: disable=attribute-defined-outside-init
2023-05-11 15:44:48 +02:00
# groups.root: this group is the root optiondescription of whole config
2024-10-31 08:53:58 +01:00
groups.root = groups.RootGroupType(
"root"
) # pylint: disable=attribute-defined-outside-init
2013-04-03 12:20:26 +02:00
2012-12-10 14:38:25 +01:00
2013-09-07 21:47:17 +02:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# populate owners with default attributes
2013-09-07 21:47:17 +02:00
owners = OwnerModule()
2023-05-11 15:44:48 +02:00
# default: is the config owner after init time
2024-10-31 08:53:58 +01:00
owners.default = owners.DefaultOwner(
"default"
) # pylint: disable=attribute-defined-outside-init
2023-05-11 15:44:48 +02:00
# user: is the generic is the generic owner
2024-10-31 08:53:58 +01:00
owners.addowner("user")
2023-05-11 15:44:48 +02:00
2024-10-31 08:53:58 +01:00
# forced: special owner when value is forced
owners.addowner("forced")
2017-11-23 16:56:14 +01:00
2023-05-11 15:44:48 +02:00
forbidden_owners = (owners.default, owners.forced) # pylint: disable=no-member
2013-02-21 17:07:00 +01:00
2013-04-03 12:20:26 +02:00
2018-10-30 11:57:04 +01:00
# ____________________________________________________________
2023-05-11 15:44:48 +02:00
class Settings:
2014-01-06 15:32:28 +01:00
"``config.Config()``'s configuration options settings"
2024-10-31 08:53:58 +01:00
__slots__ = (
"_properties",
"_permissives",
"_permissives",
"__weakref__",
"ro_append",
"ro_remove",
"rw_append",
"rw_remove",
)
2013-04-03 12:20:26 +02:00
def __init__(self):
2013-08-21 17:21:09 +02:00
"""
2013-08-21 18:34:32 +02:00
initializer
2013-08-21 17:21:09 +02:00
:param context: the root config
2013-08-21 18:34:32 +02:00
:param storage: the storage type
2013-08-21 17:21:09 +02:00
"""
2013-04-03 12:20:26 +02:00
# generic owner
2024-06-20 12:56:27 +02:00
self._properties = {None: {None: DEFAULT_PROPERTIES}}
self._permissives = {}
self.ro_append = RO_APPEND
self.ro_remove = RO_REMOVE
self.rw_append = RW_APPEND
self.rw_remove = RW_REMOVE
2013-08-20 09:47:12 +02:00
2018-10-30 11:57:04 +01:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# get properties and permissive methods
2013-03-14 11:31:44 +01:00
2024-06-20 12:56:27 +02:00
def get_context_properties(self):
2024-10-31 08:53:58 +01:00
"""get context properties"""
2024-06-20 12:56:27 +02:00
return self.get_personalize_properties()
2013-03-14 11:31:44 +01:00
2024-10-31 08:53:58 +01:00
def get_personalize_properties(
self,
path: Union[None, str] = None,
index: Union[None, int] = None,
) -> Set[str]:
"""Get the properties modified by user for a path or index"""
2023-05-11 15:44:48 +02:00
if path not in self._properties or index not in self._properties[path]:
2024-06-20 12:56:27 +02:00
return frozenset()
2023-05-11 15:44:48 +02:00
return self._properties[path][index]
2024-10-31 08:53:58 +01:00
def getproperties(
self,
subconfig: "SubConfig",
*,
apply_requires=True,
uncalculated=False,
help_property=False,
transitive_raise=True,
):
"""get properties"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-branches
2024-04-24 15:39:17 +02:00
option = subconfig.option
2019-09-01 09:41:53 +02:00
if option.impl_is_symlinkoption():
option = option.impl_getopt()
2020-01-22 20:46:18 +01:00
if apply_requires and not uncalculated and not help_property:
2024-04-24 15:39:17 +02:00
cache = subconfig.config_bag.context.properties_cache
2024-10-31 08:53:58 +01:00
is_cached, props, validated = cache.getcache(
subconfig, # pylint: disable=unused-variable
"self_props",
)
2018-06-25 21:40:16 +02:00
else:
is_cached = False
2017-11-20 17:01:36 +01:00
if not is_cached:
2019-09-01 09:41:53 +02:00
props = set()
2020-01-22 20:46:18 +01:00
# if index, get option's properties (without index) too
2024-06-20 12:56:27 +02:00
p_props = [option.impl_getproperties()]
props_config = self.get_personalize_properties(subconfig.path)
if props_config:
p_props.append(props_config)
2024-04-24 15:39:17 +02:00
if subconfig.index is not None:
2024-10-31 08:53:58 +01:00
props_config = self.get_personalize_properties(
subconfig.path,
subconfig.index,
)
2024-06-20 12:56:27 +02:00
if props_config:
p_props.append(props_config)
for prop in chain(*p_props):
2019-12-02 10:40:17 +01:00
if uncalculated or isinstance(prop, str):
2020-01-22 20:46:18 +01:00
if not help_property:
props.add(prop)
else:
props.add((prop, prop))
2019-09-01 09:41:53 +02:00
elif apply_requires:
try:
if not help_property:
2024-10-31 08:53:58 +01:00
new_prop = prop.execute(
subconfig,
for_settings=True,
)
else:
2024-10-31 08:53:58 +01:00
new_prop = prop.help(
subconfig,
for_settings=True,
)
if isinstance(new_prop, str):
new_prop = (new_prop, new_prop)
if new_prop is None:
continue
except ConfigError as err:
if transitive_raise:
raise err from err
2019-09-01 09:41:53 +02:00
continue
2024-10-31 08:53:58 +01:00
if (not help_property and not isinstance(new_prop, str)) or (
help_property and not isinstance(new_prop, tuple)
):
raise ValueError(
_(
"invalid property type {type(new_prop)} for "
"{subconfig.option.impl_getname()} with "
"{prop.function.__name__} function"
)
)
if (
not option.impl_is_optiondescription()
and option.impl_is_leader()
and new_prop not in ALLOWED_LEADER_PROPERTIES
):
raise LeadershipError(
2025-05-12 08:53:39 +02:00
subconfig, "leadership-wrong_property", prop=new_prop
2024-10-31 08:53:58 +01:00
)
2019-10-27 11:09:15 +01:00
props.add(new_prop)
2024-04-24 15:39:17 +02:00
props -= self.getpermissives(subconfig)
2024-10-31 08:53:58 +01:00
if (
not uncalculated
and apply_requires
and not subconfig.config_bag.is_unrestraint
and not help_property
and transitive_raise
):
cache.setcache(
subconfig,
props,
type_="properties",
)
if (
not uncalculated
and subconfig.parent
and subconfig.parent.transitive_properties
):
parent_properties = subconfig.parent.transitive_properties
2024-06-20 12:56:27 +02:00
parent_properties -= self.getpermissives(subconfig)
if help_property:
parent_properties = {(prop, prop) for prop in parent_properties}
return props | parent_properties
2017-11-20 17:01:36 +01:00
return props
def get_context_permissives(self):
2024-10-31 08:53:58 +01:00
"""get context permissives"""
return self.getpermissives(None)
2012-11-19 10:45:03 +01:00
2024-10-31 08:53:58 +01:00
def _getpermissives(
self,
path,
index,
):
if not path in self._permissives:
ret = frozenset()
else:
ret = self._permissives[path].get(index, frozenset())
return ret
2024-10-31 08:53:58 +01:00
def getpermissives(
self,
subconfig: "SubConfig",
):
"""get permissive"""
2024-04-24 15:39:17 +02:00
if subconfig is None:
2019-11-19 18:39:44 +01:00
path = None
index = None
else:
2024-04-24 15:39:17 +02:00
opt = subconfig.option
2019-11-19 18:39:44 +01:00
if opt.impl_is_symlinkoption():
opt = opt.impl_getopt()
path = opt.impl_getpath()
else:
2024-04-24 15:39:17 +02:00
path = subconfig.path
index = subconfig.index
2024-10-31 08:53:58 +01:00
permissives = self._getpermissives(
path,
None,
)
2019-11-19 18:39:44 +01:00
if index is not None:
option_permissives = self._permissives.get(path, {}).get(index, set())
2019-12-24 15:24:20 +01:00
permissives = frozenset(option_permissives | permissives)
2019-11-19 18:39:44 +01:00
return permissives
2024-10-31 08:53:58 +01:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# set methods
def set_context_properties(self, properties, context):
2024-10-31 08:53:58 +01:00
"""set context properties"""
2024-06-20 12:56:27 +02:00
self._properties[None][None] = properties
context.reset_cache(None)
2024-10-31 08:53:58 +01:00
def setproperties(
self,
subconfig,
properties,
):
2017-11-20 17:01:36 +01:00
"""save properties for specified path
(never save properties if same has option properties)
"""
2024-04-24 15:39:17 +02:00
opt = subconfig.option
2019-10-27 11:09:15 +01:00
if not opt.impl_is_optiondescription() and opt.impl_is_leader():
not_allowed_properties = properties - ALLOWED_LEADER_PROPERTIES
if not_allowed_properties:
2024-10-31 08:53:58 +01:00
raise LeadershipError(
2025-05-12 08:53:39 +02:00
subconfig,
"leadership-wrong_property",
prop=display_list(not_allowed_properties),
2024-10-31 08:53:58 +01:00
)
if (
"force_default_on_freeze" in properties
or "force_metaconfig_on_freeze" in properties
) and "frozen" not in properties:
2025-05-12 08:53:39 +02:00
raise LeadershipError(subconfig, "leadership-force_default_on_freeze")
2024-04-24 15:39:17 +02:00
self._properties.setdefault(subconfig.path, {})[subconfig.index] = properties
2019-02-23 19:06:23 +01:00
# values too because of follower values could have a PropertiesOptionError has value
2024-04-24 15:39:17 +02:00
subconfig.config_bag.context.reset_cache(subconfig)
subconfig.properties = properties
2017-11-20 17:01:36 +01:00
2024-10-31 08:53:58 +01:00
def set_context_permissives(
self,
permissives,
):
"""set context permissive"""
self.setpermissives(
None,
permissives,
)
def setpermissives(
self,
subconfig,
permissives,
):
2017-11-20 17:01:36 +01:00
"""
enables us to put the permissives in the storage
:param path: the option's path
:param type: str
:param opt: if an option object is set, the path is extracted.
it is better (faster) to set the path parameter
instead of passing a :class:`tiramisu.option.Option()` object.
"""
if not isinstance(permissives, frozenset):
2024-10-31 08:53:58 +01:00
raise TypeError(_("permissive must be a frozenset"))
2024-04-24 15:39:17 +02:00
if subconfig is not None:
path = subconfig.path
index = subconfig.index
2018-08-01 08:37:58 +02:00
else:
path = None
2019-11-19 18:39:44 +01:00
index = None
2017-12-28 11:47:29 +01:00
forbidden_permissives = FORBIDDEN_SET_PERMISSIVES & permissives
2017-11-20 17:01:36 +01:00
if forbidden_permissives:
2024-10-31 08:53:58 +01:00
raise ConfigError(
_("cannot add those permissives: {0}").format(
" ".join(forbidden_permissives)
)
)
self._permissives.setdefault(path, {})[index] = permissives
2024-04-24 15:39:17 +02:00
if subconfig is not None:
subconfig.config_bag.context.reset_cache(subconfig)
2017-11-20 17:01:36 +01:00
2024-10-31 08:53:58 +01:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# reset methods
2024-10-31 08:53:58 +01:00
def _get_path_index_config_option(
self,
bag: Union["SubConfig", ConfigBag],
msg: str,
):
2023-04-27 11:34:35 +02:00
if isinstance(bag, ConfigBag):
path = None
2019-11-19 18:39:44 +01:00
index = None
2023-04-27 11:34:35 +02:00
config_bag = bag
2024-10-31 08:53:58 +01:00
subconfig = None
2018-08-01 08:37:58 +02:00
else:
2024-10-31 08:53:58 +01:00
assert not bag.option.impl_is_symlinkoption(), msg.format(
bag.option.impl_get_display_name()
)
2023-04-27 11:34:35 +02:00
path = bag.path
index = bag.index
config_bag = bag.config_bag
2024-04-24 15:39:17 +02:00
subconfig = bag
return path, index, config_bag, subconfig
2023-04-27 11:34:35 +02:00
2024-10-31 08:53:58 +01:00
def reset(
self,
bag: Union["SubConfig", ConfigBag],
):
"""reset property"""
path, index, config_bag, subconfig = self._get_path_index_config_option(
bag,
_("can't reset properties to " 'the symlinkoption "{}"'),
)
if path in self._properties and index in self._properties[path]:
2023-05-11 15:44:48 +02:00
del self._properties[path][index]
2024-04-24 15:39:17 +02:00
config_bag.context.reset_cache(subconfig)
2024-10-31 08:53:58 +01:00
def reset_permissives(
self,
bag: Union["SubConfig", ConfigBag],
):
"""reset permission"""
path, index, config_bag, subconfig = self._get_path_index_config_option(
bag,
_("can't reset permissives to " 'the symlinkoption "{}"'),
)
if path in self._permissives and index in self._permissives[path]:
2023-05-11 15:44:48 +02:00
del self._permissives[path][index]
2024-04-24 15:39:17 +02:00
config_bag.context.reset_cache(subconfig)
2017-11-20 17:01:36 +01:00
2024-10-31 08:53:58 +01:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# validate properties
2024-10-31 08:53:58 +01:00
def calc_raises_properties(
self,
subconfig,
*,
apply_requires=True,
uncalculated=False,
transitive_raise=True,
not_unrestraint: bool = False,
):
"""raise if needed"""
2024-04-24 15:39:17 +02:00
if not uncalculated and apply_requires and subconfig.properties is not None:
option_properties = subconfig.properties
2019-06-12 08:45:56 +02:00
else:
2024-10-31 08:53:58 +01:00
option_properties = self.getproperties(
subconfig,
apply_requires=apply_requires,
uncalculated=uncalculated,
transitive_raise=transitive_raise,
)
return self._calc_raises_properties(
subconfig,
option_properties,
not_unrestraint,
)
def calc_transitive_properties(
self,
subconfig,
option_properties,
):
config_bag = subconfig.config_bag
2024-10-31 08:53:58 +01:00
modified, context_properties = self.calc_read(
self.rw_remove,
self.rw_append,
config_bag,
)
raises_properties = context_properties - SPECIAL_PROPERTIES
# remove global permissive properties
2024-10-31 08:53:58 +01:00
if raises_properties and "permissive" in raises_properties:
raises_properties -= config_bag.permissives
properties = option_properties & raises_properties
# at this point it should not remain any property for the option
return properties
2024-10-31 08:53:58 +01:00
def _calc_raises_properties(
self,
subconfig,
option_properties,
not_unrestraint: bool,
):
config_bag = subconfig.config_bag
2024-06-20 12:56:27 +02:00
if not_unrestraint and config_bag.is_unrestraint:
context_properties = config_bag.true_properties
else:
context_properties = config_bag.properties
raises_properties = context_properties - SPECIAL_PROPERTIES
2019-07-04 20:43:47 +02:00
# remove global permissive properties
2024-10-31 08:53:58 +01:00
if raises_properties and "permissive" in raises_properties:
2024-06-20 12:56:27 +02:00
raises_properties -= config_bag.permissives
2019-06-12 08:45:56 +02:00
properties = option_properties & raises_properties
2020-01-22 20:46:18 +01:00
# at this point it should not remain any property for the option
return properties
2017-11-20 17:01:36 +01:00
2024-10-31 08:53:58 +01:00
def validate_properties(
self,
subconfig,
*,
need_help=True,
):
"""check properties"""
2024-04-24 15:39:17 +02:00
config_properties = subconfig.config_bag.properties
2024-10-31 08:53:58 +01:00
if not config_properties or config_properties == frozenset(["cache"]):
2020-01-22 20:46:18 +01:00
# if no global property
2018-08-18 07:51:04 +02:00
return
for transitive_raise in [False, True]:
2024-10-31 08:53:58 +01:00
properties = self.calc_raises_properties(
subconfig,
transitive_raise=transitive_raise,
)
if properties != frozenset():
if need_help:
2024-10-31 08:53:58 +01:00
help_properties = dict(
self.getproperties(
subconfig,
help_property=True,
transitive_raise=transitive_raise,
)
)
calc_properties = []
2024-10-31 08:53:58 +01:00
for property_ in self._calc_raises_properties(
subconfig,
set(help_properties.keys()),
False,
):
calc_properties.append(help_properties[property_])
calc_properties = frozenset(calc_properties)
else:
calc_properties = properties
2024-10-31 08:53:58 +01:00
raise PropertiesOptionError(
subconfig,
properties,
self,
help_properties=calc_properties,
)
def validate_mandatory(
self,
subconfig,
value,
):
"""verify if option is mandatory without value"""
if "mandatory" not in subconfig.config_bag.properties:
2023-05-11 15:44:48 +02:00
return
2024-04-24 15:39:17 +02:00
values = subconfig.config_bag.context.get_values()
2024-10-31 08:53:58 +01:00
if (
not (
"permissive" in subconfig.config_bag.properties
and "mandatory" in subconfig.config_bag.permissives
)
and "mandatory" in subconfig.properties
and values.isempty(
subconfig,
value,
False,
)
):
raise PropertiesOptionError(
subconfig,
["mandatory"],
self,
)
if "empty" in subconfig.properties and values.isempty(
subconfig,
value,
True,
):
raise PropertiesOptionError(
subconfig,
["empty"],
self,
)
def validate_frozen(
self,
subconfig,
):
"""verify if option is frozen"""
if (
subconfig.config_bag.properties
and (
"everything_frozen" in subconfig.config_bag.properties
or (
"frozen" in subconfig.config_bag.properties
and "frozen" in subconfig.properties
)
)
and not (
("permissive" in subconfig.config_bag.properties)
and "frozen" in subconfig.config_bag.permissives
)
):
raise PropertiesOptionError(
subconfig,
["frozen"],
self,
)
2017-12-28 11:47:29 +01:00
return False
2024-10-31 08:53:58 +01:00
# ____________________________________________________________
2017-11-20 17:01:36 +01:00
# read only/read write
2024-10-31 08:53:58 +01:00
def calc_read(
self,
remove,
append,
config_bag,
):
2024-06-20 12:56:27 +02:00
props = self.get_personalize_properties()
2017-11-20 17:01:36 +01:00
modified = False
2017-12-28 11:47:29 +01:00
if remove & props:
2017-11-20 17:01:36 +01:00
props = props - remove
modified = True
if append & props != append:
props = props | append
modified = True
return modified, frozenset(props)
2024-10-31 08:53:58 +01:00
def _read(
self,
remove,
append,
config_bag,
):
modified, props = self.calc_read(
remove,
append,
config_bag,
)
2017-11-20 17:01:36 +01:00
if modified:
2024-10-31 08:53:58 +01:00
self.set_context_properties(
props,
config_bag.context,
)
def read_only(
self,
config_bag,
):
2017-11-20 17:01:36 +01:00
"convenience method to freeze, hide and disable"
2024-10-31 08:53:58 +01:00
self._read(
self.ro_remove,
self.ro_append,
config_bag,
)
def read_write(
self,
config_bag,
):
2017-11-20 17:01:36 +01:00
"convenience method to freeze, hide and disable"
2024-10-31 08:53:58 +01:00
self._read(
self.rw_remove,
self.rw_append,
config_bag,
)