Add ParamInformation and callback can be a coroutine

This commit is contained in:
Emmanuel Garette 2020-10-03 14:29:15 +02:00
parent afeedc1db6
commit 6910371779
8 changed files with 154 additions and 42 deletions

View File

@ -10,7 +10,7 @@ from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \ StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \
undefined, Calculation, Params, ParamOption, ParamValue, ParamIndex, calc_value, \ undefined, Calculation, Params, ParamOption, ParamValue, ParamIndex, calc_value, \
valid_ip_netmask, ParamSelfOption valid_ip_netmask, ParamSelfOption, ParamInformation
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.storage import list_sessions from tiramisu.storage import list_sessions
@ -43,6 +43,10 @@ def return_value(value=None):
return value return value
async def return_async_value(value=None):
return value
def return_value2(*args, **kwargs): def return_value2(*args, **kwargs):
value = list(args) value = list(args)
value.extend(kwargs.values()) value.extend(kwargs.values())
@ -333,6 +337,50 @@ async def test_callback_value(config_type):
assert not await list_sessions() assert not await list_sessions()
@pytest.mark.asyncio
async def test_callback_async_value(config_type):
val1 = StrOption('val1', "", 'val')
val2 = StrOption('val2', "", Calculation(return_async_value, Params(ParamOption(val1))))
val3 = StrOption('val3', "", Calculation(return_async_value, Params(ParamValue('yes'))))
val4 = StrOption('val4', "", Calculation(return_async_value, Params(kwargs={'value': ParamOption(val1)})))
val5 = StrOption('val5', "", Calculation(return_async_value, Params(ParamValue('yes'))))
maconfig = OptionDescription('rootconfig', '', [val1, val2, val3, val4, val5])
async with await Config(maconfig) as cfg:
await cfg.property.read_write()
cfg = await get_config(cfg, config_type)
assert await cfg.option('val1').value.get() == 'val'
assert await cfg.option('val2').value.get() == 'val'
assert await cfg.option('val4').value.get() == 'val'
await cfg.option('val1').value.set('new-val')
assert await cfg.option('val1').value.get() == 'new-val'
assert await cfg.option('val2').value.get() == 'new-val'
assert await cfg.option('val4').value.get() == 'new-val'
await cfg.option('val1').value.reset()
assert await cfg.option('val1').value.get() == 'val'
assert await cfg.option('val2').value.get() == 'val'
assert await cfg.option('val3').value.get() == 'yes'
assert await cfg.option('val4').value.get() == 'val'
assert await cfg.option('val5').value.get() == 'yes'
assert not await list_sessions()
@pytest.mark.asyncio
async def test_callback_information(config_type):
val1 = StrOption('val1', "", Calculation(return_value, Params(ParamInformation('information', 'no_value'))))
val2 = StrOption('val2', "", Calculation(return_value, Params(ParamInformation('information'))))
maconfig = OptionDescription('rootconfig', '', [val1, val2])
async with await Config(maconfig) as cfg:
await cfg.property.read_write()
cfg = await get_config(cfg, config_type)
assert await cfg.option('val1').value.get() == 'no_value'
with pytest.raises(ConfigError):
await cfg.option('val2').value.get()
await cfg.information.set('information', 'new_value')
assert await cfg.option('val1').value.get() == 'new_value'
assert await cfg.option('val2').value.get() == 'new_value'
assert not await list_sessions()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_callback_value_tuple(config_type): async def test_callback_value_tuple(config_type):
val1 = StrOption('val1', "", 'val1') val1 = StrOption('val1', "", 'val1')

View File

@ -18,7 +18,7 @@ from .function import calc_value, calc_value_property_help, valid_ip_netmask, \
valid_network_netmask, valid_in_network, valid_broadcast, \ valid_network_netmask, valid_in_network, valid_broadcast, \
valid_not_equal valid_not_equal
from .autolib import Calculation, Params, ParamOption, ParamDynOption, ParamSelfOption, \ from .autolib import Calculation, Params, ParamOption, ParamDynOption, ParamSelfOption, \
ParamValue, ParamIndex, ParamSuffix ParamValue, ParamIndex, ParamSuffix, ParamInformation
from .option import * from .option import *
from .error import APIError from .error import APIError
from .api import Config, MetaConfig, GroupConfig, MixConfig from .api import Config, MetaConfig, GroupConfig, MixConfig
@ -36,6 +36,7 @@ allfuncs = ['Calculation',
'ParamValue', 'ParamValue',
'ParamIndex', 'ParamIndex',
'ParamSuffix', 'ParamSuffix',
'ParamInformation',
'MetaConfig', 'MetaConfig',
'MixConfig', 'MixConfig',
'GroupConfig', 'GroupConfig',

View File

@ -885,24 +885,35 @@ def connection(func):
class TiramisuContextInformation(TiramisuConfig): class TiramisuContextInformation(TiramisuConfig):
"""Manage config informations""" """Manage config informations"""
@connection @connection
async def get(self, name, default=undefined): async def get(self,
name,
default=undefined,
):
"""Get an information""" """Get an information"""
return await self._config_bag.context.impl_get_information(self._config_bag.connection, return await self._config_bag.context.impl_get_information(self._config_bag.connection,
name, name,
default) default,
)
@connection @connection
async def set(self, name, value): async def set(self,
"""Set an information"""
await self._config_bag.context.impl_set_information(self._config_bag.connection,
name, name,
value) value,
):
"""Set an information"""
await self._config_bag.context.impl_set_information(self._config_bag,
name,
value,
)
@connection @connection
async def reset(self, name): async def reset(self,
name,
):
"""Remove an information""" """Remove an information"""
await self._config_bag.context.impl_del_information(self._config_bag.connection, await self._config_bag.context.impl_del_information(self._config_bag.connection,
name) name,
)
@connection @connection
async def list(self): async def list(self):

View File

@ -19,6 +19,7 @@
# ____________________________________________________________ # ____________________________________________________________
"enables us to carry out a calculation and return an option's value" "enables us to carry out a calculation and return an option's value"
from typing import Any, Optional, Union, Callable, Dict, List from typing import Any, Optional, Union, Callable, Dict, List
from types import CoroutineType
from itertools import chain from itertools import chain
from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning from .error import PropertiesOptionError, ConfigError, LeadershipError, ValueWarning
@ -82,8 +83,7 @@ class ParamOption(Param):
class ParamDynOption(ParamOption): class ParamDynOption(ParamOption):
__slots__ = ('suffix', __slots__ = ('suffix',)
)
def __init__(self, def __init__(self,
option: 'Option', option: 'Option',
suffix: str, suffix: str,
@ -118,6 +118,16 @@ class ParamValue(Param):
self.value = value self.value = value
class ParamInformation(Param):
__slots__ = ('information_name',)
def __init__(self,
information_name: str,
default_value: Any=undefined,
) -> None:
self.information_name = information_name
self.default_value = default_value
class ParamIndex(Param): class ParamIndex(Param):
__slots__ = tuple() __slots__ = tuple()
@ -199,7 +209,7 @@ class Break(Exception):
pass pass
async def manager_callback(callbk: Union[ParamOption, ParamValue], async def manager_callback(callbk: Param,
option, option,
index: Optional[int], index: Optional[int],
orig_value, orig_value,
@ -286,12 +296,23 @@ async def manager_callback(callbk: Union[ParamOption, ParamValue],
if isinstance(callbk, ParamValue): if isinstance(callbk, ParamValue):
return callbk.value return callbk.value
if isinstance(callbk, ParamInformation):
try:
return await config_bag.context.impl_get_information(config_bag.connection,
callbk.information_name,
callbk.default_value,
)
except ValueError as err:
raise ConfigError(_('option "{}" cannot be calculated: {}').format(option.impl_get_display_name(),
str(err),
))
if isinstance(callbk, ParamIndex): if isinstance(callbk, ParamIndex):
return index return index
if isinstance(callbk, ParamSuffix): if isinstance(callbk, ParamSuffix):
if not option.issubdyn(): if not option.issubdyn():
raise ConfigError('option "{}" is not in a dynoptiondescription'.format(option.impl_get_display_name())) raise ConfigError(_('option "{}" is not in a dynoptiondescription').format(option.impl_get_display_name()))
return option.impl_getsuffix() return option.impl_getsuffix()
if isinstance(callbk, ParamSelfOption): if isinstance(callbk, ParamSelfOption):
@ -413,7 +434,7 @@ async def carry_out_calculation(option,
kwargs[key] = {'propertyerror': str(err)} kwargs[key] = {'propertyerror': str(err)}
except Break: except Break:
continue continue
ret = calculate(option, ret = await calculate(option,
callback, callback,
allow_value_error, allow_value_error,
force_value_warning, force_value_warning,
@ -439,7 +460,7 @@ async def carry_out_calculation(option,
return ret return ret
def calculate(option, async def calculate(option,
callback: Callable, callback: Callable,
allow_value_error: bool, allow_value_error: bool,
force_value_warning: bool, force_value_warning: bool,
@ -454,7 +475,10 @@ def calculate(option,
""" """
try: try:
return callback(*args, **kwargs) ret = callback(*args, **kwargs)
if isinstance(ret, CoroutineType):
ret = await ret
return ret
except (ValueError, ValueWarning) as err: except (ValueError, ValueWarning) as err:
if allow_value_error: if allow_value_error:
if force_value_warning: if force_value_warning:

View File

@ -562,17 +562,24 @@ class _CommonConfig(SubConfig):
# information # information
async def impl_set_information(self, async def impl_set_information(self,
connection, config_bag,
key, key,
value): value,
):
"""updates the information's attribute """updates the information's attribute
:param key: information's key (ex: "help", "doc" :param key: information's key (ex: "help", "doc"
:param value: information's value (ex: "the help string") :param value: information's value (ex: "the help string")
""" """
await self._impl_values.set_information(connection, await self._impl_values.set_information(config_bag.connection,
key, key,
value) value)
for option in config_bag.context.cfgimpl_get_description()._cache_dependencies_information.get(key, []):
option_bag = OptionBag()
option_bag.set_option(option,
None,
config_bag)
await config_bag.context.cfgimpl_reset_cache(option_bag)
async def impl_get_information(self, async def impl_get_information(self,
connection, connection,

View File

@ -27,7 +27,7 @@ from itertools import chain
from .baseoption import BaseOption, submulti, STATIC_TUPLE from .baseoption import BaseOption, submulti, STATIC_TUPLE
from ..i18n import _ from ..i18n import _
from ..setting import undefined, OptionBag, Undefined from ..setting import undefined, OptionBag, Undefined
from ..autolib import Calculation, Params, ParamValue, ParamOption from ..autolib import Calculation, Params, ParamOption, ParamInformation
from ..error import (ConfigError, ValueWarning, ValueErrorWarning, PropertiesOptionError, from ..error import (ConfigError, ValueWarning, ValueErrorWarning, PropertiesOptionError,
ValueOptionError, display_list) ValueOptionError, display_list)
from .syndynoption import SynDynOption from .syndynoption import SynDynOption
@ -50,6 +50,7 @@ class Option(BaseOption):
# #
'_validators', '_validators',
# #
'_dependencies_information',
'_leadership', '_leadership',
'_choice_values', '_choice_values',
'_choice_values_params', '_choice_values_params',
@ -66,6 +67,7 @@ class Option(BaseOption):
warnings_only: bool=False, warnings_only: bool=False,
extra: Optional[Dict]=None): extra: Optional[Dict]=None):
_setattr = object.__setattr__ _setattr = object.__setattr__
_dependencies_information = []
if not multi and default_multi is not None: if not multi and default_multi is not None:
raise ValueError(_("default_multi is set whereas multi is False" raise ValueError(_("default_multi is set whereas multi is False"
" in option: {0}").format(name)) " in option: {0}").format(name))
@ -105,6 +107,8 @@ class Option(BaseOption):
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
param.option._add_dependency(self) param.option._add_dependency(self)
self._has_dependency = True self._has_dependency = True
elif isinstance(param, ParamInformation):
_dependencies_information.append(param.information_name)
self._validators = tuple(validators) self._validators = tuple(validators)
if extra is not None and extra != {}: if extra is not None and extra != {}:
@ -155,29 +159,37 @@ class Option(BaseOption):
self.sync_impl_validate(default, self.sync_impl_validate(default,
option_bag, option_bag,
check_error=False) check_error=False)
self.value_dependencies(default) self.value_dependencies(default, _dependencies_information)
if (is_multi and default != []) or \ if (is_multi and default != []) or \
(not is_multi and default is not None): (not is_multi and default is not None):
if is_multi and isinstance(default, list): if is_multi and isinstance(default, list):
default = tuple(default) default = tuple(default)
_setattr(self, '_default', default) _setattr(self, '_default', default)
if _dependencies_information:
self._dependencies_information = _dependencies_information
def value_dependencies(self, def value_dependencies(self,
value: Any) -> Any: value: Any,
_dependencies_information: List[str],
) -> Any:
if isinstance(value, list): if isinstance(value, list):
for val in value: for val in value:
if isinstance(value, list): if isinstance(value, list):
self.value_dependencies(val) self.value_dependencies(val, _dependencies_information)
elif isinstance(value, Calculation): elif isinstance(value, Calculation):
self.value_dependency(val) self.value_dependency(val, _dependencies_information)
elif isinstance(value, Calculation): elif isinstance(value, Calculation):
self.value_dependency(value) self.value_dependency(value, _dependencies_information)
def value_dependency(self, def value_dependency(self,
value: Any) -> Any: value: Any,
_dependencies_information: List[str],
) -> Any:
for param in chain(value.params.args, value.params.kwargs.values()): for param in chain(value.params.args, value.params.kwargs.values()):
if isinstance(param, ParamOption): if isinstance(param, ParamOption):
param.option._add_dependency(self) param.option._add_dependency(self)
elif isinstance(param, ParamInformation):
_dependencies_information.append(param.information_name)
#__________________________________________________________________________ #__________________________________________________________________________
# option's information # option's information
@ -191,6 +203,9 @@ class Option(BaseOption):
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
return False return False
def get_dependencies_information(self) -> List[str]:
return getattr(self, '_dependencies_information', [])
def get_type(self) -> str: def get_type(self) -> str:
# _display_name for compatibility with older version than 3.0rc3 # _display_name for compatibility with older version than 3.0rc3
return getattr(self, '_type', self._display_name) return getattr(self, '_type', self._display_name)

View File

@ -30,7 +30,9 @@ from ..error import ConfigError, ConflictError
class CacheOptionDescription(BaseOption): class CacheOptionDescription(BaseOption):
__slots__ = ('_cache_force_store_values',) __slots__ = ('_cache_force_store_values',
'_cache_dependencies_information',
)
def impl_already_build_caches(self) -> bool: def impl_already_build_caches(self) -> bool:
return self.impl_is_readonly() return self.impl_is_readonly()
@ -42,7 +44,9 @@ class CacheOptionDescription(BaseOption):
currpath: List[str]=None, currpath: List[str]=None,
cache_option=None, cache_option=None,
force_store_values=None, force_store_values=None,
display_name=None) -> None: dependencies_information=None,
display_name=None,
) -> None:
"""validate options and set option has readonly option """validate options and set option has readonly option
""" """
# _consistencies is None only when we start to build cache # _consistencies is None only when we start to build cache
@ -52,6 +56,7 @@ class CacheOptionDescription(BaseOption):
if __debug__: if __debug__:
cache_option = [] cache_option = []
force_store_values = [] force_store_values = []
dependencies_information = {}
currpath = [] currpath = []
else: else:
init = False init = False
@ -73,8 +78,11 @@ class CacheOptionDescription(BaseOption):
sub_currpath, sub_currpath,
cache_option, cache_option,
force_store_values, force_store_values,
dependencies_information,
display_name) display_name)
else: else:
for information in option.get_dependencies_information():
dependencies_information.setdefault(information, []).append(option)
is_multi = option.impl_is_multi() is_multi = option.impl_is_multi()
if not option.impl_is_symlinkoption(): if not option.impl_is_symlinkoption():
properties = option.impl_getproperties() properties = option.impl_getproperties()
@ -102,6 +110,7 @@ class CacheOptionDescription(BaseOption):
option._set_readonly() option._set_readonly()
if init: if init:
self._cache_force_store_values = force_store_values self._cache_force_store_values = force_store_values
self._cache_dependencies_information = dependencies_information
self._path = self._name self._path = self._name
self._set_readonly() self._set_readonly()

View File

@ -25,14 +25,11 @@ from copy import deepcopy
class Values: class Values:
__slots__ = ('_values', __slots__ = ('_values',
'_informations',
'_storage', '_storage',
'__weakref__') '__weakref__')
def __init__(self, storage): def __init__(self, storage):
"""init plugin means create values storage """init plugin means create values storage
""" """
#self._values = ([], [], [], [])
#self._informations = {}
self._storage = storage self._storage = storage
def _setvalue_info(self, nb, idx, value, index, follower_idx=None): def _setvalue_info(self, nb, idx, value, index, follower_idx=None):