extend arguments

This commit is contained in:
2025-10-11 18:29:11 +03:00
parent 0a1d462090
commit e481ee8775
13 changed files with 245 additions and 101 deletions
+2 -4
View File
@@ -1,5 +1,3 @@
import argparse
arg = '-repeat'
parser = argparse.ArgumentParser(prog='myprogram')
_ = parser.add_argument('--foo', help='foo of the %(prog)s program')
print(vars(parser.parse_args()))
print(arg[:arg.rfind('-')+1])
+5 -7
View File
@@ -3,19 +3,16 @@ from mock.mock_app.routers import work_router
from argenta import App, Orchestrator
from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter
from argenta.orchestrator import ArgParser
from argenta.orchestrator.argparser import BooleanArgument, ValueArgument, RequiredArgument
from argenta.orchestrator.argparser import BooleanArgument, ValueArgument
arg_parser: ArgParser = ArgParser(processed_args=[BooleanArgument(name="repeat", is_deprecated=True),
ValueArgument(name="value", possible_values=["cat", "dog"]),
RequiredArgument(name="required", is_required=True)])
ValueArgument(name="required", is_required=True)])
app: App = App(
dividing_line=DynamicDividingLine(),
autocompleter=AutoCompleter(),
)
orchestrator: Orchestrator = Orchestrator(arg_parser)
print(orchestrator.get_input_args())
orchestrator: Orchestrator = Orchestrator()
def main():
app.include_router(work_router)
@@ -23,7 +20,8 @@ def main():
app.add_message_on_startup(PredefinedMessages.USAGE)
app.add_message_on_startup(PredefinedMessages.AUTOCOMPLETE)
app.add_message_on_startup(PredefinedMessages.HELP)
# orchestrator.start_polling(app)
orchestrator.start_polling(app)
if __name__ == "__main__":
+3 -2
View File
@@ -12,8 +12,9 @@ flag = Flag('csdv', possible_values=PossibleValues.NEITHER)
Command("get",
description="Get Help",
aliases=["help", "Get_help"],
flags=Flags([PredefinedFlags.PORT,
PredefinedFlags.HOST])))
flags=Flags([PredefinedFlags.PORT, PredefinedFlags.HOST])
)
)
def command_help(response: Response):
print(response.status)
print(response.input_flags.flags)
+3 -10
View File
@@ -1,10 +1,3 @@
__all__ = [
'App',
'Orchestrator',
'Router',
]
from argenta.app import App
from argenta.orchestrator import Orchestrator
from argenta.router import Router
from argenta.app.models import App as App
from argenta.orchestrator.entity import Orchestrator as Orchestrator
from argenta.router.entity import Router as Router
+1 -4
View File
@@ -1,8 +1,5 @@
__all__ = [
"Orchestrator",
"ArgParser"
"ArgParser",
]
from argenta.orchestrator.entity import Orchestrator
from argenta.orchestrator.argparser.entity import ArgParser
@@ -1,12 +1,9 @@
__all__ = [
"ArgParser",
"RequiredArgument",
"ValueArgument",
"BooleanArgument"
"BooleanArgument",
"ValueArgument"
]
from argenta.orchestrator.argparser.entity import ArgParser
from argenta.orchestrator.argparser.arguments import (BooleanArgument,
RequiredArgument,
ValueArgument)
from argenta.orchestrator.argparser.arguments import BooleanArgument, ValueArgument
@@ -1,8 +1,8 @@
__all__ = ["BooleanArgument", "RequiredArgument", "ValueArgument"]
__all__ = ["BooleanArgument", "ValueArgument", "InputArgument"]
from argenta.orchestrator.argparser.arguments.models import (
BooleanArgument,
RequiredArgument,
ValueArgument,
InputArgument
)
@@ -1,11 +1,14 @@
from typing import Literal
class BaseArgument:
"""
Private. Base class for all arguments
"""
def __init__(self, name: str, *,
help: str,
is_required: bool,
is_deprecated: bool):
is_deprecated: bool,
prefix: Literal["-", "--", "---"]):
"""
Public. Boolean argument, does not require a value
:param name: name of the argument
@@ -15,34 +18,17 @@ class BaseArgument:
"""
self.name: str = name
self.help: str = help
self.is_required: bool = is_required
self.is_deprecated: bool = is_deprecated
self.prefix: Literal["-", "--", "---"] = prefix
class RequiredArgument(BaseArgument):
def __init__(self, name: str, *,
help: str = "Help for required argument",
default: str | None = None,
possible_values: list[str] | None = None,
is_required: bool = True,
is_deprecated: bool = False):
"""
Public. Required argument at startup
:param name: name of the argument, must not start with minus (-)
:param help: help message for the argument
:param default: default value for the argument
:param possible_values: list of possible values 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.action: str = "store"
super().__init__(name, help=help, is_required=is_required, is_deprecated=is_deprecated)
@property
def string_entity(self) -> str:
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,
@@ -51,6 +37,7 @@ class ValueArgument(BaseArgument):
"""
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
@@ -59,14 +46,15 @@ class ValueArgument(BaseArgument):
"""
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, help=help, is_required=is_required, is_deprecated=is_deprecated)
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_required: bool = False,
is_deprecated: bool = False):
"""
Public. Boolean argument, does not require a value
@@ -76,7 +64,7 @@ class BooleanArgument(BaseArgument):
:param is_deprecated: whether the argument is deprecated
"""
self.action: str = "store_true"
super().__init__(name, help=help, is_required=is_required, is_deprecated=is_deprecated)
super().__init__(name, prefix=prefix, help=help, is_deprecated=is_deprecated)
class InputArgument:
@@ -88,4 +76,7 @@ class InputArgument:
self.founder_class: type[BaseArgument] = founder_class
def __str__(self) -> str:
return f"{self.name}={self.value}"
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__}>"
+25 -16
View File
@@ -1,12 +1,11 @@
from argparse import ArgumentParser, Namespace
from typing import Self
from typing import Never, Self
from argenta.orchestrator.argparser.arguments.models import (
BaseArgument,
BooleanArgument,
InputArgument,
ValueArgument,
RequiredArgument,
ValueArgument
)
@@ -16,19 +15,30 @@ class ArgSpace:
@classmethod
def from_namespace(cls, namespace: Namespace,
processed_args: list[RequiredArgument | ValueArgument | BooleanArgument]) -> Self:
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, value, name_type_paired_args[name])
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:
def __init__(
self,
processed_args: list[RequiredArgument | ValueArgument | BooleanArgument], *,
processed_args: list[ValueArgument | BooleanArgument], *,
name: str = "Argenta",
description: str = "Argenta available arguments",
epilog: str = "github.com/koloideal/Argenta | made by kolo",
@@ -40,22 +50,21 @@ class ArgParser:
:param epilog: the epilog of the ArgParse instance
:param processed_args: registered and processed arguments
"""
self._name: str = name
self._description: str = description
self._epilog: str = epilog
self.name: str = name
self.description: str = description
self.epilog: str = epilog
self.processed_args: list[ValueArgument | BooleanArgument] = processed_args
self._entity: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog)
self._processed_args: list[RequiredArgument | ValueArgument | BooleanArgument] = processed_args
self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog)
for arg in processed_args:
if isinstance(arg, BooleanArgument):
_ = self._entity.add_argument(arg.name,
_ = self._core.add_argument(arg.string_entity,
action=arg.action,
help=arg.help,
required=arg.is_required,
deprecated=arg.is_deprecated)
else:
_ = self._entity.add_argument(arg.name,
_ = self._core.add_argument(arg.string_entity,
action=arg.action,
help=arg.help,
default=arg.default,
@@ -64,6 +73,6 @@ class ArgParser:
deprecated=arg.is_deprecated)
def parse_args(self) -> ArgSpace:
return ArgSpace.from_namespace(namespace=self._entity.parse_args(),
processed_args=self._processed_args)
return ArgSpace.from_namespace(namespace=self._core.parse_args(),
processed_args=self.processed_args)
+5 -5
View File
@@ -3,14 +3,17 @@ from argenta.orchestrator.argparser import ArgParser
from argenta.orchestrator.argparser.entity import ArgSpace
DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
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
:param arg_parser: Cmd argument parser and configurator at startup
:return: None
"""
self._arg_parser: ArgParser | None = arg_parser
self._arg_parser: ArgParser = arg_parser
def start_polling(self, app: App) -> None:
"""
@@ -18,8 +21,5 @@ class Orchestrator:
:param app: a running application
:return: None
"""
if self._arg_parser is not None:
parsed_argspace: ArgSpace = self._arg_parser.parse_args()
app.run_polling(argspace=parsed_argspace)
else:
app.run_polling(argspace=None)
@@ -4,12 +4,10 @@ from unittest import TestCase
import io
import re
from argenta.app import App
from argenta.command import Command, PredefinedFlags
from argenta.command.flag.models import ValidationStatus
from argenta.router import Router
from argenta.command.flag.flags.models import Flags
from argenta.orchestrator import Orchestrator
from argenta import Orchestrator, App, Router
from argenta.response import Response
@@ -4,12 +4,10 @@ from unittest import TestCase
import io
import re
from argenta.app import App
from argenta.command import Command, PredefinedFlags
from argenta.command.flag.models import PossibleValues, ValidationStatus
from argenta.response import Response
from argenta.router import Router
from argenta.orchestrator import Orchestrator
from argenta import Orchestrator, App, Router
from argenta.command.flag import Flag
from argenta.command.flag.flags import Flags
+164
View File
@@ -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)