This commit is contained in:
2025-11-21 19:41:35 +03:00
parent 8edd59c1b8
commit 2e76f68d4a
37 changed files with 395 additions and 482 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
from argenta.app.models import App as App
from argenta.command.models import Command as Command
from argenta.data_bridge.entity import DataBridge as DataBridge
from argenta.orchestrator.entity import Orchestrator as Orchestrator
from argenta.response.entity import Response as Response
from argenta.router.entity import Router as Router
from argenta.data_bridge.entity import DataBridge as DataBridge
+2 -4
View File
@@ -1,7 +1,5 @@
from argenta.app.autocompleter.entity import AutoCompleter as AutoCompleter
from argenta.app.defaults import PredefinedMessages as PredefinedMessages
from argenta.app.dividing_line.models import \
DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import \
StaticDividingLine as StaticDividingLine
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
from argenta.app.models import App as App
+8 -19
View File
@@ -6,10 +6,7 @@ from typing import Never
class AutoCompleter:
def __init__(
self, history_filename: str | None = None,
autocomplete_button: str = "tab"
) -> None:
def __init__(self, history_filename: str | None = None, autocomplete_button: str = "tab") -> None:
"""
Public. Configures and implements auto-completion of input command
:param history_filename: the name of the file for saving the history of the autocompleter
@@ -26,18 +23,12 @@ class AutoCompleter:
:param state: the current cursor position is relative to the beginning of the line
:return: the desired candidate as str or None
"""
matches: list[str] = sorted(
cmd for cmd in _get_history_items() if cmd.startswith(text)
)
matches: list[str] = sorted(cmd for cmd in _get_history_items() if cmd.startswith(text))
if len(matches) > 1:
common_prefix = matches[0]
for match in matches[1:]:
i = 0
while (
i < len(common_prefix)
and i < len(match)
and common_prefix[i] == match[i]
):
while i < len(common_prefix) and i < len(match) and common_prefix[i] == match[i]:
i += 1
common_prefix = common_prefix[:i]
if state == 0:
@@ -61,7 +52,7 @@ class AutoCompleter:
else:
for line in all_commands:
readline.add_history(line)
if not self.history_filename:
for line in all_commands:
readline.add_history(line)
@@ -85,19 +76,17 @@ class AutoCompleter:
pretty_history.append(line)
with open(self.history_filename, "w") as history_file:
_ = history_file.write("\n".join(pretty_history))
def _is_command_exist(command: str, existing_commands: list[str], ignore_command_register: bool) -> bool:
if ignore_command_register:
return command.lower() in existing_commands
return command in existing_commands
def _get_history_items() -> list[str] | list[Never]:
"""
Private. Returns a list of all commands entered by the user
:return: all commands entered by the user as list[str] | list[Never]
"""
return [
readline.get_history_item(i)
for i in range(1, readline.get_current_history_length() + 1)
]
return [readline.get_history_item(i) for i in range(1, readline.get_current_history_length() + 1)]
+1
View File
@@ -7,6 +7,7 @@ class PredefinedMessages(StrEnum):
"""
Public. A dataclass with predetermined messages for quick use
"""
USAGE = "[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]"
HELP = "[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]"
AUTOCOMPLETE = "[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>"
+2 -4
View File
@@ -1,4 +1,2 @@
from argenta.app.dividing_line.models import \
DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import \
StaticDividingLine as StaticDividingLine
from argenta.app.dividing_line.models import DynamicDividingLine as DynamicDividingLine
from argenta.app.dividing_line.models import StaticDividingLine as StaticDividingLine
+51 -93
View File
@@ -5,22 +5,25 @@ import re
from contextlib import redirect_stdout
from typing import Never, TypeAlias
from art import \
text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
from art import text2art
from rich.console import Console
from rich.markup import escape
from argenta.app.autocompleter import AutoCompleter
from argenta.app.dividing_line.models import (DynamicDividingLine,
StaticDividingLine)
from argenta.app.protocols import (DescriptionMessageGenerator,
EmptyCommandHandler,
NonStandardBehaviorHandler, Printer)
from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine
from argenta.app.protocols import (
DescriptionMessageGenerator,
EmptyCommandHandler,
NonStandardBehaviorHandler,
Printer,
)
from argenta.app.registered_routers.entity import RegisteredRouters
from argenta.command.exceptions import (EmptyInputCommandException,
InputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException)
from argenta.command.exceptions import (
EmptyInputCommandException,
InputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException,
)
from argenta.command.models import Command, InputCommand
from argenta.response import Response
from argenta.router import Router
@@ -40,7 +43,7 @@ class BaseApp:
system_router_title: str | None,
ignore_command_register: bool,
dividing_line: StaticDividingLine | DynamicDividingLine,
repeat_command_groups: bool,
repeat_command_groups_printing: bool,
override_system_messages: bool,
autocompleter: AutoCompleter,
print_func: Printer,
@@ -51,7 +54,7 @@ class BaseApp:
self._system_router_title: str | None = system_router_title
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
self._ignore_command_register: bool = ignore_command_register
self._repeat_command_groups_description: bool = repeat_command_groups
self._repeat_command_groups_printing_description: bool = repeat_command_groups_printing
self._override_system_messages: bool = override_system_messages
self._autocompleter: AutoCompleter = autocompleter
@@ -73,25 +76,21 @@ class BaseApp:
else self._matching_default_triggers_with_routers
)
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = (
lambda _: print_func(f"Incorrect flag syntax: {_}")
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
f"Incorrect flag syntax: {_}"
)
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = (
lambda _: print_func(f"Repeated input flags: {_}")
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(
f"Repeated input flags: {_}"
)
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func(
"Empty input command"
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func("Empty input command")
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = lambda _: print_func(
f"Unknown command: {_.trigger}"
)
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = (
lambda _: print_func(f"Unknown command: {_.trigger}")
)
self._exit_command_handler: NonStandardBehaviorHandler[Response] = (
lambda _: print_func(self._farewell_message)
self._exit_command_handler: NonStandardBehaviorHandler[Response] = lambda _: print_func(
self._farewell_message
)
def set_description_message_pattern(
self, _: DescriptionMessageGenerator, /
) -> None:
def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None:
"""
Public. Sets the output pattern of the available commands
:param _: output pattern of the available commands
@@ -99,9 +98,7 @@ class BaseApp:
"""
self._description_message_gen = _
def set_incorrect_input_syntax_handler(
self, _: NonStandardBehaviorHandler[str], /
) -> None:
def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for incorrect flags when entering a command
:param _: handler for incorrect flags when entering a command
@@ -109,9 +106,7 @@ class BaseApp:
"""
self._incorrect_input_syntax_handler = _
def set_repeated_input_flags_handler(
self, _: NonStandardBehaviorHandler[str], /
) -> None:
def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for repeated flags when entering a command
:param _: handler for repeated flags when entering a command
@@ -119,9 +114,7 @@ class BaseApp:
"""
self._repeated_input_flags_handler = _
def set_unknown_command_handler(
self, _: NonStandardBehaviorHandler[InputCommand], /
) -> None:
def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None:
"""
Public. Sets the handler for unknown commands when entering a command
:param _: handler for unknown commands when entering a command
@@ -137,9 +130,7 @@ class BaseApp:
"""
self._empty_input_command_handler = _
def set_exit_command_handler(
self, _: NonStandardBehaviorHandler[Response], /
) -> None:
def set_exit_command_handler(self, _: NonStandardBehaviorHandler[Response], /) -> None:
"""
Public. Sets the handler for exit command when entering a command
:param _: handler for exit command when entering a command
@@ -175,11 +166,7 @@ class BaseApp:
clear_text = re.sub(r"\u001b\[[0-9;]*m", "", text)
max_length_line = max([len(line) for line in clear_text.split("\n")])
max_length_line = (
max_length_line
if 10 <= max_length_line <= 80
else 80
if max_length_line > 80
else 10
max_length_line if 10 <= max_length_line <= 80 else 80 if max_length_line > 80 else 10
)
self._print_func(
@@ -196,15 +183,11 @@ class BaseApp:
elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance]
self._print_func(
self._dividing_line.get_full_static_line(
is_override=self._override_system_messages
)
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
print(text.strip("\n"))
self._print_func(
self._dividing_line.get_full_static_line(
is_override=self._override_system_messages
)
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
else:
@@ -238,14 +221,10 @@ class BaseApp:
"""
input_command_trigger = command.trigger
if self._ignore_command_register:
if input_command_trigger.lower() in list(
self._current_matching_triggers_with_routers.keys()
):
if input_command_trigger.lower() in list(self._current_matching_triggers_with_routers.keys()):
return False
else:
if input_command_trigger in list(
self._current_matching_triggers_with_routers.keys()
):
if input_command_trigger in list(self._current_matching_triggers_with_routers.keys()):
return False
return True
@@ -303,9 +282,7 @@ class BaseApp:
:return: None
"""
self._prompt = f"[italic dim bold]{self._prompt}"
self._initial_message = (
"\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
)
self._initial_message = "\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n"
self._farewell_message = (
"[bold red]\n\n"
+ str(text2art(self._farewell_message, font="chanky")) # pyright: ignore[reportUnknownArgumentType]
@@ -323,20 +300,14 @@ class BaseApp:
self._repeated_input_flags_handler = lambda raw_command: self._print_func(
f"[red bold]Repeated input flags: {escape(raw_command)}"
)
self._empty_input_command_handler = lambda: self._print_func(
"[red bold]Empty input command"
)
self._empty_input_command_handler = lambda: self._print_func("[red bold]Empty input command")
def unknown_command_handler(command: InputCommand) -> None:
cmd_trg: str = command.trigger
mst_sim_cmd: str | None = self._most_similar_command(cmd_trg)
first_part_of_text = (
f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
)
first_part_of_text = f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
second_part_of_text = (
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]"))
if mst_sim_cmd
else ""
("[red], most similar:[/red] " + ("[blue]" + mst_sim_cmd + "[/blue]")) if mst_sim_cmd else ""
)
self._print_func(first_part_of_text + second_part_of_text)
@@ -356,13 +327,9 @@ class BaseApp:
for trigger in combined:
self._matching_default_triggers_with_routers[trigger] = router_entity
self._matching_lower_triggers_with_routers[trigger.lower()] = (
router_entity
)
self._matching_lower_triggers_with_routers[trigger.lower()] = router_entity
self._autocompleter.initial_setup(
list(self._current_matching_triggers_with_routers.keys())
)
self._autocompleter.initial_setup(list(self._current_matching_triggers_with_routers.keys()))
seen = {}
for item in list(self._current_matching_triggers_with_routers.keys()):
@@ -382,7 +349,7 @@ class BaseApp:
self._print_func(message)
if self._messages_on_startup:
print("\n")
if not self._repeat_command_groups_description:
if not self._repeat_command_groups_printing_description:
self._print_command_group_description()
@@ -405,7 +372,7 @@ class App(BaseApp):
system_router_title: str | None = "System points:",
ignore_command_register: bool = True,
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
repeat_command_groups: bool = True,
repeat_command_groups_printing: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
print_func: Printer = DEFAULT_PRINT_FUNC,
@@ -420,7 +387,7 @@ class App(BaseApp):
:param system_router_title: system router title
:param ignore_command_register: whether to ignore the case of the entered commands
:param dividing_line: the entity of the dividing line
:param repeat_command_groups: whether to repeat the available commands and their description
:param repeat_command_groups_printing: whether to repeat the available commands and their description
:param override_system_messages: whether to redefine the default formatting of system messages
:param autocompleter: the entity of the autocompleter
:param print_func: system messages text output function
@@ -434,7 +401,7 @@ class App(BaseApp):
system_router_title=system_router_title,
ignore_command_register=ignore_command_register,
dividing_line=dividing_line,
repeat_command_groups=repeat_command_groups,
repeat_command_groups_printing=repeat_command_groups_printing,
override_system_messages=override_system_messages,
autocompleter=autocompleter,
print_func=print_func,
@@ -447,15 +414,13 @@ class App(BaseApp):
"""
self._pre_cycle_setup()
while True:
if self._repeat_command_groups_description:
if self._repeat_command_groups_printing_description:
self._print_command_group_description()
raw_command: str = Console().input(self._prompt)
try:
input_command: InputCommand = InputCommand.parse(
raw_command=raw_command
)
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except InputCommandException as error:
with redirect_stdout(io.StringIO()) as stderr:
self._error_handler(error, raw_command)
@@ -466,8 +431,7 @@ class App(BaseApp):
if self._is_exit_command(input_command):
system_router.finds_appropriate_handler(input_command)
self._autocompleter.exit_setup(
list(self._current_matching_triggers_with_routers.keys()),
self._ignore_command_register
list(self._current_matching_triggers_with_routers.keys()), self._ignore_command_register
)
return
@@ -478,24 +442,18 @@ class App(BaseApp):
self._print_framed_text(stdout_res)
continue
processing_router = self._current_matching_triggers_with_routers[
input_command.trigger.lower()
]
processing_router = self._current_matching_triggers_with_routers[input_command.trigger.lower()]
if processing_router.disable_redirect_stdout:
dividing_line_unit_part: str = self._dividing_line.get_unit_part()
self._print_func(
StaticDividingLine(
dividing_line_unit_part
).get_full_static_line(
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
is_override=self._override_system_messages
)
)
processing_router.finds_appropriate_handler(input_command)
self._print_func(
StaticDividingLine(
dividing_line_unit_part
).get_full_static_line(
StaticDividingLine(dividing_line_unit_part).get_full_static_line(
is_override=self._override_system_messages
)
)
+2 -1
View File
@@ -2,13 +2,14 @@ __all__ = ["NonStandardBehaviorHandler", "EmptyCommandHandler", "Printer", "Desc
from typing import Protocol, TypeVar
T = TypeVar('T', contravariant=True) # noqa: WPS111
T = TypeVar("T", contravariant=True) # noqa: WPS111
class NonStandardBehaviorHandler(Protocol[T]):
def __call__(self, __param: T) -> None:
raise NotImplementedError
class EmptyCommandHandler(Protocol):
def __call__(self) -> None:
raise NotImplementedError
+8 -8
View File
@@ -1,8 +1,8 @@
__all__ = [
"InputCommandException",
"UnprocessedInputFlagException",
"RepeatedInputFlagsException",
"EmptyInputCommandException",
"InputCommandException",
"UnprocessedInputFlagException",
"RepeatedInputFlagsException",
"EmptyInputCommandException",
]
from abc import ABC, abstractmethod
@@ -15,6 +15,7 @@ class InputCommandException(ABC, Exception):
"""
Private. Base exception class for all exceptions raised when parse input command
"""
@override
@abstractmethod
def __str__(self) -> str:
@@ -25,6 +26,7 @@ class UnprocessedInputFlagException(InputCommandException):
"""
Private. Raised when an unprocessed input flag is detected
"""
@override
def __str__(self) -> str:
return "Unprocessed Input Flags"
@@ -42,16 +44,14 @@ class RepeatedInputFlagsException(InputCommandException):
@override
def __str__(self) -> str:
string_entity: str = self.flag.string_entity
return (
"Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{string_entity}'"
)
return f"Repeated Input Flags\nDuplicate flag was detected in the input: '{string_entity}'"
class EmptyInputCommandException(InputCommandException):
"""
Private. Raised when an empty input command is detected
"""
@override
def __str__(self) -> str:
return "Input Command is empty"
+14 -16
View File
@@ -9,23 +9,21 @@ DEFAULT_PREFIX: Literal["-", "--", "---"] = "-"
class PredefinedFlags:
HELP = Flag(name="help", possible_values=PossibleValues.NEITHER)
SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
HELP = Flag(name="help", possible_values=PossibleValues.NEITHER)
SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110
SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110
SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
ALL = Flag(name="all", possible_values=PossibleValues.NEITHER)
SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
ALL = Flag(name="all", possible_values=PossibleValues.NEITHER)
SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
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=DEFAULT_PREFIX,
possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"),
)
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=DEFAULT_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=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$"))
PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$"))
+18 -16
View File
@@ -6,19 +6,21 @@ from typing import Literal, override
class PossibleValues(Enum):
NEITHER = 'NEITHER'
ALL = 'ALL'
NEITHER = "NEITHER"
ALL = "ALL"
class ValidationStatus(Enum):
VALID = 'VALID'
INVALID = 'INVALID'
UNDEFINED = 'UNDEFINED'
VALID = "VALID"
INVALID = "INVALID"
UNDEFINED = "UNDEFINED"
class Flag:
def __init__(
self, name: str, *,
self,
name: str,
*,
prefix: Literal["-", "--", "---"] = "--",
possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL,
) -> None:
@@ -65,7 +67,7 @@ class Flag:
@override
def __repr__(self) -> str:
return f'Flag<name={self.name}, prefix={self.prefix}>'
return f"Flag<name={self.name}, prefix={self.prefix}>"
@override
def __eq__(self, other: object) -> bool:
@@ -77,10 +79,12 @@ class Flag:
class InputFlag:
def __init__(
self, name: str, *,
prefix: Literal['-', '--', '---'] = '--',
self,
name: str,
*,
prefix: Literal["-", "--", "---"] = "--",
input_value: str | None,
status: ValidationStatus | None
status: ValidationStatus | None,
):
"""
Public. The entity of the flag of the entered command
@@ -90,7 +94,7 @@ class InputFlag:
:return: None
"""
self.name: str = name
self.prefix: Literal['-', '--', '---'] = prefix
self.prefix: Literal["-", "--", "---"] = prefix
self.input_value: str | None = input_value
self.status: ValidationStatus | None = status
@@ -105,17 +109,15 @@ class InputFlag:
@override
def __str__(self) -> str:
return f'{self.string_entity} {self.input_value}'
return f"{self.string_entity} {self.input_value}"
@override
def __repr__(self) -> str:
return f'InputFlag<name={self.name}, prefix={self.prefix}, value={self.input_value}, status={self.status}>'
return f"InputFlag<name={self.name}, prefix={self.prefix}, value={self.input_value}, status={self.status}>"
@override
def __eq__(self, other: object) -> bool:
if isinstance(other, InputFlag):
return (
self.name == other.name
)
return self.name == other.name
else:
raise NotImplementedError
+20 -23
View File
@@ -1,13 +1,12 @@
__all__ = [
"Command",
"InputCommand"
]
__all__ = ["Command", "InputCommand"]
from typing import Literal, Never, Self, cast
from argenta.command.exceptions import (EmptyInputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException)
from argenta.command.exceptions import (
EmptyInputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException,
)
from argenta.command.flag.flags.models import Flags, InputFlags
from argenta.command.flag.models import Flag, InputFlag, ValidationStatus
@@ -23,7 +22,8 @@ DEFAULT_WITHOUT_INPUT_FLAGS: InputFlags = InputFlags()
class Command:
def __init__(
self,
trigger: str, *,
trigger: str,
*,
description: str | None = None,
flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS,
aliases: list[str] | None = None,
@@ -40,9 +40,7 @@ class Command:
self.description: str = description if description else "Command without description"
self.aliases: list[str] = aliases if aliases else []
def validate_input_flag(
self, flag: InputFlag
) -> ValidationStatus:
def validate_input_flag(self, flag: InputFlag) -> ValidationStatus:
"""
Private. Validates the input flag
:param flag: input flag for validation
@@ -60,8 +58,7 @@ class Command:
class InputCommand:
def __init__(self, trigger: str, *,
input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS):
def __init__(self, trigger: str, *, input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS):
"""
Private. The model of the input command, after parsing
:param trigger:the trigger of the command
@@ -69,7 +66,9 @@ class InputCommand:
:return: None
"""
self.trigger: str = trigger
self.input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags])
self.input_flags: InputFlags = (
input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags])
)
@classmethod
def parse(cls, raw_command: str) -> Self:
@@ -108,13 +107,13 @@ class CommandParser:
continue
input_flag = InputFlag(
name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1:],
name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1 :],
prefix=cast(
Literal["-", "--", "---"],
crnt_flg_name[:crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1],
crnt_flg_name[: crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1],
),
input_value=crnt_flg_val,
status=None
status=None,
)
if input_flag in self._parsed_input_flags:
@@ -125,8 +124,7 @@ class CommandParser:
return (self._parsed_input_flags, crnt_flg_name, crnt_flg_val)
def _is_next_token_value(self, current_index: int,
_tokens: list[str] | list[Never]) -> bool:
def _is_next_token_value(self, current_index: int, _tokens: list[str] | list[Never]) -> bool:
next_index = current_index + 1
if next_index >= len(_tokens):
return False
@@ -134,17 +132,16 @@ class CommandParser:
next_token = _tokens[next_index]
return not next_token.startswith(MIN_FLAG_PREFIX)
def _parse_single_token(
token: str,
crnt_flag_name: str | None,
crnt_flag_val: str | None
token: str, crnt_flag_name: str | None, crnt_flag_val: str | None
) -> tuple[str | None, str | None]:
if not token.startswith(MIN_FLAG_PREFIX):
if not crnt_flag_name or crnt_flag_val:
raise UnprocessedInputFlagException
return crnt_flag_name, token
prefix = token[:token.rfind(MIN_FLAG_PREFIX)]
prefix = token[: token.rfind(MIN_FLAG_PREFIX)]
if len(token) < 2 or len(prefix) > 2:
raise UnprocessedInputFlagException
+3 -5
View File
@@ -5,8 +5,8 @@ from typing import Any, Callable, TypeVar
from dishka import Container, FromDishka
from dishka.integrations.base import is_dishka_injected, wrap_injection
from argenta.app import App
from argenta.response import Response
from argenta.app.models import App
from argenta.response.entity import Response
T = TypeVar("T")
@@ -25,9 +25,7 @@ def setup_dishka(app: App, container: Container, *, auto_inject: bool = False) -
Response.patch_by_container(container)
def _get_container_from_response(
args: tuple[Any, ...], kwargs: dict[str, Any]
) -> Container:
def _get_container_from_response(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Container:
for arg in args:
if isinstance(arg, Response):
if hasattr(arg, "_dishka_container"):
+3 -7
View File
@@ -1,5 +1,5 @@
__all__ = [
'SystemProvider',
"SystemProvider",
]
from dishka import Provider, Scope, provide
@@ -10,13 +10,9 @@ from argenta.orchestrator.argparser.entity import ArgSpace
class SystemProvider(Provider):
def __init__(self, arg_parser: ArgParser):
super().__init__()
self._arg_parser: ArgParser = arg_parser
@provide(scope=Scope.APP)
def get_argspace(self) -> ArgSpace:
return self._arg_parser.parsed_argspace
def get_argspace(self, arg_parser: ArgParser) -> ArgSpace:
return arg_parser.parsed_argspace
@provide(scope=Scope.APP)
def get_data_bridge(self) -> DataBridge:
+1 -2
View File
@@ -1,2 +1 @@
from argenta.metrics.main import \
get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup
from argenta.metrics.main import get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup
+2 -2
View File
@@ -1,5 +1,5 @@
__all__ = [
'get_time_of_pre_cycle_setup',
"get_time_of_pre_cycle_setup",
]
import io
@@ -17,6 +17,6 @@ def get_time_of_pre_cycle_setup(app: App) -> float:
"""
start = time()
with redirect_stdout(io.StringIO()):
app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage]
app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage]
end = time()
return end - start
@@ -1,6 +1,4 @@
from argenta.orchestrator.argparser.arguments import \
BooleanArgument as BooleanArgument
from argenta.orchestrator.argparser.arguments import \
ValueArgument as ValueArgument
from argenta.orchestrator.argparser.arguments import BooleanArgument as BooleanArgument
from argenta.orchestrator.argparser.arguments import ValueArgument as ValueArgument
from argenta.orchestrator.argparser.entity import ArgParser as ArgParser
from argenta.orchestrator.argparser.entity import ArgSpace as ArgSpace
@@ -1,6 +1,3 @@
from argenta.orchestrator.argparser.arguments.models import \
BooleanArgument as BooleanArgument
from argenta.orchestrator.argparser.arguments.models import \
InputArgument as InputArgument
from argenta.orchestrator.argparser.arguments.models import \
ValueArgument as ValueArgument
from argenta.orchestrator.argparser.arguments.models import BooleanArgument as BooleanArgument
from argenta.orchestrator.argparser.arguments.models import InputArgument as InputArgument
from argenta.orchestrator.argparser.arguments.models import ValueArgument as ValueArgument
@@ -1,8 +1,4 @@
__all__ = [
'BooleanArgument',
'ValueArgument',
'InputArgument'
]
__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"]
from typing import Literal
@@ -11,10 +7,8 @@ class BaseArgument:
"""
Private. Base class for all arguments
"""
def __init__(self, name: str, *,
help: str,
is_deprecated: bool,
prefix: Literal["-", "--", "---"]):
def __init__(self, name: str, *, help: str, is_deprecated: bool, prefix: Literal["-", "--", "---"]):
"""
Public. Boolean argument, does not require a value
:param name: name of the argument
@@ -33,13 +27,17 @@ class BaseArgument:
class ValueArgument(BaseArgument):
def __init__(self, name: str, *,
prefix: Literal["-", "--", "---"] = "--",
help: str = "Help message for the value argument",
possible_values: list[str] | None = None,
default: str | None = None,
is_required: bool = False,
is_deprecated: bool = False):
def __init__(
self,
name: str,
*,
prefix: Literal["-", "--", "---"] = "--",
help: str = "Help message for the value argument",
possible_values: list[str] | None = None,
default: str | None = None,
is_required: bool = False,
is_deprecated: bool = False,
):
"""
Public. Value argument, must have the value
:param name: name of the argument
@@ -58,10 +56,14 @@ class ValueArgument(BaseArgument):
class BooleanArgument(BaseArgument):
def __init__(self, name: str, *,
prefix: Literal["-", "--", "---"] = "--",
help: str = "Help message for the boolean argument",
is_deprecated: bool = False):
def __init__(
self,
name: str,
*,
prefix: Literal["-", "--", "---"] = "--",
help: str = "Help message for the boolean argument",
is_deprecated: bool = False,
):
"""
Public. Boolean argument, does not require a value
:param name: name of the argument
@@ -74,9 +76,7 @@ class BooleanArgument(BaseArgument):
class InputArgument:
def __init__(self, name: str,
value: str | Literal[True],
founder_class: type[BaseArgument]) -> None:
def __init__(self, name: str, value: str | Literal[True], founder_class: type[BaseArgument]) -> None:
self.name: str = name
self.value: str | Literal[True] = value
self.founder_class: type[BaseArgument] = founder_class
+35 -30
View File
@@ -6,10 +6,12 @@ __all__ = [
from argparse import ArgumentParser, Namespace
from typing import Never, Self
from argenta.orchestrator.argparser.arguments.models import (BaseArgument,
BooleanArgument,
InputArgument,
ValueArgument)
from argenta.orchestrator.argparser.arguments.models import (
BaseArgument,
BooleanArgument,
InputArgument,
ValueArgument,
)
class ArgSpace:
@@ -17,16 +19,16 @@ class ArgSpace:
self.all_arguments = all_arguments
@classmethod
def from_namespace(cls, namespace: Namespace,
processed_args: list[ValueArgument | BooleanArgument]) -> Self:
name_type_paired_args: dict[str, type[BaseArgument]] = {
arg.name: type(arg)
for arg in processed_args
}
return cls([InputArgument(name=name,
value=value,
founder_class=name_type_paired_args[name])
for name, value in vars(namespace).items()])
def from_namespace(
cls, namespace: Namespace, processed_args: list[ValueArgument | BooleanArgument]
) -> Self:
name_type_paired_args: dict[str, type[BaseArgument]] = {arg.name: type(arg) for arg in processed_args}
return cls(
[
InputArgument(name=name, value=value, founder_class=name_type_paired_args[name])
for name, value in vars(namespace).items()
]
)
def get_by_name(self, name: str) -> InputArgument | None:
for arg in self.all_arguments:
@@ -41,7 +43,8 @@ class ArgSpace:
class ArgParser:
def __init__(
self,
processed_args: list[ValueArgument | BooleanArgument], *,
processed_args: list[ValueArgument | BooleanArgument],
*,
name: str = "Argenta",
description: str = "Argenta available arguments",
epilog: str = "github.com/koloideal/Argenta | made by kolo",
@@ -57,28 +60,30 @@ class ArgParser:
self.description: str = description
self.epilog: str = epilog
self.processed_args: list[ValueArgument | BooleanArgument] = processed_args
self.parsed_argspace: ArgSpace = ArgSpace([])
self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog)
self._register_args(processed_args)
def _parse_args(self) -> None:
self.parsed_argspace = ArgSpace.from_namespace(namespace=self._core.parse_args(),
processed_args=self.processed_args)
self.parsed_argspace = ArgSpace.from_namespace(
namespace=self._core.parse_args(), processed_args=self.processed_args
)
def _register_args(self, processed_args: list[ValueArgument | BooleanArgument]) -> None:
for arg in processed_args:
if isinstance(arg, BooleanArgument):
_ = self._core.add_argument(arg.string_entity,
action=arg.action,
help=arg.help,
deprecated=arg.is_deprecated)
_ = self._core.add_argument(
arg.string_entity, action=arg.action, help=arg.help, deprecated=arg.is_deprecated
)
else:
_ = self._core.add_argument(arg.string_entity,
action=arg.action,
help=arg.help,
default=arg.default,
choices=arg.possible_values,
required=arg.is_required,
deprecated=arg.is_deprecated)
_ = self._core.add_argument(
arg.string_entity,
action=arg.action,
help=arg.help,
default=arg.default,
choices=arg.possible_values,
required=arg.is_required,
deprecated=arg.is_deprecated,
)
+9 -4
View File
@@ -11,9 +11,12 @@ DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
class Orchestrator:
def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER,
custom_providers: list[Provider] = [],
auto_inject_handlers: bool = True):
def __init__(
self,
arg_parser: ArgParser = DEFAULT_ARGPARSER,
custom_providers: list[Provider] = [],
auto_inject_handlers: bool = True,
):
"""
Public. An orchestrator and configurator that defines the behavior of an integrated system, one level higher than the App
:param arg_parser: Cmd argument parser and configurator at startup
@@ -31,7 +34,9 @@ class Orchestrator:
:param app: a running application
:return: None
"""
container = make_container(SystemProvider(self._arg_parser), *self._custom_providers)
container = make_container(
SystemProvider(), *self._custom_providers, context={ArgParser: self._arg_parser}
)
setup_dishka(app, container, auto_inject=self._auto_inject_handlers)
app.run_polling()
+4 -4
View File
@@ -10,12 +10,12 @@ class ResponseStatus(Enum):
UNDEFINED_AND_INVALID_FLAGS = "UNDEFINED_AND_INVALID_FLAGS"
@classmethod
def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> 'ResponseStatus':
def from_flags(cls, *, has_invalid_value_flags: bool, has_undefined_flags: bool) -> "ResponseStatus":
key = (has_invalid_value_flags, has_undefined_flags)
status_map: dict[tuple[bool, bool], ResponseStatus] = {
(True, True): cls.UNDEFINED_AND_INVALID_FLAGS,
(True, False): cls.INVALID_VALUE_FLAGS,
(False, True): cls.UNDEFINED_FLAGS,
(True, True): cls.UNDEFINED_AND_INVALID_FLAGS,
(True, False): cls.INVALID_VALUE_FLAGS,
(False, True): cls.UNDEFINED_FLAGS,
(False, False): cls.ALL_FLAGS_VALID,
}
return status_map[key]
+1 -3
View File
@@ -32,9 +32,7 @@ class CommandHandlers:
Private. The model that unites all CommandHandler of the routers
:param command_handlers: list of CommandHandlers for register
"""
self.command_handlers: list[CommandHandler] = (
command_handlers if command_handlers else []
)
self.command_handlers: list[CommandHandler] = command_handlers if command_handlers else []
def add_handler(self, command_handler: CommandHandler) -> None:
"""
+12 -22
View File
@@ -1,7 +1,6 @@
__all__ = ["Router"]
from inspect import (get_annotations, getfullargspec, getsourcefile,
getsourcelines)
from inspect import get_annotations, getfullargspec, getsourcefile, getsourcelines
from typing import Callable, TypeAlias
from rich.console import Console
@@ -10,11 +9,12 @@ from argenta.command import Command, InputCommand
from argenta.command.flag import ValidationStatus
from argenta.command.flag.flags import Flags, InputFlags
from argenta.response import Response, ResponseStatus
from argenta.router.command_handler.entity import (CommandHandler,
CommandHandlers)
from argenta.router.exceptions import (RepeatedFlagNameException,
RequiredArgumentNotPassedException,
TriggerContainSpacesException)
from argenta.router.command_handler.entity import CommandHandler, CommandHandlers
from argenta.router.exceptions import (
RepeatedFlagNameException,
RequiredArgumentNotPassedException,
TriggerContainSpacesException,
)
HandlerFunc: TypeAlias = Callable[..., None]
@@ -78,9 +78,7 @@ class Router:
if input_command_name.lower() in handle_command.aliases:
self.process_input_command(input_command_flags, command_handler)
def process_input_command(
self, input_command_flags: InputFlags, command_handler: CommandHandler
) -> None:
def process_input_command(self, input_command_flags: InputFlags, command_handler: CommandHandler) -> None:
"""
Private. Processes input command with the appropriate handler
:param input_command_flags: input command flags as InputFlags
@@ -90,9 +88,7 @@ class Router:
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
)
response: Response = _structuring_input_flags(handle_command, input_command_flags)
command_handler.handling(response)
else:
response = Response(ResponseStatus.ALL_FLAGS_VALID)
@@ -103,9 +99,7 @@ class Router:
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
)
response = Response(ResponseStatus.UNDEFINED_FLAGS, input_flags=undefined_flags)
command_handler.handling(response)
else:
response = Response(ResponseStatus.ALL_FLAGS_VALID)
@@ -142,15 +136,11 @@ class CommandDecorator:
def __call__(self, handler_func: Callable[..., None]) -> Callable[..., None]:
_validate_func_args(handler_func)
self.router.command_handlers.add_handler(
CommandHandler(handler_func, self.command)
)
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:
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