From 850919ea03dad8c2d5b5ca42c4935a032ff798ad Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Mon, 9 Mar 2026 22:33:47 +0300 Subject: [PATCH 01/15] =?UTF-8?q?refactor:=20classes=20=E2=84=961?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/coderunner/coderunner.py | 6 +- .../vim_coderunner_factory.py | 87 +++++- .../__init__.py | 1 + .../basic.py | 7 +- .../types.py | 7 + .../vim_commands_executor.py | 6 +- .../src/config_manager/__init__.py | 8 +- python_coderunner/src/config_manager/basic.py | 265 ++++-------------- .../src/config_manager/config_field.py | 50 ++++ .../src/config_manager/exceptions.py | 21 ++ .../src/config_manager/interface.py | 61 ++++ .../src/config_manager/vim_config_manager.py | 25 +- .../editor_service_for_coderunner/basic.py | 6 +- python_coderunner/src/validators/__init__.py | 5 + .../src/validators/bool_validator.py | 13 + .../validators/dispatchers_order_validator.py | 18 ++ .../src/validators/dispatchers_validator.py | 16 ++ .../src/validators/exceptions.py | 2 + python_coderunner/src/validators/interface.py | 16 ++ .../src/validators/str_validator.py | 11 + .../test_basic_config_validator.py | 115 -------- python_coderunner/tests/unit/conftest.py | 97 +++++-- 22 files changed, 463 insertions(+), 380 deletions(-) create mode 100644 python_coderunner/src/command_dispatcher_strategy_selector/types.py create mode 100644 python_coderunner/src/config_manager/config_field.py create mode 100644 python_coderunner/src/config_manager/exceptions.py create mode 100644 python_coderunner/src/config_manager/interface.py create mode 100644 python_coderunner/src/validators/__init__.py create mode 100644 python_coderunner/src/validators/bool_validator.py create mode 100644 python_coderunner/src/validators/dispatchers_order_validator.py create mode 100644 python_coderunner/src/validators/dispatchers_validator.py create mode 100644 python_coderunner/src/validators/exceptions.py create mode 100644 python_coderunner/src/validators/interface.py create mode 100644 python_coderunner/src/validators/str_validator.py delete mode 100644 python_coderunner/tests/unit/config_manager/test_basic_config_validator.py diff --git a/python_coderunner/src/coderunner/coderunner.py b/python_coderunner/src/coderunner/coderunner.py index 910ce2e..ef26907 100644 --- a/python_coderunner/src/coderunner/coderunner.py +++ b/python_coderunner/src/coderunner/coderunner.py @@ -5,7 +5,7 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import ICommandsExecutor -from ..config_manager import TBasicConfigManager +from ..config_manager import IConfigManager from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner from ..message_printer import IMessagePrinter @@ -14,13 +14,13 @@ class TCodeRunner: def __init__( self, *, - config_manager: TBasicConfigManager, + config_manager: IConfigManager, editor_service: TBasicEditorServiceForCodeRunner, command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector, commands_executor: ICommandsExecutor, message_printer: IMessagePrinter, ): - self._config_manager: TBasicConfigManager = config_manager + self._config_manager: IConfigManager = config_manager self._editor_service: TBasicEditorServiceForCodeRunner = editor_service self._command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( command_dispatcher_strategy_selector diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index f74a100..f3f7f51 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -15,21 +15,30 @@ ) from ..commands_executor import TVimCommandsExecutor from ..config_manager import ( - TBasicConfigValidator, + ConfigField, + EDispatchersTypes, TVimConfigGetter, TVimConfigManager, + UndefinedValueError, ) from ..editor import TVimEditor from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner from ..file_info_extractor import TVimFileInfoExtractor from ..message_printer import TVimMessagePrinter from ..project_info_extractor import TVimProjectInfoExtractor +from ..validators import ( + TBoolValidator, + TDispatchersOrderValidator, + TDispatchersValidator, + TStrValidator, +) from .interface import ICodeRunnerFactory class TVimCodeRunnerFactory(ICodeRunnerFactory): def create(self) -> Optional[TCodeRunner]: - config_manager: TVimConfigManager = TVimConfigManager(TVimConfigGetter(), TBasicConfigValidator()) + config_getter = TVimConfigGetter() + config_manager = self._create_config_manager(config_getter) message_printer: TVimMessagePrinter = TVimMessagePrinter() try: @@ -56,11 +65,83 @@ def create(self) -> Optional[TCodeRunner]: message_printer=message_printer, ) - except ValueError as e: + except (ValueError, UndefinedValueError) as e: message_printer.error(str(e)) return None + def _create_config_manager(self, config_getter: TVimConfigGetter) -> TVimConfigManager: + """Creates TVimConfigManager with ConfigField objects""" + config_manager = TVimConfigManager( + by_file_ext_field=ConfigField( + name="by_file_ext", + getter=config_getter.get_by_file_ext, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_file_type_field=ConfigField( + name="by_file_type", + getter=config_getter.get_by_file_type, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_glob_field=ConfigField( + name="by_glob", + getter=config_getter.get_by_glob, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + dispatchers_order_field=ConfigField( + name="runners_order", + getter=config_getter.get_dispatchers_order, + validator=TDispatchersOrderValidator(set(EDispatchersTypes)), + allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), + ), + coderunner_tempfile_prefix_field=ConfigField( + name="coderunner_tempfile_prefix", + getter=config_getter.get_coderunner_tempfile_prefix, + validator=TStrValidator(), + allowed_values_description="str value", + ), + executor_field=ConfigField( + name="executor", + getter=config_getter.get_executor, + validator=TStrValidator(), + allowed_values_description="str value", + ), + ignore_selection_field=ConfigField( + name="ignore_selection", + getter=config_getter.get_ignore_selection, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + respect_shebang_field=ConfigField( + name="respect_shebang", + getter=config_getter.get_respect_shebang, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + remove_coderunner_tempfiles_on_exit_field=ConfigField( + name="coderunner_remove_coderunner_tempfiles_on_exit", + getter=config_getter.get_remove_coderunner_tempfiles_on_exit, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_all_files_before_run_field=ConfigField( + name="save_all_files_before_run", + getter=config_getter.get_save_all_files_before_run, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_file_before_run_field=ConfigField( + name="save_file_before_run", + getter=config_getter.get_save_file_before_run, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + ) + return config_manager + def _create_command_dispatcher_strategy_selector( self, config_manager: TVimConfigManager, diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py b/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py index cb8e0bf..a39b279 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py @@ -1,3 +1,4 @@ from .basic import ( TBasicCommandDispatcherStrategySelector, ) +from .types import EDispatchersTypes diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py index 89f2d2e..21571d6 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py @@ -7,20 +7,21 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from ..config_manager import EDispatchersTypes, TBasicConfigManager +from ..config_manager import IConfigManager +from .types import EDispatchersTypes class TBasicCommandDispatcherStrategySelector: def __init__( self, *, - config_manager: TBasicConfigManager, + config_manager: IConfigManager, shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher, file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, ): - self._config_manager: TBasicConfigManager = config_manager + self._config_manager: IConfigManager = config_manager self._shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher = ( shebang_command_builders_dispatcher ) diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/types.py b/python_coderunner/src/command_dispatcher_strategy_selector/types.py new file mode 100644 index 0000000..e31d2c4 --- /dev/null +++ b/python_coderunner/src/command_dispatcher_strategy_selector/types.py @@ -0,0 +1,7 @@ +from enum import StrEnum + + +class EDispatchersTypes(StrEnum): + BY_FILE_EXT = "by_file_ext" + BY_FILE_TYPE = "by_file_type" + BY_GLOB = "by_glob" diff --git a/python_coderunner/src/commands_executor/vim_commands_executor.py b/python_coderunner/src/commands_executor/vim_commands_executor.py index 10d796d..39bea09 100644 --- a/python_coderunner/src/commands_executor/vim_commands_executor.py +++ b/python_coderunner/src/commands_executor/vim_commands_executor.py @@ -1,12 +1,12 @@ import vim -from ..config_manager import TBasicConfigManager +from ..config_manager import IConfigManager from .inteface import ICommandsExecutor class TVimCommandsExecutor(ICommandsExecutor): - def __init__(self, config_manager: TBasicConfigManager): - self._config_manager: TBasicConfigManager = config_manager + def __init__(self, config_manager: IConfigManager): + self._config_manager: IConfigManager = config_manager def execute(self, command: str) -> None: executor_command: str = self._config_manager.get_executor() diff --git a/python_coderunner/src/config_manager/__init__.py b/python_coderunner/src/config_manager/__init__.py index bb5dadb..6ed505d 100644 --- a/python_coderunner/src/config_manager/__init__.py +++ b/python_coderunner/src/config_manager/__init__.py @@ -2,8 +2,12 @@ EDispatchersTypes, IConfigGetter, TBasicConfigManager, - TBasicConfigValidator, UndefinedValueError, - ValidationError, ) +from .config_field import TConfigField +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .interface import IConfigManager from .vim_config_manager import TVimConfigGetter, TVimConfigManager + +# Public exports +ConfigField = TConfigField diff --git a/python_coderunner/src/config_manager/basic.py b/python_coderunner/src/config_manager/basic.py index dd128a2..3c916ad 100644 --- a/python_coderunner/src/config_manager/basic.py +++ b/python_coderunner/src/config_manager/basic.py @@ -1,8 +1,12 @@ from abc import ABC, abstractmethod from enum import StrEnum -from typing import Any, ClassVar, Dict, List +from typing import Any, Dict, List +from .config_field import TConfigField +from .interface import IConfigManager + +# Kept for compatibility (exported from __init__.py) class EDispatchersTypes(StrEnum): BY_FILE_EXT = "by_file_ext" BY_FILE_TYPE = "by_file_type" @@ -10,10 +14,12 @@ class EDispatchersTypes(StrEnum): class UndefinedValueError(ValueError): - pass + """Config value is not defined (raised by IConfigGetter)""" class IConfigGetter(ABC): + """Interface for getting raw config values""" + @abstractmethod def get_by_file_ext(self) -> Any: raise NotImplementedError @@ -59,230 +65,71 @@ def get_save_file_before_run(self) -> Any: raise NotImplementedError -class ValidationError(ValueError): - pass - - -class TBasicConfigValidator: - def validate_bool(self, value: Any) -> bool: - if isinstance(value, bool): - return value - if str(value).strip() in ("0", "1"): - return bool(int(value)) - raise ValidationError(f"Invalid bool value: {value}.") - - def validate_str(self, value: Any) -> str: - if isinstance(value, str): - return value - raise ValidationError(f"Invalid str type: {type(value)}.") - - def validate_dispatcher(self, value: Any) -> Dict[str, str]: - if not isinstance(value, dict): - raise ValidationError(f"Invalid dispatcher container type: {type(value)}.") - for key, val in value.items(): - if not isinstance(key, str): - raise ValidationError(f"Invalid type in dispatcher dict key: {type(key)}.") - if not isinstance(val, str): - raise ValidationError(f"Invalid type in dispatcher dict value: {type(val)}.") - return value - - def validate_dispatchers_order(self, value: Any) -> List[EDispatchersTypes]: - if not isinstance(value, list): - raise ValidationError(f"Invalid dispatcher order container type: {type(value)}.") - - if invalid_items := [v for v in value if v not in set(EDispatchersTypes)]: - raise ValidationError(f"Invalid dispatcher types values: {', '.join(map(str, invalid_items))}.") - return value - - -class TBasicConfigManager: - by_file_ext_alias: ClassVar[str] = "by_file_ext" - by_file_ext_allowed_values: ClassVar[str] = "Dict[str, str] value" - by_file_type_alias: ClassVar[str] = "by_file_type" - by_file_type_allowed_values: ClassVar[str] = "Dict[str, str] value" - by_glob_alias: ClassVar[str] = "by_glob" - by_glob_allowed_values: ClassVar[str] = "Dict[str, str] value" - - dispatchers_order_alias: ClassVar[str] = "runners_order" - dispatchers_order_allowed_values: ClassVar[str] = ", ".join( - dispatcher_type.value for dispatcher_type in EDispatchersTypes - ) - - coderunner_tempfile_prefix_alias: ClassVar[str] = "coderunner_tempfile_prefix" - coderunner_tempfile_prefix_allowed_values: ClassVar[str] = "str value" - executor_alias: ClassVar[str] = "executor" - executor_allowed_values: ClassVar[str] = "str value" - - bool_allowed_values: ClassVar[str] = "0 or 1" - ignore_selection_alias: ClassVar[str] = "ignore_selection" - ignore_selection_allowed_values: ClassVar[str] = bool_allowed_values - respect_shebang_alias: ClassVar[str] = "respect_shebang" - respect_shebang_allowed_values: ClassVar[str] = bool_allowed_values - remove_coderunner_tempfiles_on_exit_alias: ClassVar[str] = "coderunner_remove_coderunner_tempfiles_on_exit" - remove_coderunner_tempfiles_on_exit_allowed_values: ClassVar[str] = bool_allowed_values - save_all_files_before_run_alias: ClassVar[str] = "save_all_files_before_run" - save_all_files_before_run_allowed_values: ClassVar[str] = bool_allowed_values - save_file_before_run_alias: ClassVar[str] = "save_file_before_run" - save_file_before_run_allowed_values: ClassVar[str] = bool_allowed_values - - def __init__(self, config_getter: IConfigGetter, config_validator: TBasicConfigValidator): - self._config_getter: IConfigGetter = config_getter - self._config_validator: TBasicConfigValidator = config_validator +class TBasicConfigManager(IConfigManager): + """ + Configuration manager. + Aggregates TConfigField objects, each of which encapsulates: + - Value retrieval + - Validation + - Field metadata + - Error handling + """ + + def __init__( + self, + by_file_ext_field: TConfigField[Dict[str, str]], + by_file_type_field: TConfigField[Dict[str, str]], + by_glob_field: TConfigField[Dict[str, str]], + dispatchers_order_field: TConfigField[List], + coderunner_tempfile_prefix_field: TConfigField[str], + executor_field: TConfigField[str], + ignore_selection_field: TConfigField[bool], + respect_shebang_field: TConfigField[bool], + remove_coderunner_tempfiles_on_exit_field: TConfigField[bool], + save_all_files_before_run_field: TConfigField[bool], + save_file_before_run_field: TConfigField[bool], + ): + self._by_file_ext = by_file_ext_field + self._by_file_type = by_file_type_field + self._by_glob = by_glob_field + self._dispatchers_order = dispatchers_order_field + self._coderunner_tempfile_prefix = coderunner_tempfile_prefix_field + self._executor = executor_field + self._ignore_selection = ignore_selection_field + self._respect_shebang = respect_shebang_field + self._remove_coderunner_tempfiles_on_exit = remove_coderunner_tempfiles_on_exit_field + self._save_all_files_before_run = save_all_files_before_run_field + self._save_file_before_run = save_file_before_run_field def get_by_file_ext(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_file_ext() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_file_ext_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.by_file_ext_alias, self.by_file_ext_allowed_values) - ) + return self._by_file_ext.get() def get_by_file_type(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_file_type() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_file_type_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.by_file_type_alias, self.by_file_type_allowed_values) - ) + return self._by_file_type.get() def get_by_glob(self) -> Dict[str, str]: - try: - raw_value: Any = self._config_getter.get_by_glob() - validated_value: Dict[str, str] = self._config_validator.validate_dispatcher(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.by_glob_allowed_values)) - except ValidationError as e: - raise ValueError(self._format_validation_error_message(e, self.by_glob_alias, self.by_glob_allowed_values)) - - def get_dispatchers_order(self) -> List[EDispatchersTypes]: - try: - raw_value: Any = self._config_getter.get_dispatchers_order() - validated_value: List[EDispatchersTypes] = self._config_validator.validate_dispatchers_order(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.dispatchers_order_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.dispatchers_order_alias, self.dispatchers_order_allowed_values - ) - ) + return self._by_glob.get() + + def get_dispatchers_order(self) -> List: + return self._dispatchers_order.get() def get_coderunner_tempfile_prefix(self) -> str: - try: - raw_value: Any = self._config_getter.get_coderunner_tempfile_prefix() - validated_value: str = self._config_validator.validate_str(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.coderunner_tempfile_prefix_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.coderunner_tempfile_prefix_alias, self.coderunner_tempfile_prefix_allowed_values - ) - ) + return self._coderunner_tempfile_prefix.get() def get_executor(self) -> str: - try: - raw_value: Any = self._config_getter.get_executor() - validated_value: str = self._config_validator.validate_str(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.executor_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message(e, self.executor_alias, self.executor_allowed_values) - ) + return self._executor.get() def get_ignore_selection(self) -> bool: - try: - raw_value: Any = self._config_getter.get_ignore_selection() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.ignore_selection_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.ignore_selection_alias, self.ignore_selection_allowed_values - ) - ) + return self._ignore_selection.get() def get_respect_shebang(self) -> bool: - try: - raw_value: Any = self._config_getter.get_respect_shebang() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.respect_shebang_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.respect_shebang_alias, self.respect_shebang_allowed_values - ) - ) + return self._respect_shebang.get() def get_remove_coderunner_tempfiles_on_exit(self) -> bool: - try: - raw_value: Any = self._config_getter.get_remove_coderunner_tempfiles_on_exit() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.remove_coderunner_tempfiles_on_exit_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, - self.remove_coderunner_tempfiles_on_exit_alias, - self.remove_coderunner_tempfiles_on_exit_allowed_values, - ) - ) + return self._remove_coderunner_tempfiles_on_exit.get() def get_save_all_files_before_run(self) -> bool: - try: - raw_value: Any = self._config_getter.get_save_all_files_before_run() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError( - self._format_undefined_value_error_message(e, self.save_all_files_before_run_allowed_values) - ) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.save_all_files_before_run_alias, self.save_all_files_before_run_allowed_values - ) - ) + return self._save_all_files_before_run.get() def get_save_file_before_run(self) -> bool: - try: - raw_value: Any = self._config_getter.get_save_file_before_run() - validated_value: bool = self._config_validator.validate_bool(raw_value) - return validated_value - except UndefinedValueError as e: - raise ValueError(self._format_undefined_value_error_message(e, self.save_file_before_run_allowed_values)) - except ValidationError as e: - raise ValueError( - self._format_validation_error_message( - e, self.save_file_before_run_alias, self.save_file_before_run_allowed_values - ) - ) - - def _format_undefined_value_error_message(self, e: UndefinedValueError, allowed_values: str) -> str: - return f"{e} Allowed values: {allowed_values}." - - def _format_validation_error_message(self, e: ValidationError, var_alias: str, allowed_values: str) -> str: - return f"Invalid value of the {var_alias} variable. {e} Allowed values: {allowed_values}." + return self._save_file_before_run.get() diff --git a/python_coderunner/src/config_manager/config_field.py b/python_coderunner/src/config_manager/config_field.py new file mode 100644 index 0000000..de6df15 --- /dev/null +++ b/python_coderunner/src/config_manager/config_field.py @@ -0,0 +1,50 @@ +from typing import Any, Callable, Generic, TypeVar + +from ..validators.interface import IValidator +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError + +T = TypeVar("T") + + +class UndefinedValueError(Exception): + """Config value is not defined""" + + +class TConfigField(Generic[T]): + """ + Self-describing field object that encapsulates: + - Getting value (getter) + - Validating value (validator) + - Field metadata (name, allowed_values) + - Error handling + """ + + def __init__( + self, + name: str, + getter: Callable[[], Any], + validator: IValidator[T], + allowed_values_description: str, + ): + self.name = name + self.getter = getter + self.validator = validator + self.allowed_values_description = allowed_values_description + + def get(self) -> T: + """Gets and validates config value""" + try: + raw_value = self.getter() + except UndefinedValueError: + raise ConfigFieldNotFoundError(self.name, self.allowed_values_description) + + try: + return self.validator.validate(raw_value) + except Exception as e: + # Catch validator exception and convert it + raise ConfigFieldValidationError( + field_name=self.name, + value=raw_value, + reason=str(e), + allowed_values=self.allowed_values_description, + ) from e diff --git a/python_coderunner/src/config_manager/exceptions.py b/python_coderunner/src/config_manager/exceptions.py new file mode 100644 index 0000000..d4bc840 --- /dev/null +++ b/python_coderunner/src/config_manager/exceptions.py @@ -0,0 +1,21 @@ +from typing import Any + + +class ConfigFieldNotFoundError(Exception): + """Поле конфига не найдено""" + + def __init__(self, field_name: str, allowed_values: str): + self.field_name = field_name + self.allowed_values = allowed_values + super().__init__(f"Config parameter '{field_name}' not defined. Allowed values: {allowed_values}.") + + +class ConfigFieldValidationError(Exception): + """Значение поля конфига некорректно""" + + def __init__(self, field_name: str, value: Any, reason: str, allowed_values: str): + self.field_name = field_name + self.value = value + self.reason = reason + self.allowed_values = allowed_values + super().__init__(f"Invalid value of {field_name}: {reason}. Got: {value}. Allowed values: {allowed_values}.") diff --git a/python_coderunner/src/config_manager/interface.py b/python_coderunner/src/config_manager/interface.py new file mode 100644 index 0000000..b7aed4a --- /dev/null +++ b/python_coderunner/src/config_manager/interface.py @@ -0,0 +1,61 @@ +from abc import ABC, abstractmethod +from typing import Dict, List + + +class IConfigManager(ABC): + """Интерфейс менеджера конфигурации""" + + @abstractmethod + def get_by_file_ext(self) -> Dict[str, str]: + """Получает конфиг для диспетчеризации по расширению файла""" + raise NotImplementedError + + @abstractmethod + def get_by_file_type(self) -> Dict[str, str]: + """Получает конфиг для диспетчеризации по типу файла""" + raise NotImplementedError + + @abstractmethod + def get_by_glob(self) -> Dict[str, str]: + """Получает конфиг для диспетчеризации по glob паттернам""" + raise NotImplementedError + + @abstractmethod + def get_dispatchers_order(self) -> List: + """Получает порядок приоритета диспетчеров""" + raise NotImplementedError + + @abstractmethod + def get_coderunner_tempfile_prefix(self) -> str: + """Получает префикс для временных файлов кодраннера""" + raise NotImplementedError + + @abstractmethod + def get_executor(self) -> str: + """Получает исполнитель команд""" + raise NotImplementedError + + @abstractmethod + def get_ignore_selection(self) -> bool: + """Получает флаг игнорирования выделения""" + raise NotImplementedError + + @abstractmethod + def get_respect_shebang(self) -> bool: + """Получает флаг уважения shebang""" + raise NotImplementedError + + @abstractmethod + def get_remove_coderunner_tempfiles_on_exit(self) -> bool: + """Получает флаг удаления временных файлов при выходе""" + raise NotImplementedError + + @abstractmethod + def get_save_all_files_before_run(self) -> bool: + """Получает флаг сохранения всех файлов перед запуском""" + raise NotImplementedError + + @abstractmethod + def get_save_file_before_run(self) -> bool: + """Получает флаг сохранения текущего файла перед запуском""" + raise NotImplementedError diff --git a/python_coderunner/src/config_manager/vim_config_manager.py b/python_coderunner/src/config_manager/vim_config_manager.py index c094d44..7702168 100644 --- a/python_coderunner/src/config_manager/vim_config_manager.py +++ b/python_coderunner/src/config_manager/vim_config_manager.py @@ -1,15 +1,13 @@ -from typing import Any, ClassVar +from typing import Any import vim -from .basic import ( - IConfigGetter, - TBasicConfigManager, - UndefinedValueError, -) +from .basic import IConfigGetter, TBasicConfigManager, UndefinedValueError class TVimConfigGetter(IConfigGetter): + """Gets config values from Vim variables""" + def get_by_file_ext(self) -> Any: return self._get_vim_var("g:coderunner_by_file_ext") @@ -51,17 +49,4 @@ def _get_vim_var(self, var_name: str) -> Any: class TVimConfigManager(TBasicConfigManager): - by_file_ext_alias: ClassVar[str] = "g:coderunner_by_file_ext" - by_file_type_alias: ClassVar[str] = "g:coderunner_by_file_type" - by_glob_alias: ClassVar[str] = "g:coderunner_by_glob" - - dispatchers_order_alias: ClassVar[str] = "g:coderunner_runners_order" - - coderunner_tempfile_prefix_alias: ClassVar[str] = "g:coderunner_tempfile_prefix" - executor_alias: ClassVar[str] = "g:coderunner_executor" - - ignore_selection_alias: ClassVar[str] = "g:coderunner_ignore_selection" - respect_shebang_alias: ClassVar[str] = "g:coderunner_respect_shebang" - remove_coderunner_tempfiles_on_exit_alias: ClassVar[str] = "g:coderunner_remove_coderunner_tempfiles_on_exit" - save_all_files_before_run_alias: ClassVar[str] = "g:coderunner_save_all_files_before_run" - save_file_before_run_alias: ClassVar[str] = "g:coderunner_save_file_before_run" + """Config Manager for Vim, created through factory""" diff --git a/python_coderunner/src/editor_service_for_coderunner/basic.py b/python_coderunner/src/editor_service_for_coderunner/basic.py index b0d5c6b..335fac5 100644 --- a/python_coderunner/src/editor_service_for_coderunner/basic.py +++ b/python_coderunner/src/editor_service_for_coderunner/basic.py @@ -3,14 +3,14 @@ from tempfile import NamedTemporaryFile from typing import Generator, List -from ..config_manager import TBasicConfigManager +from ..config_manager import IConfigManager from ..editor import IEditor from ..file_info_extractor import IFileInfoExtractor class TBasicEditorServiceForCodeRunner: - def __init__(self, config_manager: TBasicConfigManager, editor: IEditor, file_info_extractor: IFileInfoExtractor): - self._config_manager: TBasicConfigManager = config_manager + def __init__(self, config_manager: IConfigManager, editor: IEditor, file_info_extractor: IFileInfoExtractor): + self._config_manager: IConfigManager = config_manager self._editor: IEditor = editor self._file_info_extractor: IFileInfoExtractor = file_info_extractor self._temp_files: List[str] = [] diff --git a/python_coderunner/src/validators/__init__.py b/python_coderunner/src/validators/__init__.py new file mode 100644 index 0000000..5de9b9c --- /dev/null +++ b/python_coderunner/src/validators/__init__.py @@ -0,0 +1,5 @@ +from .bool_validator import TBoolValidator +from .dispatchers_order_validator import TDispatchersOrderValidator +from .dispatchers_validator import TDispatchersValidator +from .interface import IValidator +from .str_validator import TStrValidator diff --git a/python_coderunner/src/validators/bool_validator.py b/python_coderunner/src/validators/bool_validator.py new file mode 100644 index 0000000..a3f7ef2 --- /dev/null +++ b/python_coderunner/src/validators/bool_validator.py @@ -0,0 +1,13 @@ +from typing import Any + +from .exceptions import ValidationError +from .interface import IValidator + + +class TBoolValidator(IValidator[bool]): + def validate(self, value: Any) -> bool: + if isinstance(value, bool): + return value + if str(value).strip() in ("0", "1"): + return bool(int(value)) + raise ValidationError(f"Invalid bool value: {value}.") diff --git a/python_coderunner/src/validators/dispatchers_order_validator.py b/python_coderunner/src/validators/dispatchers_order_validator.py new file mode 100644 index 0000000..833a5e9 --- /dev/null +++ b/python_coderunner/src/validators/dispatchers_order_validator.py @@ -0,0 +1,18 @@ +from typing import Any, List, Set + +from ..command_dispatcher_strategy_selector import EDispatchersTypes +from .exceptions import ValidationError +from .interface import IValidator + + +class TDispatchersOrderValidator(IValidator[List[EDispatchersTypes]]): + def __init__(self): + self.allowed_dispatcher_types: Set[EDispatchersTypes] = set(EDispatchersTypes) + + def validate(self, value: Any) -> List[EDispatchersTypes]: + if not isinstance(value, list): + raise ValidationError(f"Invalid dispatcher order container type: {type(value)}.") + + if invalid_items := [v for v in value if v not in self.allowed_dispatcher_types]: + raise ValidationError(f"Invalid dispatcher types values: {', '.join(map(str, invalid_items))}.") + return value diff --git a/python_coderunner/src/validators/dispatchers_validator.py b/python_coderunner/src/validators/dispatchers_validator.py new file mode 100644 index 0000000..77872ae --- /dev/null +++ b/python_coderunner/src/validators/dispatchers_validator.py @@ -0,0 +1,16 @@ +from typing import Any, Dict + +from .exceptions import ValidationError +from .interface import IValidator + + +class TDispatchersValidator(IValidator[Dict[str, str]]): + def validate(self, value: Any) -> Dict[str, str]: + if not isinstance(value, dict): + raise ValidationError(f"Invalid dispatcher container type: {type(value)}.") + for key, val in value.items(): + if not isinstance(key, str): + raise ValidationError(f"Invalid type in dispatcher dict key: {type(key)}.") + if not isinstance(val, str): + raise ValidationError(f"Invalid type in dispatcher dict value: {type(val)}.") + return value diff --git a/python_coderunner/src/validators/exceptions.py b/python_coderunner/src/validators/exceptions.py new file mode 100644 index 0000000..b60a2e3 --- /dev/null +++ b/python_coderunner/src/validators/exceptions.py @@ -0,0 +1,2 @@ +class ValidationError(Exception): + """Raised when value validation fails""" diff --git a/python_coderunner/src/validators/interface.py b/python_coderunner/src/validators/interface.py new file mode 100644 index 0000000..df4e18f --- /dev/null +++ b/python_coderunner/src/validators/interface.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from typing import Any, Generic, TypeVar + +ValueType = TypeVar("ValueType") + + +class IValidator(ABC, Generic[ValueType]): + """Base interface for all validators""" + + @abstractmethod + def validate(self, value: Any) -> ValueType: + """ + Validates value and returns typed result. + Raises ValidationError on validation failure. + """ + raise NotImplementedError diff --git a/python_coderunner/src/validators/str_validator.py b/python_coderunner/src/validators/str_validator.py new file mode 100644 index 0000000..aff83ae --- /dev/null +++ b/python_coderunner/src/validators/str_validator.py @@ -0,0 +1,11 @@ +from typing import Any + +from .exceptions import ValidationError +from .interface import IValidator + + +class TStrValidator(IValidator[str]): + def validate(self, value: Any) -> str: + if isinstance(value, str): + return value + raise ValidationError(f"Invalid str type: {type(value)}.") diff --git a/python_coderunner/tests/unit/config_manager/test_basic_config_validator.py b/python_coderunner/tests/unit/config_manager/test_basic_config_validator.py deleted file mode 100644 index 3e06847..0000000 --- a/python_coderunner/tests/unit/config_manager/test_basic_config_validator.py +++ /dev/null @@ -1,115 +0,0 @@ -from contextlib import nullcontext as does_not_raise - -import pytest - -from src.config_manager import ( - EDispatchersTypes, - TBasicConfigValidator, - ValidationError, -) - - -class TestConfigValidator: - @pytest.mark.parametrize( - ("content", "expected", "expectation"), - ( - (True, True, does_not_raise()), - (False, False, does_not_raise()), - ("1", True, does_not_raise()), - ("0", False, does_not_raise()), - (1, True, does_not_raise()), - (0, False, does_not_raise()), - ("invalid", None, pytest.raises(ValidationError)), - (None, None, pytest.raises(ValidationError)), - ), - ) - def test_validate_bool( - self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): - with expectation: - result = fixture_config_validator.validate_bool(content) - if expected is not None: - assert result == expected - - @pytest.mark.parametrize( - ("content", "expected", "expectation"), - ( - ("test string", "test string", does_not_raise()), - ("", "", does_not_raise()), - ("123", "123", does_not_raise()), - (123, None, pytest.raises(ValidationError)), - (True, None, pytest.raises(ValidationError)), - (None, None, pytest.raises(ValidationError)), - ([], None, pytest.raises(ValidationError)), - ({}, None, pytest.raises(ValidationError)), - ), - ) - def test_validate_str( - self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): - with expectation: - result = fixture_config_validator.validate_str(content) - if expected is not None: - assert result == expected - - @pytest.mark.parametrize( - ("content", "expected", "expectation"), - ( - ( - {"*.py": "python", "*.js": "node"}, - {"*.py": "python", "*.js": "node"}, - does_not_raise(), - ), - ({"*.py": "python"}, {"*.py": "python"}, does_not_raise()), - ("invalid", None, pytest.raises(ValidationError)), - (123, None, pytest.raises(ValidationError)), - (None, None, pytest.raises(ValidationError)), - ({"*.py": 123}, None, pytest.raises(ValidationError)), - ({123: "python"}, None, pytest.raises(ValidationError)), - ), - ) - def test_validate_dispatcher( - self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): - with expectation: - result = fixture_config_validator.validate_dispatcher(content) - if expected is not None: - assert result == expected - - @pytest.mark.parametrize( - ("content", "expected", "expectation"), - ( - ([EDispatchersTypes.BY_GLOB], [EDispatchersTypes.BY_GLOB], does_not_raise()), - ( - [EDispatchersTypes.BY_FILE_EXT, EDispatchersTypes.BY_GLOB], - [EDispatchersTypes.BY_FILE_EXT, EDispatchersTypes.BY_GLOB], - does_not_raise(), - ), - (["invalid"], None, pytest.raises(ValidationError)), - ([1], None, pytest.raises(ValidationError)), - ({}, None, pytest.raises(ValidationError)), - ), - ) - def test_validate_dispatchers_order( - self, - fixture_config_validator: TBasicConfigValidator, - content, - expected, - expectation, - ): - with expectation: - result: list[EDispatchersTypes] = fixture_config_validator.validate_dispatchers_order(content) - if expected is not None: - assert result == expected diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index 2a50973..5de3a7f 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -4,6 +4,7 @@ import tempfile import unittest from typing import Dict, Generator, Tuple +from unittest import mock from unittest.mock import MagicMock import pytest @@ -18,9 +19,10 @@ TShebangCommandBuildersDispatcher, ) from src.config_manager import ( + ConfigField, + EDispatchersTypes, IConfigGetter, - TBasicConfigManager, - TBasicConfigValidator, + IConfigManager, TVimConfigGetter, TVimConfigManager, ) @@ -32,18 +34,85 @@ IProjectInfoExtractor, TVimProjectInfoExtractor, ) +from src.validators import TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator @pytest.fixture(params=(lazy_fixture("fixture_vim_config_manager"),)) -def fixture_config_manager(request: pytest.FixtureRequest) -> TBasicConfigManager: +def fixture_config_manager(request: pytest.FixtureRequest) -> IConfigManager: return request.param @pytest.fixture -def fixture_vim_config_manager( - fixture_config_getter: IConfigGetter, fixture_config_validator: TBasicConfigValidator -) -> TBasicConfigManager: - return TVimConfigManager(fixture_config_getter, fixture_config_validator) +def fixture_vim_config_manager(fixture_config_getter: IConfigGetter) -> IConfigManager: + """Create TVimConfigManager with all ConfigField objects""" + return TVimConfigManager( + by_file_ext_field=ConfigField( + name="by_file_ext", + getter=fixture_config_getter.get_by_file_ext, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_file_type_field=ConfigField( + name="by_file_type", + getter=fixture_config_getter.get_by_file_type, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + by_glob_field=ConfigField( + name="by_glob", + getter=fixture_config_getter.get_by_glob, + validator=TDispatchersValidator(), + allowed_values_description="Dict[str, str] value", + ), + dispatchers_order_field=ConfigField( + name="runners_order", + getter=fixture_config_getter.get_dispatchers_order, + validator=TDispatchersOrderValidator(set(EDispatchersTypes)), + allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), + ), + coderunner_tempfile_prefix_field=ConfigField( + name="coderunner_tempfile_prefix", + getter=fixture_config_getter.get_coderunner_tempfile_prefix, + validator=TStrValidator(), + allowed_values_description="str value", + ), + executor_field=ConfigField( + name="executor", + getter=fixture_config_getter.get_executor, + validator=TStrValidator(), + allowed_values_description="str value", + ), + ignore_selection_field=ConfigField( + name="ignore_selection", + getter=fixture_config_getter.get_ignore_selection, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + respect_shebang_field=ConfigField( + name="respect_shebang", + getter=fixture_config_getter.get_respect_shebang, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + remove_coderunner_tempfiles_on_exit_field=ConfigField( + name="coderunner_remove_coderunner_tempfiles_on_exit", + getter=fixture_config_getter.get_remove_coderunner_tempfiles_on_exit, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_all_files_before_run_field=ConfigField( + name="save_all_files_before_run", + getter=fixture_config_getter.get_save_all_files_before_run, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + save_file_before_run_field=ConfigField( + name="save_file_before_run", + getter=fixture_config_getter.get_save_file_before_run, + validator=TBoolValidator(), + allowed_values_description="0 or 1", + ), + ) @pytest.fixture(params=(lazy_fixture("fixture_vim_config_getter"),)) @@ -56,16 +125,6 @@ def fixture_vim_config_getter() -> IConfigGetter: return TVimConfigGetter() -@pytest.fixture(params=(lazy_fixture("fixture_basic_config_validator"),)) -def fixture_config_validator(request: pytest.FixtureRequest) -> TBasicConfigValidator: - return request.param - - -@pytest.fixture -def fixture_basic_config_validator() -> TBasicConfigValidator: - return TBasicConfigValidator() - - @pytest.fixture def fixture_shebang_command_builders_dispatcher( fixture_file_info_extractor: IFileInfoExtractor, @@ -141,7 +200,7 @@ def fixture_vim_project_info_extractor( ) -> Generator[IProjectInfoExtractor, None, None]: with tempfile.TemporaryDirectory() as temp_dir: extractor: TVimProjectInfoExtractor = TVimProjectInfoExtractor(fixture_file_info_extractor) - with unittest.mock.patch.object( + with mock.patch.object( extractor, "get_workspace_root", return_value=temp_dir, @@ -164,7 +223,7 @@ def fixture_vim_file_info_extractor() -> Generator[IFileInfoExtractor, None, Non ".ts": "typescript", ".js": "javascript", } - with unittest.mock.patch.object( + with mock.patch.object( extractor, "get_file_type", side_effect=lambda file_path_abs: ext_to_lang.get(extractor.get_file_ext(file_path_abs)), From 1ec1a39a4444a7c1c91aaef850b409527de84196 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Mon, 9 Mar 2026 22:39:01 +0300 Subject: [PATCH 02/15] =?UTF-8?q?refactor:=20classes=20=E2=84=962?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/coderunner/coderunner.py | 6 ++--- .../vim_coderunner_factory.py | 2 +- .../basic.py | 6 ++--- .../vim_commands_executor.py | 6 ++--- .../src/config_manager/__init__.py | 4 +-- python_coderunner/src/config_manager/basic.py | 12 ++------- .../src/config_manager/exceptions.py | 4 +-- .../src/config_manager/interface.py | 26 +++++++++---------- .../editor_service_for_coderunner/basic.py | 6 ++--- ...ic_command_dispatcher_strategy_selector.py | 3 ++- python_coderunner/tests/unit/conftest.py | 8 +++--- 11 files changed, 38 insertions(+), 45 deletions(-) diff --git a/python_coderunner/src/coderunner/coderunner.py b/python_coderunner/src/coderunner/coderunner.py index ef26907..df942b2 100644 --- a/python_coderunner/src/coderunner/coderunner.py +++ b/python_coderunner/src/coderunner/coderunner.py @@ -5,7 +5,7 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import ICommandsExecutor -from ..config_manager import IConfigManager +from ..config_manager import IConfig from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner from ..message_printer import IMessagePrinter @@ -14,13 +14,13 @@ class TCodeRunner: def __init__( self, *, - config_manager: IConfigManager, + config_manager: IConfig, editor_service: TBasicEditorServiceForCodeRunner, command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector, commands_executor: ICommandsExecutor, message_printer: IMessagePrinter, ): - self._config_manager: IConfigManager = config_manager + self._config_manager: IConfig = config_manager self._editor_service: TBasicEditorServiceForCodeRunner = editor_service self._command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( command_dispatcher_strategy_selector diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index f3f7f51..9e0d0b2 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -11,12 +11,12 @@ TShebangCommandBuildersDispatcher, ) from ..command_dispatcher_strategy_selector import ( + EDispatchersTypes, TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import TVimCommandsExecutor from ..config_manager import ( ConfigField, - EDispatchersTypes, TVimConfigGetter, TVimConfigManager, UndefinedValueError, diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py index 21571d6..5e697c9 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py @@ -7,7 +7,7 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from ..config_manager import IConfigManager +from ..config_manager import IConfig from .types import EDispatchersTypes @@ -15,13 +15,13 @@ class TBasicCommandDispatcherStrategySelector: def __init__( self, *, - config_manager: IConfigManager, + config_manager: IConfig, shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher, file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, ): - self._config_manager: IConfigManager = config_manager + self._config_manager: IConfig = config_manager self._shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher = ( shebang_command_builders_dispatcher ) diff --git a/python_coderunner/src/commands_executor/vim_commands_executor.py b/python_coderunner/src/commands_executor/vim_commands_executor.py index 39bea09..07fd839 100644 --- a/python_coderunner/src/commands_executor/vim_commands_executor.py +++ b/python_coderunner/src/commands_executor/vim_commands_executor.py @@ -1,12 +1,12 @@ import vim -from ..config_manager import IConfigManager +from ..config_manager import IConfig from .inteface import ICommandsExecutor class TVimCommandsExecutor(ICommandsExecutor): - def __init__(self, config_manager: IConfigManager): - self._config_manager: IConfigManager = config_manager + def __init__(self, config_manager: IConfig): + self._config_manager: IConfig = config_manager def execute(self, command: str) -> None: executor_command: str = self._config_manager.get_executor() diff --git a/python_coderunner/src/config_manager/__init__.py b/python_coderunner/src/config_manager/__init__.py index 6ed505d..e218d30 100644 --- a/python_coderunner/src/config_manager/__init__.py +++ b/python_coderunner/src/config_manager/__init__.py @@ -1,13 +1,13 @@ from .basic import ( - EDispatchersTypes, IConfigGetter, TBasicConfigManager, UndefinedValueError, ) from .config_field import TConfigField from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError -from .interface import IConfigManager +from .interface import IConfig from .vim_config_manager import TVimConfigGetter, TVimConfigManager # Public exports ConfigField = TConfigField +IConfigManager = IConfig # Backward compatibility alias diff --git a/python_coderunner/src/config_manager/basic.py b/python_coderunner/src/config_manager/basic.py index 3c916ad..47aabb5 100644 --- a/python_coderunner/src/config_manager/basic.py +++ b/python_coderunner/src/config_manager/basic.py @@ -1,16 +1,8 @@ from abc import ABC, abstractmethod -from enum import StrEnum from typing import Any, Dict, List from .config_field import TConfigField -from .interface import IConfigManager - - -# Kept for compatibility (exported from __init__.py) -class EDispatchersTypes(StrEnum): - BY_FILE_EXT = "by_file_ext" - BY_FILE_TYPE = "by_file_type" - BY_GLOB = "by_glob" +from .interface import IConfig class UndefinedValueError(ValueError): @@ -65,7 +57,7 @@ def get_save_file_before_run(self) -> Any: raise NotImplementedError -class TBasicConfigManager(IConfigManager): +class TBasicConfigManager(IConfig): """ Configuration manager. Aggregates TConfigField objects, each of which encapsulates: diff --git a/python_coderunner/src/config_manager/exceptions.py b/python_coderunner/src/config_manager/exceptions.py index d4bc840..83529b1 100644 --- a/python_coderunner/src/config_manager/exceptions.py +++ b/python_coderunner/src/config_manager/exceptions.py @@ -2,7 +2,7 @@ class ConfigFieldNotFoundError(Exception): - """Поле конфига не найдено""" + """Config field not found""" def __init__(self, field_name: str, allowed_values: str): self.field_name = field_name @@ -11,7 +11,7 @@ def __init__(self, field_name: str, allowed_values: str): class ConfigFieldValidationError(Exception): - """Значение поля конфига некорректно""" + """Config field value is invalid""" def __init__(self, field_name: str, value: Any, reason: str, allowed_values: str): self.field_name = field_name diff --git a/python_coderunner/src/config_manager/interface.py b/python_coderunner/src/config_manager/interface.py index b7aed4a..f77649f 100644 --- a/python_coderunner/src/config_manager/interface.py +++ b/python_coderunner/src/config_manager/interface.py @@ -2,60 +2,60 @@ from typing import Dict, List -class IConfigManager(ABC): - """Интерфейс менеджера конфигурации""" +class IConfig(ABC): + """Configuration manager interface""" @abstractmethod def get_by_file_ext(self) -> Dict[str, str]: - """Получает конфиг для диспетчеризации по расширению файла""" + """Gets config for file extension-based dispatching""" raise NotImplementedError @abstractmethod def get_by_file_type(self) -> Dict[str, str]: - """Получает конфиг для диспетчеризации по типу файла""" + """Gets config for file type-based dispatching""" raise NotImplementedError @abstractmethod def get_by_glob(self) -> Dict[str, str]: - """Получает конфиг для диспетчеризации по glob паттернам""" + """Gets config for glob pattern-based dispatching""" raise NotImplementedError @abstractmethod def get_dispatchers_order(self) -> List: - """Получает порядок приоритета диспетчеров""" + """Gets the priority order of dispatchers""" raise NotImplementedError @abstractmethod def get_coderunner_tempfile_prefix(self) -> str: - """Получает префикс для временных файлов кодраннера""" + """Gets the prefix for coderunner temporary files""" raise NotImplementedError @abstractmethod def get_executor(self) -> str: - """Получает исполнитель команд""" + """Gets the command executor""" raise NotImplementedError @abstractmethod def get_ignore_selection(self) -> bool: - """Получает флаг игнорирования выделения""" + """Gets the flag for ignoring selection""" raise NotImplementedError @abstractmethod def get_respect_shebang(self) -> bool: - """Получает флаг уважения shebang""" + """Gets the flag for respecting shebang""" raise NotImplementedError @abstractmethod def get_remove_coderunner_tempfiles_on_exit(self) -> bool: - """Получает флаг удаления временных файлов при выходе""" + """Gets the flag for removing temporary files on exit""" raise NotImplementedError @abstractmethod def get_save_all_files_before_run(self) -> bool: - """Получает флаг сохранения всех файлов перед запуском""" + """Gets the flag for saving all files before run""" raise NotImplementedError @abstractmethod def get_save_file_before_run(self) -> bool: - """Получает флаг сохранения текущего файла перед запуском""" + """Gets the flag for saving current file before run""" raise NotImplementedError diff --git a/python_coderunner/src/editor_service_for_coderunner/basic.py b/python_coderunner/src/editor_service_for_coderunner/basic.py index 335fac5..4499bce 100644 --- a/python_coderunner/src/editor_service_for_coderunner/basic.py +++ b/python_coderunner/src/editor_service_for_coderunner/basic.py @@ -3,14 +3,14 @@ from tempfile import NamedTemporaryFile from typing import Generator, List -from ..config_manager import IConfigManager +from ..config_manager import IConfig from ..editor import IEditor from ..file_info_extractor import IFileInfoExtractor class TBasicEditorServiceForCodeRunner: - def __init__(self, config_manager: IConfigManager, editor: IEditor, file_info_extractor: IFileInfoExtractor): - self._config_manager: IConfigManager = config_manager + def __init__(self, config_manager: IConfig, editor: IEditor, file_info_extractor: IFileInfoExtractor): + self._config_manager: IConfig = config_manager self._editor: IEditor = editor self._file_info_extractor: IFileInfoExtractor = file_info_extractor self._temp_files: List[str] = [] diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index bcaa82e..c24913a 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -11,9 +11,10 @@ TShebangCommandBuildersDispatcher, ) from src.command_dispatcher_strategy_selector import ( + EDispatchersTypes, TBasicCommandDispatcherStrategySelector, ) -from src.config_manager import EDispatchersTypes, TBasicConfigManager +from src.config_manager import TBasicConfigManager @pytest.mark.parametrize( diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index 5de3a7f..fb6f7f7 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -18,11 +18,11 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) +from src.command_dispatcher_strategy_selector import EDispatchersTypes from src.config_manager import ( ConfigField, - EDispatchersTypes, + IConfig, IConfigGetter, - IConfigManager, TVimConfigGetter, TVimConfigManager, ) @@ -38,12 +38,12 @@ @pytest.fixture(params=(lazy_fixture("fixture_vim_config_manager"),)) -def fixture_config_manager(request: pytest.FixtureRequest) -> IConfigManager: +def fixture_config_manager(request: pytest.FixtureRequest) -> IConfig: return request.param @pytest.fixture -def fixture_vim_config_manager(fixture_config_getter: IConfigGetter) -> IConfigManager: +def fixture_vim_config_manager(fixture_config_getter: IConfigGetter) -> IConfig: """Create TVimConfigManager with all ConfigField objects""" return TVimConfigManager( by_file_ext_field=ConfigField( From 686ec20dbd41d3d6af1f1f0c21653ef2c5b7486d Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Mon, 9 Mar 2026 23:35:30 +0300 Subject: [PATCH 03/15] =?UTF-8?q?refactor:=20classes=20=E2=84=963?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_coderunner/pyproject.toml | 1 + .../src/coderunner/coderunner.py | 8 +- .../vim_coderunner_factory.py | 113 +++++++++--------- .../basic.py | 10 +- .../vim_commands_executor.py | 8 +- python_coderunner/src/config/__init__.py | 5 + .../src/{config_manager => config}/basic.py | 62 +--------- python_coderunner/src/config/config_field.py | 40 +++++++ python_coderunner/src/config/exceptions.py | 16 +++ .../src/config/getter/__init__.py | 15 +++ .../src/config/getter/exceptions.py | 2 + .../src/config/getter/interface.py | 3 + .../config/getter/vim_config_value_getter.py | 70 +++++++++++ .../{config_manager => config}/interface.py | 6 +- python_coderunner/src/config/vim_config.py | 5 + .../src/config_manager/__init__.py | 13 -- .../src/config_manager/config_field.py | 50 -------- .../src/config_manager/exceptions.py | 21 ---- .../src/config_manager/vim_config_manager.py | 52 -------- .../editor_service_for_coderunner/basic.py | 17 ++- python_coderunner/src/validators/__init__.py | 1 + ...ic_command_dispatcher_strategy_selector.py | 6 +- python_coderunner/tests/unit/conftest.py | 62 +++++----- 23 files changed, 280 insertions(+), 306 deletions(-) create mode 100644 python_coderunner/src/config/__init__.py rename python_coderunner/src/{config_manager => config}/basic.py (62%) create mode 100644 python_coderunner/src/config/config_field.py create mode 100644 python_coderunner/src/config/exceptions.py create mode 100644 python_coderunner/src/config/getter/__init__.py create mode 100644 python_coderunner/src/config/getter/exceptions.py create mode 100644 python_coderunner/src/config/getter/interface.py create mode 100644 python_coderunner/src/config/getter/vim_config_value_getter.py rename python_coderunner/src/{config_manager => config}/interface.py (91%) create mode 100644 python_coderunner/src/config/vim_config.py delete mode 100644 python_coderunner/src/config_manager/__init__.py delete mode 100644 python_coderunner/src/config_manager/config_field.py delete mode 100644 python_coderunner/src/config_manager/exceptions.py delete mode 100644 python_coderunner/src/config_manager/vim_config_manager.py diff --git a/python_coderunner/pyproject.toml b/python_coderunner/pyproject.toml index 8f17204..bfcca57 100644 --- a/python_coderunner/pyproject.toml +++ b/python_coderunner/pyproject.toml @@ -81,6 +81,7 @@ disable = [ "too-few-public-methods", "too-many-arguments", "too-many-positional-arguments", + "too-many-instance-attributes", "raise-missing-from", "line-too-long", ] diff --git a/python_coderunner/src/coderunner/coderunner.py b/python_coderunner/src/coderunner/coderunner.py index df942b2..696fc77 100644 --- a/python_coderunner/src/coderunner/coderunner.py +++ b/python_coderunner/src/coderunner/coderunner.py @@ -5,7 +5,7 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import ICommandsExecutor -from ..config_manager import IConfig +from ..config import IConfig from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner from ..message_printer import IMessagePrinter @@ -14,13 +14,13 @@ class TCodeRunner: def __init__( self, *, - config_manager: IConfig, + config: IConfig, editor_service: TBasicEditorServiceForCodeRunner, command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector, commands_executor: ICommandsExecutor, message_printer: IMessagePrinter, ): - self._config_manager: IConfig = config_manager + self._config: IConfig = config self._editor_service: TBasicEditorServiceForCodeRunner = editor_service self._command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( command_dispatcher_strategy_selector @@ -53,5 +53,5 @@ def remove_coderunner_tempfiles(self) -> None: self._editor_service.remove_coderunner_tempfiles() def on_exit(self) -> None: - if self._config_manager.get_remove_coderunner_tempfiles_on_exit(): + if self._config.get_remove_coderunner_tempfiles_on_exit(): self.remove_coderunner_tempfiles() diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index 9e0d0b2..cd0d714 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -15,10 +15,22 @@ TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import TVimCommandsExecutor -from ..config_manager import ( - ConfigField, - TVimConfigGetter, - TVimConfigManager, +from ..config import ( + TConfigField, + TVimConfig, +) +from ..config.getter import ( + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, UndefinedValueError, ) from ..editor import TVimEditor @@ -37,8 +49,7 @@ class TVimCodeRunnerFactory(ICodeRunnerFactory): def create(self) -> Optional[TCodeRunner]: - config_getter = TVimConfigGetter() - config_manager = self._create_config_manager(config_getter) + config = self._create_config() message_printer: TVimMessagePrinter = TVimMessagePrinter() try: @@ -47,18 +58,16 @@ def create(self) -> Optional[TCodeRunner]: editor: TVimEditor = TVimEditor() editor_service_for_coderunner: TBasicEditorServiceForCodeRunner = TBasicEditorServiceForCodeRunner( - config_manager, editor, file_info_extractor + config, editor, file_info_extractor ) command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector = ( - self._create_command_dispatcher_strategy_selector( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_command_dispatcher_strategy_selector(config, file_info_extractor, project_info_extractor) ) - commands_executor: TVimCommandsExecutor = TVimCommandsExecutor(config_manager) + commands_executor: TVimCommandsExecutor = TVimCommandsExecutor(config) return TCodeRunner( - config_manager=config_manager, + config=config, editor_service=editor_service_for_coderunner, command_dispatcher_strategy_selector=command_dispatcher_strategy_selector, commands_executor=commands_executor, @@ -70,81 +79,81 @@ def create(self) -> Optional[TCodeRunner]: return None - def _create_config_manager(self, config_getter: TVimConfigGetter) -> TVimConfigManager: - """Creates TVimConfigManager with ConfigField objects""" - config_manager = TVimConfigManager( - by_file_ext_field=ConfigField( + def _create_config(self) -> TVimConfig: + """Creates TVimConfig with ConfigField objects""" + config = TVimConfig( + by_file_ext_field=TConfigField( name="by_file_ext", - getter=config_getter.get_by_file_ext, + getter=TVimByFileExtConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - by_file_type_field=ConfigField( + by_file_type_field=TConfigField( name="by_file_type", - getter=config_getter.get_by_file_type, + getter=TVimByFileTypeConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - by_glob_field=ConfigField( + by_glob_field=TConfigField( name="by_glob", - getter=config_getter.get_by_glob, + getter=TVimByGlobConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - dispatchers_order_field=ConfigField( + dispatchers_order_field=TConfigField( name="runners_order", - getter=config_getter.get_dispatchers_order, - validator=TDispatchersOrderValidator(set(EDispatchersTypes)), + getter=TVimDispatchersOrderConfigValueGetter(), + validator=TDispatchersOrderValidator(), allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), ), - coderunner_tempfile_prefix_field=ConfigField( + coderunner_tempfile_prefix_field=TConfigField( name="coderunner_tempfile_prefix", - getter=config_getter.get_coderunner_tempfile_prefix, + getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), - executor_field=ConfigField( + executor_field=TConfigField( name="executor", - getter=config_getter.get_executor, + getter=TVimExecutorConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), - ignore_selection_field=ConfigField( + ignore_selection_field=TConfigField( name="ignore_selection", - getter=config_getter.get_ignore_selection, + getter=TVimIgnoreSelectionConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - respect_shebang_field=ConfigField( + respect_shebang_field=TConfigField( name="respect_shebang", - getter=config_getter.get_respect_shebang, + getter=TVimRespectShebangConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - remove_coderunner_tempfiles_on_exit_field=ConfigField( + remove_coderunner_tempfiles_on_exit_field=TConfigField( name="coderunner_remove_coderunner_tempfiles_on_exit", - getter=config_getter.get_remove_coderunner_tempfiles_on_exit, + getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - save_all_files_before_run_field=ConfigField( + save_all_files_before_run_field=TConfigField( name="save_all_files_before_run", - getter=config_getter.get_save_all_files_before_run, + getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - save_file_before_run_field=ConfigField( + save_file_before_run_field=TConfigField( name="save_file_before_run", - getter=config_getter.get_save_file_before_run, + getter=TVimSaveFileBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), ) - return config_manager + return config def _create_command_dispatcher_strategy_selector( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TBasicCommandDispatcherStrategySelector: @@ -152,17 +161,13 @@ def _create_command_dispatcher_strategy_selector( file_info_extractor ) file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher = ( - self._create_file_ext_command_builders_dispatcher( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_file_ext_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher = ( - self._create_file_type_command_builders_dispatcher( - config_manager, file_info_extractor, project_info_extractor - ) + self._create_file_type_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher = ( - self._create_glob_command_builders_dispatcher(config_manager, file_info_extractor, project_info_extractor) + self._create_glob_command_builders_dispatcher(config, file_info_extractor, project_info_extractor) ) return TBasicCommandDispatcherStrategySelector( @@ -170,44 +175,44 @@ def _create_command_dispatcher_strategy_selector( glob_command_builders_dispatcher=glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=file_ext_command_builders_dispatcher, file_type_command_builders_dispatcher=file_type_command_builders_dispatcher, - config_manager=config_manager, + config=config, ) def _create_file_ext_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TFileExtCommandBuildersDispatcher: return TFileExtCommandBuildersDispatcher( { key: TInterpolatorCommandBuilder(val, project_info_extractor, file_info_extractor) - for key, val in config_manager.get_by_file_ext().items() + for key, val in config.get_by_file_ext().items() }, file_info_extractor, ) def _create_file_type_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TFileTypeCommandBuildersDispatcher: return TFileTypeCommandBuildersDispatcher( { key: TInterpolatorCommandBuilder(val, project_info_extractor, file_info_extractor) - for key, val in config_manager.get_by_file_type().items() + for key, val in config.get_by_file_type().items() }, file_info_extractor, ) def _create_glob_command_builders_dispatcher( self, - config_manager: TVimConfigManager, + config: TVimConfig, file_info_extractor: TVimFileInfoExtractor, project_info_extractor: TVimProjectInfoExtractor, ) -> TGlobCommandBuildersDispatcher: - dict_with_commands: Dict[str, str] = config_manager.get_by_glob() + dict_with_commands: Dict[str, str] = config.get_by_glob() return TGlobCommandBuildersDispatcher( tuple( ( diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py index 5e697c9..9198c4f 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py @@ -7,7 +7,7 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from ..config_manager import IConfig +from ..config import IConfig from .types import EDispatchersTypes @@ -15,13 +15,13 @@ class TBasicCommandDispatcherStrategySelector: def __init__( self, *, - config_manager: IConfig, + config: IConfig, shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, glob_command_builders_dispatcher: TGlobCommandBuildersDispatcher, file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, ): - self._config_manager: IConfig = config_manager + self._config: IConfig = config self._shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher = ( shebang_command_builders_dispatcher ) @@ -49,12 +49,12 @@ def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]: command_builder: Optional[ICommandBuilder] = None if ( - self._config_manager.get_respect_shebang() + self._config.get_respect_shebang() and (command_builder := self.dispatch_by_shebang(file_path_abs)) is not None ): return command_builder - for dispatcher in self._config_manager.get_dispatchers_order(): + for dispatcher in self._config.get_dispatchers_order(): if ( dispatcher == EDispatchersTypes.BY_FILE_EXT and (command_builder := self.dispatch_by_file_ext(file_path_abs)) is not None diff --git a/python_coderunner/src/commands_executor/vim_commands_executor.py b/python_coderunner/src/commands_executor/vim_commands_executor.py index 07fd839..b339cf3 100644 --- a/python_coderunner/src/commands_executor/vim_commands_executor.py +++ b/python_coderunner/src/commands_executor/vim_commands_executor.py @@ -1,13 +1,13 @@ import vim -from ..config_manager import IConfig +from ..config import IConfig from .inteface import ICommandsExecutor class TVimCommandsExecutor(ICommandsExecutor): - def __init__(self, config_manager: IConfig): - self._config_manager: IConfig = config_manager + def __init__(self, config: IConfig): + self._config: IConfig = config def execute(self, command: str) -> None: - executor_command: str = self._config_manager.get_executor() + executor_command: str = self._config.get_executor() vim.command(f"{executor_command} {command}") diff --git a/python_coderunner/src/config/__init__.py b/python_coderunner/src/config/__init__.py new file mode 100644 index 0000000..069e87e --- /dev/null +++ b/python_coderunner/src/config/__init__.py @@ -0,0 +1,5 @@ +from .basic import TBasicConfig +from .config_field import TConfigField +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .interface import IConfig +from .vim_config import TVimConfig diff --git a/python_coderunner/src/config_manager/basic.py b/python_coderunner/src/config/basic.py similarity index 62% rename from python_coderunner/src/config_manager/basic.py rename to python_coderunner/src/config/basic.py index 47aabb5..ae22a4f 100644 --- a/python_coderunner/src/config_manager/basic.py +++ b/python_coderunner/src/config/basic.py @@ -1,63 +1,11 @@ -from abc import ABC, abstractmethod -from typing import Any, Dict, List +from typing import Dict, List +from ..command_dispatcher_strategy_selector import EDispatchersTypes from .config_field import TConfigField from .interface import IConfig -class UndefinedValueError(ValueError): - """Config value is not defined (raised by IConfigGetter)""" - - -class IConfigGetter(ABC): - """Interface for getting raw config values""" - - @abstractmethod - def get_by_file_ext(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_by_file_type(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_by_glob(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_coderunner_tempfile_prefix(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_dispatchers_order(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_executor(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_ignore_selection(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_respect_shebang(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_remove_coderunner_tempfiles_on_exit(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_save_all_files_before_run(self) -> Any: - raise NotImplementedError - - @abstractmethod - def get_save_file_before_run(self) -> Any: - raise NotImplementedError - - -class TBasicConfigManager(IConfig): +class TBasicConfig(IConfig): """ Configuration manager. Aggregates TConfigField objects, each of which encapsulates: @@ -72,7 +20,7 @@ def __init__( by_file_ext_field: TConfigField[Dict[str, str]], by_file_type_field: TConfigField[Dict[str, str]], by_glob_field: TConfigField[Dict[str, str]], - dispatchers_order_field: TConfigField[List], + dispatchers_order_field: TConfigField[List[EDispatchersTypes]], coderunner_tempfile_prefix_field: TConfigField[str], executor_field: TConfigField[str], ignore_selection_field: TConfigField[bool], @@ -102,7 +50,7 @@ def get_by_file_type(self) -> Dict[str, str]: def get_by_glob(self) -> Dict[str, str]: return self._by_glob.get() - def get_dispatchers_order(self) -> List: + def get_dispatchers_order(self) -> List[EDispatchersTypes]: return self._dispatchers_order.get() def get_coderunner_tempfile_prefix(self) -> str: diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py new file mode 100644 index 0000000..b7bce34 --- /dev/null +++ b/python_coderunner/src/config/config_field.py @@ -0,0 +1,40 @@ +from typing import Any, Callable, Generic, TypeVar + +from ..validators import IValidator, ValidationError +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError +from .getter import UndefinedValueError + +ValueType = TypeVar("ValueType") + + +class TConfigField(Generic[ValueType]): + """ + Self-describing field object that encapsulates: + - Getting value (getter) + - Validating value (validator) + - Field metadata (name, allowed_values) + - Error handling + """ + + def __init__( + self, + name: str, + getter: Callable[[], Any], + validator: IValidator[ValueType], + allowed_values_description: str, + ): + self._name = name + self._getter = getter + self._validator = validator + self._allowed_values_description = allowed_values_description + + def get(self) -> ValueType: + try: + raw_value: Any = self._getter() + except UndefinedValueError as e: + raise ConfigFieldNotFoundError.from_undefined_value_error(e, self._allowed_values_description) + + try: + return self._validator.validate(raw_value) + except ValidationError as e: + raise ConfigFieldValidationError.from_validation_error(e, self._name, self._allowed_values_description) diff --git a/python_coderunner/src/config/exceptions.py b/python_coderunner/src/config/exceptions.py new file mode 100644 index 0000000..9554f82 --- /dev/null +++ b/python_coderunner/src/config/exceptions.py @@ -0,0 +1,16 @@ +from typing import Any, Self + +from ..validators import ValidationError +from .getter import UndefinedValueError + + +class ConfigFieldNotFoundError(UndefinedValueError): + @classmethod + def from_undefined_value_error(cls, e: UndefinedValueError, allowed_values: str) -> Self: + return cls(f"{e} Allowed values: {allowed_values}.") + + +class ConfigFieldValidationError(ValidationError): + @classmethod + def from_validation_error(cls, e: ValidationError, field_name: str, allowed_values: str) -> Self: + return cls(f"Invalid value of {field_name}. {e} Allowed values: {allowed_values}.") diff --git a/python_coderunner/src/config/getter/__init__.py b/python_coderunner/src/config/getter/__init__.py new file mode 100644 index 0000000..1a6c403 --- /dev/null +++ b/python_coderunner/src/config/getter/__init__.py @@ -0,0 +1,15 @@ +from .exceptions import UndefinedValueError +from .interface import IConfigValueGetter +from .vim_config_value_getter import ( + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, +) diff --git a/python_coderunner/src/config/getter/exceptions.py b/python_coderunner/src/config/getter/exceptions.py new file mode 100644 index 0000000..0f3fdc6 --- /dev/null +++ b/python_coderunner/src/config/getter/exceptions.py @@ -0,0 +1,2 @@ +class UndefinedValueError(ValueError): + """Config value is not defined""" diff --git a/python_coderunner/src/config/getter/interface.py b/python_coderunner/src/config/getter/interface.py new file mode 100644 index 0000000..7438728 --- /dev/null +++ b/python_coderunner/src/config/getter/interface.py @@ -0,0 +1,3 @@ +from typing import Any, Callable + +IConfigValueGetter = Callable[[], Any] diff --git a/python_coderunner/src/config/getter/vim_config_value_getter.py b/python_coderunner/src/config/getter/vim_config_value_getter.py new file mode 100644 index 0000000..4f4a95c --- /dev/null +++ b/python_coderunner/src/config/getter/vim_config_value_getter.py @@ -0,0 +1,70 @@ +from typing import Any + +import vim + +from .exceptions import UndefinedValueError + + +class TBaseVimConfigValueGetter: + """Base class for getting Vim config values""" + + def _get_vim_var(self, var_name: str) -> Any: + try: + return vim.eval(var_name) + except vim.error: + raise UndefinedValueError(f"Vim variable {var_name} is not defined. Please set it in your vimrc.") + + +class TVimByFileExtConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_file_ext") + + +class TVimByFileTypeConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_file_type") + + +class TVimByGlobConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_by_glob") + + +class TVimCoderunnerTempfilePrefixConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_tempfile_prefix") + + +class TVimDispatchersOrderConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_runners_order") + + +class TVimExecutorConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_executor") + + +class TVimIgnoreSelectionConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_ignore_selection") + + +class TVimRespectShebangConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_respect_shebang") + + +class TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_remove_coderunner_tempfiles_on_exit") + + +class TVimSaveAllFilesBeforeRunConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_save_all_files_before_run") + + +class TVimSaveFileBeforeRunConfigValueGetter(TBaseVimConfigValueGetter): + def __call__(self) -> Any: + return self._get_vim_var("g:coderunner_save_file_before_run") diff --git a/python_coderunner/src/config_manager/interface.py b/python_coderunner/src/config/interface.py similarity index 91% rename from python_coderunner/src/config_manager/interface.py rename to python_coderunner/src/config/interface.py index f77649f..6ac5c3d 100644 --- a/python_coderunner/src/config_manager/interface.py +++ b/python_coderunner/src/config/interface.py @@ -1,9 +1,11 @@ from abc import ABC, abstractmethod from typing import Dict, List +from ..command_dispatcher_strategy_selector import EDispatchersTypes + class IConfig(ABC): - """Configuration manager interface""" + """Configuration interface""" @abstractmethod def get_by_file_ext(self) -> Dict[str, str]: @@ -21,7 +23,7 @@ def get_by_glob(self) -> Dict[str, str]: raise NotImplementedError @abstractmethod - def get_dispatchers_order(self) -> List: + def get_dispatchers_order(self) -> List[EDispatchersTypes]: """Gets the priority order of dispatchers""" raise NotImplementedError diff --git a/python_coderunner/src/config/vim_config.py b/python_coderunner/src/config/vim_config.py new file mode 100644 index 0000000..d8bbe23 --- /dev/null +++ b/python_coderunner/src/config/vim_config.py @@ -0,0 +1,5 @@ +from .basic import TBasicConfig + + +class TVimConfig(TBasicConfig): + """Config for Vim, created through factory""" diff --git a/python_coderunner/src/config_manager/__init__.py b/python_coderunner/src/config_manager/__init__.py deleted file mode 100644 index e218d30..0000000 --- a/python_coderunner/src/config_manager/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .basic import ( - IConfigGetter, - TBasicConfigManager, - UndefinedValueError, -) -from .config_field import TConfigField -from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError -from .interface import IConfig -from .vim_config_manager import TVimConfigGetter, TVimConfigManager - -# Public exports -ConfigField = TConfigField -IConfigManager = IConfig # Backward compatibility alias diff --git a/python_coderunner/src/config_manager/config_field.py b/python_coderunner/src/config_manager/config_field.py deleted file mode 100644 index de6df15..0000000 --- a/python_coderunner/src/config_manager/config_field.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import Any, Callable, Generic, TypeVar - -from ..validators.interface import IValidator -from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError - -T = TypeVar("T") - - -class UndefinedValueError(Exception): - """Config value is not defined""" - - -class TConfigField(Generic[T]): - """ - Self-describing field object that encapsulates: - - Getting value (getter) - - Validating value (validator) - - Field metadata (name, allowed_values) - - Error handling - """ - - def __init__( - self, - name: str, - getter: Callable[[], Any], - validator: IValidator[T], - allowed_values_description: str, - ): - self.name = name - self.getter = getter - self.validator = validator - self.allowed_values_description = allowed_values_description - - def get(self) -> T: - """Gets and validates config value""" - try: - raw_value = self.getter() - except UndefinedValueError: - raise ConfigFieldNotFoundError(self.name, self.allowed_values_description) - - try: - return self.validator.validate(raw_value) - except Exception as e: - # Catch validator exception and convert it - raise ConfigFieldValidationError( - field_name=self.name, - value=raw_value, - reason=str(e), - allowed_values=self.allowed_values_description, - ) from e diff --git a/python_coderunner/src/config_manager/exceptions.py b/python_coderunner/src/config_manager/exceptions.py deleted file mode 100644 index 83529b1..0000000 --- a/python_coderunner/src/config_manager/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any - - -class ConfigFieldNotFoundError(Exception): - """Config field not found""" - - def __init__(self, field_name: str, allowed_values: str): - self.field_name = field_name - self.allowed_values = allowed_values - super().__init__(f"Config parameter '{field_name}' not defined. Allowed values: {allowed_values}.") - - -class ConfigFieldValidationError(Exception): - """Config field value is invalid""" - - def __init__(self, field_name: str, value: Any, reason: str, allowed_values: str): - self.field_name = field_name - self.value = value - self.reason = reason - self.allowed_values = allowed_values - super().__init__(f"Invalid value of {field_name}: {reason}. Got: {value}. Allowed values: {allowed_values}.") diff --git a/python_coderunner/src/config_manager/vim_config_manager.py b/python_coderunner/src/config_manager/vim_config_manager.py deleted file mode 100644 index 7702168..0000000 --- a/python_coderunner/src/config_manager/vim_config_manager.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Any - -import vim - -from .basic import IConfigGetter, TBasicConfigManager, UndefinedValueError - - -class TVimConfigGetter(IConfigGetter): - """Gets config values from Vim variables""" - - def get_by_file_ext(self) -> Any: - return self._get_vim_var("g:coderunner_by_file_ext") - - def get_by_file_type(self) -> Any: - return self._get_vim_var("g:coderunner_by_file_type") - - def get_by_glob(self) -> Any: - return self._get_vim_var("g:coderunner_by_glob") - - def get_coderunner_tempfile_prefix(self) -> Any: - return self._get_vim_var("g:coderunner_tempfile_prefix") - - def get_dispatchers_order(self) -> Any: - return self._get_vim_var("g:coderunner_runners_order") - - def get_executor(self) -> Any: - return self._get_vim_var("g:coderunner_executor") - - def get_ignore_selection(self) -> Any: - return self._get_vim_var("g:coderunner_ignore_selection") - - def get_respect_shebang(self) -> Any: - return self._get_vim_var("g:coderunner_respect_shebang") - - def get_remove_coderunner_tempfiles_on_exit(self) -> Any: - return self._get_vim_var("g:coderunner_remove_coderunner_tempfiles_on_exit") - - def get_save_all_files_before_run(self) -> Any: - return self._get_vim_var("g:coderunner_save_all_files_before_run") - - def get_save_file_before_run(self) -> Any: - return self._get_vim_var("g:coderunner_save_file_before_run") - - def _get_vim_var(self, var_name: str) -> Any: - try: - return vim.eval(var_name) - except vim.error: - raise UndefinedValueError(f"Vim variable {var_name} is not defined. Please set it in your vimrc.") - - -class TVimConfigManager(TBasicConfigManager): - """Config Manager for Vim, created through factory""" diff --git a/python_coderunner/src/editor_service_for_coderunner/basic.py b/python_coderunner/src/editor_service_for_coderunner/basic.py index 4499bce..5397bbf 100644 --- a/python_coderunner/src/editor_service_for_coderunner/basic.py +++ b/python_coderunner/src/editor_service_for_coderunner/basic.py @@ -3,14 +3,14 @@ from tempfile import NamedTemporaryFile from typing import Generator, List -from ..config_manager import IConfig +from ..config import IConfig from ..editor import IEditor from ..file_info_extractor import IFileInfoExtractor class TBasicEditorServiceForCodeRunner: - def __init__(self, config_manager: IConfig, editor: IEditor, file_info_extractor: IFileInfoExtractor): - self._config_manager: IConfig = config_manager + def __init__(self, config: IConfig, editor: IEditor, file_info_extractor: IFileInfoExtractor): + self._config: IConfig = config self._editor: IEditor = editor self._file_info_extractor: IFileInfoExtractor = file_info_extractor self._temp_files: List[str] = [] @@ -19,15 +19,12 @@ def __init__(self, config_manager: IConfig, editor: IEditor, file_info_extractor def get_file_for_run(self) -> Generator[str, None, None]: file_path_abs: str = self._editor.get_current_file_name() - if ( - not self._config_manager.get_ignore_selection() - and (selected_text := self._editor.get_selected_text()) is not None - ): + if not self._config.get_ignore_selection() and (selected_text := self._editor.get_selected_text()) is not None: with NamedTemporaryFile( "w", encoding="utf-8", dir=self._file_info_extractor.get_dir(file_path_abs), - prefix=self._config_manager.get_coderunner_tempfile_prefix(), + prefix=self._config.get_coderunner_tempfile_prefix(), suffix=self._file_info_extractor.get_file_ext(file_path_abs), delete=False, ) as temp_file: @@ -45,10 +42,10 @@ def get_file_for_run(self) -> Generator[str, None, None]: yield file_path_abs def prepare_for_run(self) -> None: - if self._config_manager.get_save_all_files_before_run(): + if self._config.get_save_all_files_before_run(): self._editor.save_all_files() return - if self._config_manager.get_save_file_before_run(): + if self._config.get_save_file_before_run(): self._editor.save_file() return diff --git a/python_coderunner/src/validators/__init__.py b/python_coderunner/src/validators/__init__.py index 5de9b9c..355ba05 100644 --- a/python_coderunner/src/validators/__init__.py +++ b/python_coderunner/src/validators/__init__.py @@ -1,5 +1,6 @@ from .bool_validator import TBoolValidator from .dispatchers_order_validator import TDispatchersOrderValidator from .dispatchers_validator import TDispatchersValidator +from .exceptions import ValidationError from .interface import IValidator from .str_validator import TStrValidator diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index c24913a..960939b 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -14,7 +14,7 @@ EDispatchersTypes, TBasicCommandDispatcherStrategySelector, ) -from src.config_manager import TBasicConfigManager +from src.config import TBasicConfig @pytest.mark.parametrize( @@ -78,7 +78,7 @@ def test_basic_command_dispatcher_strategy_selector( ): file_path_abs = tmp_path / file_path file_path_abs.write_bytes(content) - config_manager: TBasicConfigManager = MagicMock( + config: TBasicConfig = MagicMock( get_dispatchers_order=MagicMock(return_value=order), get_respect_shebang=MagicMock(return_value=respect_shebang) ) selector = TBasicCommandDispatcherStrategySelector( @@ -86,7 +86,7 @@ def test_basic_command_dispatcher_strategy_selector( glob_command_builders_dispatcher=fixture_glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=fixture_file_ext_command_builders_dispatcher, file_type_command_builders_dispatcher=fixture_file_type_command_builders_dispatcher, - config_manager=config_manager, + config=config, ) dispatch_result: Optional[ICommandBuilder] = selector.dispatch(str(file_path_abs)) diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index fb6f7f7..0fb0bf7 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -19,12 +19,22 @@ TShebangCommandBuildersDispatcher, ) from src.command_dispatcher_strategy_selector import EDispatchersTypes -from src.config_manager import ( +from src.config import ( ConfigField, IConfig, - IConfigGetter, - TVimConfigGetter, - TVimConfigManager, + IConfigValueGetter, + TVimByFileExtConfigValueGetter, + TVimByFileTypeConfigValueGetter, + TVimByGlobConfigValueGetter, + TVimCoderunnerTempfilePrefixConfigValueGetter, + TVimConfig, + TVimDispatchersOrderConfigValueGetter, + TVimExecutorConfigValueGetter, + TVimIgnoreSelectionConfigValueGetter, + TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter, + TVimRespectShebangConfigValueGetter, + TVimSaveAllFilesBeforeRunConfigValueGetter, + TVimSaveFileBeforeRunConfigValueGetter, ) from src.file_info_extractor import ( IFileInfoExtractor, @@ -37,94 +47,84 @@ from src.validators import TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator -@pytest.fixture(params=(lazy_fixture("fixture_vim_config_manager"),)) -def fixture_config_manager(request: pytest.FixtureRequest) -> IConfig: +@pytest.fixture(params=(lazy_fixture("fixture_vim_config"),)) +def fixture_config(request: pytest.FixtureRequest) -> IConfig: return request.param @pytest.fixture -def fixture_vim_config_manager(fixture_config_getter: IConfigGetter) -> IConfig: - """Create TVimConfigManager with all ConfigField objects""" - return TVimConfigManager( +def fixture_vim_config() -> IConfig: + """Create TVimConfig with all ConfigField objects""" + return TVimConfig( by_file_ext_field=ConfigField( name="by_file_ext", - getter=fixture_config_getter.get_by_file_ext, + getter=TVimByFileExtConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_file_type_field=ConfigField( name="by_file_type", - getter=fixture_config_getter.get_by_file_type, + getter=TVimByFileTypeConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_glob_field=ConfigField( name="by_glob", - getter=fixture_config_getter.get_by_glob, + getter=TVimByGlobConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), dispatchers_order_field=ConfigField( name="runners_order", - getter=fixture_config_getter.get_dispatchers_order, - validator=TDispatchersOrderValidator(set(EDispatchersTypes)), + getter=TVimDispatchersOrderConfigValueGetter(), + validator=TDispatchersOrderValidator(), allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), ), coderunner_tempfile_prefix_field=ConfigField( name="coderunner_tempfile_prefix", - getter=fixture_config_getter.get_coderunner_tempfile_prefix, + getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), executor_field=ConfigField( name="executor", - getter=fixture_config_getter.get_executor, + getter=TVimExecutorConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), ignore_selection_field=ConfigField( name="ignore_selection", - getter=fixture_config_getter.get_ignore_selection, + getter=TVimIgnoreSelectionConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), respect_shebang_field=ConfigField( name="respect_shebang", - getter=fixture_config_getter.get_respect_shebang, + getter=TVimRespectShebangConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), remove_coderunner_tempfiles_on_exit_field=ConfigField( name="coderunner_remove_coderunner_tempfiles_on_exit", - getter=fixture_config_getter.get_remove_coderunner_tempfiles_on_exit, + getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), save_all_files_before_run_field=ConfigField( name="save_all_files_before_run", - getter=fixture_config_getter.get_save_all_files_before_run, + getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), save_file_before_run_field=ConfigField( name="save_file_before_run", - getter=fixture_config_getter.get_save_file_before_run, + getter=TVimSaveFileBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), ) -@pytest.fixture(params=(lazy_fixture("fixture_vim_config_getter"),)) -def fixture_config_getter(request: pytest.FixtureRequest) -> IConfigGetter: - return request.param - - -@pytest.fixture -def fixture_vim_config_getter() -> IConfigGetter: - return TVimConfigGetter() - - @pytest.fixture def fixture_shebang_command_builders_dispatcher( fixture_file_info_extractor: IFileInfoExtractor, From 123a6f7740ccb07e16bd85e89d78687dce5e2dae Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Mon, 9 Mar 2026 23:43:37 +0300 Subject: [PATCH 04/15] =?UTF-8?q?refactor:=20classes=20=E2=84=964?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_coderunner/src/config/basic.py | 37 +++++++++++++------- python_coderunner/src/config/config_field.py | 8 ++--- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py index ae22a4f..4c8d384 100644 --- a/python_coderunner/src/config/basic.py +++ b/python_coderunner/src/config/basic.py @@ -1,9 +1,12 @@ -from typing import Dict, List +from typing import Any, Dict, List, TypeVar from ..command_dispatcher_strategy_selector import EDispatchersTypes from .config_field import TConfigField +from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError from .interface import IConfig +ValueType = TypeVar("ValueType") + class TBasicConfig(IConfig): """ @@ -41,35 +44,45 @@ def __init__( self._save_all_files_before_run = save_all_files_before_run_field self._save_file_before_run = save_file_before_run_field + def _get_field_value(self, field: TConfigField[ValueType]) -> Any[ValueType]: + """ + Get field value, converting ConfigField exceptions to ValueError. + Preserves exception chain with 'raise from'. + """ + try: + return field.get() + except (ConfigFieldNotFoundError, ConfigFieldValidationError) as e: + raise ValueError(str(e)) from e + def get_by_file_ext(self) -> Dict[str, str]: - return self._by_file_ext.get() + return self._get_field_value(self._by_file_ext) def get_by_file_type(self) -> Dict[str, str]: - return self._by_file_type.get() + return self._get_field_value(self._by_file_type) def get_by_glob(self) -> Dict[str, str]: - return self._by_glob.get() + return self._get_field_value(self._by_glob) def get_dispatchers_order(self) -> List[EDispatchersTypes]: - return self._dispatchers_order.get() + return self._get_field_value(self._dispatchers_order) def get_coderunner_tempfile_prefix(self) -> str: - return self._coderunner_tempfile_prefix.get() + return self._get_field_value(self._coderunner_tempfile_prefix) def get_executor(self) -> str: - return self._executor.get() + return self._get_field_value(self._executor) def get_ignore_selection(self) -> bool: - return self._ignore_selection.get() + return self._get_field_value(self._ignore_selection) def get_respect_shebang(self) -> bool: - return self._respect_shebang.get() + return self._get_field_value(self._respect_shebang) def get_remove_coderunner_tempfiles_on_exit(self) -> bool: - return self._remove_coderunner_tempfiles_on_exit.get() + return self._get_field_value(self._remove_coderunner_tempfiles_on_exit) def get_save_all_files_before_run(self) -> bool: - return self._save_all_files_before_run.get() + return self._get_field_value(self._save_all_files_before_run) def get_save_file_before_run(self) -> bool: - return self._save_file_before_run.get() + return self._get_field_value(self._save_file_before_run) diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py index b7bce34..6d91055 100644 --- a/python_coderunner/src/config/config_field.py +++ b/python_coderunner/src/config/config_field.py @@ -23,10 +23,10 @@ def __init__( validator: IValidator[ValueType], allowed_values_description: str, ): - self._name = name - self._getter = getter - self._validator = validator - self._allowed_values_description = allowed_values_description + self._name: str = name + self._getter: Callable[[], Any] = getter + self._validator: IValidator[ValueType] = validator + self._allowed_values_description: str = allowed_values_description def get(self) -> ValueType: try: From e89506e3565bea9a9f31bff9cbf70dedf222f0b3 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 00:04:07 +0300 Subject: [PATCH 05/15] =?UTF-8?q?refactor:=20classes=20=E2=84=965?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vim_coderunner_factory.py | 43 ++++++++----------- .../__init__.py | 1 - .../basic.py | 3 +- .../types.py | 7 --- python_coderunner/src/config/__init__.py | 2 +- python_coderunner/src/config/basic.py | 3 +- python_coderunner/src/config/config_field.py | 2 +- python_coderunner/src/config/exceptions.py | 4 +- python_coderunner/src/config/interface.py | 7 ++- .../validator}/__init__.py | 0 .../validator}/bool_validator.py | 0 .../validator}/dispatchers_order_validator.py | 2 +- .../validator}/dispatchers_validator.py | 0 .../validator}/exceptions.py | 0 .../validator}/interface.py | 0 .../validator}/str_validator.py | 0 ...ic_command_dispatcher_strategy_selector.py | 3 +- python_coderunner/tests/unit/conftest.py | 4 +- 18 files changed, 35 insertions(+), 46 deletions(-) delete mode 100644 python_coderunner/src/command_dispatcher_strategy_selector/types.py rename python_coderunner/src/{validators => config/validator}/__init__.py (100%) rename python_coderunner/src/{validators => config/validator}/bool_validator.py (100%) rename python_coderunner/src/{validators => config/validator}/dispatchers_order_validator.py (91%) rename python_coderunner/src/{validators => config/validator}/dispatchers_validator.py (100%) rename python_coderunner/src/{validators => config/validator}/exceptions.py (100%) rename python_coderunner/src/{validators => config/validator}/interface.py (100%) rename python_coderunner/src/{validators => config/validator}/str_validator.py (100%) diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index cd0d714..3a4864e 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -11,14 +11,10 @@ TShebangCommandBuildersDispatcher, ) from ..command_dispatcher_strategy_selector import ( - EDispatchersTypes, TBasicCommandDispatcherStrategySelector, ) from ..commands_executor import TVimCommandsExecutor -from ..config import ( - TConfigField, - TVimConfig, -) +from ..config import EDispatchersTypes, TConfigField, TVimConfig from ..config.getter import ( TVimByFileExtConfigValueGetter, TVimByFileTypeConfigValueGetter, @@ -31,19 +27,18 @@ TVimRespectShebangConfigValueGetter, TVimSaveAllFilesBeforeRunConfigValueGetter, TVimSaveFileBeforeRunConfigValueGetter, - UndefinedValueError, ) -from ..editor import TVimEditor -from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner -from ..file_info_extractor import TVimFileInfoExtractor -from ..message_printer import TVimMessagePrinter -from ..project_info_extractor import TVimProjectInfoExtractor -from ..validators import ( +from ..config.validator import ( TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator, ) +from ..editor import TVimEditor +from ..editor_service_for_coderunner import TBasicEditorServiceForCodeRunner +from ..file_info_extractor import TVimFileInfoExtractor +from ..message_printer import TVimMessagePrinter +from ..project_info_extractor import TVimProjectInfoExtractor from .interface import ICodeRunnerFactory @@ -74,7 +69,7 @@ def create(self) -> Optional[TCodeRunner]: message_printer=message_printer, ) - except (ValueError, UndefinedValueError) as e: + except ValueError as e: message_printer.error(str(e)) return None @@ -83,67 +78,67 @@ def _create_config(self) -> TVimConfig: """Creates TVimConfig with ConfigField objects""" config = TVimConfig( by_file_ext_field=TConfigField( - name="by_file_ext", + name="g:by_file_ext", getter=TVimByFileExtConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_file_type_field=TConfigField( - name="by_file_type", + name="g:by_file_type", getter=TVimByFileTypeConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_glob_field=TConfigField( - name="by_glob", + name="g:by_glob", getter=TVimByGlobConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), dispatchers_order_field=TConfigField( - name="runners_order", + name="g:runners_order", getter=TVimDispatchersOrderConfigValueGetter(), validator=TDispatchersOrderValidator(), allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), ), coderunner_tempfile_prefix_field=TConfigField( - name="coderunner_tempfile_prefix", + name="g:coderunner_tempfile_prefix", getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), executor_field=TConfigField( - name="executor", + name="g:executor", getter=TVimExecutorConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), ignore_selection_field=TConfigField( - name="ignore_selection", + name="g:ignore_selection", getter=TVimIgnoreSelectionConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), respect_shebang_field=TConfigField( - name="respect_shebang", + name="g:respect_shebang", getter=TVimRespectShebangConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), remove_coderunner_tempfiles_on_exit_field=TConfigField( - name="coderunner_remove_coderunner_tempfiles_on_exit", + name="g:coderunner_remove_coderunner_tempfiles_on_exit", getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), save_all_files_before_run_field=TConfigField( - name="save_all_files_before_run", + name="g:save_all_files_before_run", getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), save_file_before_run_field=TConfigField( - name="save_file_before_run", + name="g:save_file_before_run", getter=TVimSaveFileBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py b/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py index a39b279..cb8e0bf 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/__init__.py @@ -1,4 +1,3 @@ from .basic import ( TBasicCommandDispatcherStrategySelector, ) -from .types import EDispatchersTypes diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py index 9198c4f..071aba0 100644 --- a/python_coderunner/src/command_dispatcher_strategy_selector/basic.py +++ b/python_coderunner/src/command_dispatcher_strategy_selector/basic.py @@ -7,8 +7,7 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from ..config import IConfig -from .types import EDispatchersTypes +from ..config import EDispatchersTypes, IConfig class TBasicCommandDispatcherStrategySelector: diff --git a/python_coderunner/src/command_dispatcher_strategy_selector/types.py b/python_coderunner/src/command_dispatcher_strategy_selector/types.py deleted file mode 100644 index e31d2c4..0000000 --- a/python_coderunner/src/command_dispatcher_strategy_selector/types.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import StrEnum - - -class EDispatchersTypes(StrEnum): - BY_FILE_EXT = "by_file_ext" - BY_FILE_TYPE = "by_file_type" - BY_GLOB = "by_glob" diff --git a/python_coderunner/src/config/__init__.py b/python_coderunner/src/config/__init__.py index 069e87e..a7c16fd 100644 --- a/python_coderunner/src/config/__init__.py +++ b/python_coderunner/src/config/__init__.py @@ -1,5 +1,5 @@ from .basic import TBasicConfig from .config_field import TConfigField from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError -from .interface import IConfig +from .interface import EDispatchersTypes, IConfig from .vim_config import TVimConfig diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py index 4c8d384..6b28b97 100644 --- a/python_coderunner/src/config/basic.py +++ b/python_coderunner/src/config/basic.py @@ -1,9 +1,8 @@ from typing import Any, Dict, List, TypeVar -from ..command_dispatcher_strategy_selector import EDispatchersTypes from .config_field import TConfigField from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError -from .interface import IConfig +from .interface import EDispatchersTypes, IConfig ValueType = TypeVar("ValueType") diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py index 6d91055..5e80174 100644 --- a/python_coderunner/src/config/config_field.py +++ b/python_coderunner/src/config/config_field.py @@ -1,8 +1,8 @@ from typing import Any, Callable, Generic, TypeVar -from ..validators import IValidator, ValidationError from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError from .getter import UndefinedValueError +from .validator import IValidator, ValidationError ValueType = TypeVar("ValueType") diff --git a/python_coderunner/src/config/exceptions.py b/python_coderunner/src/config/exceptions.py index 9554f82..fe16ac8 100644 --- a/python_coderunner/src/config/exceptions.py +++ b/python_coderunner/src/config/exceptions.py @@ -1,7 +1,7 @@ -from typing import Any, Self +from typing import Self -from ..validators import ValidationError from .getter import UndefinedValueError +from .validator import ValidationError class ConfigFieldNotFoundError(UndefinedValueError): diff --git a/python_coderunner/src/config/interface.py b/python_coderunner/src/config/interface.py index 6ac5c3d..d94a9a5 100644 --- a/python_coderunner/src/config/interface.py +++ b/python_coderunner/src/config/interface.py @@ -1,7 +1,12 @@ from abc import ABC, abstractmethod +from enum import StrEnum from typing import Dict, List -from ..command_dispatcher_strategy_selector import EDispatchersTypes + +class EDispatchersTypes(StrEnum): + BY_FILE_EXT = "by_file_ext" + BY_FILE_TYPE = "by_file_type" + BY_GLOB = "by_glob" class IConfig(ABC): diff --git a/python_coderunner/src/validators/__init__.py b/python_coderunner/src/config/validator/__init__.py similarity index 100% rename from python_coderunner/src/validators/__init__.py rename to python_coderunner/src/config/validator/__init__.py diff --git a/python_coderunner/src/validators/bool_validator.py b/python_coderunner/src/config/validator/bool_validator.py similarity index 100% rename from python_coderunner/src/validators/bool_validator.py rename to python_coderunner/src/config/validator/bool_validator.py diff --git a/python_coderunner/src/validators/dispatchers_order_validator.py b/python_coderunner/src/config/validator/dispatchers_order_validator.py similarity index 91% rename from python_coderunner/src/validators/dispatchers_order_validator.py rename to python_coderunner/src/config/validator/dispatchers_order_validator.py index 833a5e9..04f777e 100644 --- a/python_coderunner/src/validators/dispatchers_order_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_order_validator.py @@ -1,6 +1,6 @@ from typing import Any, List, Set -from ..command_dispatcher_strategy_selector import EDispatchersTypes +from ..interface import EDispatchersTypes from .exceptions import ValidationError from .interface import IValidator diff --git a/python_coderunner/src/validators/dispatchers_validator.py b/python_coderunner/src/config/validator/dispatchers_validator.py similarity index 100% rename from python_coderunner/src/validators/dispatchers_validator.py rename to python_coderunner/src/config/validator/dispatchers_validator.py diff --git a/python_coderunner/src/validators/exceptions.py b/python_coderunner/src/config/validator/exceptions.py similarity index 100% rename from python_coderunner/src/validators/exceptions.py rename to python_coderunner/src/config/validator/exceptions.py diff --git a/python_coderunner/src/validators/interface.py b/python_coderunner/src/config/validator/interface.py similarity index 100% rename from python_coderunner/src/validators/interface.py rename to python_coderunner/src/config/validator/interface.py diff --git a/python_coderunner/src/validators/str_validator.py b/python_coderunner/src/config/validator/str_validator.py similarity index 100% rename from python_coderunner/src/validators/str_validator.py rename to python_coderunner/src/config/validator/str_validator.py diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index 960939b..ce2853d 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -11,10 +11,9 @@ TShebangCommandBuildersDispatcher, ) from src.command_dispatcher_strategy_selector import ( - EDispatchersTypes, TBasicCommandDispatcherStrategySelector, ) -from src.config import TBasicConfig +from src.config import EDispatchersTypes, TBasicConfig @pytest.mark.parametrize( diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index 0fb0bf7..30f371d 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -18,9 +18,9 @@ TGlobCommandBuildersDispatcher, TShebangCommandBuildersDispatcher, ) -from src.command_dispatcher_strategy_selector import EDispatchersTypes from src.config import ( ConfigField, + EDispatchersTypes, IConfig, IConfigValueGetter, TVimByFileExtConfigValueGetter, @@ -36,6 +36,7 @@ TVimSaveAllFilesBeforeRunConfigValueGetter, TVimSaveFileBeforeRunConfigValueGetter, ) +from src.config.validator import TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator from src.file_info_extractor import ( IFileInfoExtractor, TVimFileInfoExtractor, @@ -44,7 +45,6 @@ IProjectInfoExtractor, TVimProjectInfoExtractor, ) -from src.validators import TBoolValidator, TDispatchersOrderValidator, TDispatchersValidator, TStrValidator @pytest.fixture(params=(lazy_fixture("fixture_vim_config"),)) From 2259d4513144ac1cb78e50a31c66d6bd7225e77b Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 00:12:26 +0300 Subject: [PATCH 06/15] =?UTF-8?q?refactor:=20classes=20=E2=84=966?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vim_coderunner_factory.py | 19 ++++--- python_coderunner/src/config/basic.py | 4 +- python_coderunner/tests/unit/conftest.py | 51 ++++++++++--------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index 3a4864e..8d4d195 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -75,28 +75,27 @@ def create(self) -> Optional[TCodeRunner]: return None def _create_config(self) -> TVimConfig: - """Creates TVimConfig with ConfigField objects""" config = TVimConfig( by_file_ext_field=TConfigField( - name="g:by_file_ext", + name="g:coderunner_by_file_ext", getter=TVimByFileExtConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_file_type_field=TConfigField( - name="g:by_file_type", + name="g:coderunner_by_file_type", getter=TVimByFileTypeConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), by_glob_field=TConfigField( - name="g:by_glob", + name="g:coderunner_by_glob", getter=TVimByGlobConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), dispatchers_order_field=TConfigField( - name="g:runners_order", + name="g:coderunner_runners_order", getter=TVimDispatchersOrderConfigValueGetter(), validator=TDispatchersOrderValidator(), allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), @@ -108,19 +107,19 @@ def _create_config(self) -> TVimConfig: allowed_values_description="str value", ), executor_field=TConfigField( - name="g:executor", + name="g:coderunner_executor", getter=TVimExecutorConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), ignore_selection_field=TConfigField( - name="g:ignore_selection", + name="g:coderunner_ignore_selection", getter=TVimIgnoreSelectionConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), respect_shebang_field=TConfigField( - name="g:respect_shebang", + name="g:coderunner_respect_shebang", getter=TVimRespectShebangConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", @@ -132,13 +131,13 @@ def _create_config(self) -> TVimConfig: allowed_values_description="0 or 1", ), save_all_files_before_run_field=TConfigField( - name="g:save_all_files_before_run", + name="g:coderunner_save_all_files_before_run", getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), save_file_before_run_field=TConfigField( - name="g:save_file_before_run", + name="g:coderunner_save_file_before_run", getter=TVimSaveFileBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py index 6b28b97..d80b063 100644 --- a/python_coderunner/src/config/basic.py +++ b/python_coderunner/src/config/basic.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, TypeVar +from typing import Dict, List, TypeVar from .config_field import TConfigField from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError @@ -43,7 +43,7 @@ def __init__( self._save_all_files_before_run = save_all_files_before_run_field self._save_file_before_run = save_file_before_run_field - def _get_field_value(self, field: TConfigField[ValueType]) -> Any[ValueType]: + def _get_field_value(self, field: TConfigField[ValueType]) -> ValueType: """ Get field value, converting ConfigField exceptions to ValueError. Preserves exception chain with 'raise from'. diff --git a/python_coderunner/tests/unit/conftest.py b/python_coderunner/tests/unit/conftest.py index 30f371d..70a6685 100644 --- a/python_coderunner/tests/unit/conftest.py +++ b/python_coderunner/tests/unit/conftest.py @@ -19,15 +19,17 @@ TShebangCommandBuildersDispatcher, ) from src.config import ( - ConfigField, EDispatchersTypes, IConfig, + TConfigField, + TVimConfig, +) +from src.config.getter import ( IConfigValueGetter, TVimByFileExtConfigValueGetter, TVimByFileTypeConfigValueGetter, TVimByGlobConfigValueGetter, TVimCoderunnerTempfilePrefixConfigValueGetter, - TVimConfig, TVimDispatchersOrderConfigValueGetter, TVimExecutorConfigValueGetter, TVimIgnoreSelectionConfigValueGetter, @@ -54,70 +56,69 @@ def fixture_config(request: pytest.FixtureRequest) -> IConfig: @pytest.fixture def fixture_vim_config() -> IConfig: - """Create TVimConfig with all ConfigField objects""" return TVimConfig( - by_file_ext_field=ConfigField( - name="by_file_ext", + by_file_ext_field=TConfigField( + name="g:coderunner_by_file_ext", getter=TVimByFileExtConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - by_file_type_field=ConfigField( - name="by_file_type", + by_file_type_field=TConfigField( + name="g:coderunner_by_file_type", getter=TVimByFileTypeConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - by_glob_field=ConfigField( - name="by_glob", + by_glob_field=TConfigField( + name="g:coderunner_by_glob", getter=TVimByGlobConfigValueGetter(), validator=TDispatchersValidator(), allowed_values_description="Dict[str, str] value", ), - dispatchers_order_field=ConfigField( - name="runners_order", + dispatchers_order_field=TConfigField( + name="g:coderunner_runners_order", getter=TVimDispatchersOrderConfigValueGetter(), validator=TDispatchersOrderValidator(), allowed_values_description=", ".join(dispatcher_type.value for dispatcher_type in EDispatchersTypes), ), - coderunner_tempfile_prefix_field=ConfigField( - name="coderunner_tempfile_prefix", + coderunner_tempfile_prefix_field=TConfigField( + name="g:coderunner_tempfile_prefix", getter=TVimCoderunnerTempfilePrefixConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), - executor_field=ConfigField( - name="executor", + executor_field=TConfigField( + name="g:coderunner_executor", getter=TVimExecutorConfigValueGetter(), validator=TStrValidator(), allowed_values_description="str value", ), - ignore_selection_field=ConfigField( - name="ignore_selection", + ignore_selection_field=TConfigField( + name="g:coderunner_ignore_selection", getter=TVimIgnoreSelectionConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - respect_shebang_field=ConfigField( - name="respect_shebang", + respect_shebang_field=TConfigField( + name="g:coderunner_respect_shebang", getter=TVimRespectShebangConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - remove_coderunner_tempfiles_on_exit_field=ConfigField( - name="coderunner_remove_coderunner_tempfiles_on_exit", + remove_coderunner_tempfiles_on_exit_field=TConfigField( + name="g:coderunner_remove_coderunner_tempfiles_on_exit", getter=TVimRemoveCoderunnerTempfilesOnExitConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - save_all_files_before_run_field=ConfigField( - name="save_all_files_before_run", + save_all_files_before_run_field=TConfigField( + name="g:coderunner_save_all_files_before_run", getter=TVimSaveAllFilesBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", ), - save_file_before_run_field=ConfigField( - name="save_file_before_run", + save_file_before_run_field=TConfigField( + name="g:coderunner_save_file_before_run", getter=TVimSaveFileBeforeRunConfigValueGetter(), validator=TBoolValidator(), allowed_values_description="0 or 1", From 699baf301a29cb0d79f271cd59bbe58f0667109e Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 00:21:32 +0300 Subject: [PATCH 07/15] =?UTF-8?q?refactor:=20classes=20=E2=84=967?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/unit/config/__init__.py | 0 .../tests/unit/config/test_validator.py | 127 ++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 python_coderunner/tests/unit/config/__init__.py create mode 100644 python_coderunner/tests/unit/config/test_validator.py diff --git a/python_coderunner/tests/unit/config/__init__.py b/python_coderunner/tests/unit/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python_coderunner/tests/unit/config/test_validator.py b/python_coderunner/tests/unit/config/test_validator.py new file mode 100644 index 0000000..ab995c2 --- /dev/null +++ b/python_coderunner/tests/unit/config/test_validator.py @@ -0,0 +1,127 @@ +"""Tests for config validators.""" + +from contextlib import nullcontext as does_not_raise + +import pytest + +from src.config import EDispatchersTypes +from src.config.validator import ( + TBoolValidator, + TDispatchersOrderValidator, + TDispatchersValidator, + TStrValidator, + ValidationError, +) + + +class TestBoolValidator: + @pytest.mark.parametrize( + ("content", "expected", "expectation"), + ( + (True, True, does_not_raise()), + (False, False, does_not_raise()), + ("1", True, does_not_raise()), + ("0", False, does_not_raise()), + (1, True, does_not_raise()), + (0, False, does_not_raise()), + ("invalid", None, pytest.raises(ValidationError)), + (None, None, pytest.raises(ValidationError)), + ), + ) + def test_validate_bool( + self, + content, + expected, + expectation, + ): + validator = TBoolValidator() + with expectation: + result = validator.validate(content) + if expected is not None: + assert result == expected + + +class TestStrValidator: + @pytest.mark.parametrize( + ("content", "expected", "expectation"), + ( + ("test string", "test string", does_not_raise()), + ("", "", does_not_raise()), + ("123", "123", does_not_raise()), + (123, None, pytest.raises(ValidationError)), + (True, None, pytest.raises(ValidationError)), + (None, None, pytest.raises(ValidationError)), + ([], None, pytest.raises(ValidationError)), + ({}, None, pytest.raises(ValidationError)), + ), + ) + def test_validate_str( + self, + content, + expected, + expectation, + ): + """Test string validation with various inputs.""" + validator = TStrValidator() + with expectation: + result = validator.validate(content) + if expected is not None: + assert result == expected + + +class TestDispatchersValidator: + @pytest.mark.parametrize( + ("content", "expected", "expectation"), + ( + ( + {"*.py": "python", "*.js": "node"}, + {"*.py": "python", "*.js": "node"}, + does_not_raise(), + ), + ({"*.py": "python"}, {"*.py": "python"}, does_not_raise()), + ("invalid", None, pytest.raises(ValidationError)), + (123, None, pytest.raises(ValidationError)), + (None, None, pytest.raises(ValidationError)), + ({"*.py": 123}, None, pytest.raises(ValidationError)), + ({123: "python"}, None, pytest.raises(ValidationError)), + ), + ) + def test_validate_dispatcher( + self, + content, + expected, + expectation, + ): + validator = TDispatchersValidator() + with expectation: + result = validator.validate(content) + if expected is not None: + assert result == expected + + +class TestDispatchersOrderValidator: + @pytest.mark.parametrize( + ("content", "expected", "expectation"), + ( + ([EDispatchersTypes.BY_GLOB], [EDispatchersTypes.BY_GLOB], does_not_raise()), + ( + [EDispatchersTypes.BY_FILE_EXT, EDispatchersTypes.BY_GLOB], + [EDispatchersTypes.BY_FILE_EXT, EDispatchersTypes.BY_GLOB], + does_not_raise(), + ), + (["invalid"], None, pytest.raises(ValidationError)), + ([1], None, pytest.raises(ValidationError)), + ({}, None, pytest.raises(ValidationError)), + ), + ) + def test_validate_dispatchers_order( + self, + content, + expected, + expectation, + ): + validator = TDispatchersOrderValidator() + with expectation: + result = validator.validate(content) + if expected is not None: + assert result == expected From 522fc94a98a1e0524f6c2022956ebca1a50635ca Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 00:32:10 +0300 Subject: [PATCH 08/15] =?UTF-8?q?refactor:=20classes=20=E2=84=968?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_coderunner/src/config/basic.py | 1 - .../tests/unit/config/test_validator.py | 35 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py index d80b063..2693bf8 100644 --- a/python_coderunner/src/config/basic.py +++ b/python_coderunner/src/config/basic.py @@ -9,7 +9,6 @@ class TBasicConfig(IConfig): """ - Configuration manager. Aggregates TConfigField objects, each of which encapsulates: - Value retrieval - Validation diff --git a/python_coderunner/tests/unit/config/test_validator.py b/python_coderunner/tests/unit/config/test_validator.py index ab995c2..d3144f8 100644 --- a/python_coderunner/tests/unit/config/test_validator.py +++ b/python_coderunner/tests/unit/config/test_validator.py @@ -1,6 +1,5 @@ -"""Tests for config validators.""" - from contextlib import nullcontext as does_not_raise +from typing import Any, ContextManager import pytest @@ -30,10 +29,10 @@ class TestBoolValidator: ) def test_validate_bool( self, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: validator = TBoolValidator() with expectation: result = validator.validate(content) @@ -57,10 +56,10 @@ class TestStrValidator: ) def test_validate_str( self, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: """Test string validation with various inputs.""" validator = TStrValidator() with expectation: @@ -88,10 +87,10 @@ class TestDispatchersValidator: ) def test_validate_dispatcher( self, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: validator = TDispatchersValidator() with expectation: result = validator.validate(content) @@ -116,10 +115,10 @@ class TestDispatchersOrderValidator: ) def test_validate_dispatchers_order( self, - content, - expected, - expectation, - ): + content: Any, + expected: Any, + expectation: ContextManager[Any], + ) -> None: validator = TDispatchersOrderValidator() with expectation: result = validator.validate(content) From 5aa73526101b6e29367255e32547ff4db80863e1 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 00:47:08 +0300 Subject: [PATCH 09/15] =?UTF-8?q?refactor:=20classes=20=E2=84=969?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vim_coderunner_factory.py | 7 ++- python_coderunner/src/config/basic.py | 52 ++++++++----------- 2 files changed, 25 insertions(+), 34 deletions(-) diff --git a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py index 8d4d195..ecbc2f8 100644 --- a/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py +++ b/python_coderunner/src/coderunner_factory/vim_coderunner_factory.py @@ -44,7 +44,7 @@ class TVimCodeRunnerFactory(ICodeRunnerFactory): def create(self) -> Optional[TCodeRunner]: - config = self._create_config() + config: TVimConfig = self._create_config() message_printer: TVimMessagePrinter = TVimMessagePrinter() try: @@ -75,7 +75,7 @@ def create(self) -> Optional[TCodeRunner]: return None def _create_config(self) -> TVimConfig: - config = TVimConfig( + return TVimConfig( by_file_ext_field=TConfigField( name="g:coderunner_by_file_ext", getter=TVimByFileExtConfigValueGetter(), @@ -143,7 +143,6 @@ def _create_config(self) -> TVimConfig: allowed_values_description="0 or 1", ), ) - return config def _create_command_dispatcher_strategy_selector( self, @@ -165,11 +164,11 @@ def _create_command_dispatcher_strategy_selector( ) return TBasicCommandDispatcherStrategySelector( + config=config, shebang_command_builders_dispatcher=shebang_command_builders_dispatcher, glob_command_builders_dispatcher=glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=file_ext_command_builders_dispatcher, file_type_command_builders_dispatcher=file_type_command_builders_dispatcher, - config=config, ) def _create_file_ext_command_builders_dispatcher( diff --git a/python_coderunner/src/config/basic.py b/python_coderunner/src/config/basic.py index 2693bf8..a7f85f5 100644 --- a/python_coderunner/src/config/basic.py +++ b/python_coderunner/src/config/basic.py @@ -8,14 +8,6 @@ class TBasicConfig(IConfig): - """ - Aggregates TConfigField objects, each of which encapsulates: - - Value retrieval - - Validation - - Field metadata - - Error handling - """ - def __init__( self, by_file_ext_field: TConfigField[Dict[str, str]], @@ -30,17 +22,17 @@ def __init__( save_all_files_before_run_field: TConfigField[bool], save_file_before_run_field: TConfigField[bool], ): - self._by_file_ext = by_file_ext_field - self._by_file_type = by_file_type_field - self._by_glob = by_glob_field - self._dispatchers_order = dispatchers_order_field - self._coderunner_tempfile_prefix = coderunner_tempfile_prefix_field - self._executor = executor_field - self._ignore_selection = ignore_selection_field - self._respect_shebang = respect_shebang_field - self._remove_coderunner_tempfiles_on_exit = remove_coderunner_tempfiles_on_exit_field - self._save_all_files_before_run = save_all_files_before_run_field - self._save_file_before_run = save_file_before_run_field + self._by_file_ext_field: TConfigField[Dict[str, str]] = by_file_ext_field + self._by_file_type_field: TConfigField[Dict[str, str]] = by_file_type_field + self._by_glob_field: TConfigField[Dict[str, str]] = by_glob_field + self._dispatchers_order_field: TConfigField[List[EDispatchersTypes]] = dispatchers_order_field + self._coderunner_tempfile_prefix_field: TConfigField[str] = coderunner_tempfile_prefix_field + self._executor_field: TConfigField[str] = executor_field + self._ignore_selection_field: TConfigField[bool] = ignore_selection_field + self._respect_shebang_field: TConfigField[bool] = respect_shebang_field + self._remove_coderunner_tempfiles_on_exit_field: TConfigField[bool] = remove_coderunner_tempfiles_on_exit_field + self._save_all_files_before_run_field: TConfigField[bool] = save_all_files_before_run_field + self._save_file_before_run_field: TConfigField[bool] = save_file_before_run_field def _get_field_value(self, field: TConfigField[ValueType]) -> ValueType: """ @@ -53,34 +45,34 @@ def _get_field_value(self, field: TConfigField[ValueType]) -> ValueType: raise ValueError(str(e)) from e def get_by_file_ext(self) -> Dict[str, str]: - return self._get_field_value(self._by_file_ext) + return self._get_field_value(self._by_file_ext_field) def get_by_file_type(self) -> Dict[str, str]: - return self._get_field_value(self._by_file_type) + return self._get_field_value(self._by_file_type_field) def get_by_glob(self) -> Dict[str, str]: - return self._get_field_value(self._by_glob) + return self._get_field_value(self._by_glob_field) def get_dispatchers_order(self) -> List[EDispatchersTypes]: - return self._get_field_value(self._dispatchers_order) + return self._get_field_value(self._dispatchers_order_field) def get_coderunner_tempfile_prefix(self) -> str: - return self._get_field_value(self._coderunner_tempfile_prefix) + return self._get_field_value(self._coderunner_tempfile_prefix_field) def get_executor(self) -> str: - return self._get_field_value(self._executor) + return self._get_field_value(self._executor_field) def get_ignore_selection(self) -> bool: - return self._get_field_value(self._ignore_selection) + return self._get_field_value(self._ignore_selection_field) def get_respect_shebang(self) -> bool: - return self._get_field_value(self._respect_shebang) + return self._get_field_value(self._respect_shebang_field) def get_remove_coderunner_tempfiles_on_exit(self) -> bool: - return self._get_field_value(self._remove_coderunner_tempfiles_on_exit) + return self._get_field_value(self._remove_coderunner_tempfiles_on_exit_field) def get_save_all_files_before_run(self) -> bool: - return self._get_field_value(self._save_all_files_before_run) + return self._get_field_value(self._save_all_files_before_run_field) def get_save_file_before_run(self) -> bool: - return self._get_field_value(self._save_file_before_run) + return self._get_field_value(self._save_file_before_run_field) From a28c8a4aa8876419186b2d3bb33d7d62fb505442 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 01:17:51 +0300 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20classes=20=E2=84=9610?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interpolator_command_builder.py | 16 ++++++------ .../glob_command_builders_dispatcher.py | 4 +-- .../validator/dispatchers_order_validator.py | 2 +- .../test_interpolator_command_builder.py | 4 +-- ...st_file_ext_command_builders_dispatcher.py | 2 +- ...t_file_type_command_builders_dispatcher.py | 2 +- .../test_glob_command_builders_dispatcher.py | 2 +- ...est_shebang_command_builders_dispatcher.py | 15 +++++++---- ...ic_command_dispatcher_strategy_selector.py | 5 ++-- .../test_interface_file_info_extractor.py | 25 +++++++++++-------- .../test_interface_project_info_extractor.py | 4 +-- 11 files changed, 45 insertions(+), 36 deletions(-) diff --git a/python_coderunner/src/command_builder/interpolator_command_builder.py b/python_coderunner/src/command_builder/interpolator_command_builder.py index 3740ab7..4843bef 100644 --- a/python_coderunner/src/command_builder/interpolator_command_builder.py +++ b/python_coderunner/src/command_builder/interpolator_command_builder.py @@ -7,14 +7,14 @@ class TInterpolatorCommandBuilder(ICommandBuilder): - WORKSPACE_ROOT_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$workspaceRoot") - FULL_FILE_NAME_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fullFileName") - FILE_NAME_WITHOUT_EXT_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fileNameWithoutExt") - FILE_NAME_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$fileName") - FILE_EXT: ClassVar[re.Pattern] = re.compile(r"\$fileExt") - DRIVE_LETTER_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$driveLetter") - DIR_WITHOUT_TRAILING_SLASH_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$dirWithoutTrailingSlash") - DIR_PATTERN: ClassVar[re.Pattern] = re.compile(r"\$dir") + WORKSPACE_ROOT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$workspaceRoot") + FULL_FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fullFileName") + FILE_NAME_WITHOUT_EXT_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileNameWithoutExt") + FILE_NAME_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$fileName") + FILE_EXT: ClassVar[re.Pattern[str]] = re.compile(r"\$fileExt") + DRIVE_LETTER_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$driveLetter") + DIR_WITHOUT_TRAILING_SLASH_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dirWithoutTrailingSlash") + DIR_PATTERN: ClassVar[re.Pattern[str]] = re.compile(r"\$dir") def __init__( self, diff --git a/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py b/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py index f092abf..509fea0 100644 --- a/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py +++ b/python_coderunner/src/command_builders_dispatcher/glob_command_builders_dispatcher.py @@ -6,8 +6,8 @@ class TGlobCommandBuildersDispatcher(ICommandBuildersDispatcher): - def __init__(self, glob_to_builder: Tuple[Tuple[re.Pattern, ICommandBuilder], ...]): - self._glob_to_builder: Final[Tuple[Tuple[re.Pattern, ICommandBuilder], ...]] = glob_to_builder + def __init__(self, glob_to_builder: Tuple[Tuple[re.Pattern[str], ICommandBuilder], ...]): + self._glob_to_builder: Final[Tuple[Tuple[re.Pattern[str], ICommandBuilder], ...]] = glob_to_builder def dispatch(self, file_path_abs: str) -> Optional[ICommandBuilder]: for pattern, builder in self._glob_to_builder: diff --git a/python_coderunner/src/config/validator/dispatchers_order_validator.py b/python_coderunner/src/config/validator/dispatchers_order_validator.py index 04f777e..5de8e3e 100644 --- a/python_coderunner/src/config/validator/dispatchers_order_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_order_validator.py @@ -6,7 +6,7 @@ class TDispatchersOrderValidator(IValidator[List[EDispatchersTypes]]): - def __init__(self): + def __init__(self) -> None: self.allowed_dispatcher_types: Set[EDispatchersTypes] = set(EDispatchersTypes) def validate(self, value: Any) -> List[EDispatchersTypes]: diff --git a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py index 3b5677f..ab68501 100644 --- a/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py +++ b/python_coderunner/tests/unit/command_builder/test_interpolator_command_builder.py @@ -6,7 +6,7 @@ class TestInterpolatorCommandBuilder: def test_build( self, fixture_project_info_extractor: IProjectInfoExtractor, fixture_file_info_extractor: IFileInfoExtractor - ): + ) -> None: comparator: TestInterpolatorCommandBuilder.TComparator = TestInterpolatorCommandBuilder.TComparator( fixture_project_info_extractor, fixture_file_info_extractor ) @@ -30,7 +30,7 @@ def __init__(self, project_info_extractor: IProjectInfoExtractor, file_info_extr self._project_info_extractor: IProjectInfoExtractor = project_info_extractor self._file_info_extractor: IFileInfoExtractor = file_info_extractor - def __call__(self, pattern: str, file_path_abs: str, expected_result: str): + def __call__(self, pattern: str, file_path_abs: str, expected_result: str) -> None: builder: TInterpolatorCommandBuilder = TInterpolatorCommandBuilder( pattern, self._project_info_extractor, self._file_info_extractor ) diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py index cc14da4..25f12ce 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_ext_command_builders_dispatcher.py @@ -22,7 +22,7 @@ def test_file_ext_command_builders_dispatcher( fixture_file_ext_command_builders_dispatcher: TFileExtCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_file_ext_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py index f51ffeb..a18c676 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_file_type_command_builders_dispatcher.py @@ -22,7 +22,7 @@ def test_file_type_command_builders_dispatcher( fixture_file_type_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_file_type_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py index a063187..a38be74 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_glob_command_builders_dispatcher.py @@ -25,7 +25,7 @@ def test_glob_command_builders_dispatcher( fixture_glob_command_builders_dispatcher: TFileTypeCommandBuildersDispatcher, file_path: str, expected_build_result: Optional[str], -): +) -> None: dispatch_result: Optional[ICommandBuilder] = fixture_glob_command_builders_dispatcher.dispatch(file_path) if expected_build_result is not None: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py index 69f5688..62501a4 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py @@ -1,8 +1,10 @@ +from pathlib import Path from typing import Optional import pytest -from src.command_builder import TConcatenatorCommandBuilder +from src.command_builder import ICommandBuilder +from src.command_builders_dispatcher import TShebangCommandBuildersDispatcher @pytest.mark.parametrize( @@ -22,8 +24,11 @@ ], ) def test_shebang_command_builders_dispatcher( - fixture_shebang_command_builders_dispatcher, content, expected_result, tmp_path -): + fixture_shebang_command_builders_dispatcher: TShebangCommandBuildersDispatcher, + content: Optional[bytes], + expected_result: Optional[str], + tmp_path: Path, +) -> None: file_path_abs = tmp_path / "test_file" if content is None: @@ -31,8 +36,8 @@ def test_shebang_command_builders_dispatcher( return file_path_abs.write_bytes(content) - dispatch_result: Optional[TConcatenatorCommandBuilder] = fixture_shebang_command_builders_dispatcher.dispatch( - file_path_abs + dispatch_result: Optional[ICommandBuilder] = fixture_shebang_command_builders_dispatcher.dispatch( + str(file_path_abs) ) if expected_result is None: diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index ce2853d..8d12b16 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import List, Optional from unittest.mock import MagicMock @@ -73,8 +74,8 @@ def test_basic_command_dispatcher_strategy_selector( order: List[EDispatchersTypes], respect_shebang: bool, expected: Optional[str], - tmp_path, -): + tmp_path: Path, +) -> None: file_path_abs = tmp_path / file_path file_path_abs.write_bytes(content) config: TBasicConfig = MagicMock( diff --git a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py index dfcf3f8..371c704 100644 --- a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py +++ b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py @@ -1,5 +1,6 @@ import os import tempfile +from typing import Optional from unittest.mock import patch import pytest @@ -8,7 +9,7 @@ class TestFileInfoExtractorInterface: - def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_dir("/home/user/project/file.py") == "/home/user/project/" assert fixture_file_info_extractor.get_dir("/home/file.txt") == "/home/" assert fixture_file_info_extractor.get_dir("/home/") == "/home/" @@ -16,42 +17,42 @@ def test_get_dir(self, fixture_file_info_extractor: IFileInfoExtractor): assert fixture_file_info_extractor.get_dir("/file.txt") == "/" assert fixture_file_info_extractor.get_dir("/") == "/" - def test_get_dir_without_trailing_slash(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_dir_without_trailing_slash(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_dir_without_trailing_slash("/file.py") == "" assert ( fixture_file_info_extractor.get_dir_without_trailing_slash("/home/user/project/file.py") == "/home/user/project" ) - def test_get_file_name(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_name(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_name("/home/user/project/file.py") == "file.py" - def test_get_file_name_without_ext(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_name_without_ext(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file") == "file" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file.py") == "file" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/file.tar.gz") == "file.tar" assert fixture_file_info_extractor.get_file_name_without_ext("/home/user/project/.zig.zon") == ".zig" - def test_get_file_ext(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_ext(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_ext("/home/user/project/.zig.zon") == ".zon" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file") == "" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file.py") == ".py" assert fixture_file_info_extractor.get_file_ext("/home/user/project/file.cPp") == ".cpp" - def test_get_file_type(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_file_type(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: assert fixture_file_info_extractor.get_file_type("/home/user/project/file.py") == "python" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.rs") == "rust" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.cpp") == "cpp" assert fixture_file_info_extractor.get_file_type("/home/user/project/file.unknownext") is None assert fixture_file_info_extractor.get_file_type("/home/user/project/file.PY") == "python" - def test_get_drive_letter_windows(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_drive_letter_windows(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: # On Unix, drive will be '', on Windows, 'C:' path = "C:\\Users\\user\\file.py" expected = os.path.splitdrive(path)[0] assert fixture_file_info_extractor.get_drive_letter(path) == expected - def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: path = "/home/user/file.py" assert fixture_file_info_extractor.get_drive_letter(path) == "" @@ -71,7 +72,9 @@ def test_get_drive_letter_unix(self, fixture_file_info_extractor: IFileInfoExtra pytest.param("#!/bin/bash -x\ndef f():\n\treturn0", "/bin/bash -x", id="shebang with multiple lines"), ], ) - def test_get_shebang(self, fixture_file_info_extractor: IFileInfoExtractor, content, expected): + def test_get_shebang( + self, fixture_file_info_extractor: IFileInfoExtractor, content: Optional[str], expected: Optional[str] + ) -> None: if content is None: assert fixture_file_info_extractor.get_shebang("/bad/path") is expected return @@ -84,10 +87,10 @@ def test_get_shebang(self, fixture_file_info_extractor: IFileInfoExtractor, cont finally: os.unlink(file_path) - def test_get_shebang_io_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_shebang_io_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: with patch("builtins.open", side_effect=IOError("Permission denied")): assert fixture_file_info_extractor.get_shebang("/some/file") is None - def test_get_shebang_unicode_decode_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor): + def test_get_shebang_unicode_decode_error_handling(self, fixture_file_info_extractor: IFileInfoExtractor) -> None: with patch("builtins.open", side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "Invalid byte")): assert fixture_file_info_extractor.get_shebang("/some/file") is None diff --git a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py index db6b954..aa27400 100644 --- a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py +++ b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py @@ -5,7 +5,7 @@ class TestProjectInfoExtractorInterface: - def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IProjectInfoExtractor): + def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) test_files = ( @@ -29,7 +29,7 @@ def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IPro workspace_root / "dir_0" / "py.py", } - def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: IProjectInfoExtractor): + def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) test_files = ( From 4da203d8aeaa39c208e7f6149782864e5de91b6e Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 01:34:34 +0300 Subject: [PATCH 11/15] =?UTF-8?q?refactor:=20classes=20=E2=84=9611?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_coderunner/src/config/config_field.py | 4 ++-- python_coderunner/src/config/getter/interface.py | 4 ++-- .../src/project_info_extractor/interface.py | 2 +- .../test_shebang_command_builders_dispatcher.py | 2 +- ...t_basic_command_dispatcher_strategy_selector.py | 2 +- .../test_interface_file_info_extractor.py | 2 +- .../test_interface_project_info_extractor.py | 14 ++++++++------ 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py index 5e80174..e405afc 100644 --- a/python_coderunner/src/config/config_field.py +++ b/python_coderunner/src/config/config_field.py @@ -1,7 +1,7 @@ from typing import Any, Callable, Generic, TypeVar from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError -from .getter import UndefinedValueError +from .getter import IConfigValueGetter, UndefinedValueError from .validator import IValidator, ValidationError ValueType = TypeVar("ValueType") @@ -19,7 +19,7 @@ class TConfigField(Generic[ValueType]): def __init__( self, name: str, - getter: Callable[[], Any], + getter: IConfigValueGetter, validator: IValidator[ValueType], allowed_values_description: str, ): diff --git a/python_coderunner/src/config/getter/interface.py b/python_coderunner/src/config/getter/interface.py index 7438728..8456121 100644 --- a/python_coderunner/src/config/getter/interface.py +++ b/python_coderunner/src/config/getter/interface.py @@ -1,3 +1,3 @@ -from typing import Any, Callable +from typing import Any, Callable, TypeAlias -IConfigValueGetter = Callable[[], Any] +IConfigValueGetter: TypeAlias = Callable[[], Any] diff --git a/python_coderunner/src/project_info_extractor/interface.py b/python_coderunner/src/project_info_extractor/interface.py index 4966813..be01ca6 100644 --- a/python_coderunner/src/project_info_extractor/interface.py +++ b/python_coderunner/src/project_info_extractor/interface.py @@ -7,7 +7,7 @@ class IProjectInfoExtractor(ABC): def __init__(self, file_info_extractor: IFileInfoExtractor): - self._file_info_extractor = file_info_extractor + self._file_info_extractor: IFileInfoExtractor = file_info_extractor @abstractmethod def get_workspace_root(self) -> str: diff --git a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py index 62501a4..927674c 100644 --- a/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py +++ b/python_coderunner/tests/unit/command_builders_dispatcher/test_shebang_command_builders_dispatcher.py @@ -29,7 +29,7 @@ def test_shebang_command_builders_dispatcher( expected_result: Optional[str], tmp_path: Path, ) -> None: - file_path_abs = tmp_path / "test_file" + file_path_abs: Path = tmp_path / "test_file" if content is None: assert fixture_shebang_command_builders_dispatcher.dispatch(str(file_path_abs)) is None diff --git a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py index 8d12b16..81003db 100644 --- a/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py +++ b/python_coderunner/tests/unit/command_dispatcher_strategy_selector/test_basic_command_dispatcher_strategy_selector.py @@ -81,7 +81,7 @@ def test_basic_command_dispatcher_strategy_selector( config: TBasicConfig = MagicMock( get_dispatchers_order=MagicMock(return_value=order), get_respect_shebang=MagicMock(return_value=respect_shebang) ) - selector = TBasicCommandDispatcherStrategySelector( + selector: TBasicCommandDispatcherStrategySelector = TBasicCommandDispatcherStrategySelector( shebang_command_builders_dispatcher=fixture_shebang_command_builders_dispatcher, glob_command_builders_dispatcher=fixture_glob_command_builders_dispatcher, file_ext_command_builders_dispatcher=fixture_file_ext_command_builders_dispatcher, diff --git a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py index 371c704..6f6262b 100644 --- a/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py +++ b/python_coderunner/tests/unit/file_info_extractor/test_interface_file_info_extractor.py @@ -81,7 +81,7 @@ def test_get_shebang( with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file: temp_file.write(content) - file_path = temp_file.name + file_path: str = temp_file.name try: assert fixture_file_info_extractor.get_shebang(file_path) == expected finally: diff --git a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py index aa27400..73b0b41 100644 --- a/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py +++ b/python_coderunner/tests/unit/project_info_extractor/test_interface_project_info_extractor.py @@ -6,9 +6,9 @@ class TestProjectInfoExtractorInterface: def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: - workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) + workspace_root: Path = Path(fixture_project_info_extractor.get_workspace_root()) - test_files = ( + test_files: tuple[Path, ...] = ( workspace_root / "file_0.py", workspace_root / "file_0.txt", workspace_root / "file_1.py", @@ -21,7 +21,7 @@ def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IPro file_path.touch(exist_ok=True) exts: Set[str] = {".py", ".md"} - result = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_exts(exts)} + result: Set[Path] = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_exts(exts)} assert result == { workspace_root / "file_0.py", workspace_root / "file_1.py", @@ -30,9 +30,9 @@ def test_get_all_files_filter_by_exts(self, fixture_project_info_extractor: IPro } def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: IProjectInfoExtractor) -> None: - workspace_root = Path(fixture_project_info_extractor.get_workspace_root()) + workspace_root: Path = Path(fixture_project_info_extractor.get_workspace_root()) - test_files = ( + test_files: tuple[Path, ...] = ( workspace_root / "file_0.py", workspace_root / "file_0.cpp", workspace_root / "file_0.txt", @@ -46,7 +46,9 @@ def test_get_all_files_filter_by_file_type(self, fixture_project_info_extractor: file_path.touch(exist_ok=True) file_types: Set[str] = {"python", "cpp", "cobol"} - result = {Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_file_type(file_types)} + result: Set[Path] = { + Path(p) for p in fixture_project_info_extractor.get_all_files_filter_by_file_type(file_types) + } assert result == { workspace_root / "file_0.py", workspace_root / "file_0.cpp", From 15b5c4d190ddf5a90c5a5030320e860f27501e62 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 01:38:55 +0300 Subject: [PATCH 12/15] =?UTF-8?q?refactor:=20classes=20=E2=84=9612?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- python_coderunner/src/config/config_field.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py index e405afc..24cacd3 100644 --- a/python_coderunner/src/config/config_field.py +++ b/python_coderunner/src/config/config_field.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Generic, TypeVar from .exceptions import ConfigFieldNotFoundError, ConfigFieldValidationError from .getter import IConfigValueGetter, UndefinedValueError @@ -24,7 +24,7 @@ def __init__( allowed_values_description: str, ): self._name: str = name - self._getter: Callable[[], Any] = getter + self._getter: IConfigValueGetter = getter self._validator: IValidator[ValueType] = validator self._allowed_values_description: str = allowed_values_description From 439fa9249233b9ba5917fcde9b72df66ce36da63 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 01:51:50 +0300 Subject: [PATCH 13/15] =?UTF-8?q?refactor:=20classes=20=E2=84=9613?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 64 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 86d1b1e..e9735c9 100644 --- a/README.md +++ b/README.md @@ -135,21 +135,23 @@ pre-commit install ### Plugin architecture ```mermaid classDiagram -TCodeRunner "1" o--> "1" IConfigManager : aggregates +TCodeRunner "1" o--> "1" IConfig : aggregates TCodeRunner "1" o--> "1" ICommandsExecutor : aggregates TCodeRunner "1" o--> "1" IMessagePrinter : aggregates TCodeRunner "1" o--> "1" TBasicEditorServiceForCodeRunner : aggregates TCodeRunner "1" o--> "1" TBasicCommandDispatcherStrategySelector : aggregates -IConfigManager "1" o--> "1" IConfigGetter : aggregates -IConfigManager "1" o--> "1" TBasicConfigValidator : aggregates +TBasicConfig ..|> IConfig : implements +TBasicConfig "1" o--> "n" TConfigField : aggregates +TConfigField "1" o--> "1" IConfigValueGetter : aggregates +TConfigField "1" o--> "1" IValidator : aggregates TBasicEditorServiceForCodeRunner "1" o--> "1" IEditor : aggregates -TBasicEditorServiceForCodeRunner "1" o--> "1" IConfigManager : aggregates -ICommandsExecutor "1" o--> "1" IConfigManager : aggregates +TBasicEditorServiceForCodeRunner "1" o--> "1" IConfig : aggregates +ICommandsExecutor "1" o--> "1" IConfig : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TShebangCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TGlobCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TFileExtCommandBuildersDispatcher : aggregates TBasicCommandDispatcherStrategySelector "1" o--> "1" TFileTypeCommandBuildersDispatcher : aggregates -TBasicCommandDispatcherStrategySelector "1" o--> "1" IConfigManager : aggregates +TBasicCommandDispatcherStrategySelector "1" o--> "1" IConfig : aggregates TShebangCommandBuildersDispatcher ..|> ICommandBuildersDispatcher : implements TShebangCommandBuildersDispatcher "1" o--> "1" IFileInfoExtractor : aggregates TGlobCommandBuildersDispatcher ..|> ICommandBuildersDispatcher : implements @@ -166,7 +168,7 @@ TBaseFileInfoExtractor ..|> IFileInfoExtractor : implements class TCodeRunner { - # config_manager: IConfigManager + # config: IConfig # editor_service: TBasicEditorServiceForCodeRunner # command_dispatcher_strategy_selector: TBasicCommandDispatcherStrategySelector # commands_executor: ICommandsExecutor @@ -180,40 +182,48 @@ class TCodeRunner { } - -class IConfigManager { +class IConfig { <> - + get_dispatchers_order() list[str] + + get_by_file_ext() Dict[str, str] + + get_by_file_type() Dict[str, str] + + get_by_glob() Dict[str, str] + + get_dispatchers_order() List[EDispatchersTypes] + + get_coderunner_tempfile_prefix() str + get_executor() str + get_ignore_selection() bool + get_respect_shebang() bool - + get_save_all_files() bool - + get_save_file() bool + + get_remove_coderunner_tempfiles_on_exit() bool + + get_save_all_files_before_run() bool + + get_save_file_before_run() bool } -class IConfigGetter { - <> - + get_dispatchers_order() Any - + get_executor() Any - + get_ignore_selection() Any - + get_respect_shebang() Any - + get_save_all_files() Any - + get_save_file() Any +class TConfigField { + # name: str + # getter: IConfigValueGetter + # validator: IValidator + # allowed_values_description: str + + get() ValueType +} + + +class IConfigValueGetter { + <> + + __call__() Any } -class TBasicConfigValidator { - + validate_bool() bool - + validate_str() str - + validate_dispatcher() Dict[str, str] - + validate_dispatchers_order() List[EDispatchersTypes] +class IValidator { + <> + + __call__(value: Any) ValueType + } class TBasicEditorServiceForCodeRunner { # editor: IEditor - # config_manager: IConfigManager + # config:IConfig + # file_info_extractor: IFileInfoExtractor Creates context which will delete file if it's temporary + get_file_for_run() Context[str] Runs save_file or save_all_files if the command_builder is found, @@ -246,7 +256,7 @@ class IMessagePrinter { class TBasicCommandDispatcherStrategySelector { - # config_manager: IConfigManager + # config: IConfig # shebang_dispatcher: TShebangCommandBuildersDispatcher # file_type_dispatcher: TFileExtCommandBuildersDispatcher # file_ext_dispatcher: TFileTypeCommandBuildersDispatcher From 6972311d0a6a3ee5158f2015761f95efee7064d1 Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 02:00:40 +0300 Subject: [PATCH 14/15] refactor(validator): replace validate() method with __call__() - Changed IValidator interface to use __call__() instead of validate() - Updated all validator implementations (bool, str, dispatchers, dispatchers_order) - Updated TConfigField to call validators as callables - Updated all tests to use validator() instead of validator.validate() - All 87 tests pass, mypy validation successful --- python_coderunner/src/config/config_field.py | 2 +- python_coderunner/src/config/validator/bool_validator.py | 2 +- .../src/config/validator/dispatchers_order_validator.py | 2 +- .../src/config/validator/dispatchers_validator.py | 2 +- python_coderunner/src/config/validator/interface.py | 2 +- python_coderunner/src/config/validator/str_validator.py | 2 +- python_coderunner/tests/unit/config/test_validator.py | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/python_coderunner/src/config/config_field.py b/python_coderunner/src/config/config_field.py index 24cacd3..5508a6a8 100644 --- a/python_coderunner/src/config/config_field.py +++ b/python_coderunner/src/config/config_field.py @@ -35,6 +35,6 @@ def get(self) -> ValueType: raise ConfigFieldNotFoundError.from_undefined_value_error(e, self._allowed_values_description) try: - return self._validator.validate(raw_value) + return self._validator(raw_value) except ValidationError as e: raise ConfigFieldValidationError.from_validation_error(e, self._name, self._allowed_values_description) diff --git a/python_coderunner/src/config/validator/bool_validator.py b/python_coderunner/src/config/validator/bool_validator.py index a3f7ef2..be5c6a4 100644 --- a/python_coderunner/src/config/validator/bool_validator.py +++ b/python_coderunner/src/config/validator/bool_validator.py @@ -5,7 +5,7 @@ class TBoolValidator(IValidator[bool]): - def validate(self, value: Any) -> bool: + def __call__(self, value: Any) -> bool: if isinstance(value, bool): return value if str(value).strip() in ("0", "1"): diff --git a/python_coderunner/src/config/validator/dispatchers_order_validator.py b/python_coderunner/src/config/validator/dispatchers_order_validator.py index 5de8e3e..9fd390c 100644 --- a/python_coderunner/src/config/validator/dispatchers_order_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_order_validator.py @@ -9,7 +9,7 @@ class TDispatchersOrderValidator(IValidator[List[EDispatchersTypes]]): def __init__(self) -> None: self.allowed_dispatcher_types: Set[EDispatchersTypes] = set(EDispatchersTypes) - def validate(self, value: Any) -> List[EDispatchersTypes]: + def __call__(self, value: Any) -> List[EDispatchersTypes]: if not isinstance(value, list): raise ValidationError(f"Invalid dispatcher order container type: {type(value)}.") diff --git a/python_coderunner/src/config/validator/dispatchers_validator.py b/python_coderunner/src/config/validator/dispatchers_validator.py index 77872ae..303f274 100644 --- a/python_coderunner/src/config/validator/dispatchers_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_validator.py @@ -5,7 +5,7 @@ class TDispatchersValidator(IValidator[Dict[str, str]]): - def validate(self, value: Any) -> Dict[str, str]: + def __call__(self, value: Any) -> Dict[str, str]: if not isinstance(value, dict): raise ValidationError(f"Invalid dispatcher container type: {type(value)}.") for key, val in value.items(): diff --git a/python_coderunner/src/config/validator/interface.py b/python_coderunner/src/config/validator/interface.py index df4e18f..694b730 100644 --- a/python_coderunner/src/config/validator/interface.py +++ b/python_coderunner/src/config/validator/interface.py @@ -8,7 +8,7 @@ class IValidator(ABC, Generic[ValueType]): """Base interface for all validators""" @abstractmethod - def validate(self, value: Any) -> ValueType: + def __call__(self, value: Any) -> ValueType: """ Validates value and returns typed result. Raises ValidationError on validation failure. diff --git a/python_coderunner/src/config/validator/str_validator.py b/python_coderunner/src/config/validator/str_validator.py index aff83ae..750bc6b 100644 --- a/python_coderunner/src/config/validator/str_validator.py +++ b/python_coderunner/src/config/validator/str_validator.py @@ -5,7 +5,7 @@ class TStrValidator(IValidator[str]): - def validate(self, value: Any) -> str: + def __call__(self, value: Any) -> str: if isinstance(value, str): return value raise ValidationError(f"Invalid str type: {type(value)}.") diff --git a/python_coderunner/tests/unit/config/test_validator.py b/python_coderunner/tests/unit/config/test_validator.py index d3144f8..204368e 100644 --- a/python_coderunner/tests/unit/config/test_validator.py +++ b/python_coderunner/tests/unit/config/test_validator.py @@ -35,7 +35,7 @@ def test_validate_bool( ) -> None: validator = TBoolValidator() with expectation: - result = validator.validate(content) + result = validator(content) if expected is not None: assert result == expected @@ -63,7 +63,7 @@ def test_validate_str( """Test string validation with various inputs.""" validator = TStrValidator() with expectation: - result = validator.validate(content) + result = validator(content) if expected is not None: assert result == expected @@ -93,7 +93,7 @@ def test_validate_dispatcher( ) -> None: validator = TDispatchersValidator() with expectation: - result = validator.validate(content) + result = validator(content) if expected is not None: assert result == expected @@ -121,6 +121,6 @@ def test_validate_dispatchers_order( ) -> None: validator = TDispatchersOrderValidator() with expectation: - result = validator.validate(content) + result = validator(content) if expected is not None: assert result == expected From 1f6e3f0aaaa04ac66f8faf9af1a81cf048bc971a Mon Sep 17 00:00:00 2001 From: ZaharChernenko Date: Tue, 10 Mar 2026 02:02:27 +0300 Subject: [PATCH 15/15] =?UTF-8?q?refactor:=20classes=20=E2=84=9614?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../src/config/validator/dispatchers_order_validator.py | 1 + python_coderunner/src/config/validator/dispatchers_validator.py | 1 + python_coderunner/src/config/validator/interface.py | 2 -- 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9735c9..e8371cd 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,7 @@ class ICommandsExecutor { class IMessagePrinter { + <> + info(text: str) None + error(text: str) None } diff --git a/python_coderunner/src/config/validator/dispatchers_order_validator.py b/python_coderunner/src/config/validator/dispatchers_order_validator.py index 9fd390c..25e4b5e 100644 --- a/python_coderunner/src/config/validator/dispatchers_order_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_order_validator.py @@ -15,4 +15,5 @@ def __call__(self, value: Any) -> List[EDispatchersTypes]: if invalid_items := [v for v in value if v not in self.allowed_dispatcher_types]: raise ValidationError(f"Invalid dispatcher types values: {', '.join(map(str, invalid_items))}.") + return value diff --git a/python_coderunner/src/config/validator/dispatchers_validator.py b/python_coderunner/src/config/validator/dispatchers_validator.py index 303f274..4992f6e 100644 --- a/python_coderunner/src/config/validator/dispatchers_validator.py +++ b/python_coderunner/src/config/validator/dispatchers_validator.py @@ -13,4 +13,5 @@ def __call__(self, value: Any) -> Dict[str, str]: raise ValidationError(f"Invalid type in dispatcher dict key: {type(key)}.") if not isinstance(val, str): raise ValidationError(f"Invalid type in dispatcher dict value: {type(val)}.") + return value diff --git a/python_coderunner/src/config/validator/interface.py b/python_coderunner/src/config/validator/interface.py index 694b730..e30e4ef 100644 --- a/python_coderunner/src/config/validator/interface.py +++ b/python_coderunner/src/config/validator/interface.py @@ -5,8 +5,6 @@ class IValidator(ABC, Generic[ValueType]): - """Base interface for all validators""" - @abstractmethod def __call__(self, value: Any) -> ValueType: """