tiramisu/tiramisu/config.py

1886 lines
64 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2025-09-11 22:07:53 +02:00
# Copyright (C) 2012-2025 Team tiramisu (see AUTHORS for all contributors)
#
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.
#
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.
#
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-10-05 16:00:07 +02:00
# 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
# ____________________________________________________________
2023-05-11 15:44:48 +02:00
"""options handler global entry point
"""
import weakref
from copy import copy, deepcopy
2023-04-27 11:34:35 +02:00
from typing import Optional, List, Any, Union
2024-06-20 12:56:27 +02:00
from os.path import commonprefix
2015-04-18 22:53:45 +02:00
2025-10-15 09:43:56 +02:00
from .error import (
PropertiesOptionError,
ConfigError,
ConflictError,
LeadershipError,
AttributeOptionError,
)
2023-05-11 15:44:48 +02:00
from .option import DynOptionDescription, Leadership, Option
2024-04-24 15:39:17 +02:00
from .setting import ConfigBag, Settings, undefined, groups
2023-04-27 11:34:35 +02:00
from .value import Values, owners
from .i18n import _
from .cacheobj import Cache
from .autolib import Calculation
2024-04-24 15:39:17 +02:00
from . import autolib
2013-02-22 11:09:17 +01:00
2024-06-20 12:56:27 +02:00
def get_common_path(path1, path2):
common_path = commonprefix([path1, path2])
if common_path in [path1, path2]:
return common_path
2024-10-31 08:53:58 +01:00
if common_path.endswith("."):
2024-06-20 12:56:27 +02:00
return common_path[:-1]
elif "." in common_path:
2024-10-31 08:53:58 +01:00
return common_path.rsplit(".", 1)[0]
2024-06-20 12:56:27 +02:00
return None
2024-04-24 15:39:17 +02:00
class CCache:
__slots__ = tuple()
2024-10-31 08:53:58 +01:00
2024-04-24 15:39:17 +02:00
# =============================================================================
# CACHE
2024-10-31 08:53:58 +01:00
def reset_cache(
self,
subconfig,
resetted_opts=None,
):
"""reset all settings in cache"""
2024-04-24 15:39:17 +02:00
if resetted_opts is None:
resetted_opts = []
if subconfig is not None:
2024-10-31 08:53:58 +01:00
if "cache" not in subconfig.config_bag.properties:
2024-04-24 15:39:17 +02:00
return
2024-10-31 08:53:58 +01:00
subconfig.config_bag.properties = subconfig.config_bag.properties - {
"cache"
}
self.reset_one_option_cache(
subconfig,
resetted_opts,
False,
2024-10-31 08:53:58 +01:00
)
subconfig.config_bag.properties = subconfig.config_bag.properties | {
"cache"
}
2024-04-24 15:39:17 +02:00
else:
self._impl_values_cache.reset_all_cache() # pylint: disable=no-member
self.properties_cache.reset_all_cache() # pylint: disable=no-member
2024-10-31 08:53:58 +01:00
def reset_one_option_cache(
self,
subconfig,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
):
"""reset cache for one option"""
2024-04-24 15:39:17 +02:00
if subconfig.path in resetted_opts:
return
resetted_opts.append(subconfig.path)
config_bag = subconfig.config_bag
# if is_default and config_bag.context.get_owner(subconfig) != owners.default:
# return
for is_default, woption in subconfig.option.get_dependencies(subconfig.option):
2024-04-24 15:39:17 +02:00
option = woption()
if option.issubdyn():
# it's an option in dynoptiondescription, remove cache for all generated option
2024-10-31 08:53:58 +01:00
self.reset_cache_dyn_option(
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
option,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
2024-04-24 15:39:17 +02:00
elif option.impl_is_dynoptiondescription():
2025-09-11 22:07:53 +02:00
self.reset_cache_dyn_optiondescription(
2024-10-31 08:53:58 +01:00
option,
config_bag,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
2024-04-24 15:39:17 +02:00
else:
2024-10-31 08:53:58 +01:00
option_subconfig = self.get_sub_config(
config_bag,
option.impl_getpath(),
None,
properties=None,
validate_properties=False,
)
self.reset_one_option_cache(
option_subconfig,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
2024-04-24 15:39:17 +02:00
del option
2024-10-31 08:53:58 +01:00
subconfig.option.reset_cache(
subconfig.path,
config_bag,
resetted_opts,
)
2025-09-11 22:07:53 +02:00
def get_dynamic_from_dyn_optiondescription(self, config_bag, option):
2024-04-24 15:39:17 +02:00
path = option.impl_getpath()
2024-10-31 08:53:58 +01:00
if "." in path:
parent_path = path.rsplit(".", 1)[0]
parent_subconfig = self.get_sub_config(
config_bag,
parent_path,
None,
properties=None,
validate_properties=False,
)
2024-04-24 15:39:17 +02:00
else:
parent_subconfig = self.get_root(config_bag)
2025-09-11 22:07:53 +02:00
return parent_subconfig.dyn_to_subconfig(
2024-10-31 08:53:58 +01:00
option,
False,
2025-09-11 22:07:53 +02:00
)
def reset_cache_dyn_optiondescription(
self,
option,
config_bag,
resetted_opts,
is_default,
2025-09-11 22:07:53 +02:00
):
# reset cache for all chidren
for subconfig in self.get_dynamic_from_dyn_optiondescription(
config_bag,
option,
2024-10-31 08:53:58 +01:00
):
self.reset_one_option_cache(
subconfig,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
for walk_subconfig in self.walk(
subconfig,
no_value=True,
validate_properties=False,
):
self.reset_one_option_cache(
walk_subconfig,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
2025-09-11 22:07:53 +02:00
def get_dynamic_from_dyn_option(self, subconfig, option):
config_bag = subconfig.config_bag
2024-04-24 15:39:17 +02:00
sub_paths = option.impl_getpath()
current_paths = subconfig.path.split(".")
2025-09-11 22:07:53 +02:00
current_paths_max_index = len(current_paths) - 1
current_subconfigs = []
parent = subconfig
while True:
current_subconfigs.insert(0, parent)
parent = parent.parent
if parent.path is None:
break
currents = [self.get_root(config_bag)]
for idx, sub_path in enumerate(sub_paths.split(".")):
2024-04-24 15:39:17 +02:00
new_currents = []
for current in currents:
2024-10-31 08:53:58 +01:00
sub_option = current.option.get_child(
sub_path,
config_bag,
current,
allow_dynoption=True,
)
2024-04-24 15:39:17 +02:00
if sub_option.impl_is_dynoptiondescription():
if (
idx <= current_paths_max_index
and sub_option == current_subconfigs[idx].option
):
2025-09-11 22:07:53 +02:00
new_currents.append(current_subconfigs[idx])
else:
new_currents.extend(
list(
current.dyn_to_subconfig(
sub_option,
False,
)
2024-10-31 08:53:58 +01:00
)
)
2024-04-24 15:39:17 +02:00
else:
2024-10-31 08:53:58 +01:00
new_currents.append(
current.get_child(
sub_option,
None,
False,
properties=None,
),
)
2024-04-24 15:39:17 +02:00
currents = new_currents
2025-09-11 22:07:53 +02:00
return currents
def reset_cache_dyn_option(
self,
subconfig,
option,
resetted_opts,
is_default,
2025-09-11 22:07:53 +02:00
):
for dyn_option_subconfig in self.get_dynamic_from_dyn_option(subconfig, option):
2024-10-31 08:53:58 +01:00
self.reset_one_option_cache(
dyn_option_subconfig,
resetted_opts,
is_default,
2024-10-31 08:53:58 +01:00
)
2024-04-24 15:39:17 +02:00
class SubConfig:
2024-10-31 08:53:58 +01:00
__slots__ = (
"config_bag",
"option",
"parent",
"index",
"path",
"true_path",
"_properties",
"apply_requires",
"transitive_properties",
"is_dynamic",
"is_dynamic_without_identifiers",
2024-10-31 08:53:58 +01:00
"identifiers",
"_length",
)
def __init__(
self,
option: Option,
index: Optional[int],
path: str,
config_bag: ConfigBag,
parent: Optional["SubConfig"],
identifiers: Optional[list[str]],
*,
true_path: Optional[str] = None,
# for python 3.9 properties: Union[list[str], undefined] = undefined,
2025-05-12 08:53:39 +02:00
properties=undefined,
2024-10-31 08:53:58 +01:00
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
2024-10-31 08:53:58 +01:00
) -> None:
2024-04-24 15:39:17 +02:00
self.index = index
2024-10-22 11:04:57 +02:00
self.identifiers = identifiers
2024-04-24 15:39:17 +02:00
self.option = option
self.config_bag = config_bag
self.parent = parent
self._length = None
self.path = path
if true_path is None:
true_path = path
2024-10-31 08:53:58 +01:00
is_follower = (
not option.impl_is_optiondescription() and option.impl_is_follower()
)
2024-10-22 11:04:57 +02:00
self.apply_requires = not is_follower or index is not None
2024-04-24 15:39:17 +02:00
self.true_path = true_path
if self.option.impl_is_dynoptiondescription():
self.is_dynamic = True
2025-10-15 09:43:56 +02:00
self.is_dynamic_without_identifiers = identifiers is None or (
parent and identifiers == parent.identifiers
)
if (
check_dynamic_without_identifiers
and parent
and parent.is_dynamic
2025-10-18 11:50:49 +02:00
and parent.is_dynamic_without_identifiers
2025-10-15 09:43:56 +02:00
and self.is_dynamic_without_identifiers
!= parent.is_dynamic_without_identifiers
):
raise AttributeOptionError(true_path, "option-dynamic")
elif parent:
self.is_dynamic = parent.is_dynamic
self.is_dynamic_without_identifiers = parent.is_dynamic_without_identifiers
else:
self.is_dynamic = False
self.is_dynamic_without_identifiers = False
2024-10-22 11:04:57 +02:00
self._properties = properties
2024-04-24 15:39:17 +02:00
if validate_properties:
2024-10-22 11:04:57 +02:00
if self.path and self._properties is undefined:
settings = config_bag.context.get_settings()
2024-10-31 08:53:58 +01:00
self._properties = settings.getproperties(
self,
apply_requires=False,
)
2024-10-22 11:04:57 +02:00
self.config_bag.context.get_settings().validate_properties(self)
self._properties = undefined
2024-04-24 15:39:17 +02:00
self.config_bag.context.get_settings().validate_properties(self)
2024-10-22 11:04:57 +02:00
if self.apply_requires and self.option.impl_is_optiondescription():
if self.path and self.properties is not None:
2024-10-22 11:04:57 +02:00
settings = config_bag.context.get_settings()
2024-10-31 08:53:58 +01:00
self.transitive_properties = settings.calc_transitive_properties(
self,
self.properties,
)
2024-06-20 12:56:27 +02:00
else:
self.transitive_properties = frozenset()
2024-06-20 12:56:27 +02:00
2024-10-22 11:04:57 +02:00
@property
def properties(self):
if self._properties is undefined:
if self.path is None:
self._properties = frozenset()
else:
settings = self.config_bag.context.get_settings()
self._properties = frozenset()
2024-10-31 08:53:58 +01:00
self._properties = settings.getproperties(
self,
apply_requires=self.apply_requires,
)
2024-10-22 11:04:57 +02:00
return self._properties
@properties.setter
def properties(self, properties):
self._properties = properties
2024-04-24 15:39:17 +02:00
def __repr__(self):
2024-10-31 08:53:58 +01:00
return f"<SubConfig path={self.path}, index={self.index}>"
def dyn_to_subconfig(
self,
child: Option,
validate_properties: bool,
*,
true_path: Optional[str] = None,
) -> List["SubConfig"]:
2024-04-24 15:39:17 +02:00
config_bag = self.config_bag
2024-10-22 11:04:57 +02:00
for identifier in child.get_identifiers(self):
2024-04-24 15:39:17 +02:00
try:
2024-10-22 11:04:57 +02:00
name = child.impl_getname(identifier)
2024-04-24 15:39:17 +02:00
if not validate_properties:
properties = None
else:
properties = undefined
2024-10-31 08:53:58 +01:00
yield self.get_child(
child,
None,
validate_properties,
identifier=identifier,
name=name,
properties=properties,
true_path=true_path,
)
2024-04-24 15:39:17 +02:00
except PropertiesOptionError as err:
2024-10-31 08:53:58 +01:00
if err.proptype in (["mandatory"], ["empty"]):
2024-04-24 15:39:17 +02:00
raise err
2024-10-31 08:53:58 +01:00
def get_leadership_children(
self,
validate_properties,
):
2024-04-24 15:39:17 +02:00
# it's a leadership so walk to leader and follower
# followers has specific length
leader, *followers = self.option.get_children()
2024-10-31 08:53:58 +01:00
yield self.get_child(
leader,
None,
validate_properties,
)
2024-04-24 15:39:17 +02:00
for idx in range(self.get_length_leadership()):
for follower in followers:
try:
2024-10-31 08:53:58 +01:00
yield self.get_child(
follower,
idx,
validate_properties,
)
2024-04-24 15:39:17 +02:00
except PropertiesOptionError as err:
2024-10-31 08:53:58 +01:00
if err.proptype in (["mandatory"], ["empty"]):
2024-04-24 15:39:17 +02:00
raise err from err
2024-10-31 08:53:58 +01:00
def get_children(
self,
validate_properties,
*,
uncalculated: bool = False,
2025-09-11 22:07:53 +02:00
with_index: bool = True,
2025-10-18 06:31:40 +02:00
check_dynamic_without_identifiers: bool = True,
2024-10-31 08:53:58 +01:00
):
2025-09-11 22:07:53 +02:00
if self.option.impl_is_leadership() and not uncalculated and with_index:
2024-04-24 15:39:17 +02:00
yield from self.get_leadership_children(validate_properties)
else:
children_name = []
2024-04-24 15:39:17 +02:00
for child in self.option.get_children():
if child.impl_is_dynoptiondescription() and not uncalculated:
for dyn_child in self.dyn_to_subconfig(
2024-10-31 08:53:58 +01:00
child,
validate_properties,
):
yield dyn_child
if child.could_conflict:
name = dyn_child.path
if name in children_name:
raise ConflictError(
2025-10-15 09:43:56 +02:00
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
2024-04-24 15:39:17 +02:00
else:
try:
2024-10-31 08:53:58 +01:00
yield self.get_child(
child,
None,
validate_properties,
2025-10-18 06:31:40 +02:00
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
2024-10-31 08:53:58 +01:00
)
2024-04-24 15:39:17 +02:00
except PropertiesOptionError as err:
2024-10-31 08:53:58 +01:00
if err.proptype in (["mandatory"], ["empty"]):
2024-04-24 15:39:17 +02:00
raise err
if child.could_conflict:
name = child.impl_getpath()
if name in children_name:
raise ConflictError(
2025-10-15 09:43:56 +02:00
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
children_name.append(name)
2024-04-24 15:39:17 +02:00
2024-10-31 08:53:58 +01:00
def get_child(
self,
option: Option,
index: Optional[int],
validate_properties: bool,
*,
properties=undefined,
identifier: Optional[str] = None,
name: Optional[str] = None,
check_index: bool = True,
config_bag: ConfigBag = None,
true_path: Optional[str] = None,
check_dynamic_without_identifiers: bool = True,
2024-10-31 08:53:58 +01:00
) -> "SubConfig":
2024-04-24 15:39:17 +02:00
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments
if config_bag is None:
config_bag = self.config_bag
if not self.option.impl_is_optiondescription():
raise TypeError(f'"{self.path}" is not an optiondescription')
2024-10-31 08:53:58 +01:00
path = self.get_path(
name,
option,
)
2024-10-22 11:04:57 +02:00
if identifier is None:
identifiers = self.identifiers
2024-04-24 15:39:17 +02:00
else:
2024-10-22 11:04:57 +02:00
if self.identifiers:
identifiers = self.identifiers + [identifier]
2024-04-24 15:39:17 +02:00
else:
2024-10-22 11:04:57 +02:00
identifiers = [identifier]
2024-10-31 08:53:58 +01:00
subsubconfig = SubConfig(
option,
index,
path,
self.config_bag,
self,
identifiers,
properties=properties,
validate_properties=validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
2024-10-31 08:53:58 +01:00
)
2024-06-20 12:56:27 +02:00
if check_index and index is not None:
2024-10-31 08:53:58 +01:00
if option.impl_is_optiondescription() or not option.impl_is_follower():
raise ConfigError("index must be set only with a follower option")
2024-06-20 12:56:27 +02:00
length = self.get_length_leadership()
if index >= length:
2024-10-31 08:53:58 +01:00
raise LeadershipError(
2025-05-12 08:53:39 +02:00
subsubconfig, "leadership-greater", index=index, length=length
2024-10-31 08:53:58 +01:00
)
2024-06-20 12:56:27 +02:00
return subsubconfig
2024-04-24 15:39:17 +02:00
2024-10-31 08:53:58 +01:00
def get_path(
self,
name: str,
option: Option,
) -> str:
2024-04-24 15:39:17 +02:00
if name is None:
name = option.impl_getname()
if self.path is None:
path = name
else:
2024-10-31 08:53:58 +01:00
path = self.path + "." + name
2024-04-24 15:39:17 +02:00
return path
def get_length_leadership(self):
2024-10-31 08:53:58 +01:00
"""Get the length of leader option (useful to know follower's length)"""
2024-04-24 15:39:17 +02:00
if self._length is None:
cconfig_bag = self.config_bag.copy()
cconfig_bag.remove_validation()
leader = self.option.get_leader()
2024-10-31 08:53:58 +01:00
path = self.get_path(
None,
leader,
)
subconfig = SubConfig(
leader,
None,
path,
cconfig_bag,
self,
self.identifiers,
validate_properties=False,
)
2024-04-24 15:39:17 +02:00
self._length = len(cconfig_bag.context.get_value(subconfig))
return self._length
2024-06-20 12:56:27 +02:00
2024-10-31 08:53:58 +01:00
def get_common_child(
self,
search_option: "BaseOption",
true_path: Optional[str] = None,
validate_properties: bool = True,
check_dynamic_without_identifiers: bool = True,
2024-10-31 08:53:58 +01:00
):
2024-06-20 12:56:27 +02:00
current_option_path = self.option.impl_getpath()
search_option_path = search_option.impl_getpath()
common_path = get_common_path(current_option_path, search_option_path)
config_bag = self.config_bag
index = None
2024-10-31 08:53:58 +01:00
if (
not self.option.impl_is_optiondescription()
and self.option.impl_is_follower()
and search_option.impl_is_follower()
and self.parent.option == search_option.impl_get_leadership()
):
2024-06-20 12:56:27 +02:00
index = self.index
search_child_number = 0
parents = [self.parent]
else:
if common_path:
parent = self.parent
2024-10-31 08:53:58 +01:00
common_parent_number = common_path.count(".") + 1
for idx in range(current_option_path.count(".") - common_parent_number):
2024-06-20 12:56:27 +02:00
parent = parent.parent
parents = [parent]
else:
common_parent_number = 0
parents = [config_bag.context.get_root(config_bag)]
2024-10-31 08:53:58 +01:00
search_child_number = search_option_path.count(".") - common_parent_number
2024-06-20 12:56:27 +02:00
subconfigs_is_a_list = False
if search_child_number:
if common_parent_number:
2024-10-31 08:53:58 +01:00
parent_paths = search_option_path.rsplit(".", search_child_number + 1)[
1:-1
]
2024-06-20 12:56:27 +02:00
else:
2024-10-31 08:53:58 +01:00
parent_paths = search_option_path.split(".")[:-1]
2024-06-20 12:56:27 +02:00
for parent_path in parent_paths:
new_parents = []
for parent in parents:
2024-10-31 08:53:58 +01:00
sub_option = parent.option.get_child(
parent_path,
config_bag,
parent,
allow_dynoption=True,
)
2024-06-20 12:56:27 +02:00
if sub_option.impl_is_dynoptiondescription():
2024-10-31 08:53:58 +01:00
new_parents.extend(
parent.dyn_to_subconfig(
sub_option,
True,
true_path=true_path,
)
)
2024-06-20 12:56:27 +02:00
subconfigs_is_a_list = True
else:
2024-10-31 08:53:58 +01:00
new_parents.append(
parent.get_child(
sub_option,
None,
validate_properties,
true_path=true_path,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
2024-10-31 08:53:58 +01:00
)
)
2024-06-20 12:56:27 +02:00
parents = new_parents
subconfigs = []
for parent in parents:
2024-10-31 08:53:58 +01:00
subconfigs.append(
parent.get_child(
search_option,
index,
validate_properties,
check_dynamic_without_identifiers=check_dynamic_without_identifiers,
2024-10-31 08:53:58 +01:00
)
)
2024-06-20 12:56:27 +02:00
if subconfigs_is_a_list:
return subconfigs
return subconfigs[0]
2024-04-24 15:39:17 +02:00
2025-09-11 22:07:53 +02:00
def change_context(self, context) -> "SubConfig":
config_bag = self.config_bag.copy()
config_bag.context = context
return SubConfig(
self.option,
self.index,
self.path,
config_bag,
self.parent,
self.identifiers,
true_path=self.true_path,
validate_properties=False,
)
2025-09-11 22:07:53 +02:00
2024-04-24 15:39:17 +02:00
class _Config(CCache):
2013-09-23 22:55:54 +02:00
"""Sub configuration management entry.
Tree if OptionDescription's responsability. SubConfig are generated
on-demand. A Config is also a SubConfig.
Root Config is call context below
"""
2024-10-31 08:53:58 +01:00
__slots__ = (
"_impl_context",
"_impl_descr",
"_impl_path",
)
def __init__(
self,
descr,
context,
subpath=None,
):
"""Configuration option management class
2013-02-19 11:24:17 +01:00
2012-10-05 16:00:07 +02:00
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
2013-02-07 16:20:21 +01:00
:param context: the current root config
:type context: `Config`
:type subpath: `str` with the path name
2012-10-05 16:00:07 +02:00
"""
# main option description
self._impl_descr = descr
self._impl_context = context
self._impl_path = subpath
def get_description(self):
2024-10-31 08:53:58 +01:00
"""get root description"""
assert self._impl_descr is not None, _(
"there is no option description for this config" " (may be GroupConfig)"
)
return self._impl_descr
2017-11-13 22:45:53 +01:00
def get_settings(self):
2024-10-31 08:53:58 +01:00
"""get settings object"""
2023-05-11 15:44:48 +02:00
return self._impl_settings # pylint: disable=no-member
def get_values(self):
2024-10-31 08:53:58 +01:00
"""get values object"""
2023-05-11 15:44:48 +02:00
return self._impl_values # pylint: disable=no-member
2023-05-11 15:44:48 +02:00
def get_values_cache(self):
2024-10-31 08:53:58 +01:00
"""get cache for values"""
2023-05-11 15:44:48 +02:00
return self._impl_values_cache # pylint: disable=no-member
2013-05-02 11:34:57 +02:00
# # =============================================================================
# # WALK
2024-10-31 08:53:58 +01:00
def walk_valid_value(
self,
subconfig,
only_mandatory,
):
value = self.get_value(
subconfig,
need_help=False,
)
2024-04-24 15:39:17 +02:00
ori_config_bag = subconfig.config_bag
config_bag = ori_config_bag.copy()
if only_mandatory:
2024-10-31 08:53:58 +01:00
config_bag.properties |= {"mandatory", "empty"}
2024-04-24 15:39:17 +02:00
subconfig.config_bag = config_bag
2024-10-31 08:53:58 +01:00
self.get_settings().validate_mandatory(
subconfig,
value,
)
2024-04-24 15:39:17 +02:00
subconfig.config_bag = ori_config_bag
2023-04-27 11:34:35 +02:00
return value
2024-10-31 08:53:58 +01:00
def get_root(
self,
config_bag: ConfigBag,
) -> SubConfig:
return SubConfig(
config_bag.context.get_description(),
None,
None,
config_bag,
None,
None,
)
def get_sub_config(
self,
config_bag,
path,
index,
*,
validate_properties: bool = True,
properties=undefined,
true_path: Optional[str] = None,
allow_dynoption: bool = False,
valid_conflict: bool = True,
2024-10-31 08:53:58 +01:00
):
2024-04-24 15:39:17 +02:00
subconfig = self.get_root(config_bag)
if path is None:
paths = []
len_path = 0
else:
2024-10-31 08:53:58 +01:00
if "." in path:
paths = path.split(".")
2024-04-24 15:39:17 +02:00
else:
paths = [path]
len_path = len(paths) - 1
for idx, name in enumerate(paths):
if idx != len_path:
index_ = None
true_path_ = None
else:
index_ = index
true_path_ = true_path
if not subconfig.option.impl_is_optiondescription():
raise TypeError(f'"{subconfig.true_path}" is not an optiondescription')
2024-10-31 08:53:58 +01:00
option = subconfig.option.get_child(
name,
config_bag,
subconfig,
with_identifier=True,
allow_dynoption=allow_dynoption,
)
2024-04-24 15:39:17 +02:00
if isinstance(option, tuple):
2024-10-22 11:04:57 +02:00
identifier, option = option
2024-04-24 15:39:17 +02:00
else:
2024-10-22 11:04:57 +02:00
identifier = None
if valid_conflict and option.could_conflict:
for ref in option.could_conflict:
child = ref()
if child.impl_is_dynoptiondescription():
for dyn_child in subconfig.dyn_to_subconfig(
child,
validate_properties,
):
if path == dyn_child.path:
raise ConflictError(
2025-10-15 09:43:56 +02:00
_('option name "{0}" is not unique in {1}').format(
name,
option.impl_get_display_name(
subconfig, with_quote=True
),
)
)
elif child.impl_getname() == name:
raise ConflictError(
2025-10-15 09:43:56 +02:00
_('option name "{0}" is not unique in {1}').format(
name,
self.option.impl_get_display_name(
self, with_quote=True
),
)
)
2024-10-31 08:53:58 +01:00
subconfig = subconfig.get_child(
option,
index_,
validate_properties,
properties=properties,
name=name,
identifier=identifier,
true_path=true_path_,
)
2024-04-24 15:39:17 +02:00
return subconfig
2024-10-31 08:53:58 +01:00
def walk(
self,
root_subconfig: SubConfig,
*,
no_value: bool = False,
only_mandatory: bool = False,
validate_properties: bool = True,
):
2024-04-24 15:39:17 +02:00
if only_mandatory or no_value:
ret = []
2018-08-19 15:19:42 +02:00
else:
2024-04-24 15:39:17 +02:00
ret = {}
for subconfig in root_subconfig.get_children(validate_properties):
# pylint: disable=too-many-branches,too-many-locals,too-many-arguments,
if only_mandatory and subconfig.option.impl_is_symlinkoption():
continue
if subconfig.option.impl_is_optiondescription():
2024-10-31 08:53:58 +01:00
values = self.walk(
subconfig,
no_value=no_value,
only_mandatory=only_mandatory,
validate_properties=validate_properties,
)
2024-04-24 15:39:17 +02:00
if only_mandatory or no_value:
ret.extend(values)
else:
ret[subconfig] = values
2023-04-27 11:34:35 +02:00
else:
2024-04-24 15:39:17 +02:00
if no_value:
ret.append(subconfig)
else:
2024-10-31 08:53:58 +01:00
option = self.walk_option(
subconfig,
only_mandatory,
)
2024-04-24 15:39:17 +02:00
if only_mandatory:
if option:
ret.append(subconfig)
elif option[0]:
ret[subconfig] = option[1]
2023-04-27 11:34:35 +02:00
return ret
2024-10-31 08:53:58 +01:00
def walk_option(
self,
subconfig: SubConfig,
only_mandatory: bool,
):
2024-04-24 15:39:17 +02:00
try:
2024-10-31 08:53:58 +01:00
value = self.walk_valid_value(
subconfig,
only_mandatory,
)
2024-04-24 15:39:17 +02:00
except PropertiesOptionError as err:
2024-10-31 08:53:58 +01:00
if err.proptype in (["mandatory"], ["empty"]):
2024-04-24 15:39:17 +02:00
if only_mandatory:
return True
else:
raise err from err
else:
if not only_mandatory:
return True, value
if only_mandatory:
return False
return False, None
# =============================================================================
# Manage value
def set_value(
self,
subconfig,
value: Any,
) -> Any:
"""set value"""
2025-09-11 22:07:53 +02:00
self.get_settings().validate_properties(subconfig)
return self.get_values().set_value(subconfig, value)
2025-09-11 22:07:53 +02:00
2024-10-31 08:53:58 +01:00
def get_value(
self,
subconfig,
need_help=True,
):
"""
:return: option's value if name is an option name, OptionDescription
otherwise
"""
2024-08-07 08:55:43 +02:00
original_index = subconfig.index
2024-10-31 08:53:58 +01:00
subconfig = self._get(
subconfig,
need_help,
)
2024-04-24 15:39:17 +02:00
if isinstance(subconfig, list):
2023-04-27 11:34:35 +02:00
value = []
2024-04-24 15:39:17 +02:00
follower_subconfig = None
2024-06-20 12:56:27 +02:00
is_follower = not subconfig or subconfig[0].option.impl_is_follower()
2024-04-24 15:39:17 +02:00
for sconfig in subconfig:
2024-06-20 12:56:27 +02:00
if not is_follower or follower_subconfig is None:
2024-10-31 08:53:58 +01:00
follower_subconfig = self.get_sub_config(
sconfig.config_bag,
sconfig.path,
sconfig.index,
)
2024-04-24 15:39:17 +02:00
else:
2024-10-31 08:53:58 +01:00
follower_subconfig = follower_subconfig.parent.get_child(
sconfig.option,
sconfig.index,
False,
)
value.append(
self.get_value(
follower_subconfig,
need_help=need_help,
)
)
2023-04-27 11:34:35 +02:00
else:
2024-04-24 15:39:17 +02:00
value = self.get_values().get_cached_value(subconfig)
if subconfig.option.impl_is_follower():
length = subconfig.parent.get_length_leadership()
follower_len = self.get_values().get_max_length(subconfig.path)
2023-04-27 11:34:35 +02:00
if follower_len > length:
2024-10-31 08:53:58 +01:00
option_name = subconfig.option.impl_get_display_name(
subconfig, with_quote=True
)
raise LeadershipError(
2025-05-12 08:53:39 +02:00
subconfig,
"leadership-follower-greater",
index=follower_len,
length=length,
2024-10-31 08:53:58 +01:00
)
self.get_settings().validate_mandatory(
subconfig,
value,
)
2024-08-07 08:55:43 +02:00
if original_index != subconfig.index:
value = value[original_index]
2023-04-27 11:34:35 +02:00
return value
2024-10-31 08:53:58 +01:00
def _get(
self,
subconfig: "SubConfig",
need_help: bool,
validate_properties: bool = True,
) -> "OptionBag":
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-locals
2024-04-24 15:39:17 +02:00
option = subconfig.option
if not option.impl_is_symlinkoption():
return subconfig
suboption = option.impl_getopt()
if suboption.issubdyn():
dynopt = suboption.getsubdyn()
2024-10-31 08:53:58 +01:00
return subconfig.get_common_child(
suboption,
true_path=subconfig.path,
validate_properties=validate_properties,
)
2024-08-07 08:55:43 +02:00
if suboption.impl_is_follower() and subconfig.index is None:
2024-10-31 08:53:58 +01:00
subconfig = self.get_sub_config(
subconfig.config_bag, # pylint: disable=no-member
suboption.impl_getpath(),
None,
validate_properties=validate_properties,
true_path=subconfig.path,
)
2024-04-24 15:39:17 +02:00
leadership_length = subconfig.parent.get_length_leadership()
ret = []
follower = subconfig.option
parent = subconfig.parent
for idx in range(leadership_length):
2024-10-31 08:53:58 +01:00
ret.append(
parent.get_child(
follower,
idx,
True,
)
)
2024-04-24 15:39:17 +02:00
return ret
2024-08-07 08:55:43 +02:00
if suboption.impl_is_leader():
index = None
else:
index = subconfig.index
2024-10-31 08:53:58 +01:00
s_subconfig = self.get_sub_config(
subconfig.config_bag, # pylint: disable=no-member
suboption.impl_getpath(),
index,
validate_properties=validate_properties,
true_path=subconfig.path,
)
return self._get(
s_subconfig,
need_help,
)
def get_owner(
self,
subconfig: "SubConfig",
2025-09-11 22:07:53 +02:00
*,
validate_meta=True,
2024-10-31 08:53:58 +01:00
):
"""get owner"""
subconfigs = self._get(
subconfig,
need_help=True,
)
2024-04-24 15:39:17 +02:00
if isinstance(subconfigs, list):
for sc in subconfigs:
2024-10-31 08:53:58 +01:00
owner = self.get_owner(
sc,
2025-09-11 22:07:53 +02:00
validate_meta=validate_meta,
2024-10-31 08:53:58 +01:00
)
2023-05-11 15:44:48 +02:00
if owner != owners.default:
break
2023-04-27 11:34:35 +02:00
else:
2023-05-11 15:44:48 +02:00
owner = owners.default
else:
2025-09-11 22:07:53 +02:00
owner = self.get_values().getowner(subconfigs, validate_meta=validate_meta)
2023-04-27 11:34:35 +02:00
return owner
2013-04-03 12:20:26 +02:00
2024-04-24 15:39:17 +02:00
class _CommonConfig(_Config):
2018-08-14 22:15:40 +02:00
"abstract base class for the Config, KernelGroupConfig and the KernelMetaConfig"
2024-10-31 08:53:58 +01:00
__slots__ = (
"_impl_values",
"_impl_values_cache",
"_impl_settings",
"properties_cache",
"_impl_permissives_cache",
"parents",
"impl_type",
)
2013-04-03 12:20:26 +02:00
def _impl_build_all_caches(self, descr):
2015-05-03 09:56:03 +02:00
if not descr.impl_already_build_caches():
2023-05-11 15:44:48 +02:00
descr._group_type = groups.root # pylint: disable=protected-access
2024-10-31 08:53:58 +01:00
descr._build_cache(
self._display_name
) # pylint: disable=no-member,protected-access
if not hasattr(descr, "_cache_force_store_values"):
raise ConfigError(
_("option description seems to be part of an other " "config")
)
2019-08-05 22:31:56 +02:00
def get_parents(self):
2024-10-31 08:53:58 +01:00
"""get parents"""
2023-05-11 15:44:48 +02:00
for parent in self.parents: # pylint: disable=no-member
2019-08-05 22:31:56 +02:00
yield parent()
# information
2024-10-31 08:53:58 +01:00
def impl_set_information(
self,
config_bag,
key,
value,
):
"""updates the information's attribute
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
2024-10-31 08:53:58 +01:00
self._impl_values.set_information(
None, # pylint: disable=no-member
key,
value,
)
for option in self.get_description()._cache_dependencies_information.get(
key, []
): # pylint: disable=protected-access
# option_bag = OptionBag(option,
2024-04-24 15:39:17 +02:00
# None,
# config_bag,
# properties=None,
# )
option_bag = None
2023-05-11 15:44:48 +02:00
self.reset_cache(option_bag)
2024-10-31 08:53:58 +01:00
def impl_get_information(
self,
subconfig,
key,
default,
):
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
2024-10-31 08:53:58 +01:00
return self._impl_values.get_information(
None, # pylint: disable=no-member
key,
default,
)
def impl_del_information(
self,
key,
raises=True,
):
"""delete an information"""
self._impl_values.del_information(
key, # pylint: disable=no-member
raises,
)
def impl_list_information(self):
2024-10-31 08:53:58 +01:00
"""list information keys for context"""
2023-05-11 15:44:48 +02:00
return self._impl_values.list_information() # pylint: disable=no-member
2012-10-12 11:35:07 +02:00
2024-10-31 08:53:58 +01:00
def gen_fake_context(self) -> "KernelConfig":
"""generate a fake values to improve validation when assign a new value"""
2023-05-11 15:44:48 +02:00
export = deepcopy(self.get_values()._values) # pylint: disable=protected-access
2024-10-31 08:53:58 +01:00
fake_context = KernelConfig(
self._impl_descr,
force_values=export,
force_settings=self.get_settings(),
name=self._impl_name, # pylint: disable=no-member
)
2024-04-24 15:39:17 +02:00
fake_context.parents = self.parents # pylint: disable=no-member
return fake_context
2015-04-18 22:53:45 +02:00
2024-10-31 08:53:58 +01:00
def duplicate(
self,
force_values=None,
force_settings=None,
metaconfig_prefix=None,
child=None,
deep=None,
name=None,
):
"""duplication config"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-arguments
if name is None:
2023-05-11 15:44:48 +02:00
name = self._impl_name # pylint: disable=no-member
2018-09-05 20:22:16 +02:00
if isinstance(self, KernelConfig):
2024-10-31 08:53:58 +01:00
duplicated_config = KernelConfig(
self._impl_descr,
_duplicate=True,
force_values=force_values,
force_settings=force_settings,
name=name,
)
2018-10-31 08:00:19 +01:00
else:
2024-10-31 08:53:58 +01:00
duplicated_config = KernelMetaConfig(
[],
_duplicate=True,
optiondescription=self._impl_descr,
name=name,
)
duplicated_values = duplicated_config.get_values()
duplicated_settings = duplicated_config.get_settings()
2024-10-31 08:53:58 +01:00
duplicated_values._values = deepcopy(
self.get_values()._values
) # pylint: disable=protected-access
duplicated_values._informations = deepcopy(
self.get_values()._informations
) # pylint: disable=protected-access
duplicated_settings._properties = deepcopy(
self.get_settings()._properties
) # pylint: disable=protected-access
duplicated_settings._permissives = deepcopy(
self.get_settings()._permissives
) # pylint: disable=protected-access
duplicated_settings.ro_append = self.get_settings().ro_append
duplicated_settings.rw_append = self.get_settings().rw_append
duplicated_settings.ro_remove = self.get_settings().ro_remove
duplicated_settings.rw_remove = self.get_settings().rw_remove
2024-10-31 08:53:58 +01:00
# duplicated_settings.default_properties = self.get_settings().default_properties
duplicated_config.reset_cache(None, None)
2018-09-13 17:00:52 +02:00
if child is not None:
2024-10-31 08:53:58 +01:00
duplicated_config._impl_children.append(
child
) # pylint: disable=protected-access
2019-08-05 22:31:56 +02:00
child.parents.append(weakref.ref(duplicated_config))
2023-05-11 15:44:48 +02:00
if self.parents: # pylint: disable=no-member
if deep is not None:
2023-05-11 15:44:48 +02:00
for parent in self.parents: # pylint: disable=no-member
wparent = parent()
if wparent not in deep:
deep.append(wparent)
subname = wparent.impl_getname()
if metaconfig_prefix:
subname = metaconfig_prefix + subname
2024-10-31 08:53:58 +01:00
duplicated_config = wparent.duplicate(
deep=deep,
metaconfig_prefix=metaconfig_prefix,
child=duplicated_config,
name=subname,
)
2018-09-05 20:22:16 +02:00
else:
2023-05-11 15:44:48 +02:00
duplicated_config.parents = self.parents # pylint: disable=no-member
for parent in self.parents: # pylint: disable=no-member
2024-10-31 08:53:58 +01:00
parent()._impl_children.append(
duplicated_config
) # pylint: disable=protected-access
2018-09-13 17:00:52 +02:00
return duplicated_config
2015-07-24 17:54:10 +02:00
def get_config_path(self):
2024-10-31 08:53:58 +01:00
"""get config path"""
path = self.impl_getname()
2023-05-11 15:44:48 +02:00
for parent in self.parents: # pylint: disable=no-member
wparent = parent()
if wparent is None: # pragma: no cover
2024-10-31 08:53:58 +01:00
raise ConfigError(
_("parent of {0} not already exists").format(self._impl_name)
) # pylint: disable=no-member
path = parent().get_config_path() + "." + path
2019-08-05 22:31:56 +02:00
return path
def impl_getname(self):
2024-10-31 08:53:58 +01:00
"""get config name"""
2023-05-11 15:44:48 +02:00
return self._impl_name # pylint: disable=no-member
2013-09-30 16:22:08 +02:00
# ____________________________________________________________
2018-08-14 22:15:40 +02:00
class KernelConfig(_CommonConfig):
2024-10-31 08:53:58 +01:00
"""main configuration management entry"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-instance-attributes
2024-10-31 08:53:58 +01:00
__slots__ = (
"__weakref__",
"_impl_name",
"_display_name",
"_impl_symlink",
"_storage",
)
impl_type = "config"
def __init__(
self,
descr,
force_values=None,
force_settings=None,
name=None,
display_name=None,
_duplicate=False,
):
"""Configuration option management class
2013-09-30 16:22:08 +02:00
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
:param context: the current root config
:type context: `Config`
"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-arguments,too-many-arguments
self._display_name = display_name
2019-08-05 22:31:56 +02:00
self.parents = []
self._impl_symlink = []
self._impl_name = name
2019-02-23 19:06:23 +01:00
if isinstance(descr, Leadership):
2024-10-31 08:53:58 +01:00
raise ConfigError(
_("cannot set leadership object has root optiondescription")
)
2018-04-12 23:04:33 +02:00
if isinstance(descr, DynOptionDescription):
2024-10-31 08:53:58 +01:00
msg = _("cannot set dynoptiondescription object has root optiondescription")
raise ConfigError(msg)
if force_settings is not None and force_values is not None:
self._impl_settings = force_settings
self._impl_permissives_cache = Cache()
self.properties_cache = Cache()
self._impl_values = Values(force_values)
self._impl_values_cache = Cache()
else:
self._impl_settings = Settings()
self._impl_permissives_cache = Cache()
self.properties_cache = Cache()
self._impl_values = Values()
self._impl_values_cache = Cache()
2019-12-24 15:24:20 +01:00
self._impl_context = weakref.ref(self)
if None in [force_settings, force_values]:
self._impl_build_all_caches(descr)
2024-10-31 08:53:58 +01:00
super().__init__(
descr,
self._impl_context,
None,
)
2020-01-22 20:46:18 +01:00
2018-08-14 22:15:40 +02:00
class KernelGroupConfig(_CommonConfig):
2024-10-31 08:53:58 +01:00
"""Group a config with same optiondescription tree"""
__slots__ = (
"__weakref__",
"_impl_children",
"_impl_name",
"_display_name",
)
impl_type = "group"
def __init__(
self,
children,
display_name=None,
name=None,
_descr=None,
):
2023-05-11 15:44:48 +02:00
# pylint: disable=super-init-not-called
names = []
for child in children:
2025-09-11 22:07:53 +02:00
if not isinstance(child, (KernelConfig, KernelGroupConfig)):
raise TypeError(
_("child must be a Config, GroupConfig, MixConfig or MetaConfig")
)
name_ = child._impl_name
names.append(name_)
if len(names) != len(set(names)):
2023-05-11 15:44:48 +02:00
while range(1, len(names) + 1):
name = names.pop(0)
if name in names:
2024-10-31 08:53:58 +01:00
raise ConflictError(
_(
"config name must be uniq in " 'groupconfig for "{0}"'
).format(name)
)
2013-09-17 09:02:10 +02:00
self._impl_children = children
2019-08-05 22:31:56 +02:00
self.parents = []
self._display_name = display_name
if name:
self._impl_name = name
self._impl_context = weakref.ref(self)
self._impl_descr = _descr
self._impl_path = None
2013-09-17 09:02:10 +02:00
def get_children(self):
2024-10-31 08:53:58 +01:00
"""get all children"""
2013-09-17 09:02:10 +02:00
return self._impl_children
2024-10-31 08:53:58 +01:00
def reset_cache(
self,
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
resetted_opts=None,
):
2017-12-19 23:11:45 +01:00
if resetted_opts is None:
resetted_opts = []
2018-10-31 18:26:20 +01:00
if isinstance(self, KernelMixConfig):
2024-10-31 08:53:58 +01:00
super().reset_cache(
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
resetted_opts=copy(resetted_opts),
)
2013-09-17 09:02:10 +02:00
for child in self._impl_children:
2025-09-11 22:07:53 +02:00
if subconfig is not None:
parent_subconfig = subconfig.change_context(child)
else:
2025-09-11 22:07:53 +02:00
parent_subconfig = None
2024-10-31 08:53:58 +01:00
child.reset_cache(
2025-09-11 22:07:53 +02:00
parent_subconfig,
2024-10-31 08:53:58 +01:00
resetted_opts=copy(resetted_opts),
)
def set_value(
self,
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
value,
only_config=False,
):
"""Setattr not in current KernelGroupConfig, but in each children"""
ret = []
2013-09-17 09:02:10 +02:00
for child in self._impl_children:
2025-09-11 22:07:53 +02:00
cconfig_bag = subconfig.config_bag.copy()
2018-08-03 22:56:04 +02:00
cconfig_bag.context = child
2018-10-31 08:00:19 +01:00
if isinstance(child, KernelGroupConfig):
2024-10-31 08:53:58 +01:00
ret.extend(
child.set_value(
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
value,
only_config=only_config,
)
)
2018-10-31 08:00:19 +01:00
else:
settings = child.get_settings()
2025-09-11 22:07:53 +02:00
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
2019-12-24 15:24:20 +01:00
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
2018-10-31 08:00:19 +01:00
try:
2024-04-24 15:39:17 +02:00
# GROUP
2025-09-11 22:07:53 +02:00
coption_bag = child.get_sub_config(
2024-10-31 08:53:58 +01:00
cconfig_bag,
2025-09-11 22:07:53 +02:00
subconfig.path,
subconfig.index,
validate_properties=False,
2024-10-31 08:53:58 +01:00
)
child.set_value(
coption_bag,
value,
)
2018-10-31 08:00:19 +01:00
except PropertiesOptionError as err:
2023-05-11 15:44:48 +02:00
# pylint: disable=protected-access
2024-10-31 08:53:58 +01:00
ret.append(
PropertiesOptionError(
2025-09-11 22:07:53 +02:00
err._subconfig,
2024-10-31 08:53:58 +01:00
err.proptype,
err._settings,
err._opt_type,
err._name,
err._orig_opt,
)
)
2019-02-23 19:06:23 +01:00
except (ValueError, LeadershipError, AttributeError) as err:
2018-10-31 08:00:19 +01:00
ret.append(err)
return ret
2017-07-16 23:11:12 +02:00
2024-10-31 08:53:58 +01:00
def find_group(
self,
config_bag,
byname=None,
bypath=undefined,
byoption=undefined,
byvalue=undefined,
raise_if_not_found=True,
_sub=False,
):
"""Find first not in current KernelGroupConfig, but in each children"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-arguments
# if KernelMetaConfig, all children have same OptionDescription in
# context so search only one time the option for all children
2024-10-31 08:53:58 +01:00
if bypath is undefined and byname is not None and self.impl_type == "meta":
root_option_bag = OptionBag(
self.get_description(),
None,
config_bag,
)
next(
self.find(
root_option_bag,
bytype=None,
byname=byname,
byvalue=undefined,
raise_if_not_found=raise_if_not_found,
with_option=True,
)
)
byname = None
2018-04-10 12:33:51 +02:00
ret = []
2013-09-17 09:02:10 +02:00
for child in self._impl_children:
2018-08-14 22:15:40 +02:00
if isinstance(child, KernelGroupConfig):
2024-10-31 08:53:58 +01:00
ret.extend(
child.find_group(
byname=byname,
bypath=bypath,
byoption=byoption,
byvalue=byvalue,
config_bag=config_bag,
raise_if_not_found=False,
_sub=True,
)
)
2018-04-10 12:33:51 +02:00
else:
2019-12-24 15:24:20 +01:00
cconfig_bag = config_bag.copy()
cconfig_bag.context = child
2023-04-27 11:34:35 +02:00
if cconfig_bag.properties is None:
settings = child.get_settings()
2025-09-11 22:07:53 +02:00
properties = settings.get_context_properties()
2023-04-27 11:34:35 +02:00
permissives = settings.get_context_permissives()
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
2024-10-31 08:53:58 +01:00
root_option_bag = OptionBag(
child.get_description(),
None,
cconfig_bag,
)
2023-05-11 15:44:48 +02:00
try:
2024-10-31 08:53:58 +01:00
next(
child.find(
root_option_bag,
None,
byname,
byvalue,
raise_if_not_found=False,
only_path=bypath,
only_option=byoption,
)
)
2018-04-10 12:33:51 +02:00
ret.append(child)
2023-05-11 15:44:48 +02:00
except StopIteration:
pass
if not _sub:
2024-10-31 08:53:58 +01:00
self._find_return_results(
ret != [], # pylint: disable=use-implicit-booleaness-not-comparison
raise_if_not_found,
)
return ret
2013-05-02 11:34:57 +02:00
2024-10-31 08:53:58 +01:00
def reset(
self,
path: str,
2025-09-11 22:07:53 +02:00
only_children: bool,
2024-10-31 08:53:58 +01:00
config_bag: ConfigBag,
) -> None:
"""reset value for specified path"""
2018-09-29 18:52:13 +02:00
for child in self._impl_children:
2023-04-27 11:34:35 +02:00
settings = child.get_settings()
cconfig_bag = config_bag.copy()
cconfig_bag.context = child
settings = child.get_settings()
2025-09-11 22:07:53 +02:00
properties = settings.get_context_properties()
permissives = settings.get_context_permissives()
2023-04-27 11:34:35 +02:00
cconfig_bag.properties = properties
cconfig_bag.permissives = permissives
cconfig_bag.remove_validation()
2024-04-24 15:39:17 +02:00
# GROUP
2025-09-11 22:07:53 +02:00
subconfig = child.get_sub_config(
2024-10-31 08:53:58 +01:00
cconfig_bag,
path,
None,
2025-09-11 22:07:53 +02:00
validate_properties=False,
)
child.get_values().reset(subconfig)
2018-09-29 18:52:13 +02:00
2024-10-31 08:53:58 +01:00
def getconfig(
self,
name: str,
) -> KernelConfig:
"""get a child from a config name"""
for child in self._impl_children:
if name == child.impl_getname():
return child
raise ConfigError(_('unknown config "{}"').format(name))
2014-11-10 23:15:08 +01:00
2020-01-22 20:46:18 +01:00
2018-10-31 18:26:20 +01:00
class KernelMixConfig(KernelGroupConfig):
2024-10-31 08:53:58 +01:00
"""Kernel mixconfig: this config can have differents optiondescription tree"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-instance-attributes
2024-10-31 08:53:58 +01:00
__slots__ = (
"_impl_symlink",
"_storage",
)
impl_type = "mix"
def __init__(
self,
optiondescription,
children,
name=None,
display_name=None,
_duplicate=False,
):
self._impl_name = name
self._impl_symlink = []
2013-09-30 16:22:08 +02:00
for child in children:
2018-10-31 18:38:44 +01:00
if not isinstance(child, (KernelConfig, KernelMixConfig)):
raise TypeError(_("child must be a Config, MixConfig or MetaConfig"))
2019-08-05 22:31:56 +02:00
child.parents.append(weakref.ref(self))
self._impl_settings = Settings()
self._impl_settings._properties = deepcopy(self._impl_settings._properties)
self._impl_settings._permissives = deepcopy(self._impl_settings._permissives)
self._impl_permissives_cache = Cache()
self.properties_cache = Cache()
self._impl_values = Values()
self._impl_values._values = deepcopy(self._impl_values._values)
self._impl_values_cache = Cache()
self._display_name = display_name
self._impl_build_all_caches(optiondescription)
2024-10-31 08:53:58 +01:00
super().__init__(
children,
_descr=optiondescription,
display_name=display_name,
)
def set_value(
self,
2025-09-11 22:07:53 +02:00
subconfig,
2024-10-31 08:53:58 +01:00
value,
only_config=False,
force_default=False,
force_dont_change_value=False,
force_default_if_same=False,
):
"""only_config: could be set if you want modify value in all Config included in
2024-10-31 08:53:58 +01:00
this KernelMetaConfig
"""
2023-05-11 15:44:48 +02:00
# pylint: disable=too-many-branches,too-many-nested-blocks,too-many-locals,too-many-arguments
2023-04-27 11:34:35 +02:00
ret = []
if only_config:
if force_default or force_default_if_same or force_dont_change_value:
2024-10-31 08:53:58 +01:00
raise ValueError(
_(
"force_default, force_default_if_same or "
"force_dont_change_value cannot be set with"
" only_config"
)
)
2023-04-27 11:34:35 +02:00
else:
if force_default or force_default_if_same or force_dont_change_value:
if force_default and force_dont_change_value:
2024-10-31 08:53:58 +01:00
raise ValueError(
_(
"force_default and force_dont_change_value"
" cannot be set together"
)
)
2023-04-27 11:34:35 +02:00
for child in self._impl_children:
2025-09-11 22:07:53 +02:00
cconfig_bag = subconfig.config_bag.copy()
2023-04-27 11:34:35 +02:00
cconfig_bag.context = child
settings = child.get_settings()
2025-09-11 22:07:53 +02:00
properties = settings.get_context_properties()
2023-04-27 11:34:35 +02:00
cconfig_bag.properties = properties
cconfig_bag.permissives = settings.get_context_permissives()
try:
2024-10-31 08:53:58 +01:00
if self.impl_type == "meta":
2023-04-27 11:34:35 +02:00
obj = self
2018-10-31 08:00:19 +01:00
else:
2023-04-27 11:34:35 +02:00
obj = child
2024-10-31 08:53:58 +01:00
validate_properties = (
not force_default and not force_default_if_same
)
2024-04-24 15:39:17 +02:00
# MIX
2025-09-11 22:07:53 +02:00
moption_bag = obj.get_sub_config(
2024-10-31 08:53:58 +01:00
cconfig_bag,
2025-09-11 22:07:53 +02:00
subconfig.path,
subconfig.index,
validate_properties=validate_properties,
)
2023-04-27 11:34:35 +02:00
if force_default_if_same:
if not child.get_values().hasvalue(
subconfig.path, index=subconfig.index
):
2023-04-27 11:34:35 +02:00
child_value = undefined
else:
child_value = child.get_value(moption_bag)
2024-10-31 08:53:58 +01:00
if force_default or (
force_default_if_same and value == child_value
):
2023-04-27 11:34:35 +02:00
child.get_values().reset(moption_bag)
continue
if force_dont_change_value:
child_value = child.get_value(moption_bag)
if value != child_value:
2024-10-31 08:53:58 +01:00
child.set_value(
moption_bag,
child_value,
)
2023-04-27 11:34:35 +02:00
except PropertiesOptionError as err:
2023-05-11 15:44:48 +02:00
# pylint: disable=protected-access
2024-10-31 08:53:58 +01:00
ret.append(
PropertiesOptionError(
2025-09-11 22:07:53 +02:00
err._subconfig,
2024-10-31 08:53:58 +01:00
err.proptype,
err._settings,
err._opt_type,
err._name,
err._orig_opt,
)
)
2023-04-27 11:34:35 +02:00
except (ValueError, LeadershipError, AttributeError) as err:
ret.append(err)
2018-01-03 21:07:51 +01:00
try:
2024-04-24 15:39:17 +02:00
# MIX
2025-09-11 22:07:53 +02:00
moption_bag = self.get_sub_config(
subconfig.config_bag,
subconfig.path,
subconfig.index,
validate_properties=not only_config,
)
2023-04-27 11:34:35 +02:00
if only_config:
2024-10-31 08:53:58 +01:00
ret = super().set_value(
moption_bag,
value,
only_config=only_config,
)
2019-12-24 15:24:20 +01:00
else:
2024-10-31 08:53:58 +01:00
_CommonConfig.set_value(
self,
moption_bag,
value,
)
2019-02-23 19:06:23 +01:00
except (PropertiesOptionError, ValueError, LeadershipError) as err:
2018-01-03 21:07:51 +01:00
ret.append(err)
return ret
2017-07-08 15:59:56 +02:00
2024-10-31 08:53:58 +01:00
def reset(
self,
path: str,
only_children: bool,
config_bag: ConfigBag,
) -> None:
"""reset value for a specified path"""
2023-05-11 15:44:48 +02:00
# pylint: disable=arguments-differ
2018-08-02 22:35:40 +02:00
rconfig_bag = config_bag.copy()
2018-08-17 23:11:25 +02:00
rconfig_bag.remove_validation()
2024-10-31 08:53:58 +01:00
if self.impl_type == "meta":
2024-04-24 15:39:17 +02:00
# MIX
2025-09-11 22:07:53 +02:00
subconfig = self.get_sub_config(
2024-10-31 08:53:58 +01:00
config_bag,
path,
None,
2025-09-11 22:07:53 +02:00
validate_properties=True,
)
2018-10-31 16:08:22 +01:00
elif not only_children:
try:
2024-04-24 15:39:17 +02:00
# MIX
2025-09-11 22:07:53 +02:00
subconfig = self.get_sub_config(
2024-10-31 08:53:58 +01:00
rconfig_bag,
path,
None,
2025-09-11 22:07:53 +02:00
validate_properties=True,
)
2018-10-31 16:08:22 +01:00
except AttributeError:
only_children = True
for child in self._impl_children:
2018-10-31 16:08:22 +01:00
rconfig_bag.context = child
try:
2024-10-31 08:53:58 +01:00
if self.impl_type == "meta":
2025-09-11 22:07:53 +02:00
moption_bag = subconfig
2018-10-31 16:08:22 +01:00
moption_bag.config_bag = rconfig_bag
else:
2024-04-24 15:39:17 +02:00
# MIX
2025-09-11 22:07:53 +02:00
moption_bag = child.get_sub_config(
2024-10-31 08:53:58 +01:00
rconfig_bag,
path,
None,
2025-09-11 22:07:53 +02:00
validate_properties=True,
)
child.get_values().reset(moption_bag)
2018-10-31 16:08:22 +01:00
except AttributeError:
pass
2018-10-31 18:26:20 +01:00
if isinstance(child, KernelMixConfig):
2024-10-31 08:53:58 +01:00
child.reset(
path,
False,
rconfig_bag,
)
2018-10-31 16:08:22 +01:00
if not only_children:
2025-09-11 22:07:53 +02:00
subconfig.config_bag = config_bag
self.get_values().reset(subconfig)
2024-10-31 08:53:58 +01:00
def new_config(
self,
name=None,
type_="config",
):
"""Create a new config/metaconfig/mixconfig and add it to this MixConfig"""
if name:
for child in self._impl_children:
if child.impl_getname() == name:
2024-10-31 08:53:58 +01:00
raise ConflictError(
_("config name must be uniq in " "groupconfig for {0}").format(
child
)
)
assert type_ in ("config", "metaconfig", "mixconfig"), _(
"unknown type {}"
).format(type_)
if type_ == "config":
config = KernelConfig(self._impl_descr, name=name)
elif type_ == "metaconfig":
config = KernelMetaConfig(
[],
optiondescription=self._impl_descr,
name=name,
)
elif type_ == "mixconfig":
config = KernelMixConfig(
children=[],
optiondescription=self._impl_descr,
name=name,
)
2020-03-04 15:39:47 +01:00
# Copy context properties/permissives
settings = config.get_settings()
2024-06-20 12:56:27 +02:00
properties = settings.get_context_properties()
2024-10-31 08:53:58 +01:00
settings.set_context_properties(
properties,
config,
)
settings.set_context_permissives(settings.get_context_permissives())
settings.ro_append = settings.ro_append
settings.rw_append = settings.rw_append
settings.ro_remove = settings.ro_remove
settings.rw_remove = settings.rw_remove
2024-10-31 08:53:58 +01:00
# settings.default_properties = settings.default_properties
2020-03-04 15:39:47 +01:00
config.parents.append(weakref.ref(self))
self._impl_children.append(config)
return config
2024-10-31 08:53:58 +01:00
def add_config(
self,
config,
):
"""Add a child config to a mix config"""
if not config.impl_getname():
2024-10-31 08:53:58 +01:00
raise ConfigError(_("config added has no name, the name is mandatory"))
if config.impl_getname() in [
child.impl_getname() for child in self._impl_children
]:
raise ConflictError(
_('config name "{0}" is not uniq in ' 'groupconfig "{1}"').format(
config.impl_getname(), self.impl_getname()
),
)
2019-08-05 22:31:56 +02:00
config.parents.append(weakref.ref(self))
self._impl_children.append(config)
config.reset_cache(None, None)
2024-10-31 08:53:58 +01:00
def remove_config(
self,
name,
):
"""Remove a child config to a mix config by it's name"""
for current_index, child in enumerate(self._impl_children):
if name == child.impl_getname():
child.reset_cache(None, None)
2019-08-05 22:31:56 +02:00
break
else:
2024-10-31 08:53:58 +01:00
raise ConfigError(_("cannot find the config {0}").format(name))
for child_index, parent in enumerate(child.parents):
if parent() == self:
break
else: # pragma: no cover
2024-10-31 08:53:58 +01:00
raise ConfigError(
_("cannot find the config {0}").format(self.impl_getname())
)
self._impl_children.pop(current_index)
child.parents.pop(child_index)
2019-08-05 22:31:56 +02:00
return child
2018-10-31 08:00:19 +01:00
2018-10-31 18:26:20 +01:00
class KernelMetaConfig(KernelMixConfig):
2024-10-31 08:53:58 +01:00
"""Meta config"""
2018-10-31 08:00:19 +01:00
__slots__ = tuple()
2024-10-31 08:53:58 +01:00
impl_type = "meta"
def __init__(
self,
children,
optiondescription=None,
name=None,
display_name=None,
_duplicate=False,
):
2018-10-31 08:00:19 +01:00
descr = None
if optiondescription is not None:
if not _duplicate:
new_children = []
for child_name in children:
2024-10-31 08:53:58 +01:00
assert isinstance(child_name, str), _(
"MetaConfig with optiondescription"
" must have string has child, "
"not {}"
).format(child_name)
new_children.append(
KernelConfig(optiondescription, name=child_name)
)
2018-10-31 08:00:19 +01:00
children = new_children
descr = optiondescription
for child in children:
2024-10-31 08:53:58 +01:00
if __debug__ and not isinstance(child, (KernelConfig, KernelMetaConfig)):
2018-10-31 18:38:44 +01:00
raise TypeError(_("child must be a Config or MetaConfig"))
2018-10-31 08:00:19 +01:00
if descr is None:
descr = child.get_description()
elif descr is not child.get_description():
2024-10-31 08:53:58 +01:00
raise ValueError(
_(
"all config in metaconfig must "
"have the same optiondescription"
)
)
super().__init__(
descr,
children,
name=name,
display_name=display_name,
)
def add_config(
self,
config,
):
if self._impl_descr is not config.get_description():
raise ValueError(_("metaconfig must have the same optiondescription"))
super().add_config(config)