from typing import Union, Callable, List from ..scripts.scripts import Script from ..parameters.parameters import BaseParameter, Parameters from ..variables.datavars import NamespaceNode, DependenceAPI,\ VariableNotFoundError from ..variables.loader import Datavars from ..utils.io_module import IOModule class CommandDescriptionError(Exception): '''Исключение кидаемое при наличии ошибок во время создания объекта команды.''' def __init__(self, message: str, command_id: str = '', title: str = ''): self.message: str = message self.command_id: str = command_id self.command_title: str = title def __str__(self) -> str: if self.command_title: return (f'can not create command {self.command_title}:' f' {self.message}') if self.command_id: return f'can not create command {self.command_id}: {self.message}' return f'can not create command: {self.message}' class CommandInitializationError(Exception): '''Исключение кидаемое при наличии ошибок во время инициализации.''' def __init__(self, message: str, title: str): self.message: str = message self.command_title: str = title def __str__(self) -> str: if self.command_title: return (f'can not initialize command {self.command_title}:' f' {self.message}') class Command: '''Класс описания команды, предназначен для хранения информации о команде и создания по данному описанию объекта лаунчера команды.''' def __init__(self, command_id: str = '', category: str = '', title: str = '', command: str = '', script: Union[Callable, Script, None] = None, args: Union[tuple, list] = tuple(), parameters: Union[List[BaseParameter], None] = None, namespace: Union[str, NamespaceNode, None] = None, gui: bool = False, icon: Union[str, List[str]] = '', setvars: dict = {}, rights: List[str] = []): # Идентификатор команды обязателен. if not command_id: raise CommandDescriptionError('command ID is not set') self.__id: str = command_id # Если собственно команда не была указана, получаем ее из ID. if command: self.__command: str = command else: self.__command: str = f'cl_{self.__id}' # Название команды. if not title: raise CommandDescriptionError("title is not set", command_id=command_id) self.__title: str = title # Категория, к которой относится команда. if not category: raise CommandDescriptionError("category is not set", command_title=title) self.__category: str = category # Пространство имен относительно которого, будет выполняться скрипт. self.__namespace: Union[str, NamespaceNode, None] = namespace # Параметры, указываемые при вызове этой команды. if parameters is None: raise CommandDescriptionError("parameters is not set", command_title=title) self.__parameters: List[BaseParameter] = parameters # Скрипт выполняемый при вызове этой команды. if not script: raise CommandDescriptionError("script is not set", command_title=title) self.__script: Script = script # Аргументы, с которыми будет запущен скрипт. Пока только статичные # значения. self.__args = args # Параметр указывающий, должена ли данная команда отображаться в # графическом интерфейсе. self.__gui: bool = gui # Устанавливаем название иконки. if not icon and self.__gui: raise CommandDescriptionError("icon is not set", command_title=title) self.__icon: Union[str, List[str]] = icon # Словарь с переменными и значениями, которые нужно им присвоить. self.__setvars: dict = setvars # Права, необходимые данной команде. # TODO разобраться с тем, как использовать. if not rights: raise CommandDescriptionError("rights is not set", title=title) self.__rights = rights def make_runner(self, datavars: Union[Datavars, NamespaceNode], output: IOModule): '''Метод создающий на основе данного описания команды экземпляр раннера. ''' return CommandRunner(self, datavars, output) # Интерфейс иммутабельного объекта описания команды. @property def id(self) -> str: return self.__id @property def command(self) -> str: return self.__command @property def title(self) -> str: return self.__title @property def category(self) -> str: return self.__category @property def namespace(self) -> str: return self.__namespace @property def parameters(self) -> str: return self.__parameters @property def script(self) -> Script: return self.__script @property def args(self) -> tuple: return self.__args @property def gui(self) -> bool: return self.__gui @property def icon(self) -> Union[str, List[str]]: return self.__icon @property def setvars(self) -> dict: return self.__setvars @property def rights(self) -> List[str]: return self.__rights def __repr__(self): return f"" class CommandRunner: def __init__(self, command: Command, datavars: Union[Datavars, NamespaceNode], output: IOModule): '''Класс инкапcулирующий все данные о команде, а также необходимые для нее параметры, модуль ввода-вывода, переменные и прочее. Предназначен для конфигурации команды и запуска ее в воркере.''' self._command = command self._datavars = datavars self._output = output # Ищем пространство имен. if self._command.namespace is not None: if isinstance(self._command.namespace, str): self._namespace = self._find_namespace( self._command.namespace, datavars) # Инициализируем параметры. self._parameters = Parameters(self._datavars) self._parameters.add(*self._command.parameters) # Получаем запускатель скрипта. self._script_launcher = self._command.script.make_launcher( output, datavars, self._namespace) # Устанавливаем переменные, если нужно. if self._command.setvars: self._set_variables(self._command.setvars, datavars, self._namespace) def run_command(self): args = self._command.args self._script_launcher(*args) def set_parameters(self, values: dict): pass def get_parameters(self, group: Union[str, None] = None): '''Метод для установки параметров.''' pass def _find_namespace(self, namespace_path: str, datavars: Union[Datavars, NamespaceNode] ) -> NamespaceNode: '''Метод для поиска пространств имен.''' # TODO сделать где-нибудь единственный статический вариант. parts = namespace_path.split('.') namespace = datavars for part in parts: namespace = namespace[part] return namespace def _set_variables(self, setvars: dict, datavars: Union[Datavars, NamespaceNode], namespace: Union[NamespaceNode, None]) -> None: '''Метод для установки указанных значений, указанным переменным.''' force = False for varname, value in setvars.items(): if varname.endswith('!'): varname = varname[:-1] force = True else: force = False try: variable = DependenceAPI.find_variable( varname, datavars, current_namespace=namespace) except VariableNotFoundError: self._output.set_error(f"Can not set variable '{varname}':" " variable does not exist.") if not variable.readonly or force: variable.set(value) else: self._output.set_error("Can not set readonly variable " f"'{variable.get_fullname}'.")