tiramisu/tiramisu/option/baseoption.py

325 lines
12 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2023-04-12 12:33:44 +02:00
# Copyright (C) 2014-2023 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
"""base option
"""
from typing import FrozenSet, Set, Any, List
2017-11-13 22:45:53 +01:00
import weakref
from itertools import chain
2017-07-09 09:49:03 +02:00
2017-07-22 16:26:06 +02:00
from ..i18n import _
2023-05-11 15:44:48 +02:00
from ..setting import undefined
from ..autolib import Calculation, ParamOption
2017-07-22 16:26:06 +02:00
2017-11-20 17:01:36 +01:00
STATIC_TUPLE = frozenset()
2014-07-06 15:31:57 +02:00
submulti = 2
2013-02-25 15:52:10 +01:00
2013-04-03 12:20:26 +02:00
2013-02-25 15:52:10 +01:00
def valid_name(name):
2023-05-11 15:44:48 +02:00
"""valid option name
"""
if not isinstance(name, str):
2013-04-14 12:01:32 +02:00
return False
2021-05-18 18:52:42 +02:00
# if '.' in name:
# return False
2018-12-07 23:33:11 +01:00
return True
2017-07-23 18:14:34 +02:00
#____________________________________________________________
#
2018-11-15 16:17:39 +01:00
class Base:
2017-07-24 18:27:24 +02:00
"""Base use by all *Option* classes (Option, OptionDescription, SymLinkOption, ...)
"""
2017-07-22 16:26:06 +02:00
__slots__ = ('_name',
2018-09-04 08:36:02 +02:00
'_path',
2017-07-22 16:26:06 +02:00
'_informations',
'_subdyns',
2017-07-22 16:26:06 +02:00
'_properties',
'_has_dependency',
'_dependencies',
'_suffixes_dependencies',
2017-07-22 16:26:06 +02:00
'__weakref__'
)
2017-12-07 22:20:19 +01:00
def __init__(self,
2018-11-15 16:17:39 +01:00
name: str,
doc: str,
2017-12-07 22:20:19 +01:00
properties=None,
2018-11-15 16:17:39 +01:00
is_multi: bool=False) -> None:
if not valid_name(name):
2018-11-15 16:17:39 +01:00
raise ValueError(_('"{0}" is an invalid name for an option').format(name))
if properties is None:
2017-12-29 11:38:41 +01:00
properties = frozenset()
2019-09-01 09:41:53 +02:00
elif isinstance(properties, tuple):
2017-12-29 11:38:41 +01:00
properties = frozenset(properties)
2019-11-19 18:52:20 +01:00
if is_multi:
2023-05-11 15:44:48 +02:00
# if option is a multi, it cannot be 'empty' (None not allowed in the list)
# and cannot have multiple time the same value
2019-11-19 18:52:20 +01:00
# 'empty' and 'unique' are removed for follower's option
if 'notunique' not in properties:
2019-11-19 18:52:20 +01:00
properties = properties | {'unique'}
if 'notempty' not in properties:
2019-11-19 18:52:20 +01:00
properties = properties | {'empty'}
2019-09-01 09:41:53 +02:00
assert isinstance(properties, frozenset), _('invalid properties type {0} for {1},'
' must be a frozenset').format(type(properties),
name)
for prop in properties:
if not isinstance(prop, str):
if not isinstance(prop, Calculation):
2023-05-11 15:44:48 +02:00
raise ValueError(_('invalid property type {0} for {1}, must be a string or a '
'Calculation').format(type(prop), name))
2019-10-27 11:09:15 +01:00
for param in chain(prop.params.args, prop.params.kwargs.values()):
2019-09-01 09:41:53 +02:00
if isinstance(param, ParamOption):
param.option._add_dependency(self)
2019-10-27 11:09:15 +01:00
_setattr = object.__setattr__
_setattr(self, '_name', name)
_setattr(self, '_informations', {'doc': doc})
if properties:
_setattr(self, '_properties', properties)
2017-12-07 22:20:19 +01:00
def impl_has_dependency(self,
2023-05-11 15:44:48 +02:00
self_is_dep: bool=True,
) -> bool:
"""this has dependency
"""
2017-09-17 15:55:32 +02:00
if self_is_dep is True:
return getattr(self, '_has_dependency', False)
2018-11-15 16:17:39 +01:00
return hasattr(self, '_dependencies')
2017-09-17 15:55:32 +02:00
def get_dependencies(self,
context_od,
) -> Set[str]:
2017-11-13 22:45:53 +01:00
ret = set(getattr(self, '_dependencies', STATIC_TUPLE))
2018-11-15 16:17:39 +01:00
if context_od and hasattr(context_od, '_dependencies'):
2020-08-04 16:49:21 +02:00
# add options that have context is set in calculation
2023-05-11 15:44:48 +02:00
return set(context_od._dependencies) | ret # pylint: disable=protected-access
2018-11-15 16:17:39 +01:00
return ret
2016-10-16 21:37:55 +02:00
def _get_suffixes_dependencies(self) -> Set[str]:
return getattr(self, '_suffixes_dependencies', STATIC_TUPLE)
2017-12-07 22:20:19 +01:00
def _add_dependency(self,
option,
is_suffix: bool=False,
) -> None:
woption = weakref.ref(option)
options = self.get_dependencies(None)
2023-05-11 15:44:48 +02:00
options.add(woption)
self._dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init
if is_suffix:
options = list(self._get_suffixes_dependencies())
2023-05-11 15:44:48 +02:00
options.append(woption)
self._suffixes_dependencies = tuple(options) # pylint: disable=attribute-defined-outside-init
2016-10-16 21:37:55 +02:00
2018-11-15 16:17:39 +01:00
def impl_is_optiondescription(self) -> bool:
2023-05-11 15:44:48 +02:00
"""option is an option description
"""
return False
2014-06-19 23:22:39 +02:00
2018-11-15 16:17:39 +01:00
def impl_is_dynoptiondescription(self) -> bool:
2023-05-11 15:44:48 +02:00
"""option is not a dyn option description
"""
return False
2014-06-19 23:22:39 +02:00
def impl_is_sub_dyn_optiondescription(self):
return False
2018-11-15 16:17:39 +01:00
def impl_getname(self) -> str:
2023-05-11 15:44:48 +02:00
"""get name
"""
return self._name # pylint: disable=no-member
2017-07-22 16:26:06 +02:00
2018-11-15 16:17:39 +01:00
def _set_readonly(self) -> None:
2023-05-11 15:44:48 +02:00
if isinstance(self._informations, dict): # pylint: disable=no-member
2017-07-22 16:26:06 +02:00
_setattr = object.__setattr__
2023-05-11 15:44:48 +02:00
dico = self._informations # pylint: disable=no-member
2017-07-22 16:26:06 +02:00
keys = tuple(dico.keys())
if len(keys) == 1:
dico = dico['doc']
else:
dico = tuple([keys, tuple(dico.values())])
_setattr(self, '_informations', dico)
2017-09-17 15:55:32 +02:00
extra = getattr(self, '_extra', None)
if extra is not None:
_setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())]))
2017-07-22 16:26:06 +02:00
2018-11-15 16:17:39 +01:00
def impl_is_readonly(self) -> str:
2023-05-11 15:44:48 +02:00
"""the option is readonly
"""
2019-02-11 20:28:03 +01:00
# _path is None when initialise SymLinkOption
2023-05-11 15:44:48 +02:00
return hasattr(self, '_path') and self._path is not None # pylint: disable=no-member
2018-11-15 16:17:39 +01:00
def impl_getproperties(self) -> FrozenSet[str]:
2023-05-11 15:44:48 +02:00
"""get properties
"""
2018-11-15 16:17:39 +01:00
return getattr(self, '_properties', frozenset())
2018-04-09 21:37:49 +02:00
def _setsubdyn(self,
subdyn,
) -> None:
2023-05-11 15:44:48 +02:00
# pylint: disable=attribute-defined-outside-init
if getattr(self, '_subdyns', None) is None:
self._subdyns = []
self._subdyns.append(subdyn)
2017-07-22 16:26:06 +02:00
2018-11-15 16:17:39 +01:00
def issubdyn(self) -> bool:
2023-05-11 15:44:48 +02:00
"""is sub dynoption
"""
return getattr(self, '_subdyns', None) is not None
2018-04-09 21:37:49 +02:00
def getsubdyn(self):
2023-05-11 15:44:48 +02:00
"""get sub dynoption
"""
return self._subdyns[0]()
def get_sub_dyns(self):
return self._subdyns
2018-04-09 21:37:49 +02:00
2017-07-22 16:26:06 +02:00
# ____________________________________________________________
# information
2017-12-07 22:20:19 +01:00
def impl_get_information(self,
2018-11-15 16:17:39 +01:00
key: str,
default: Any=undefined,
) -> Any:
2017-07-22 16:26:06 +02:00
"""retrieves one information's item
:param key: the item string (ex: "help")
"""
2023-05-11 15:44:48 +02:00
dico = self._informations # pylint: disable=no-member
2017-07-22 16:26:06 +02:00
if isinstance(dico, tuple):
if key in dico[0]:
return dico[1][dico[0].index(key)]
2018-11-15 16:17:39 +01:00
elif isinstance(dico, str):
2017-07-22 16:26:06 +02:00
if key == 'doc':
return dico
elif isinstance(dico, dict):
if key in dico:
return dico[key]
if default is not undefined:
return default
2023-05-11 15:44:48 +02:00
# pylint: disable=no-member
raise ValueError(_(f'information\'s item for "{self.impl_get_display_name()}" '
f'not found: "{key}"'))
2017-07-22 16:26:06 +02:00
2017-12-07 22:20:19 +01:00
def impl_set_information(self,
2018-11-15 16:17:39 +01:00
key: str,
value: Any,
) -> None:
2017-07-22 16:26:06 +02:00
"""updates the information's attribute
(which is a dictionary)
:param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string")
"""
if self.impl_is_readonly():
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
2017-12-07 22:20:19 +01:00
" read-only").format(self.__class__.__name__,
self,
key))
2023-05-11 15:44:48 +02:00
self._informations[key] = value # pylint: disable=no-member
2017-07-22 16:26:06 +02:00
def impl_list_information(self) -> Any:
2023-05-11 15:44:48 +02:00
"""get the list of information keys
"""
dico = self._informations # pylint: disable=no-member
if isinstance(dico, tuple):
return list(dico[0])
2023-05-11 15:44:48 +02:00
if isinstance(dico, str):
return ['doc']
# it's a dict
return list(dico.keys())
2013-09-02 15:06:55 +02:00
class BaseOption(Base):
"""This abstract base class stands for attribute access
in options that have to be set only once, it is of course done in the
__setattr__ method
"""
__slots__ = ('_display_name_function',)
2017-12-07 22:20:19 +01:00
def __setattr__(self,
2018-11-15 16:17:39 +01:00
name: str,
value: Any,
) -> Any:
"""set once and only once some attributes in the option,
2018-11-15 16:17:39 +01:00
like `_name`. `_name` cannot be changed once the option is
pushed in the :class:`tiramisu.option.OptionDescription`.
if the attribute `_readonly` is set to `True`, the option is
2018-11-15 16:17:39 +01:00
"frozen" (which has nothing to do with the high level "freeze"
propertie or "read_only" property)
"""
2018-11-15 16:17:39 +01:00
# never change _name in an option or attribute when object is readonly
if self.impl_is_readonly():
raise AttributeError(_('"{}" ({}) object attribute "{}" is'
' read-only').format(self.__class__.__name__,
self.impl_get_display_name(),
name))
2023-05-11 15:44:48 +02:00
super().__setattr__(name, value)
2018-11-15 16:17:39 +01:00
def impl_getpath(self) -> str:
2023-05-11 15:44:48 +02:00
"""get the path of the option
"""
try:
return self._path
2023-05-11 15:44:48 +02:00
except AttributeError as err:
raise AttributeError(_(f'"{self.impl_get_display_name()}" not part of any Config')) \
from err
2014-07-06 15:31:57 +02:00
2023-12-11 19:38:35 +01:00
def impl_get_display_name(self,
dynopt=None,
) -> str:
"""get display name
"""
if dynopt is None:
dynopt = self
if hasattr(self, '_display_name_function'):
return self._display_name_function(dynopt)
name = self.impl_get_information('doc', None)
2016-10-23 23:18:06 +02:00
if name is None or name == '':
2023-12-11 19:38:35 +01:00
name = dynopt.impl_getname()
2016-10-23 23:18:06 +02:00
return name
2017-11-20 17:01:36 +01:00
def reset_cache(self,
2018-11-15 16:17:39 +01:00
path: str,
config_bag: 'OptionBag',
2023-05-11 15:44:48 +02:00
resetted_opts: List[Base], # pylint: disable=unused-argument
) -> None:
"""reset cache
"""
context = config_bag.context
context.properties_cache.delcache(path)
2023-05-11 15:44:48 +02:00
context._impl_permissives_cache.delcache(path) # pylint: disable=protected-access
2018-04-09 21:37:49 +02:00
if not self.impl_is_optiondescription():
2023-05-11 15:44:48 +02:00
context.get_values_cache().delcache(path) # pylint: disable=protected-access
2017-07-08 15:59:56 +02:00
2018-11-15 16:17:39 +01:00
def impl_is_symlinkoption(self) -> bool:
2023-05-11 15:44:48 +02:00
"""the option is not a symlinkoption
"""
return False
2023-01-24 07:46:44 +01:00
def get_dependencies_information(self) -> List[str]:
2023-05-11 15:44:48 +02:00
"""get dependencies information
"""
return getattr(self, '_dependencies_information', {})