osci/actions/base.py (87 lines of code) (raw):

"""Copyright since 2021, EPAM Systems This file is part of OSCI. OSCI is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OSCI 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 General Public License for more details. You should have received a copy of the GNU General Public License along with OSCI. If not, see <http://www.gnu.org/licenses/>.""" from typing import Type, NamedTuple, Optional, Dict, Union, Tuple, FrozenSet from .exceptions import NotSuchParameterException, MissedRequiredParameterException from osci.actions.consts import DAY_FORMAT, get_default_day from datetime import datetime import logging log = logging.getLogger(__name__) class ActionParam(NamedTuple): """Params for using in Action""" name: str short_name: Optional[str] type: Union[Type, Tuple[Type]] = str required: bool = False description: Optional[str] = '' datetime_format: Optional[str] = None default: Optional[str] = None choices: Optional[FrozenSet[str]] = None class Action: """Check and validate command before execute commands""" params = ( ActionParam(name='day', type=datetime, required=True, short_name='d'), ) @classmethod def help_text(cls) -> str: """help text for cli click command""" return '' @classmethod def name(cls) -> str: """Name of the action""" raise NotImplementedError() def execute(self, **kwargs): """Process params and run _execute""" log.info(f'Execute action `{self.name()}`') log.info(f'Action params `{kwargs}`') return self._execute(**self._process_params(kwargs)) def _execute(self, **kwargs): """Action process logic. Need to implement in subclasses""" raise NotImplementedError() @property def required_params(self) -> Dict[str, ActionParam]: return {param.name: param for param in self.params if param.required} @property def param_map(self) -> Dict[str, ActionParam]: return {param.name: param for param in self.params} @property def default_params(self) -> Dict[str, ActionParam]: return {param.name: param for param in self.params if param.default} def _set_default_params(self, processed_params: dict): """Set default params for not required params""" not_set_params = self.default_params.keys() - processed_params.keys() if not_set_params: for key in not_set_params: processed_params[key] = self.param_map[key].default def _check_required_params(self, passed: dict): missed_required = self.required_params.keys() - passed.keys() - self.default_params.keys() if missed_required: raise MissedRequiredParameterException('; '.join(missed_required)) def _intersect_required_and_default_params(self): """Check intersection of required and default params""" common_param = set(self.required_params.keys()) & set(self.default_params.keys()) if common_param: raise AttributeError(f"Parameter {common_param} " f"must be only `required` or `default`, not both") def _check_unknown_params(self, passed: dict): unknown = passed.keys() - self.param_map.keys() if unknown: raise NotSuchParameterException('; '.join(unknown)) def _check_types(self, passed: dict): for k, v in passed.items(): param = self.param_map[k] if param.type == datetime and isinstance(v, str): datetime.strptime(v, param.datetime_format or DAY_FORMAT) continue if param.type == int and isinstance(v, str): int(v) continue if not isinstance(v, param.type): raise TypeError(f'Param `{k}` type must be: `{param.type}` not `{type(v)}` (passed value: `{v}`)') if param.choices and (v not in param.choices): raise AttributeError(f"Param `{k}` must have values: {param.choices}") def _validate_params(self, passed: dict): self._check_required_params(passed) self._check_unknown_params(passed) self._check_types(passed) def _process_params(self, passed: dict): self._intersect_required_and_default_params() processed_params = passed.copy() self._set_default_params(processed_params) self._validate_params(passed) for param in self.params: if param.type == datetime and param.name in processed_params: processed_params[param.name] = datetime.strptime(processed_params[param.name], param.datetime_format or DAY_FORMAT) elif param.type == int and param.name in processed_params: processed_params[param.name] = int(processed_params[param.name]) return processed_params