diff --git a/docs/code_snippets/flags/deploy_handler.py b/docs/code_snippets/flags/deploy_handler.py new file mode 100644 index 0000000..d0f2e2b --- /dev/null +++ b/docs/code_snippets/flags/deploy_handler.py @@ -0,0 +1,24 @@ +from argenta import Router, Response +from argenta.command import Command, Flag, PossibleValues +from argenta.command.flag import ValidationStatus + +router = Router() + + +@router.command(Command( + "deploy", + flags=Flag("verbose", possible_values=PossibleValues.NEITHER) +)) +def deploy_handler(response: Response): + # Check for toggle flag presence + verbose_flag = response.input_flags.get_flag_by_name("verbose") + + if verbose_flag and verbose_flag.status == ValidationStatus.VALID: + print("Deploying with verbose output...") + # Detailed logic + elif verbose_flag and verbose_flag.status == ValidationStatus.INVALID: + print("Incorrect flag value") + return + else: + print("Deploying...") + # Normal logic diff --git a/docs/code_snippets/flags/greet_handler.py b/docs/code_snippets/flags/greet_handler.py new file mode 100644 index 0000000..41f215f --- /dev/null +++ b/docs/code_snippets/flags/greet_handler.py @@ -0,0 +1,16 @@ +from argenta import Router, Response +from argenta.command import Command, Flag + +router = Router() + + +@router.command(Command("greet", flags=Flag("name"))) +def greet_handler(response: Response): + # Get flag by name + name_flag = response.input_flags.get_flag_by_name("name") + + # Check if flag was passed + if name_flag: + print(f"Hello, {name_flag.input_value}!") + else: + print("Hello, stranger!") diff --git a/docs/code_snippets/input_flag/snippet3.py b/docs/code_snippets/input_flag/snippet3.py index 0ac07d6..bc100f3 100644 --- a/docs/code_snippets/input_flag/snippet3.py +++ b/docs/code_snippets/input_flag/snippet3.py @@ -5,9 +5,9 @@ flag_with_value = InputFlag( ) flag_without_value = InputFlag( - name="help", prefix="-", input_value=None, status=ValidationStatus.VALID + name="help", prefix="-", input_value='', status=ValidationStatus.VALID ) # String representation includes value print(str(flag_with_value)) # --output result.txt -print(str(flag_without_value)) # -help None +print(str(flag_without_value)) # -help diff --git a/docs/code_snippets/quickstart/calculator_app.py b/docs/code_snippets/quickstart/calculator_app.py new file mode 100644 index 0000000..38a42f6 --- /dev/null +++ b/docs/code_snippets/quickstart/calculator_app.py @@ -0,0 +1,68 @@ +import operator +import re + +from argenta import App, Orchestrator, Response, Router +from argenta.app import DynamicDividingLine +from argenta.command import Command, Flag, Flags +from argenta.response.status import ResponseStatus + +router = Router("Calculator") + +operations = { + 'mul': operator.mul, + 'sub': operator.sub, + 'add': operator.add +} + +@router.command( + Command( + "calc", + description="Calculator with two numbers", + flags=Flags( + [ + Flag("a", possible_values=re.compile(r"^\d{,5}$")), # First number + Flag("b", possible_values=re.compile(r"^\d{,5}$")), # Second number + Flag("operation", possible_values=["add", "sub", "mul"]), # Operation: add, sub, mul + ] + ), + ) +) +def calc_handler(response: Response): + # Get flag values + a_flag = response.input_flags.get_flag_by_name("a") + b_flag = response.input_flags.get_flag_by_name("b") + op_flag = response.input_flags.get_flag_by_name("op") + + # Check that all flags are provided + if response.status != ResponseStatus.ALL_FLAGS_VALID or not all([a_flag, b_flag, op_flag]): + print("Error: must specify --a, --b and --op") + return + + a = float(a_flag.input_value) + b = float(b_flag.input_value) + operation = op_flag.input_value + + try: + result = operations[operation](a, b) + except ZeroDivisionError: + print("Can't divide by zero") + else: + print(f"Result: {result}") + + +app = App( + initial_message="Calculator", + repeat_command_groups_printing=False, + prompt=">> ", + dividing_line=DynamicDividingLine("~"), +) +orchestrator = Orchestrator() + + +def main(): + app.include_router(router) + orchestrator.start_polling(app) + + +if __name__ == "__main__": + main() diff --git a/docs/index.rst b/docs/index.rst index c571115..4bdbfe2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,9 +17,9 @@ Argenta .. image:: https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True :alt: App example -Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что при запуске пользователь входит в интерактивную сессию, где ему доступна вся реализованная вами функциональность. +Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что приложение запускается один раз и создаёт интерактивную сессию, похожую на Python REPL или MySQL консоль. При запуске пользователь входит в эту сессию, где ему доступна вся реализованная вами функциональность. -Один из ключевых принципов библиотеки — цикличность. После выполнения команды пользователь остаётся в интерактивной сессии, в отличие от таких библиотек, как ``argparse``, ``click`` и ``typer``. Выход из сессии контролируется пользователем. +Один из ключевых принципов библиотеки — цикличность. После выполнения команды пользователь остаётся в интерактивной сессии и имеет доступ к созданной вами функциональности, в отличие от таких библиотек, как ``argparse``, ``click`` и ``typer``. Выход из сессии контролируется пользователем. **Ключевые особенности:** diff --git a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po index 7d8bcf0..1f5b824 100644 --- a/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po +++ b/docs/locales/en/LC_MESSAGES/root/api/orchestrator/index.po @@ -64,7 +64,7 @@ msgid "" "к БД или API-клиентов) в DI-контейнер." msgstr "" "``custom_providers``: List of custom ``dishka.Provider`` providers for adding your services " -"(e.g., database connections or API clients) to the DI container." +"(e.g., database connections or API clients) to the``di``container." #: ../../root/api/orchestrator/index.rst:32 msgid "" diff --git a/docs/locales/en/LC_MESSAGES/root/dependency_injection.po b/docs/locales/en/LC_MESSAGES/root/dependency_injection.po index 4e2afba..624c1fa 100644 --- a/docs/locales/en/LC_MESSAGES/root/dependency_injection.po +++ b/docs/locales/en/LC_MESSAGES/root/dependency_injection.po @@ -93,7 +93,7 @@ msgid "Как это работает?" msgstr "How Does It Work?" #: ../../root/dependency_injection.rst:48 -msgid "В основе DI в Argenta лежат **провайдеры** и **контейнер**." +msgid "В основе``di``в Argenta лежат **провайдеры** и **контейнер**." msgstr "DI in Argenta is based on **providers** and a **container**." #: ../../root/dependency_injection.rst:50 diff --git a/docs/root/api/command/input_flag.rst b/docs/root/api/command/input_flag.rst index 21ca776..1932707 100644 --- a/docs/root/api/command/input_flag.rst +++ b/docs/root/api/command/input_flag.rst @@ -30,7 +30,7 @@ InputFlag .. py:attribute:: input_value - Значение, переданное с флагом. Может быть `None` для флагов без значений. + Значение, переданное с флагом. Может быть ``''`` (пустой строкой) для флагов без значений. .. py:attribute:: status :no-index: diff --git a/docs/root/api/orchestrator/argparser.rst b/docs/root/api/orchestrator/argparser.rst index 60eb582..28696dd 100644 --- a/docs/root/api/orchestrator/argparser.rst +++ b/docs/root/api/orchestrator/argparser.rst @@ -44,7 +44,7 @@ ArgParser Лучшие практики --------------- -Использовать атрибут ``parsed_argspace`` рекомендуется только на этапе настройки приложения. В обработчиках лучшей практикой является получение ``ArgSpace`` через DI. Подробнее см. :ref:`здесь `. +Использовать атрибут ``parsed_argspace`` рекомендуется только на этапе настройки приложения. В обработчиках лучшей практикой является получение ``ArgSpace`` через ``di``. Подробнее см. :ref:`здесь `. **Пример использования:** diff --git a/docs/root/api/orchestrator/index.rst b/docs/root/api/orchestrator/index.rst index 826c85e..a7381d0 100644 --- a/docs/root/api/orchestrator/index.rst +++ b/docs/root/api/orchestrator/index.rst @@ -28,7 +28,7 @@ Orchestrator Создаёт и конфигурирует экземпляр ``Orchestrator``. * ``arg_parser``: Экземпляр ``ArgParser``, отвечающий за парсинг аргументов командной строки при запуске скрипта (не путать с командами в интерактивном режиме). -* ``custom_providers``: Список пользовательских провайдеров ``dishka.Provider`` для добавления ваших сервисов (например, подключений к БД или API-клиентов) в DI-контейнер. +* ``custom_providers``: Список пользовательских провайдеров ``dishka.Provider`` для добавления ваших сервисов (например, подключений к БД или API-клиентов) в di-контейнер. * ``auto_inject_handlers``: Если **True** (по умолчанию), ``dishka`` автоматически внедрит зависимости в обработчики команд, инспектируя их сигнатуры. ----- diff --git a/docs/root/dependency_injection.rst b/docs/root/dependency_injection.rst index ae200d8..a96f08f 100644 --- a/docs/root/dependency_injection.rst +++ b/docs/root/dependency_injection.rst @@ -3,9 +3,9 @@ Внедрение зависимостей ======================= -Внедрение зависимостей (Dependency Injection, DI) — это паттерн проектирования, который помогает писать слабосвязанный, легко тестируемый и расширяемый код. Вместо того чтобы обработчики сами создавали нужные им объекты (зависимости), они получают их извне. +Внедрение зависимостей (Dependency Injection, ``di`` ) — это паттерн проектирования, который помогает писать слабосвязанный, легко тестируемый и расширяемый код. Вместо того чтобы обработчики сами создавали нужные им объекты (зависимости), они получают их извне. -``Argenta`` использует библиотеку ``dishka`` для реализации DI, что позволяет декларативно объявлять зависимости прямо в сигнатурах ваших обработчиков. +``Argenta`` использует библиотеку ``dishka`` для реализации ``di``, что позволяет декларативно объявлять зависимости прямо в сигнатурах ваших обработчиков. Подробнее о **DI**, **IoC** и API для создания провайдеров можно прочитать в `официальной документации dishka `_. ----- @@ -32,7 +32,10 @@ :language: python :linenos: -После создания провайдера его необходимо зарегистрировать в оркестраторе: +После создания провайдера его необходимо зарегистрировать в оркестраторе. + +.. note:: + Провайдеры регистрируются в ``Orchestrator``, а не в ``App``, так как оркестратор отвечает за настройку di-контейнера на уровне всего приложения. Вы можете передать список из нескольких провайдеров через параметр ``custom_providers``. **Пример использования:** @@ -45,7 +48,7 @@ Как это работает? ----------------- -В основе DI в Argenta лежат **провайдеры** и **контейнер**. +В основе ``di`` в Argenta лежат **провайдеры** и **контейнер**. * **Провайдер (Provider)** — это "рецепт", который объясняет, как создавать и настраивать ту или иную зависимость (например, подключение к БД, API-клиент или любой другой сервис). * **Контейнер (IoC Container)** — это "фабрика", которая хранит все рецепты (провайдеры) и по запросу создаёт и выдаёт готовые зависимости. @@ -68,7 +71,7 @@ Обмен данными между обработчиками ---------------------------------- -Помимо DI, обработчики могут обмениваться данными в рамках сессии через **объект контекста**. В ``Argenta`` эту роль выполняет объект ``DataBridge``. +Помимо ``di``, обработчики могут обмениваться данными в рамках сессии через **объект контекста**. В ``Argenta`` эту роль выполняет объект ``DataBridge``. Каждый обработчик может записывать в него данные, а также читать, обновлять и удалять их. diff --git a/docs/root/flags.rst b/docs/root/flags.rst index db2efb9..e72d561 100644 --- a/docs/root/flags.rst +++ b/docs/root/flags.rst @@ -1,7 +1,7 @@ .. _root_flags: -Флаги вводимых команд -===================== +Флаги команд +============ Флаги — это специальные параметры, которые пользователь может добавлять к командам для управления их поведением. @@ -56,6 +56,36 @@ Флаг состоит из префикса (``-``, ``--`` или ``---``), имени и, опционально, значения, которое указывается через пробел. +**Примеры:** + +.. code-block:: shell + + greet --name John # Flag with value + deploy --verbose # Flag without value (switch) + backup -f --compress # Several flags + +----- + +Работа с флагами в обработчиках +-------------------------------- + +Чтобы получить значение флага в обработчике, используйте объект ``response.input_flags`` типа :ref:`InputFlags `. + +**Пример с флагом, имеющим значение:** + +.. literalinclude:: ../code_snippets/flags/greet_handler.py + :language: python + :linenos: + +**Пример с флагом-переключателем:** + +.. literalinclude:: ../code_snippets/flags/deploy_handler.py + :language: python + :linenos: + +.. seealso:: + Подробнее о работе с объектом ``InputFlags`` см. в разделе :ref:`InputFlags `. + ----- Два типа флагов diff --git a/docs/root/quickstart.rst b/docs/root/quickstart.rst index 92eef94..042cdbb 100644 --- a/docs/root/quickstart.rst +++ b/docs/root/quickstart.rst @@ -23,6 +23,14 @@ :language: python :linenos: +**Запуск** + +Сохраните код в файл (например, ``main.py``) и запустите: + +.. code-block:: shell + + python main.py + **Результат** .. image:: https://i.ibb.co/35q24Bh8/image.png @@ -30,10 +38,30 @@ ----- -Более сложный пример: Менеджер задач +Промежуточный пример: Калькулятор с флагами +-------------------------------------------- + +Прежде чем перейти к сложному примеру с ``di``, рассмотрим промежуточный вариант — калькулятор, который использует флаги для управления поведением. + +.. literalinclude:: ../code_snippets/quickstart/calculator_app.py + :language: python + :linenos: + +**Использование:** + +.. code-block:: shell + + calc --a 10 --b 5 --operation add # Result: 15.0 + calc --a 10 --b 5 --operation mul # Result: 50.0 + +Этот пример показывает, как работать с флагами без использования ``di``. Теперь перейдём к более сложному примеру. + +----- + +Сложный пример: Менеджер задач с ``di`` -------------------------------------- -В этом руководстве мы создадим простое, но полнофункциональное CLI-приложение «Менеджер задач», которое продемонстрирует ключевые возможности Argenta. +В этом руководстве мы создадим полнофункциональное CLI-приложение «Менеджер задач», которое продемонстрирует работу с внедрением зависимостей. 1. **Установка** diff --git a/docs/root/redirect_stdout.rst b/docs/root/redirect_stdout.rst index 555bb56..2fca44c 100644 --- a/docs/root/redirect_stdout.rst +++ b/docs/root/redirect_stdout.rst @@ -7,6 +7,20 @@ ----- +Когда нужно отключать перехват stdout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Отключайте перехват ``stdout`` (``disable_redirect_stdout=True`` в ``Router``), если ваши команды: + +✓ Используют ``input()`` для интерактивного ввода данных от пользователя +✓ Используют прогресс-бары (``tqdm``, ``rich.progress``) +✓ Выводят данные в реальном времени (streaming, логи) +✓ Используют библиотеки, которые напрямую работают с ``stdout`` + +Для обычных команд с ``print()`` перехват можно оставить включённым — это не влияет на их работу. + +----- + Механизм перехвата ``stdout`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/argenta/command/flag/models.py b/src/argenta/command/flag/models.py index f4816fd..86be447 100644 --- a/src/argenta/command/flag/models.py +++ b/src/argenta/command/flag/models.py @@ -5,6 +5,9 @@ from re import Pattern from typing import Literal, override +PREFIX_TYPE = Literal["-", "--", "---"] + + class PossibleValues(Enum): NEITHER = "NEITHER" ALL = "ALL" @@ -21,7 +24,7 @@ class Flag: self, name: str, *, - prefix: Literal["-", "--", "---"] = "--", + prefix: PREFIX_TYPE = "--", possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL, ) -> None: """ @@ -32,23 +35,23 @@ class Flag: :return: None """ self.name: str = name - self.prefix: Literal["-", "--", "---"] = prefix + self.prefix: PREFIX_TYPE = prefix self.possible_values: list[str] | Pattern[str] | PossibleValues = possible_values - def validate_input_flag_value(self, input_flag_value: str | None) -> bool: + def validate_input_flag_value(self, input_flag_value: str) -> bool: """ Private. Validates the input flag value :param input_flag_value: The input flag value to validate :return: whether the entered flag is valid as bool """ if self.possible_values == PossibleValues.NEITHER: - return input_flag_value is None + return input_flag_value == '' if self.possible_values == PossibleValues.ALL: - return input_flag_value is not None + return input_flag_value != '' if isinstance(self.possible_values, Pattern): - return isinstance(input_flag_value, str) and bool(self.possible_values.match(input_flag_value)) + return bool(self.possible_values.match(input_flag_value)) if isinstance(self.possible_values, list): return input_flag_value in self.possible_values @@ -85,8 +88,8 @@ class InputFlag: self, name: str, *, - prefix: Literal["-", "--", "---"] = "--", - input_value: str | None, + prefix: PREFIX_TYPE = "--", + input_value: str, status: ValidationStatus | None, ): """ @@ -97,8 +100,8 @@ class InputFlag: :return: None """ self.name: str = name - self.prefix: Literal["-", "--", "---"] = prefix - self.input_value: str | None = input_value + self.prefix: PREFIX_TYPE = prefix + self.input_value: str = input_value self.status: ValidationStatus | None = status @property diff --git a/src/argenta/command/models.py b/src/argenta/command/models.py index 19f7adc..42b61f9 100644 --- a/src/argenta/command/models.py +++ b/src/argenta/command/models.py @@ -1,6 +1,8 @@ __all__ = ["Command", "InputCommand"] -from typing import Literal, Never, Self, cast +import shlex +from typing import Never, Self, cast +from typing_extensions import Literal from argenta.command.exceptions import ( EmptyInputCommandException, @@ -14,6 +16,7 @@ ParseFlagsResult = tuple[InputFlags, str | None, str | None] ParseResult = tuple[str, InputFlags] MIN_FLAG_PREFIX: str = "-" +PREFIX_TYPE = Literal["-", "--", "---"] DEFAULT_WITHOUT_FLAGS: Flags = Flags() DEFAULT_WITHOUT_ALIASES: list[Never] = [] @@ -78,75 +81,50 @@ class InputCommand: :param raw_command: raw input command :return: model of the input command, after parsing as InputCommand """ - trigger, input_flags = CommandParser(raw_command).parse_raw_command() - - return cls(trigger=trigger, input_flags=input_flags) - - -class CommandParser: - def __init__(self, raw_command: str) -> None: - self.raw_command: str = raw_command - self._parsed_input_flags: InputFlags = InputFlags() - - def parse_raw_command(self) -> ParseResult: - if not self.raw_command: - raise EmptyInputCommandException() - - input_flags, crnt_flag_name, crnt_flag_val = self._parse_flags(self.raw_command.split()[1:]) - - if any([crnt_flag_name, crnt_flag_val]): - raise UnprocessedInputFlagException() - else: - return (self.raw_command.split()[0], input_flags) - - def _parse_flags(self, _tokens: list[str] | list[Never]) -> ParseFlagsResult: - crnt_flg_name, crnt_flg_val = None, None - for index, token in enumerate(_tokens): - crnt_flg_name, crnt_flg_val = _parse_single_token(token, crnt_flg_name, crnt_flg_val) - - if not crnt_flg_name or self._is_next_token_value(index, _tokens): - continue - + tokens = shlex.split(raw_command) + + if not tokens: + raise EmptyInputCommandException + + command = tokens[0] + flags: InputFlags = InputFlags() + + i = 1 + while i < len(tokens): + token = tokens[i] + + if token.startswith("---"): + prefix = "---" + name = token[3:] + elif token.startswith("--"): + prefix = "--" + name = token[2:] + elif token.startswith("-"): + prefix = "-" + name = token[1:] + else: + raise UnprocessedInputFlagException + + if not name: + raise UnprocessedInputFlagException + + if i + 1 < len(tokens) and not tokens[i + 1].startswith("-"): + input_value = tokens[i + 1] + i += 2 + else: + input_value = "" + i += 1 + input_flag = InputFlag( - 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], - ), - input_value=crnt_flg_val, - status=None, + name=name, + prefix=cast(PREFIX_TYPE, prefix), # pyright: ignore[reportUnnecessaryCast] + input_value=input_value, + status=None ) - - if input_flag in self._parsed_input_flags: + + if input_flag in flags: raise RepeatedInputFlagsException(input_flag) - - self._parsed_input_flags.add_flag(input_flag) - crnt_flg_name, crnt_flg_val = None, None - - 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: - next_index = current_index + 1 - if next_index >= len(_tokens): - return False - - 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 -) -> 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)] - if len(token) < 2 or len(prefix) > 2: - raise UnprocessedInputFlagException - - new_flag_name = token - new_flag_value = None - - return new_flag_name, new_flag_value + + flags.add_flag(input_flag) + + return cls(command, input_flags=flags) diff --git a/src/argenta/router/entity.py b/src/argenta/router/entity.py index 260808a..cb5d5dd 100644 --- a/src/argenta/router/entity.py +++ b/src/argenta/router/entity.py @@ -22,8 +22,8 @@ HandlerFunc: TypeAlias = Callable[..., None] class Router: def __init__( self, - *, title: str | None = "Default title", + *, disable_redirect_stdout: bool = False, ): """ diff --git a/tests/unit_tests/test_command.py b/tests/unit_tests/test_command.py index a95090d..fbd5bab 100644 --- a/tests/unit_tests/test_command.py +++ b/tests/unit_tests/test_command.py @@ -6,8 +6,8 @@ from argenta.command.exceptions import (EmptyInputCommandException, UnprocessedInputFlagException) from argenta.command.flag import Flag, InputFlag from argenta.command.flag.flags import Flags -from argenta.command.flag.models import PossibleValues -from argenta.command.models import Command, InputCommand, ValidationStatus +from argenta.command.flag.models import PossibleValues, ValidationStatus +from argenta.command.models import Command, InputCommand class TestInputCommand(unittest.TestCase): @@ -28,7 +28,7 @@ class TestInputCommand(unittest.TestCase): def test_validate_invalid_input_flag1(self): command = Command('some', flags=Flag('test')) - self.assertEqual(command.validate_input_flag(InputFlag('test', input_value=None, status=None)), ValidationStatus.INVALID) + self.assertEqual(command.validate_input_flag(InputFlag('test', input_value='', status=None)), ValidationStatus.INVALID) def test_validate_valid_input_flag2(self): command = Command('some', flags=Flags([Flag('test'), Flag('more')])) @@ -36,15 +36,15 @@ class TestInputCommand(unittest.TestCase): def test_validate_undefined_input_flag1(self): command = Command('some', flags=Flag('test')) - self.assertEqual(command.validate_input_flag(InputFlag('more', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('more', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_validate_undefined_input_flag2(self): command = Command('some', flags=Flags([Flag('test'), Flag('more')])) - self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_validate_undefined_input_flag3(self): command = Command('some') - self.assertEqual(command.validate_input_flag(InputFlag('case', input_value=None, status=None)), ValidationStatus.UNDEFINED) + self.assertEqual(command.validate_input_flag(InputFlag('case', input_value='', status=None)), ValidationStatus.UNDEFINED) def test_invalid_input_flag1(self): command = Command('some', flags=Flag('test', possible_values=PossibleValues.NEITHER)) diff --git a/tests/unit_tests/test_flag.py b/tests/unit_tests/test_flag.py index 4d55b1e..d85a446 100644 --- a/tests/unit_tests/test_flag.py +++ b/tests/unit_tests/test_flag.py @@ -29,8 +29,8 @@ class TestFlag(unittest.TestCase): '--') def test_get_flag_value_without_set(self): - self.assertEqual(InputFlag(name='test', input_value=None, status=None).input_value, - None) + self.assertEqual(InputFlag(name='test', input_value='', status=None).input_value, + '') def test_get_flag_value_with_set(self): flag = InputFlag(name='test', input_value='example', status=None) @@ -54,11 +54,11 @@ class TestFlag(unittest.TestCase): def test_validate_correct_empty_flag_value_without_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) - self.assertEqual(flag.validate_input_flag_value(None), True) + self.assertEqual(flag.validate_input_flag_value(''), True) def test_validate_correct_empty_flag_value_with_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) - self.assertEqual(flag.validate_input_flag_value(None), True) + self.assertEqual(flag.validate_input_flag_value(''), True) def test_validate_incorrect_random_flag_value_without_possible_flag_values(self): flag = Flag(name='test', possible_values=PossibleValues.NEITHER) @@ -69,19 +69,19 @@ class TestFlag(unittest.TestCase): self.assertEqual(flag.validate_input_flag_value('random value'), True) def test_get_input_flag1(self): - flag = InputFlag(name='test', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) input_flags = InputFlags([flag]) self.assertEqual(input_flags.get_flag_by_name('test'), flag) def test_get_input_flag2(self): - flag = InputFlag(name='test', input_value=None, status=None) - flag2 = InputFlag(name='some', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) + flag2 = InputFlag(name='some', input_value='', status=None) input_flags = InputFlags([flag, flag2]) self.assertEqual(input_flags.get_flag_by_name('some'), flag2) def test_get_undefined_input_flag(self): - flag = InputFlag(name='test', input_value=None, status=None) - flag2 = InputFlag(name='some', input_value=None, status=None) + flag = InputFlag(name='test', input_value='', status=None) + flag2 = InputFlag(name='some', input_value='', status=None) input_flags = InputFlags([flag, flag2]) self.assertEqual(input_flags.get_flag_by_name('case'), None)