ref: typehints, enum instead of raw string, abc and other (#1)

Full code coverage with annotations, fixing errors in various linters: ruff, wps, etc. Fixing errors in type checkers: ty, mypy, pyright. Formatting and bringing code to a consistent style, applying best practices in various aspects.
This commit is contained in:
kolo
2025-10-08 13:37:31 +03:00
committed by GitHub
parent 22f1171192
commit 73303b1c08
45 changed files with 983 additions and 996 deletions
+3 -3
View File
@@ -1,4 +1,4 @@
__all__ = ["Router"]
from argenta.router.entity import Router
__all__ = ["Router"]
+7 -27
View File
@@ -1,18 +1,19 @@
from typing import Callable, Iterator
from collections.abc import Iterator
from typing import Callable
from argenta.command import Command
from argenta.response import Response
class CommandHandler:
def __init__(self, handler: Callable[[Response], None], handled_command: Command):
def __init__(self, handler_as_func: Callable[[Response], None], handled_command: Command):
"""
Private. Entity of the model linking the handler and the command being processed
:param handler: the handler being called
:param handled_command: the command being processed
"""
self._handler = handler
self._handled_command = handled_command
self.handler_as_func: Callable[[Response], None] = handler_as_func
self.handled_command: Command = handled_command
def handling(self, response: Response) -> None:
"""
@@ -20,21 +21,7 @@ class CommandHandler:
:param response: the entity of response: various groups of flags and status of response
:return: None
"""
self._handler(response)
def get_handler(self) -> Callable[[Response], None]:
"""
Private. Returns the handler being called
:return: the handler being called as Callable[[Response], None]
"""
return self._handler
def get_handled_command(self) -> Command:
"""
Private. Returns the command being processed
:return: the command being processed as Command
"""
return self._handled_command
self.handler_as_func(response)
class CommandHandlers:
@@ -43,14 +30,7 @@ class CommandHandlers:
Private. The model that unites all CommandHandler of the routers
:param command_handlers: list of CommandHandlers for register
"""
self.command_handlers = command_handlers if command_handlers else []
def get_handlers(self) -> list[CommandHandler]:
"""
Private. Returns the list of CommandHandlers
:return: the list of CommandHandlers as list[CommandHandler]
"""
return self.command_handlers
self.command_handlers: list[CommandHandler] = command_handlers if command_handlers else []
def add_handler(self, command_handler: CommandHandler) -> None:
"""
+127 -155
View File
@@ -1,17 +1,14 @@
from typing import Callable, Literal, Type
from typing import Callable, TypeAlias
from inspect import getfullargspec, get_annotations, getsourcefile, getsourcelines
from rich.console import Console
from argenta.command import Command
from argenta.command.models import InputCommand
from argenta.response import Response, Status
from argenta.command import Command, InputCommand
from argenta.command.flag import ValidationStatus
from argenta.response import Response, ResponseStatus
from argenta.router.command_handler.entity import CommandHandlers, CommandHandler
from argenta.command.flag.flags import (
Flags,
InputFlags,
UndefinedInputFlags,
ValidInputFlags,
InvalidValueInputFlags,
InputFlags
)
from argenta.router.exceptions import (
RepeatedFlagNameException,
@@ -21,9 +18,13 @@ from argenta.router.exceptions import (
)
HandlerFunc: TypeAlias = Callable[[Response], None]
class Router:
def __init__(
self, title: str | None = "Awesome title", disable_redirect_stdout: bool = False
self, *, title: str | None = "Default title",
disable_redirect_stdout: bool = False
):
"""
Public. Directly configures and manages handlers
@@ -35,13 +36,13 @@ class Router:
which is ambiguous behavior and can lead to unexpected work
:return: None
"""
self.title = title
self.disable_redirect_stdout = disable_redirect_stdout
self.title: str | None = title
self.disable_redirect_stdout: bool = disable_redirect_stdout
self._command_handlers: CommandHandlers = CommandHandlers()
self._ignore_command_register: bool = False
self.command_handlers: CommandHandlers = CommandHandlers()
self.command_register_ignore: bool = False
def command(self, command: Command | str) -> Callable:
def command(self, command: Command | str) -> Callable[[HandlerFunc], HandlerFunc]:
"""
Public. Registers handler
:param command: Registered command
@@ -51,18 +52,16 @@ class Router:
redefined_command = Command(command)
else:
redefined_command = command
self._validate_command(redefined_command)
def command_decorator(func):
Router._validate_func_args(func)
self._command_handlers.add_handler(CommandHandler(func, redefined_command))
_validate_command(redefined_command)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
def decorator(func: HandlerFunc) -> HandlerFunc:
_validate_func_args(func)
self.command_handlers.add_handler(CommandHandler(func, redefined_command))
return wrapper
return func
return command_decorator
return decorator
def finds_appropriate_handler(self, input_command: InputCommand) -> None:
"""
@@ -70,14 +69,14 @@ class Router:
:param input_command: input command as InputCommand
:return: None
"""
input_command_name: str = input_command.get_trigger()
input_command_flags: InputFlags = input_command.get_input_flags()
input_command_name: str = input_command.trigger
input_command_flags: InputFlags = input_command.input_flags
for command_handler in self._command_handlers:
handle_command = command_handler.get_handled_command()
if input_command_name.lower() == handle_command.get_trigger().lower():
for command_handler in self.command_handlers:
handle_command = command_handler.handled_command
if input_command_name.lower() == handle_command.trigger.lower():
self.process_input_command(input_command_flags, command_handler)
if input_command_name.lower() in handle_command.get_aliases():
if input_command_name.lower() in handle_command.aliases:
self.process_input_command(input_command_flags, command_handler)
def process_input_command(
@@ -89,152 +88,125 @@ class Router:
:param command_handler: command handler for input command as CommandHandler
:return: None
"""
handle_command = command_handler.get_handled_command()
response: Response = Response()
if handle_command.get_registered_flags().get_flags():
if input_command_flags.get_flags():
response: Response = self._structuring_input_flags( handle_command, input_command_flags )
handle_command = command_handler.handled_command
if handle_command.registered_flags.flags:
if input_command_flags.flags:
response: Response = _structuring_input_flags(handle_command, input_command_flags)
command_handler.handling(response)
else:
response.status = Status.ALL_FLAGS_VALID
response = Response(ResponseStatus.ALL_FLAGS_VALID)
command_handler.handling(response)
else:
if input_command_flags.get_flags():
response.status = Status.UNDEFINED_FLAGS
response.undefined_flags = UndefinedInputFlags()
response.undefined_flags.add_flags(input_command_flags.get_flags())
if input_command_flags.flags:
undefined_flags = InputFlags()
for input_flag in input_command_flags:
input_flag.status = ValidationStatus.UNDEFINED
undefined_flags.add_flag(input_flag)
response = Response(ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags)
command_handler.handling(response)
else:
response.status = Status.ALL_FLAGS_VALID
response = Response(ResponseStatus.ALL_FLAGS_VALID)
command_handler.handling(response)
@staticmethod
def _structuring_input_flags(
handled_command: Command, input_flags: InputFlags
) -> Response:
"""
Private. Validates flags of input command
:param handled_command: entity of the handled command
:param input_flags:
:return: entity of response as Response
"""
valid_input_flags: ValidInputFlags = ValidInputFlags()
invalid_value_input_flags: InvalidValueInputFlags = InvalidValueInputFlags()
undefined_input_flags: UndefinedInputFlags = UndefinedInputFlags()
for flag in input_flags:
flag_status: Literal["Undefined", "Valid", "Invalid"] = (
handled_command.validate_input_flag(flag)
)
if flag_status == "Valid":
valid_input_flags.add_flag(flag)
elif flag_status == "Undefined":
undefined_input_flags.add_flag(flag)
elif flag_status == "Invalid":
invalid_value_input_flags.add_flag(flag)
if (
not invalid_value_input_flags.get_flags()
and not undefined_input_flags.get_flags()
):
status = Status.ALL_FLAGS_VALID
elif (
invalid_value_input_flags.get_flags()
and not undefined_input_flags.get_flags()
):
status = Status.INVALID_VALUE_FLAGS
elif (
not invalid_value_input_flags.get_flags()
and undefined_input_flags.get_flags()
):
status = Status.UNDEFINED_FLAGS
else:
status = Status.UNDEFINED_AND_INVALID_FLAGS
return Response(
invalid_value_flags=invalid_value_input_flags,
valid_flags=valid_input_flags,
status=status,
undefined_flags=undefined_input_flags,
)
@staticmethod
def _validate_command(command: Command) -> None:
"""
Private. Validates the command registered in handler
:param command: validated command
:return: None if command is valid else raise exception
"""
command_name: str = command.get_trigger()
if command_name.find(" ") != -1:
raise TriggerContainSpacesException()
flags: Flags = command.get_registered_flags()
if flags:
flags_name: list = [x.get_string_entity().lower() for x in flags]
if len(set(flags_name)) < len(flags_name):
raise RepeatedFlagNameException()
@staticmethod
def _validate_func_args(func: Callable) -> None:
"""
Private. Validates the arguments of the handler
:param func: entity of the handler func
:return: None if func is valid else raise exception
"""
transferred_args = getfullargspec(func).args
if len(transferred_args) > 1:
raise TooManyTransferredArgsException()
elif len(transferred_args) == 0:
raise RequiredArgumentNotPassedException()
transferred_arg: str = transferred_args[0]
func_annotations: dict[str, Type] = get_annotations(func)
if arg_annotation := func_annotations.get(transferred_arg):
if arg_annotation is Response:
pass
else:
file_path: str | None = getsourcefile(func)
source_line: int = getsourcelines(func)[1]
fprint = Console().print
fprint(
f'\nFile "{file_path}", line {source_line}\n[b red]WARNING:[/b red] [i]The typehint '
f"of argument([green]{transferred_arg}[/green]) passed to the handler is [/i][bold blue]{Response}[/bold blue],"
f" [i]but[/i] [bold blue]{arg_annotation}[/bold blue] [i]is specified[/i]",
highlight=False,
)
def set_command_register_ignore(self, _: bool) -> None:
"""
Private. Sets the router behavior on the input commands register
:param _: is command register ignore
:return: None
"""
self._ignore_command_register = _
def get_triggers(self) -> list[str]:
@property
def triggers(self) -> list[str]:
"""
Public. Gets registered triggers
:return: registered in router triggers as list[str]
"""
all_triggers: list[str] = []
for command_handler in self._command_handlers:
all_triggers.append(command_handler.get_handled_command().get_trigger())
for command_handler in self.command_handlers:
all_triggers.append(command_handler.handled_command.trigger)
return all_triggers
def get_aliases(self) -> list[str]:
@property
def aliases(self) -> list[str]:
"""
Public. Gets registered aliases
:return: registered in router aliases as list[str]
"""
all_aliases: list[str] = []
for command_handler in self._command_handlers:
if command_handler.get_handled_command().get_aliases():
all_aliases.extend(command_handler.get_handled_command().get_aliases())
for command_handler in self.command_handlers:
if command_handler.handled_command.aliases:
all_aliases.extend(command_handler.handled_command.aliases)
return all_aliases
def get_command_handlers(self) -> CommandHandlers:
"""
Private. Gets registered command handlers
:return: registered command handlers as CommandHandlers
"""
return self._command_handlers
class CommandDecorator:
def __init__(self, router_instance: Router, command: Command):
self.router: Router = router_instance
self.command: Command = command
def __call__(self, handler_func: Callable[[Response], None]) -> Callable[[Response], None]:
_validate_func_args(handler_func)
self.router.command_handlers.add_handler(CommandHandler(handler_func, self.command))
return handler_func
def _structuring_input_flags(handled_command: Command,
input_flags: InputFlags) -> Response:
"""
Private. Validates flags of input command
:param handled_command: entity of the handled command
:param input_flags:
:return: entity of response as Response
"""
invalid_value_flags, undefined_flags = False, False
for flag in input_flags:
flag_status: ValidationStatus = (handled_command.validate_input_flag(flag))
flag.status = flag_status
if flag_status == ValidationStatus.INVALID:
invalid_value_flags = True
elif flag_status == ValidationStatus.UNDEFINED:
undefined_flags = True
status = ResponseStatus.from_flags(has_invalid_value_flags=invalid_value_flags,
has_undefined_flags=undefined_flags)
return Response(
status=status,
input_flags=input_flags
)
def _validate_func_args(func: Callable[[Response], None]) -> None:
"""
Private. Validates the arguments of the handler
:param func: entity of the handler func
:return: None if func is valid else raise exception
"""
transferred_args = getfullargspec(func).args
if len(transferred_args) > 1:
raise TooManyTransferredArgsException()
elif len(transferred_args) == 0:
raise RequiredArgumentNotPassedException()
transferred_arg: str = transferred_args[0]
func_annotations: dict[str, None] = get_annotations(func)
arg_annotation = func_annotations.get(transferred_arg)
if arg_annotation is not None:
if arg_annotation is not Response:
source_line: int = getsourcelines(func)[1]
Console().print(
f'\nFile "{getsourcefile(func)}", line {source_line}\n[b red]WARNING:[/b red] [i]The typehint ' +
f"of argument([green]{transferred_arg}[/green]) passed to the handler must be [/i][bold blue]{Response}[/bold blue]," +
f" [i]but[/i] [bold blue]{arg_annotation}[/bold blue] [i]is specified[/i]",
highlight=False,
)
def _validate_command(command: Command) -> None:
"""
Private. Validates the command registered in handler
:param command: validated command
:return: None if command is valid else raise exception
"""
command_name: str = command.trigger
if command_name.find(" ") != -1:
raise TriggerContainSpacesException()
flags: Flags = command.registered_flags
flags_name: list[str] = [flag.string_entity.lower() for flag in flags]
if len(set(flags_name)) < len(flags_name):
raise RepeatedFlagNameException()
+11 -8
View File
@@ -1,9 +1,12 @@
from typing import override
class RepeatedFlagNameException(Exception):
"""
Private. Raised when a repeated flag name is registered
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Repeated registered flag names in register command"
@@ -11,8 +14,8 @@ class TooManyTransferredArgsException(Exception):
"""
Private. Raised when too many arguments are passed
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Too many transferred arguments"
@@ -20,8 +23,8 @@ class RequiredArgumentNotPassedException(Exception):
"""
Private. Raised when a required argument is not passed
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Required argument not passed"
@@ -29,6 +32,6 @@ class TriggerContainSpacesException(Exception):
"""
Private. Raised when there is a space in the trigger being registered
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Command trigger cannot contain spaces"