new models, a model is passed to the command handler instead of a dictionary, removal of checks for intersection of processed triggers in handlers and much, much more

This commit is contained in:
2025-03-31 19:14:42 +03:00
parent 2918bc9f81
commit 5c6fa5151a
24 changed files with 283 additions and 279 deletions
+4 -4
View File
@@ -2,7 +2,7 @@ from typing import Callable
from inspect import getfullargspec
import re
from argenta.command import Command
from argenta.command.models import Command, InputCommand
from argenta.router import Router
from argenta.router.defaults import system_router
from argenta.command.exceptions import (UnprocessedInputFlagException,
@@ -78,7 +78,7 @@ class App:
raw_command: str = input()
try:
input_command: Command = Command.parse_input_command(raw_command=raw_command)
input_command: InputCommand = InputCommand.parse_input_command(raw_command=raw_command)
except UnprocessedInputFlagException:
self.print_func(self.line_separate)
self._invalid_input_flags_handler(raw_command)
@@ -223,7 +223,7 @@ class App:
self.include_router(system_router)
def _is_exit_command(self, command: Command):
def _is_exit_command(self, command: InputCommand):
if command.get_trigger().lower() == self.exit_command.lower():
if self.ignore_exit_command_register:
system_router.input_command_handler(command)
@@ -235,7 +235,7 @@ class App:
return False
def _check_is_command_unknown(self, command: Command):
def _check_is_command_unknown(self, command: InputCommand):
for router_entity in self._registered_routers:
for command_handler in router_entity.get_command_handlers():
handled_command_trigger = command_handler.get_handled_command().get_trigger()
+1 -1
View File
@@ -1,3 +1,3 @@
__all__ = ["Command"]
from .entity import Command
from .models import Command
+1 -2
View File
@@ -1,5 +1,4 @@
from argenta.command.flag.input_flag.entity import InputFlag
from argenta.command.flag.registered_flag.entity import Flag
from argenta.command.flag.models import InputFlag, Flag
class UnprocessedInputFlagException(Exception):
-41
View File
@@ -1,41 +0,0 @@
from typing import Literal, Pattern
class BaseFlag:
def __init__(self, flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '--',
possible_flag_values: list[str] | Pattern[str] | False = True):
self._flag_name = flag_name
self._flag_prefix = flag_prefix
self.possible_flag_values = possible_flag_values
def get_string_entity(self):
string_entity: str = self._flag_prefix + self._flag_name
return string_entity
def get_flag_name(self):
return self._flag_name
def get_flag_prefix(self):
return self._flag_prefix
def validate_input_flag_value(self, input_flag_value: str | None):
if self.possible_flag_values is False:
if input_flag_value is None:
return True
else:
return False
elif isinstance(self.possible_flag_values, Pattern):
is_valid = bool(self.possible_flag_values.match(input_flag_value))
if bool(is_valid):
return True
else:
return False
elif isinstance(self.possible_flag_values, list):
if input_flag_value in self.possible_flag_values:
return True
else:
return False
else:
return True
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from argenta.command.flag.models import Flag
import re
@dataclass
class DefaultFlags:
HELP = Flag(name='help', possible_values=False)
SHORT_HELP = Flag(name='h', prefix='-', possible_values=False)
INFO = Flag(name='info', possible_values=False)
SHORT_INFO = Flag(name='i', prefix='-', possible_values=False)
ALL = Flag(name='all', possible_values=False)
SHORT_ALL = Flag(name='a', prefix='-', possible_values=False)
HOST = Flag(name='host', possible_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
SHORT_HOST = Flag(name='h', prefix='-', possible_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
PORT = Flag(name='port', possible_values=re.compile(r'^\d{1,5}$'))
SHORT_PORT = Flag(name='p', prefix='-', possible_values=re.compile(r'^\d{1,5}$'))
@@ -1,4 +0,0 @@
__all__ = ["FlagsGroup"]
from .entity import FlagsGroup
@@ -1,36 +0,0 @@
from argenta.command.flag.input_flag.entity import InputFlag
from argenta.command.flag.registered_flag import Flag
class FlagsGroup:
def __init__(self, *flags: Flag | InputFlag):
self._flags: list[Flag | InputFlag] = [] if not flags else flags
def get_flags(self) -> list[Flag | InputFlag]:
return self._flags
def add_flag(self, flag: Flag | InputFlag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag | InputFlag]):
self._flags.extend(flags)
def unparse_to_dict(self):
result_dict: dict[str, dict] = {}
for flag in self.get_flags():
result_dict[flag.get_flag_name()] = {
'name': flag.get_flag_name(),
'string_entity': flag.get_string_entity(),
'prefix': flag.get_flag_prefix(),
'value': flag.get_value()
}
return result_dict
def __iter__(self):
return iter(self._flags)
def __next__(self):
return next(iter(self))
def __getitem__(self, item):
return self._flags[item]
-18
View File
@@ -1,18 +0,0 @@
from re import Pattern
from typing import Literal
from argenta.command.flag.base_flag.entity import BaseFlag
class InputFlag(BaseFlag):
def __init__(self, flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '--',
possible_flag_values: list[str] | Pattern[str] | False = True):
super().__init__(flag_name, flag_prefix, possible_flag_values)
self._flag_value = None
def get_value(self):
return self._flag_value
def set_value(self, value):
self._flag_value = value
+133
View File
@@ -0,0 +1,133 @@
from typing import Literal, Pattern
from abc import ABC, abstractmethod
class BaseFlag:
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
possible_values: list[str] | Pattern[str] | False = True):
self._name = name
self._prefix = prefix
self.possible_values = possible_values
def get_string_entity(self):
string_entity: str = self._prefix + self._name
return string_entity
def get_name(self):
return self._name
def get_prefix(self):
return self._prefix
class InputFlag(BaseFlag):
def __init__(self, name: str,
prefix: Literal['-', '--', '---'] = '--',
possible_values: list[str] | Pattern[str] | False = True):
super().__init__(name, prefix, possible_values)
self._flag_value = None
def get_value(self):
return self._flag_value
def set_value(self, value):
self._flag_value = value
class Flag(BaseFlag):
def validate_input_flag_value(self, input_flag_value: str | None):
if self.possible_values is False:
if input_flag_value is None:
return True
else:
return False
elif isinstance(self.possible_values, Pattern):
is_valid = bool(self.possible_values.match(input_flag_value))
if bool(is_valid):
return True
else:
return False
elif isinstance(self.possible_values, list):
if input_flag_value in self.possible_values:
return True
else:
return False
else:
return True
class BaseFlags(ABC):
__slots__ = ('_flags',)
@abstractmethod
def get_flags(self):
pass
@abstractmethod
def add_flag(self, flag: Flag | InputFlag):
pass
@abstractmethod
def add_flags(self, flags: list[Flag] | list[InputFlag]):
pass
@abstractmethod
def get_flag(self, name: str):
pass
def __iter__(self):
return iter(self._flags)
def __next__(self):
return next(iter(self))
def __getitem__(self, item):
return self._flags[item]
class Flags(BaseFlags, ABC):
def __init__(self, *flags: Flag):
self._flags = flags if flags else []
def get_flags(self) -> list[Flag]:
return self._flags
def add_flag(self, flag: Flag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> Flag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
class InputFlags(BaseFlags, ABC):
def __init__(self, *flags: InputFlag):
self._flags = flags if flags else []
def get_flags(self) -> list[InputFlag]:
return self._flags
def add_flag(self, flag: InputFlag):
self._flags.append(flag)
def add_flags(self, flags: list[InputFlag]):
self._flags.extend(flags)
def get_flag(self, name: str) -> InputFlag | None:
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
@@ -1,5 +0,0 @@
__all__ = ["Flag", "FlagsGroup"]
from .entity import Flag
from argenta.command.flag.flags_group import FlagsGroup
@@ -1,21 +0,0 @@
from dataclasses import dataclass
from argenta.command.flag.registered_flag import Flag
import re
@dataclass
class DefaultFlags:
HELP = Flag(flag_name='help', possible_flag_values=False)
SHORT_HELP = Flag(flag_name='h', flag_prefix='-', possible_flag_values=False)
INFO = Flag(flag_name='info', possible_flag_values=False)
SHORT_INFO = Flag(flag_name='i', flag_prefix='-', possible_flag_values=False)
ALL = Flag(flag_name='all', possible_flag_values=False)
SHORT_ALL = Flag(flag_name='a', flag_prefix='-', possible_flag_values=False)
HOST = Flag(flag_name='host', possible_flag_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
SHORT_HOST = Flag(flag_name='h', flag_prefix='-', possible_flag_values=re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'))
PORT = Flag(flag_name='port', possible_flag_values=re.compile(r'^\d{1,5}$'))
SHORT_PORT = Flag(flag_name='p', flag_prefix='-', possible_flag_values=re.compile(r'^\d{1,5}$'))
@@ -1,5 +0,0 @@
from argenta.command.flag.base_flag.entity import BaseFlag
class Flag(BaseFlag):
pass
@@ -1,40 +1,40 @@
from argenta.command.flag.registered_flag.entity import Flag
from argenta.command.flag.input_flag.entity import InputFlag
from argenta.command.flag.flags_group import FlagsGroup
from .exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from argenta.command.flag.models import Flag, InputFlag, Flags, InputFlags
from argenta.command.exceptions import (UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException)
from typing import Generic, TypeVar, cast, Literal
CommandType = TypeVar('CommandType')
BaseCommandType = TypeVar('BaseCommandType')
class Command(Generic[CommandType]):
class BaseCommand(Generic[BaseCommandType]):
def __init__(self, trigger: str,
description: str = None,
flags: Flag | FlagsGroup = None):
flags: Flag | Flags = None):
self._trigger = trigger
self._description = f'description for "{self._trigger}" command' if not description else description
self._registered_flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup(flags) if isinstance(flags, Flag) else flags
self._input_flags: FlagsGroup | None = None
def get_trigger(self) -> str:
return self._trigger
def get_description(self) -> str:
return self._description
def get_registered_flags(self) -> FlagsGroup | None:
class Command(BaseCommand):
def __init__(self, trigger: str,
description: str = None,
flags: Flag | Flags = None):
super().__init__(trigger, description)
self._registered_flags: Flags = flags if isinstance(flags, Flags) else Flags(flags) if isinstance(flags, Flag) else Flags()
def get_registered_flags(self) -> Flags:
return self._registered_flags
def validate_input_flag(self, flag: InputFlag):
registered_flags: FlagsGroup | None = self.get_registered_flags()
registered_flags: Flags | None = self.get_registered_flags()
if registered_flags:
if isinstance(registered_flags, Flag):
if registered_flags.get_string_entity() == flag.get_string_entity():
@@ -50,21 +50,29 @@ class Command(Generic[CommandType]):
return False
def _set_input_flags(self, input_flags: FlagsGroup):
class InputCommand(BaseCommand):
def __init__(self, trigger: str,
description: str = None,
input_flags: InputFlag | InputFlags = None):
super().__init__(trigger, description)
self._input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags(input_flags) if isinstance(input_flags, InputFlag) else InputFlags()
def _set_input_flags(self, input_flags: InputFlags):
self._input_flags = input_flags
def get_input_flags(self) -> FlagsGroup:
def get_input_flags(self) -> InputFlags:
return self._input_flags
@staticmethod
def parse_input_command(raw_command: str) -> CommandType:
def parse_input_command(raw_command: str) -> BaseCommandType:
if not raw_command:
raise EmptyInputCommandException()
list_of_tokens = raw_command.split()
command = list_of_tokens[0]
list_of_tokens.pop(0)
flags: FlagsGroup = FlagsGroup()
input_flags: InputFlags = InputFlags()
current_flag_name = None
current_flag_value = None
for k, _ in enumerate(list_of_tokens):
@@ -80,19 +88,19 @@ class Command(Generic[CommandType]):
else:
current_flag_value = _
if current_flag_name:
if not len(list_of_tokens) == k+1:
if not list_of_tokens[k+1].startswith('-'):
if not len(list_of_tokens) == k + 1:
if not list_of_tokens[k + 1].startswith('-'):
continue
flag_prefix_last_symbol_index = current_flag_name.rfind('-')
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index+1]
flag_name = current_flag_name[flag_prefix_last_symbol_index+1:]
input_flag = InputFlag(flag_name=flag_name,
flag_prefix=cast(Literal['-', '--', '---'], flag_prefix))
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index + 1]
flag_name = current_flag_name[flag_prefix_last_symbol_index + 1:]
input_flag = InputFlag(name=flag_name,
prefix=cast(Literal['-', '--', '---'], flag_prefix))
input_flag.set_value(current_flag_value)
all_flags = [x.get_string_entity() for x in flags.get_flags()]
all_flags = [x.get_string_entity() for x in input_flags.get_flags()]
if input_flag.get_string_entity() not in all_flags:
flags.add_flag(input_flag)
input_flags.add_flag(input_flag)
else:
raise RepeatedInputFlagsException(input_flag)
@@ -100,12 +108,10 @@ class Command(Generic[CommandType]):
current_flag_value = None
if any([current_flag_name, current_flag_value]):
raise UnprocessedInputFlagException()
if len(flags.get_flags()) == 0:
return Command(trigger=command)
if len(input_flags.get_flags()) == 0:
return InputCommand(trigger=command)
else:
input_command = Command(trigger=command)
input_command._set_input_flags(flags)
input_command = InputCommand(trigger=command)
input_command._set_input_flags(input_flags)
return input_command
+4 -3
View File
@@ -1,14 +1,15 @@
from typing import Callable
from argenta.command import Command
from argenta.command.flag.models import InputFlags
class CommandHandler:
def __init__(self, handler: Callable[[], None] | Callable[[dict], None], handled_command: Command):
def __init__(self, handler: Callable[[], None] | Callable[[InputFlags], None], handled_command: Command):
self.handler = handler
self.handled_command = handled_command
def handling(self, input_flags: dict = None):
if input_flags is not None:
def handling(self, input_flags: InputFlags):
if input_flags.get_flags():
self.handler(input_flags)
else:
self.handler()
+13 -13
View File
@@ -2,21 +2,21 @@ from typing import Callable, Any
from inspect import getfullargspec
from argenta.command import Command
from argenta.command.models import InputCommand
from argenta.router.command_handlers.entity import CommandHandlers
from argenta.router.command_handler.entity import CommandHandler
from argenta.command.flag.registered_flag import Flag, FlagsGroup
from argenta.command.flag.models import Flag, Flags, InputFlags
from argenta.router.exceptions import (RepeatedFlagNameException,
TooManyTransferredArgsException,
RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
TooManyTransferredArgsException,
RequiredArgumentNotPassedException,
IncorrectNumberOfHandlerArgsException,
TriggerCannotContainSpacesException)
class Router:
def __init__(self,
title: str = 'Commands group title:',
name: str = 'Default'):
self._title = title
self._name = name
@@ -48,9 +48,9 @@ class Router:
self._not_valid_flag_handler = func
def input_command_handler(self, input_command: Command):
def input_command_handler(self, input_command: InputCommand):
input_command_name: str = input_command.get_trigger()
input_command_flags: FlagsGroup = input_command.get_input_flags()
input_command_flags: InputFlags = input_command.get_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():
@@ -61,7 +61,7 @@ class Router:
if not is_valid:
self._not_valid_flag_handler(flag)
return
command_handler.handling(input_command_flags.unparse_to_dict())
command_handler.handling(input_command_flags)
return
else:
command_handler.handling({})
@@ -81,7 +81,7 @@ class Router:
if command_name.find(' ') != -1:
raise TriggerCannotContainSpacesException()
flags: FlagsGroup = command.get_registered_flags()
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):
@@ -92,12 +92,12 @@ class Router:
def _validate_func_args(command: Command, func: Callable):
registered_args = command.get_registered_flags()
transferred_args = getfullargspec(func).args
if registered_args and transferred_args:
if registered_args.get_flags() and transferred_args:
if len(transferred_args) != 1:
raise TooManyTransferredArgsException()
elif registered_args and not transferred_args:
elif registered_args.get_flags() and not transferred_args:
raise RequiredArgumentNotPassedException()
elif not registered_args and transferred_args:
elif not registered_args.get_flags() and transferred_args:
raise TooManyTransferredArgsException()