mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 10:05:28 +03:00
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:
+127
-155
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user