# -*- coding: utf-8 -*- # Copyright (C) 2017-2018 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # ____________________________________________________________ from inspect import ismethod, getdoc from .error import APIError, ConfigError, SlaveError from .i18n import _ from .setting import ConfigBag, owners, undefined from .option import ChoiceOption from time import time from copy import deepcopy TIRAMISU_VERSION = 3 try: from .value import Multi except: Multi = list COUNT_TIME = False #COUNT_TIME = {} def count(func): global MOD_COUNT_TIME class_name = func.__str__().split()[1].split('.')[0] func_name = func.__name__ def wrapper(*args, **kwargs): time1 = time() ret = func(*args, **kwargs) time2 = time() diff = (time2 - time1) * 1000.0 MOD_COUNT_TIME[class_name][func_name]['max'] = max(MOD_COUNT_TIME[class_name][func_name]['max'], diff) MOD_COUNT_TIME[class_name][func_name]['min'] = min(MOD_COUNT_TIME[class_name][func_name]['min'], diff) MOD_COUNT_TIME[class_name][func_name]['total'] += diff MOD_COUNT_TIME[class_name][func_name]['nb'] += 1 #print('%s function took %0.3f ms' % (func_name, diff)) #print(COUNT_TIME) return ret if COUNT_TIME is not False: COUNT_TIME.setdefault(class_name, {}) COUNT_TIME[class_name][func_name] = {'max': 0, 'min': 1000, 'nb': 0, 'total': 0} MOD_COUNT_TIME = deepcopy(COUNT_TIME) return wrapper return func def display_count(): if COUNT_TIME is not False: global MOD_COUNT_TIME #print(MOD_COUNT_TIME) print() for class_name in MOD_COUNT_TIME: print('>', class_name) for func in MOD_COUNT_TIME[class_name]: print('=>', func) print('==> nb:', MOD_COUNT_TIME[class_name][func]['nb']) if MOD_COUNT_TIME[class_name][func]['nb'] != 0: print('==> min:', MOD_COUNT_TIME[class_name][func]['min']) print('==> max:', MOD_COUNT_TIME[class_name][func]['max']) print('==> moy:', MOD_COUNT_TIME[class_name][func]['total'] / MOD_COUNT_TIME[class_name][func]['nb']) MOD_COUNT_TIME = deepcopy(COUNT_TIME) class CommonTiramisu(object): allow_optiondescription = True def _get_option(self): option = self.config_bag.option if option is None: option = self.subconfig.cfgimpl_get_description().impl_getchild(self.name, self.config_bag, self.subconfig) self.config_bag.option = option if self.index is not None and option.impl_is_master_slaves('slave') and \ self.index >= self.subconfig.cfgimpl_get_length(): raise SlaveError(_('index "{}" is higher than the master length "{}" ' 'for option "{}"').format(self.index, self.subconfig.cfgimpl_get_length(), option.impl_get_display_name())) if not self.allow_optiondescription and option.impl_is_optiondescription(): raise APIError(_('option must not be an optiondescription')) return option class CommonTiramisuOption(CommonTiramisu): icon = '\u2937' tmpl_help = u' {} {}: {}' allow_unrestraint = False allow_optiondescription = False slave_need_index = True def __init__(self, name, path, index, subconfig, config_bag): self.path = path self.index = index self.config_bag = config_bag self.name = name self.subconfig = subconfig if self.slave_need_index: self._test_slave_index() if not self.allow_unrestraint and self.config_bag.force_unrestraint: self._unrestraint_not_allowed(self.config_bag.force_unrestraint) def _test_slave_index(self): option = self._get_option() if not option.impl_is_optiondescription(): if self.index is None and option.impl_is_master_slaves('slave'): raise APIError('index must be set with a slave option') elif self.index is not None and not option.impl_is_master_slaves('slave'): raise APIError('index must be set only with a slave option') def _unrestraint_not_allowed(self, force_unrestraint): name = self.__class__.__name__[14:].lower() raise APIError(_('{} cannot be unrestraint').format(name)) def __getattr__(self, name): if name == 'help': return self._help() else: if not hasattr(CommonTiramisuOption, name): raise APIError(_('unknown method {}').format(name)) else: super().__getattribute__(name) def _help(self): txt = [] for func_name in dir(self): if not func_name.startswith('_'): func = getattr(self, func_name) if ismethod(func): txt.append(self.tmpl_help.format(self.icon, func_name, getdoc(func))) return '\n'.join(txt) class TiramisuOptionOption(CommonTiramisuOption): """get information from an option""" allow_unrestraint = True allow_optiondescription = True slave_need_index = False @count def get(self): return self._get_option() @count def _ismulti(self): """test if option could have multi value""" option = self._get_option() return option.impl_is_multi() @count def _issubmulti(self): """test if option could have submulti value""" option = self._get_option() return option.impl_is_submulti() @count def ismasterslaves(self): """test if option is a master or a slave""" option = self._get_option() return option.impl_is_master_slaves() @count def _ismaster(self): """test if option is a master""" option = self._get_option() return option.impl_is_master_slaves('master') @count def _isslave(self): """test if option is a slave""" option = self._get_option() return option.impl_is_master_slaves('slave') @count def doc(self): option = self._get_option() return option.impl_get_display_name() @count def _default(self): option = self._get_option() return option.impl_getdefault() @count def _defaultmulti(self): option = self._get_option() return option.impl_getdefault_multi() @count def has_dependency(self, self_is_dep=True): option = self._get_option() return option.impl_has_dependency(self_is_dep) @count def _consistencies(self): option = self._get_option() return option.get_consistencies() @count def _callbacks(self): option = self._get_option() return option.impl_get_callback() @count def requires(self): option = self._get_option() return option.impl_getrequires() def __getattr__(self, name): if not self._get_option().impl_is_optiondescription() and name != 'get_option': subkey = '_' + name if subkey in dir(self): func = getattr(self, subkey) if callable(func): return func raise APIError(_('{} is unknown').format(name)) def isoptiondescription(self): return self._get_option().impl_is_optiondescription() class TiramisuOptionOwner(CommonTiramisuOption): """manager option's owner""" allow_unrestraint = True def __init__(self, name, path, index, subconfig, config_bag): super().__init__(name, path, index, subconfig, config_bag) self.values = self.config_bag.config.cfgimpl_get_values() @count def get(self): """get owner for a specified option""" option = self._get_option() return self.values.getowner(self.path, self.index, self.config_bag) @count def isdefault(self): """is option has defaut value""" self._get_option() return self.values.is_default_owner(self.path, self.index, self.config_bag) @count def set(self, owner): """get owner for a specified option""" self._get_option() if TIRAMISU_VERSION == 2: if owner in ['default', 'forced', 'meta']: raise ConfigError() try: obj_owner = getattr(owners, owner) except AttributeError: owners.addowner(owner) obj_owner = getattr(owners, owner) self.values.setowner(self.path, self.index, obj_owner, self.config_bag) class TiramisuOptionProperty(CommonTiramisuOption): """manager option's property""" allow_optiondescription = True allow_unrestraint = True slave_need_index = False def __init__(self, name, path, index, subconfig, config_bag): super().__init__(name, path, index, subconfig, config_bag) self.settings = config_bag.config.cfgimpl_get_settings() @count def get(self, apply_requires=True): self._get_option() if apply_requires: self._test_slave_index() properties = self.settings.getproperties(self.path, self.index, self.config_bag, apply_requires) if TIRAMISU_VERSION == 2: properties = properties.get() return set(properties) @count def add(self, prop): #FIXME not index !! self._get_option() self.settings.addproperty(self.path, prop, self.config_bag) @count def pop(self, prop): self._get_option() self.settings.popproperty(self.path, prop, self.config_bag) @count def reset(self): """reset all personalised properties """ self._get_option() self.settings.reset(opt=self.config_bag.option, path=self.path) class TiramisuOptionPermissive(CommonTiramisuOption): """manager option's property""" allow_unrestraint = True allow_optiondescription = True slave_need_index = False def __init__(self, name, path, index, subconfig, config_bag): super().__init__(name, path, index, subconfig, config_bag) self.settings = config_bag.config.cfgimpl_get_settings() @count def get(self): """get permissive value for a specified path""" if TIRAMISU_VERSION == 2: args = [self.setting_properties, self._path] else: args = [self._get_option(), self.path] return self.settings.getpermissive(*args) @count def set(self, permissives): if TIRAMISU_VERSION == 2: permissives = tuple(permissives) path = self._path opt = self._opt self.settings.setpermissive(opt=opt, path=path, permissive=permissives) else: path = self.path opt = self._get_option() self.settings.setpermissive(opt=opt, path=path, permissives=permissives) @count def reset(self, path): """reset all personalised permissive """ self.set(tuple()) class TiramisuOptionInformation(CommonTiramisuOption): allow_unrestraint = True allow_optiondescription = True slave_need_index = False @count def get(self, name, default=undefined): option = self._get_option() return option.impl_get_information(name, default) class TiramisuOptionValue(CommonTiramisuOption): """manager option's value""" slave_need_index = False allow_unrestraint = True @count def get(self): self._get_option() self._test_slave_index() settings = self.config_bag.config.cfgimpl_get_settings() value = self.subconfig.getattr(self.name, self.index, self.config_bag) if isinstance(value, Multi): value = list(value) return value @count def set(self, value): """set a value for a specified option""" self._get_option() self._test_slave_index() values = self.config_bag.config.cfgimpl_get_values() if isinstance(value, list): while undefined in value: idx = value.index(undefined) value[idx] = values.getdefaultvalue(self.path, idx, self.config_bag) else: if value == undefined: value = values.getdefaultvalue(self.path, self.index, self.config_bag) self.subconfig.setattr(self.name, self.index, value, self.config_bag) @count def _pop(self, index): """pop value for a specified master values """ self._get_option() self.config_bag.config.delattr(self.path, index, self.config_bag) @count def reset(self): """reset value for a value""" self._get_option() self._test_slave_index() self.config_bag.config.delattr(self.path, self.index, self.config_bag) @count def _len(self): self._get_option() subconfig_path = self.path.rsplit('.', 1)[0] if self.config_bag.setting_properties is not None: self.config_bag.config.cfgimpl_get_settings().validate_properties(self.path, self.index, self.config_bag) config_bag = self.config_bag.copy('nooption') subconfig = config_bag.config.getattr(subconfig_path, None, config_bag) return subconfig.cfgimpl_get_length() def __getattr__(self, name): if name == 'list' and isinstance(self._get_option(), ChoiceOption): return self._list elif name == 'pop' and self._get_option().impl_is_master_slaves('master'): return self._pop elif name == 'len' and self._get_option().impl_is_master_slaves('slave'): return self._len raise APIError(_('{} is unknown').format(name)) @count def _list(self): return self.config_bag.option.impl_get_values(self.config_bag) def registers(registers, prefix): for module_name in globals().keys(): if module_name != prefix and module_name.startswith(prefix): module = globals()[module_name] func_name = module_name[len(prefix):].lower() registers[func_name] = module class TiramisuOption(CommonTiramisu): icon = '\u2937' tmpl_help = ' {} {}: {}' def __init__(self, name, path, index, subconfig, config_bag): self.name = name self.subconfig = subconfig self.path = path self.index = index self.config_bag = config_bag self.registers = {} registers(self.registers, self.__class__.__name__) def _help(self): txt = [] for module_name, module in self.registers.items(): module_doc = getdoc(module) txt.append(self.tmpl_help.format(self.icon, module_name, module_doc)) txt.append(module(None, None).help) return '\n'.join(txt) def __getattr__(self, subfunc): if subfunc in self.registers: return self.registers[subfunc](self.name, self.path, self.index, self.subconfig, self.config_bag) elif subfunc == 'help': return self._help() elif subfunc == 'make_dict' and self._get_option().impl_is_optiondescription(): return self._make_dict elif subfunc == 'list' and self._get_option().impl_is_optiondescription(): return self._list elif subfunc == 'group_type' and self._get_option().impl_is_optiondescription(): return self._group_type else: raise APIError(_('please specify a valid sub function ({})').format(subfunc)) @count def _make_dict(self, flatten=False, withvalue=undefined, withoption=None, fullpath=False): return self.config_bag.config.getattr(self.path, None, self.config_bag).make_dict(config_bag=self.config_bag, flatten=flatten, fullpath=fullpath, withoption=withoption, withvalue=withvalue) @count def group_type(self): return self._get_option().impl_get_group_type() @count def _list(self, type='all', group_type=None): if type == 'optiondescription': return self.config_bag.config.getattr(self.path, None, self.config_bag ).iter_groups(self.config_bag, group_type) elif type == 'all': return self.config_bag.config.getattr(self.path, None, self.config_bag ).cfgimpl_get_children(self.config_bag) else: raise APIError(_('unknown list type {}').format(type)) class TiramisuContext(object): def __init__(self, config_bag): self.config_bag = config_bag class TiramisuContextInformation(TiramisuContext): @count def get(self, name, default=undefined): return self.config_bag.config.impl_get_information(name, default) @count def set(self, name, value): self.config_bag.config.impl_set_information(name, value) @count def reset(self, name): self.config_bag.config.impl_del_information(name) class TiramisuContextValue(TiramisuContext): @count def mandatory_warnings(self): return self.config_bag.config.cfgimpl_get_values().mandatory_warnings(self.config_bag) @count def get_modified(self): return self.config_bag.config.cfgimpl_get_values().get_modified_values() def set(self, path, value, index=None, only_config=undefined, force_default=undefined, force_default_if_same=undefined, force_dont_change_value=undefined): kwargs = {} if only_config is not undefined: kwargs['only_config'] = only_config if force_default is not undefined: kwargs['force_default'] = force_default if force_default_if_same is not undefined: kwargs['force_default_if_same'] = force_default_if_same if force_dont_change_value is not undefined: kwargs['force_dont_change_value'] = force_dont_change_value return self.config_bag.config.set_value(path, index, value, self.config_bag, **kwargs) def reset(self, path): self.config_bag.config.reset(path, self.config_bag) class TiramisuContextOwner(TiramisuContext): @count def get(self): return self.config_bag.config.cfgimpl_get_settings().getowner() @count def set(self, owner): try: obj_owner = getattr(owners, owner) except AttributeError: owners.addowner(owner) obj_owner = getattr(owners, owner) self.config_bag.config.cfgimpl_get_settings().setowner(obj_owner) class TiramisuContextProperty(TiramisuContext): @count def read_only(self): settings = self.config_bag.config.cfgimpl_get_settings() settings.read_only() self.config_bag.setting_properties = settings.get_context_properties() @count def read_write(self): settings = self.config_bag.config.cfgimpl_get_settings() settings.read_write() # #FIXME ? settings.set_context_permissive(frozenset(['hidden'])) self.config_bag.setting_properties = settings.get_context_properties() #/FIXME ? @count def add(self, prop): props = self.get() props.add(prop) self.set(frozenset(props)) self.config_bag.setting_properties = self.config_bag.config.cfgimpl_get_settings().get_context_properties() @count def pop(self, prop): props = self.get() if prop in props: props.remove(prop) self.set(frozenset(props)) self.config_bag.setting_properties = self.config_bag.config.cfgimpl_get_settings().get_context_properties() @count def get(self): return set(self.config_bag.setting_properties) @count def get_modified(self): return self.config_bag.config.cfgimpl_get_settings().get_modified_properties() @count def set_modified(self, props): return self.config_bag.config.cfgimpl_get_settings().set_modified_properties(props) @count def set(self, props): self.config_bag.config.cfgimpl_get_settings().set_context_properties(props) self.config_bag.setting_properties = self.config_bag.config.cfgimpl_get_settings().get_context_properties() def reset(self): self.config_bag.config.cfgimpl_get_settings().reset() class TiramisuContextPermissive(TiramisuContext): @count def set(self, permissives): self.config_bag.config.cfgimpl_get_settings().set_context_permissive(permissives) class TiramisuContextOption(TiramisuContext): @count def find_first(self, name, type='option'): check_properties = self.config_bag.force_unrestraint or self.config_bag.force_unrestraint return self.config_bag.config.find_first(byname=name, type_=type, config_bag=self.config_bag) @count def find(self, name, type='option'): return self.config_bag.config.find(byname=name, type_=type, config_bag=self.config_bag) @count def get(self, path): config_bag = self.config_bag.copy() config_bag.validate = False config_bag.force_unrestraint = True config_bag.setting_properties = None return self.config_bag.config.unwrap_from_path(path, config_bag) @count def make_dict(self, flatten=False, withvalue=undefined, withoption=None, fullpath=False): return self.config_bag.config.make_dict(self.config_bag, flatten=flatten, fullpath=fullpath, withoption=withoption, withvalue=withvalue) @count def list(self, type='all', group_type=None): if type == 'optiondescription': return self.config_bag.config.iter_groups(self.config_bag, group_type) elif type == 'all': return self.config_bag.config.cfgimpl_get_children(self.config_bag) else: raise APIError(_('unknown list type {}').format(type)) class TiramisuContextConfig(TiramisuContext): def find_first(self, name, byvalue=undefined): return self.config_bag.config.find_firsts(byname=name, byvalue=byvalue, config_bag=self.config_bag) class TiramisuDispatcherConfig(TiramisuContextConfig): def __call__(self, path): config = self.config_bag.config if path is None: return TiramisuAPI(config, force_permissive=self.config_bag.force_permissive, force_unrestraint=self.config_bag.force_unrestraint) spaths = path.split('.') for spath in spaths: config = config.getconfig(spath) return TiramisuAPI(config, force_permissive=self.config_bag.force_permissive, force_unrestraint=self.config_bag.force_unrestraint) class TiramisuDispatcherOption(TiramisuContextOption): def __call__(self, path, index=None): if path is None: return self config_bag = self.config_bag.copy() validate = not config_bag.force_unrestraint if not validate: config_bag.setting_properties = None subconfig, name = config_bag.config.cfgimpl_get_home_by_path(path, config_bag) return TiramisuOption(name, path, index, subconfig, config_bag) class TiramisuAPI(object): icon = '\u2937' tmpl_help = ' {} {}: {}' def __init__(self, config, force_permissive=False, force_unrestraint=False): self._config = config self.force_permissive = force_permissive self.force_unrestraint = force_unrestraint self.registers = {} registers(self.registers, 'TiramisuContext') registers(self.registers, 'TiramisuDispatcher') def __getattr__(self, subfunc): if subfunc == 'forcepermissive': return TiramisuAPI(config=self._config, force_permissive=True, force_unrestraint=self.force_unrestraint) elif subfunc == 'unrestraint': return TiramisuAPI(config=self._config, force_permissive=self.force_permissive, force_unrestraint=True) elif subfunc == 'help': return self._help() elif subfunc in self.registers: config_bag = ConfigBag(config=self._config, force_permissive=self.force_permissive, force_unrestraint=self.force_unrestraint) return self.registers[subfunc](config_bag) else: raise APIError(_('please specify a valid sub function ({})').format(subfunc)) def _help(self): txt = ['[forcepermissive]'] for module_name, module in self.registers.items(): module_doc = getdoc(module) txt.append(self.tmpl_help.format(self.icon, module_name, module_doc)) txt.append(module(None, None).help) return '\n'.join(txt) @count def getapi(config): """instanciate TiramisuAPI :param config: Config object :type descr: an instance of ``config.Config`` """ return TiramisuAPI(config)