mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 18:15:28 +03:00
Merge pull request #2 from koloideal/feat/full_support_argparser
Added support for optional parameters for various types of arguments
This commit is contained in:
+2
-4
@@ -1,5 +1,3 @@
|
|||||||
import argparse
|
arg = '-repeat'
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog='myprogram')
|
print(arg[:arg.rfind('-')+1])
|
||||||
_ = parser.add_argument('--foo', help='foo of the %(prog)s program')
|
|
||||||
parser.print_help()
|
|
||||||
@@ -3,20 +3,19 @@ from mock.mock_app.routers import work_router
|
|||||||
from argenta import App, Orchestrator
|
from argenta import App, Orchestrator
|
||||||
from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter
|
from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter
|
||||||
from argenta.orchestrator import ArgParser
|
from argenta.orchestrator import ArgParser
|
||||||
from argenta.orchestrator.argparser import BooleanArgument
|
from argenta.orchestrator.argparser import BooleanArgument, ValueArgument
|
||||||
|
|
||||||
|
|
||||||
arg_parser = ArgParser(processed_args=[BooleanArgument("repeat")])
|
arg_parser: ArgParser = ArgParser(processed_args=[BooleanArgument(name="repeat", is_deprecated=True),
|
||||||
|
ValueArgument(name="required", is_required=True)])
|
||||||
app: App = App(
|
app: App = App(
|
||||||
dividing_line=DynamicDividingLine(),
|
dividing_line=DynamicDividingLine(),
|
||||||
autocompleter=AutoCompleter(),
|
autocompleter=AutoCompleter(),
|
||||||
)
|
)
|
||||||
orchestrator: Orchestrator = Orchestrator(arg_parser)
|
orchestrator: Orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
app.include_router(work_router)
|
app.include_router(work_router)
|
||||||
print(f"\n\n{orchestrator.get_input_args()}")
|
|
||||||
|
|
||||||
app.add_message_on_startup(PredefinedMessages.USAGE)
|
app.add_message_on_startup(PredefinedMessages.USAGE)
|
||||||
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ flag = Flag('csdv', possible_values=PossibleValues.NEITHER)
|
|||||||
Command("get",
|
Command("get",
|
||||||
description="Get Help",
|
description="Get Help",
|
||||||
aliases=["help", "Get_help"],
|
aliases=["help", "Get_help"],
|
||||||
flags=Flags([PredefinedFlags.PORT,
|
flags=Flags([PredefinedFlags.PORT, PredefinedFlags.HOST])
|
||||||
PredefinedFlags.HOST])))
|
)
|
||||||
|
)
|
||||||
def command_help(response: Response):
|
def command_help(response: Response):
|
||||||
print(response.status)
|
print(response.status)
|
||||||
print(response.input_flags.flags)
|
print(response.input_flags.flags)
|
||||||
|
|||||||
+5
-2
@@ -9,7 +9,7 @@ license = { text = "MIT" }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"rich (>=14.0.0,<15.0.0)",
|
"rich (>=14.0.0,<15.0.0)",
|
||||||
"art (>=6.4,<7.0)",
|
"art (>=6.4,<7.0)",
|
||||||
"pyreadline3>=3.5.4",
|
"pyreadline3>=3.5.4; sys_platform == 'win32'",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
@@ -20,7 +20,10 @@ exclude = [
|
|||||||
"poetry.lock",
|
"poetry.lock",
|
||||||
".__pycache__",
|
".__pycache__",
|
||||||
"tests"
|
"tests"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
__all__ = [
|
__all__ = ["App", "Orchestrator", "Router"]
|
||||||
'App',
|
|
||||||
'Orchestrator',
|
|
||||||
'Router',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
from argenta.app import App
|
from argenta.orchestrator.entity import Orchestrator, App
|
||||||
from argenta.orchestrator import Orchestrator
|
from argenta.router.entity import Router
|
||||||
from argenta.router import Router
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import re
|
|||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from typing import Never, TypeAlias
|
from typing import Never, TypeAlias
|
||||||
|
|
||||||
|
from argenta.orchestrator.argparser.entity import ArgSpace
|
||||||
from art import text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
|
from art import text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.markup import escape
|
from rich.markup import escape
|
||||||
@@ -41,7 +42,8 @@ class BaseApp:
|
|||||||
repeat_command_groups: bool,
|
repeat_command_groups: bool,
|
||||||
override_system_messages: bool,
|
override_system_messages: bool,
|
||||||
autocompleter: AutoCompleter,
|
autocompleter: AutoCompleter,
|
||||||
print_func: Printer) -> None:
|
print_func: Printer,
|
||||||
|
argspace: ArgSpace | None = None) -> None:
|
||||||
self._prompt: str = prompt
|
self._prompt: str = prompt
|
||||||
self._print_func: Printer = print_func
|
self._print_func: Printer = print_func
|
||||||
self._exit_command: Command = exit_command
|
self._exit_command: Command = exit_command
|
||||||
@@ -51,6 +53,7 @@ class BaseApp:
|
|||||||
self._repeat_command_groups_description: bool = repeat_command_groups
|
self._repeat_command_groups_description: bool = repeat_command_groups
|
||||||
self._override_system_messages: bool = override_system_messages
|
self._override_system_messages: bool = override_system_messages
|
||||||
self._autocompleter: AutoCompleter = autocompleter
|
self._autocompleter: AutoCompleter = autocompleter
|
||||||
|
self._argspace: ArgSpace | None = argspace
|
||||||
|
|
||||||
self._farewell_message: str = farewell_message
|
self._farewell_message: str = farewell_message
|
||||||
self._initial_message: str = initial_message
|
self._initial_message: str = initial_message
|
||||||
@@ -392,11 +395,12 @@ class App(BaseApp):
|
|||||||
print_func=print_func,
|
print_func=print_func,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run_polling(self) -> None:
|
def run_polling(self, argspace: ArgSpace | None) -> None:
|
||||||
"""
|
"""
|
||||||
Private. Starts the user input processing cycle
|
Private. Starts the user input processing cycle
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
self._argspace = argspace
|
||||||
self._pre_cycle_setup()
|
self._pre_cycle_setup()
|
||||||
while True:
|
while True:
|
||||||
if self._repeat_command_groups_description:
|
if self._repeat_command_groups_description:
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
__all__ = [
|
__all__ = ["ArgParser", "Orchestrator"]
|
||||||
"Orchestrator",
|
|
||||||
"ArgParser"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
from argenta.orchestrator.entity import Orchestrator
|
|
||||||
from argenta.orchestrator.argparser.entity import ArgParser
|
from argenta.orchestrator.argparser.entity import ArgParser
|
||||||
|
from argenta.orchestrator.entity import Orchestrator
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"ArgParser",
|
"ArgParser",
|
||||||
"PositionalArgument",
|
"BooleanArgument",
|
||||||
"OptionalArgument",
|
"ValueArgument"
|
||||||
"BooleanArgument"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
from argenta.orchestrator.argparser.entity import ArgParser
|
from argenta.orchestrator.argparser.entity import ArgParser
|
||||||
from argenta.orchestrator.argparser.arguments import (BooleanArgument,
|
from argenta.orchestrator.argparser.arguments import BooleanArgument, ValueArgument
|
||||||
PositionalArgument,
|
|
||||||
OptionalArgument)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
__all__ = ["BooleanArgument", "PositionalArgument", "OptionalArgument"]
|
__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"]
|
||||||
|
|
||||||
|
|
||||||
from argenta.orchestrator.argparser.arguments.models import (
|
from argenta.orchestrator.argparser.arguments.models import (
|
||||||
BooleanArgument,
|
BooleanArgument,
|
||||||
PositionalArgument,
|
ValueArgument,
|
||||||
OptionalArgument,
|
InputArgument
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,62 +1,82 @@
|
|||||||
from abc import ABC, abstractmethod
|
from typing import Literal
|
||||||
from typing import Literal, override
|
|
||||||
|
|
||||||
|
|
||||||
class BaseArgument(ABC):
|
class BaseArgument:
|
||||||
"""
|
"""
|
||||||
Private. Base class for all arguments
|
Private. Base class for all arguments
|
||||||
"""
|
"""
|
||||||
@property
|
def __init__(self, name: str, *,
|
||||||
@abstractmethod
|
help: str,
|
||||||
def string_entity(self) -> str:
|
is_deprecated: bool,
|
||||||
"""
|
prefix: Literal["-", "--", "---"]):
|
||||||
Public. Returns the string representation of the argument
|
|
||||||
:return: the string representation as a str
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class PositionalArgument(BaseArgument):
|
|
||||||
def __init__(self, name: str):
|
|
||||||
"""
|
|
||||||
Public. Required argument at startup
|
|
||||||
:param name: name of the argument, must not start with minus (-)
|
|
||||||
"""
|
|
||||||
self.name: str = name
|
|
||||||
|
|
||||||
@property
|
|
||||||
@override
|
|
||||||
def string_entity(self) -> str:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class OptionalArgument(BaseArgument):
|
|
||||||
def __init__(self, name: str, prefix: Literal["-", "--", "---"] = "--"):
|
|
||||||
"""
|
|
||||||
Public. Optional argument, must have the value
|
|
||||||
:param name: name of the argument
|
|
||||||
:param prefix: prefix of the argument
|
|
||||||
"""
|
|
||||||
self.name: str = name
|
|
||||||
self.prefix: Literal["-", "--", "---"] = prefix
|
|
||||||
|
|
||||||
@property
|
|
||||||
@override
|
|
||||||
def string_entity(self) -> str:
|
|
||||||
return self.prefix + self.name
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanArgument(BaseArgument):
|
|
||||||
def __init__(self, name: str, prefix: Literal["-", "--", "---"] = "--"):
|
|
||||||
"""
|
"""
|
||||||
Public. Boolean argument, does not require a value
|
Public. Boolean argument, does not require a value
|
||||||
:param name: name of the argument
|
:param name: name of the argument
|
||||||
:param prefix: prefix of the argument
|
:param help: help message for the argument
|
||||||
|
:param is_required: whether the argument is required
|
||||||
|
:param is_deprecated: whether the argument is deprecated
|
||||||
"""
|
"""
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
|
self.help: str = help
|
||||||
|
self.is_deprecated: bool = is_deprecated
|
||||||
self.prefix: Literal["-", "--", "---"] = prefix
|
self.prefix: Literal["-", "--", "---"] = prefix
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@override
|
|
||||||
def string_entity(self) -> str:
|
def string_entity(self) -> str:
|
||||||
return self.prefix + self.name
|
return self.prefix + self.name
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Public. Value argument, must have the value
|
||||||
|
:param name: name of the argument
|
||||||
|
:param prefix: prefix for the argument
|
||||||
|
:param help: help message for the argument
|
||||||
|
:param possible_values: list of possible values for the argument
|
||||||
|
:param default: default value for the argument
|
||||||
|
:param is_required: whether the argument is required
|
||||||
|
:param is_deprecated: whether the argument is deprecated
|
||||||
|
"""
|
||||||
|
self.default: str | None = default
|
||||||
|
self.possible_values: list[str] | None = possible_values
|
||||||
|
self.is_required: bool = is_required
|
||||||
|
self.action: str = "store"
|
||||||
|
super().__init__(name, prefix=prefix, help=help, is_deprecated=is_deprecated)
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanArgument(BaseArgument):
|
||||||
|
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
|
||||||
|
:param help: help message for the argument
|
||||||
|
:param is_required: whether the argument is required
|
||||||
|
:param is_deprecated: whether the argument is deprecated
|
||||||
|
"""
|
||||||
|
self.action: str = "store_true"
|
||||||
|
super().__init__(name, prefix=prefix, help=help, is_deprecated=is_deprecated)
|
||||||
|
|
||||||
|
|
||||||
|
class InputArgument:
|
||||||
|
def __init__(self, name: str,
|
||||||
|
value: str | None,
|
||||||
|
founder_class: type[BaseArgument]) -> None:
|
||||||
|
self.name: str = name
|
||||||
|
self.value: str | None = value
|
||||||
|
self.founder_class: type[BaseArgument] = founder_class
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"InputArgument({self.name}={self.value})"
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"InputArgument<name={self.name}, value={self.value}, founder_class={self.founder_class.__name__}>"
|
||||||
|
|||||||
@@ -1,16 +1,44 @@
|
|||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
|
from typing import Never, Self
|
||||||
|
|
||||||
from argenta.orchestrator.argparser.arguments.models import (
|
from argenta.orchestrator.argparser.arguments.models import (
|
||||||
|
BaseArgument,
|
||||||
BooleanArgument,
|
BooleanArgument,
|
||||||
OptionalArgument,
|
InputArgument,
|
||||||
PositionalArgument,
|
ValueArgument
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ArgSpace:
|
||||||
|
def __init__(self, all_arguments: list[InputArgument]) -> None:
|
||||||
|
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 get_by_name(self, name: str) -> InputArgument | None:
|
||||||
|
for arg in self.all_arguments:
|
||||||
|
if arg.name == name:
|
||||||
|
return arg
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_by_type(self, arg_type: type[BaseArgument]) -> list[InputArgument] | list[Never]:
|
||||||
|
return [arg for arg in self.all_arguments if arg.founder_class is arg_type]
|
||||||
|
|
||||||
|
|
||||||
class ArgParser:
|
class ArgParser:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
processed_args: list[PositionalArgument | OptionalArgument | BooleanArgument], *,
|
processed_args: list[ValueArgument | BooleanArgument], *,
|
||||||
name: str = "Argenta",
|
name: str = "Argenta",
|
||||||
description: str = "Argenta available arguments",
|
description: str = "Argenta available arguments",
|
||||||
epilog: str = "github.com/koloideal/Argenta | made by kolo",
|
epilog: str = "github.com/koloideal/Argenta | made by kolo",
|
||||||
@@ -22,18 +50,29 @@ class ArgParser:
|
|||||||
:param epilog: the epilog of the ArgParse instance
|
:param epilog: the epilog of the ArgParse instance
|
||||||
:param processed_args: registered and processed arguments
|
:param processed_args: registered and processed arguments
|
||||||
"""
|
"""
|
||||||
self._name: str = name
|
self.name: str = name
|
||||||
self._description: str = description
|
self.description: str = description
|
||||||
self._epilog: str = epilog
|
self.epilog: str = epilog
|
||||||
|
self.processed_args: list[ValueArgument | BooleanArgument] = processed_args
|
||||||
|
|
||||||
self._entity: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog)
|
self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog)
|
||||||
self._processed_args: list[PositionalArgument | OptionalArgument | BooleanArgument] = processed_args
|
|
||||||
|
|
||||||
for arg in processed_args:
|
for arg in processed_args:
|
||||||
if isinstance(arg, PositionalArgument) or isinstance(arg, OptionalArgument):
|
if isinstance(arg, BooleanArgument):
|
||||||
_ = self._entity.add_argument(arg.string_entity)
|
_ = self._core.add_argument(arg.string_entity,
|
||||||
|
action=arg.action,
|
||||||
|
help=arg.help,
|
||||||
|
deprecated=arg.is_deprecated)
|
||||||
else:
|
else:
|
||||||
_ = self._entity.add_argument(arg.string_entity, action="store_true")
|
_ = 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)
|
||||||
|
|
||||||
|
def parse_args(self) -> ArgSpace:
|
||||||
|
return ArgSpace.from_namespace(namespace=self._core.parse_args(),
|
||||||
|
processed_args=self.processed_args)
|
||||||
|
|
||||||
def parse_args(self) -> Namespace:
|
|
||||||
return self._entity.parse_args()
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
from argparse import Namespace
|
from argenta.app.models import App
|
||||||
|
|
||||||
from argenta.app import App
|
|
||||||
from argenta.orchestrator.argparser import ArgParser
|
from argenta.orchestrator.argparser import ArgParser
|
||||||
|
from argenta.orchestrator.argparser.entity import ArgSpace
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
|
||||||
|
|
||||||
|
|
||||||
class Orchestrator:
|
class Orchestrator:
|
||||||
def __init__(self, arg_parser: ArgParser | None = None):
|
def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER):
|
||||||
"""
|
"""
|
||||||
Public. An orchestrator and configurator that defines the behavior of an integrated system, one level higher than the App
|
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
|
:param arg_parser: Cmd argument parser and configurator at startup
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._arg_parser: ArgParser | None = arg_parser
|
self._arg_parser: ArgParser = arg_parser
|
||||||
|
|
||||||
def start_polling(self, app: App) -> None:
|
def start_polling(self, app: App) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -19,14 +21,5 @@ class Orchestrator:
|
|||||||
:param app: a running application
|
:param app: a running application
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
app.run_polling()
|
parsed_argspace: ArgSpace = self._arg_parser.parse_args()
|
||||||
|
app.run_polling(argspace=parsed_argspace)
|
||||||
def get_input_args(self) -> Namespace | None:
|
|
||||||
"""
|
|
||||||
Public. Returns the arguments parsed
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self._arg_parser:
|
|
||||||
return self._arg_parser.parse_args()
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -3,18 +3,24 @@ from unittest.mock import patch, MagicMock
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from argenta.app import App
|
|
||||||
from argenta.command import Command, PredefinedFlags
|
from argenta.command import Command, PredefinedFlags
|
||||||
from argenta.command.flag.models import ValidationStatus
|
from argenta.command.flag.models import ValidationStatus
|
||||||
from argenta.router import Router
|
|
||||||
from argenta.command.flag.flags.models import Flags
|
from argenta.command.flag.flags.models import Flags
|
||||||
from argenta.orchestrator import Orchestrator
|
from argenta import Orchestrator, App, Router
|
||||||
from argenta.response import Response
|
from argenta.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedArgvTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.patcher = patch.object(sys, 'argv', ['program.py'])
|
||||||
|
self.mock_argv = self.patcher.start()
|
||||||
|
self.addCleanup(self.patcher.stop)
|
||||||
|
|
||||||
class TestSystemHandlerNormalWork(TestCase):
|
|
||||||
|
class TestSystemHandlerNormalWork(PatchedArgvTestCase):
|
||||||
@patch("builtins.input", side_effect=["help", "q"])
|
@patch("builtins.input", side_effect=["help", "q"])
|
||||||
@patch("sys.stdout", new_callable=io.StringIO)
|
@patch("sys.stdout", new_callable=io.StringIO)
|
||||||
def test_input_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
|
def test_input_incorrect_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
|
||||||
|
|||||||
@@ -3,19 +3,25 @@ from unittest.mock import patch, MagicMock
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from argenta.app import App
|
|
||||||
from argenta.command import Command, PredefinedFlags
|
from argenta.command import Command, PredefinedFlags
|
||||||
from argenta.command.flag.models import PossibleValues, ValidationStatus
|
from argenta.command.flag.models import PossibleValues, ValidationStatus
|
||||||
from argenta.response import Response
|
from argenta.response import Response
|
||||||
from argenta.router import Router
|
from argenta import Orchestrator, App, Router
|
||||||
from argenta.orchestrator import Orchestrator
|
|
||||||
from argenta.command.flag import Flag
|
from argenta.command.flag import Flag
|
||||||
from argenta.command.flag.flags import Flags
|
from argenta.command.flag.flags import Flags
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedArgvTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.patcher = patch.object(sys, 'argv', ['program.py'])
|
||||||
|
self.mock_argv = self.patcher.start()
|
||||||
|
self.addCleanup(self.patcher.stop)
|
||||||
|
|
||||||
class TestSystemHandlerNormalWork(TestCase):
|
|
||||||
|
class TestSystemHandlerNormalWork(PatchedArgvTestCase):
|
||||||
@patch("builtins.input", side_effect=["test", "q"])
|
@patch("builtins.input", side_effect=["test", "q"])
|
||||||
@patch("sys.stdout", new_callable=io.StringIO)
|
@patch("sys.stdout", new_callable=io.StringIO)
|
||||||
def test_input_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
|
def test_input_correct_command(self, mock_stdout: _io.StringIO, magick_mock: MagicMock):
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from argparse import Namespace
|
||||||
|
|
||||||
|
from argenta.orchestrator.argparser.entity import ArgParser, ArgSpace
|
||||||
|
from argenta.orchestrator.argparser.arguments.models import (
|
||||||
|
ValueArgument,
|
||||||
|
BooleanArgument,
|
||||||
|
InputArgument,
|
||||||
|
BaseArgument
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestArgumentClasses(unittest.TestCase):
|
||||||
|
def test_value_argument_creation(self):
|
||||||
|
arg = ValueArgument(
|
||||||
|
name="test_arg",
|
||||||
|
prefix="--",
|
||||||
|
help="A test argument.",
|
||||||
|
possible_values=["one", "two"],
|
||||||
|
default="one",
|
||||||
|
is_required=True,
|
||||||
|
is_deprecated=False
|
||||||
|
)
|
||||||
|
self.assertEqual(arg.name, "test_arg")
|
||||||
|
self.assertEqual(arg.prefix, "--")
|
||||||
|
self.assertEqual(arg.help, "A test argument.")
|
||||||
|
self.assertEqual(arg.possible_values, ["one", "two"])
|
||||||
|
self.assertEqual(arg.default, "one")
|
||||||
|
self.assertTrue(arg.is_required)
|
||||||
|
self.assertFalse(arg.is_deprecated)
|
||||||
|
self.assertEqual(arg.action, "store")
|
||||||
|
self.assertEqual(arg.string_entity, "--test_arg")
|
||||||
|
|
||||||
|
def test_boolean_argument_creation(self):
|
||||||
|
arg = BooleanArgument(
|
||||||
|
name="verbose",
|
||||||
|
prefix="-",
|
||||||
|
help="Enable verbose mode.",
|
||||||
|
is_deprecated=True
|
||||||
|
)
|
||||||
|
self.assertEqual(arg.name, "verbose")
|
||||||
|
self.assertEqual(arg.prefix, "-")
|
||||||
|
self.assertEqual(arg.help, "Enable verbose mode.")
|
||||||
|
self.assertTrue(arg.is_deprecated)
|
||||||
|
self.assertEqual(arg.action, "store_true")
|
||||||
|
self.assertEqual(arg.string_entity, "-verbose")
|
||||||
|
|
||||||
|
def test_input_argument_creation(self):
|
||||||
|
arg = InputArgument(
|
||||||
|
name="file",
|
||||||
|
value="/path/to/file",
|
||||||
|
founder_class=ValueArgument
|
||||||
|
)
|
||||||
|
self.assertEqual(arg.name, "file")
|
||||||
|
self.assertEqual(arg.value, "/path/to/file")
|
||||||
|
self.assertEqual(arg.founder_class, ValueArgument)
|
||||||
|
|
||||||
|
|
||||||
|
class TestArgParser(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.value_arg = ValueArgument(name="config", help="Path to config file")
|
||||||
|
self.bool_arg = BooleanArgument(name="debug", help="Enable debug mode")
|
||||||
|
self.processed_args = [self.value_arg, self.bool_arg]
|
||||||
|
|
||||||
|
def test_argparser_initialization(self):
|
||||||
|
parser = ArgParser(
|
||||||
|
processed_args=self.processed_args,
|
||||||
|
name="TestApp",
|
||||||
|
description="A test application.",
|
||||||
|
epilog="Test epilog."
|
||||||
|
)
|
||||||
|
self.assertEqual(parser.name, "TestApp")
|
||||||
|
self.assertEqual(parser.description, "A test application.")
|
||||||
|
self.assertEqual(parser.epilog, "Test epilog.")
|
||||||
|
self.assertEqual(parser.processed_args, self.processed_args)
|
||||||
|
|
||||||
|
@patch('argenta.orchestrator.argparser.entity.ArgumentParser.parse_args')
|
||||||
|
def test_parse_args(self, mock_parse_args: MagicMock):
|
||||||
|
mock_namespace = Namespace(config='config.json', debug=True)
|
||||||
|
mock_parse_args.return_value = mock_namespace
|
||||||
|
|
||||||
|
parser = ArgParser(processed_args=self.processed_args)
|
||||||
|
arg_space = parser.parse_args()
|
||||||
|
|
||||||
|
self.assertIsInstance(arg_space, ArgSpace)
|
||||||
|
self.assertEqual(len(arg_space.all_arguments), 2)
|
||||||
|
|
||||||
|
config_arg = arg_space.get_by_name('config')
|
||||||
|
debug_arg = arg_space.get_by_name('debug')
|
||||||
|
|
||||||
|
self.assertIsNotNone(config_arg)
|
||||||
|
if config_arg:
|
||||||
|
self.assertEqual(config_arg.value, 'config.json')
|
||||||
|
self.assertEqual(config_arg.founder_class, ValueArgument)
|
||||||
|
|
||||||
|
self.assertIsNotNone(debug_arg)
|
||||||
|
if debug_arg:
|
||||||
|
self.assertTrue(debug_arg.value)
|
||||||
|
self.assertEqual(debug_arg.founder_class, BooleanArgument)
|
||||||
|
|
||||||
|
|
||||||
|
class TestArgSpace(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.input_arg1 = InputArgument(name="arg1", value="val1", founder_class=ValueArgument)
|
||||||
|
self.input_arg2 = InputArgument(name="arg2", value="val2", founder_class=BooleanArgument)
|
||||||
|
self.input_arg3 = InputArgument(name="arg3", value="val3", founder_class=ValueArgument)
|
||||||
|
self.arg_space = ArgSpace(all_arguments=[self.input_arg1, self.input_arg2, self.input_arg3])
|
||||||
|
|
||||||
|
def test_argspace_initialization(self):
|
||||||
|
self.assertEqual(len(self.arg_space.all_arguments), 3)
|
||||||
|
self.assertIn(self.input_arg1, self.arg_space.all_arguments)
|
||||||
|
self.assertIn(self.input_arg2, self.arg_space.all_arguments)
|
||||||
|
self.assertIn(self.input_arg3, self.arg_space.all_arguments)
|
||||||
|
|
||||||
|
def test_get_by_name(self):
|
||||||
|
found_arg = self.arg_space.get_by_name("arg1")
|
||||||
|
self.assertIsNotNone(found_arg)
|
||||||
|
if found_arg:
|
||||||
|
self.assertEqual(found_arg, self.input_arg1)
|
||||||
|
|
||||||
|
def test_get_by_name_not_found(self):
|
||||||
|
found_arg = self.arg_space.get_by_name("non_existent_arg")
|
||||||
|
self.assertIsNone(found_arg)
|
||||||
|
|
||||||
|
def test_get_by_type(self):
|
||||||
|
value_args = self.arg_space.get_by_type(ValueArgument)
|
||||||
|
self.assertEqual(len(value_args), 2)
|
||||||
|
self.assertIn(self.input_arg1, value_args)
|
||||||
|
self.assertIn(self.input_arg3, value_args)
|
||||||
|
|
||||||
|
bool_args = self.arg_space.get_by_type(BooleanArgument)
|
||||||
|
self.assertEqual(len(bool_args), 1)
|
||||||
|
self.assertIn(self.input_arg2, bool_args)
|
||||||
|
|
||||||
|
def test_get_by_type_not_found(self):
|
||||||
|
class OtherArgument(BaseArgument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
other_args = self.arg_space.get_by_type(OtherArgument)
|
||||||
|
self.assertEqual(len(other_args), 0)
|
||||||
|
|
||||||
|
def test_from_namespace(self):
|
||||||
|
namespace = Namespace(arg1="val1", debug=True)
|
||||||
|
processed_args = [
|
||||||
|
ValueArgument(name="arg1", prefix="--"),
|
||||||
|
BooleanArgument(name="debug", prefix="-")
|
||||||
|
]
|
||||||
|
|
||||||
|
arg_space = ArgSpace.from_namespace(namespace, processed_args)
|
||||||
|
self.assertEqual(len(arg_space.all_arguments), 2)
|
||||||
|
|
||||||
|
arg1 = arg_space.get_by_name('arg1')
|
||||||
|
debug_arg = arg_space.get_by_name('debug')
|
||||||
|
|
||||||
|
self.assertIsNotNone(arg1)
|
||||||
|
if arg1:
|
||||||
|
self.assertEqual(arg1.value, "val1")
|
||||||
|
self.assertEqual(arg1.founder_class, ValueArgument)
|
||||||
|
|
||||||
|
self.assertIsNotNone(debug_arg)
|
||||||
|
if debug_arg:
|
||||||
|
self.assertTrue(debug_arg.value)
|
||||||
|
self.assertEqual(debug_arg.founder_class, BooleanArgument)
|
||||||
Reference in New Issue
Block a user