mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 01:55:29 +03:00
docs
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from pytest import CaptureFixture
|
||||
|
||||
from argenta import App, Orchestrator, Router, Command, Response
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patched_argv():
|
||||
with patch.object(sys, 'argv', ['program.py']):
|
||||
yield
|
||||
|
||||
def test_input_incorrect_command(capsys: CaptureFixture[str]):
|
||||
router = Router()
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
@router.command(Command('test'))
|
||||
def test(response: Response) -> None:
|
||||
print('test command')
|
||||
|
||||
app = App(override_system_messages=True, print_func=print)
|
||||
app.include_router(router)
|
||||
app.set_unknown_command_handler(
|
||||
lambda command: print(f'Unknown command: {command.trigger}')
|
||||
)
|
||||
|
||||
with patch("builtins.input", side_effect=["help", "q"]):
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "\nUnknown command: help\n" in output
|
||||
@@ -1,25 +1,21 @@
|
||||
import io
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from argenta import App, Router, Command, Response
|
||||
from argenta.command import InputCommand
|
||||
|
||||
|
||||
class TestAppIntegration(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.app = App(override_system_messages=True, repeat_command_groups=False)
|
||||
self.router = Router(title="App")
|
||||
def test_simple_app() -> None:
|
||||
app = App(override_system_messages=True, repeat_command_groups_printing=False)
|
||||
router = Router(title="App")
|
||||
|
||||
@self.router.command(Command("HELP", description="Show help"))
|
||||
def help_cmd(response: Response):
|
||||
print("Available commands: HELP")
|
||||
@router.command(Command("HELP", description="Show help"))
|
||||
def help_cmd(response: Response):
|
||||
print("Available commands: HELP")
|
||||
|
||||
_ = help_cmd # appease linter: function is registered via decorator
|
||||
app.include_router(router)
|
||||
|
||||
self.app.include_router(self.router)
|
||||
|
||||
def test_help_command(self):
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
self.router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
||||
self.assertIn("Available commands:", stdout.getvalue())
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
||||
|
||||
assert "Available commands:" in stdout.getvalue()
|
||||
|
||||
@@ -1,59 +1,42 @@
|
||||
import io
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
from unittest.mock import Mock
|
||||
|
||||
from dishka import Provider, provide, make_container, Scope # pyright: ignore[reportUnknownVariableType]
|
||||
from argenta.command import InputCommand
|
||||
from dishka import Provider, make_container, Scope
|
||||
|
||||
from argenta import Router, Command
|
||||
from argenta.di.integration import inject, setup_dishka, FromDishka
|
||||
from argenta.response import Response
|
||||
from argenta.response.status import ResponseStatus
|
||||
from argenta import Router, Response
|
||||
from argenta.di.integration import setup_dishka, FromDishka
|
||||
|
||||
|
||||
class Service:
|
||||
def hello(self) -> str:
|
||||
return "world"
|
||||
|
||||
|
||||
def get_service() -> Service:
|
||||
return Service()
|
||||
|
||||
|
||||
router = Router(title="DI")
|
||||
|
||||
|
||||
@router.command(Command("HELLO"))
|
||||
@inject # Auto-inject dependencies from the Response container
|
||||
@router.command("HELLO")
|
||||
def hello(response: Response, service: FromDishka[Service]) -> None:
|
||||
print(f"hello {service.hello()}")
|
||||
|
||||
|
||||
class TestDIHandler(unittest.TestCase):
|
||||
def test_hello_uses_service(self):
|
||||
# Prepare DI container with a stub
|
||||
fake = Mock(spec=Service)
|
||||
fake.hello.return_value = "mocked"
|
||||
|
||||
class TestProvider(Provider):
|
||||
scope = Scope.APP
|
||||
|
||||
@provide(scope=Scope.APP)
|
||||
def service(self) -> Service:
|
||||
return fake
|
||||
|
||||
container = make_container(TestProvider())
|
||||
|
||||
# Bind container to Response via integration
|
||||
setup_dishka(app=_FakeApp(), container=container, auto_inject=False) # type: ignore[arg-type]
|
||||
|
||||
# Create Response bound to the container
|
||||
r = Response(ResponseStatus.ALL_FLAGS_VALID)
|
||||
r._dishka_container = container # direct assignment is acceptable in tests # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
# Call handler directly
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
hello(r)
|
||||
|
||||
self.assertIn("hello mocked", stdout.getvalue())
|
||||
|
||||
|
||||
|
||||
|
||||
class _FakeApp:
|
||||
# Minimal stub for setup_dishka; app object is not used in unit tests
|
||||
registered_routers = []
|
||||
registered_routers = [router]
|
||||
|
||||
|
||||
def test_hello_uses_service():
|
||||
provider = Provider(scope=Scope.APP)
|
||||
provider.provide(get_service)
|
||||
|
||||
container = make_container(provider)
|
||||
setup_dishka(app=_FakeApp(), container=container, auto_inject=True)
|
||||
|
||||
# Call handler
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse('HELLO'))
|
||||
|
||||
assert "hello world" in stdout.getvalue()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import io
|
||||
import unittest
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from argenta import Router, Command, Response
|
||||
@@ -8,15 +7,13 @@ from argenta.command import InputCommand
|
||||
|
||||
router = Router(title="Demo")
|
||||
|
||||
|
||||
@router.command(Command("PING", description="Ping command"))
|
||||
def ping(response: Response):
|
||||
print("PONG")
|
||||
|
||||
|
||||
class TestSimpleHandler(unittest.TestCase):
|
||||
def test_ping_prints_pong(self):
|
||||
# Имитация запуска хендлера через роутер
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
||||
self.assertIn("PONG", stdout.getvalue())
|
||||
def test_ping_prints_pong():
|
||||
# Call handler
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
||||
assert "PONG" in stdout.getvalue()
|
||||
|
||||
+1
-5
@@ -12,7 +12,7 @@ default:
|
||||
@{{sphinxbuild}} -M help "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
||||
|
||||
# Build all language versions
|
||||
build-all:
|
||||
build:
|
||||
{{sphinxbuild}} -b html -D language=ru {{sourcedir}} {{builddir}}/html/ru
|
||||
{{sphinxbuild}} -b html -D language =en {{sourcedir}} {{builddir}}/html/en
|
||||
|
||||
@@ -28,7 +28,3 @@ live-en:
|
||||
update-langs:
|
||||
{{sphinxbuild}} -b gettext . _build/gettext
|
||||
sphinx-intl update -p _build/gettext -l en
|
||||
|
||||
# Generic build target (html, latex, etc.)
|
||||
build target:
|
||||
{{sphinxbuild}} -M {{target}} "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
||||
|
||||
@@ -75,7 +75,7 @@ msgstr ""
|
||||
|
||||
#: ../../root/api/app/index.rst:47
|
||||
msgid ""
|
||||
"``repeat_command_groups``: Если ``True``, список доступных команд "
|
||||
"``repeat_command_groups_printing``: Если ``True``, список доступных команд "
|
||||
"выводится перед каждым вводом."
|
||||
msgstr ""
|
||||
|
||||
@@ -296,7 +296,7 @@ msgstr ""
|
||||
#~ msgstr ""
|
||||
|
||||
#~ msgid ""
|
||||
#~ "``repeat_command_groups``: Если **True** (по "
|
||||
#~ "``repeat_command_groups_printing``: Если **True** (по "
|
||||
#~ "умолчанию), описание доступных команд будет"
|
||||
#~ " выводиться перед каждым вводом."
|
||||
#~ msgstr ""
|
||||
|
||||
@@ -30,7 +30,7 @@ App
|
||||
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) -> None
|
||||
@@ -40,14 +40,14 @@ App
|
||||
* ``prompt``: Приглашение к вводу, отображаемое перед каждой командой.
|
||||
* ``initial_message``: Сообщение, выводимое при запуске приложения.
|
||||
* ``farewell_message``: Сообщение, выводимое при выходе из приложения.
|
||||
* ``exit_command``: Команда, используемая для выхода из приложения.
|
||||
* ``exit_command``: Команда, которая маркируется как триггер для выхода из приложения.
|
||||
* ``system_router_title``: Заголовок для системного роутера (содержит команду выхода).
|
||||
* ``ignore_command_register``: Если ``True``, регистр команд игнорируется при поиске обработчика.
|
||||
* ``dividing_line``: Стиль разделительной линии (``StaticDividingLine`` или ``DynamicDividingLine``).
|
||||
* ``repeat_command_groups``: Если ``True``, список доступных команд выводится перед каждым вводом.
|
||||
* ``ignore_command_register``: Если ``True``, регистр вводимых команд игнорируется при поиске обработчика.
|
||||
* ``dividing_line``: Тип разделительной линии (``StaticDividingLine`` или ``DynamicDividingLine``).
|
||||
* ``repeat_command_groups_printing``: Если ``True``, список доступных команд выводится перед каждым вводом.
|
||||
* ``override_system_messages``: Если ``True``, стандартное форматирование (цвета, ASCII-арт) отключается.
|
||||
* ``autocompleter``: Объект, отвечающий за автодополнение команд.
|
||||
* ``print_func``: Функция для вывода всех системных сообщений (по умолчанию ``rich.print``).
|
||||
* ``autocompleter``: Экземпляр класса :ref:`AutoCompleter <root_api_app_autocompleter>`, отвечающий за автодополнение команд.
|
||||
* ``print_func``: Функция для вывода всех системных сообщений (по умолчанию ``rich.Console().print``).
|
||||
|
||||
-----
|
||||
|
||||
@@ -68,7 +68,7 @@ App
|
||||
|
||||
- .. py:method:: add_message_on_startup(self, message: str) -> None
|
||||
|
||||
Добавляет текстовое сообщение, которое выводится при запуске приложения после `initial_message`.
|
||||
Добавляет текстовое сообщение, которое выводится при запуске приложения после ``initial_message``.
|
||||
|
||||
:param message: Строка с сообщением.
|
||||
|
||||
@@ -89,7 +89,7 @@ App
|
||||
|
||||
.. py:method:: set_description_message_pattern(self, handler: Callable[[str, str], str]) -> None
|
||||
|
||||
Устанавливает шаблон для форматирования строки описания команды.
|
||||
Устанавливает шаблон для форматирования описания команды.
|
||||
|
||||
Обработчик принимает триггер команды (``str``) и её описание (``str``), а возвращает отформатированную строку.
|
||||
|
||||
|
||||
+12
-14
@@ -27,14 +27,12 @@
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from argenta import (
|
||||
App, Orchestrator, Router, Command, Response
|
||||
)
|
||||
from argenta import App, Orchestrator, Router, Command, Response
|
||||
|
||||
* :ref:`App <root_api_app_index>` — Основной класс приложения.
|
||||
* :ref:`Orchestrator <root_api_orchestrator_index>` — Класс для управления жизненным циклом.
|
||||
* :ref:`App <root_api_app_index>` — Объект приложения, который отвечает за логику роутинга, настройки, валидации и т.д.
|
||||
* :ref:`Orchestrator <root_api_orchestrator_index>` — Класс для конфигурирования и запуска всего приложения.
|
||||
* :ref:`Router <root_api_router>` — Класс для группировки и регистрации команд.
|
||||
* :ref:`Command <root_api_command_index>` — Класс для создания команд.
|
||||
* :ref:`Command <root_api_command_index>` — Класс для создания команд при инициализации хэндлеров.
|
||||
* :ref:`Response <root_api_response>` — Объект ответа, передаваемый в обработчики.
|
||||
|
||||
.. rubric:: Команды и флаги
|
||||
@@ -55,9 +53,9 @@
|
||||
* :ref:`Flags <root_api_command_flags>` — Коллекция для регистрации флагов.
|
||||
* :ref:`InputFlag <root_api_command_input_flag>` — Класс для введённого пользователем флага.
|
||||
* :ref:`InputFlags <root_api_command_input_flags>` — Коллекция введённых флагов.
|
||||
* :ref:`PossibleValues <root_api_command_possible_values>` — Правила валидации значений флагов.
|
||||
* :ref:`PossibleValues <root_api_command_possible_values>` — Правила валидации значений флага.
|
||||
* :ref:`ValidationStatus <root_api_command_validation_status>` — Статусы валидации флагов.
|
||||
* ``PredefinedFlags`` — Готовые наборы флагов (например, ``--help``).
|
||||
* :ref:`PredefinedFlags <root_api_command_flag_predefined_flags>` — Коллекция предопределённых флагов.
|
||||
|
||||
.. rubric:: Настройка приложения
|
||||
|
||||
@@ -70,10 +68,10 @@
|
||||
PredefinedMessages
|
||||
)
|
||||
|
||||
* :ref:`AutoCompleter <root_api_app_autocompleter>` — Базовый класс для автодополнения.
|
||||
* :ref:`StaticDividingLine <root_api_app_dividing_lines>` — Статическая разделительная линия.
|
||||
* :ref:`DynamicDividingLine <root_api_app_dividing_lines>` — Динамическая разделительная линия.
|
||||
* ``PredefinedMessages`` — Готовые системные сообщения.
|
||||
* :ref:`AutoCompleter <root_api_app_autocompleter>` - Класс для настройки автодополнения.
|
||||
* :ref:`StaticDividingLine <root_api_app_dividing_lines>` — Статическая разделительная линия для оформления вывода.
|
||||
* :ref:`DynamicDividingLine <root_api_app_dividing_lines>` — Динамическая разделительная линия для оформления вывода.
|
||||
* :ref:`PredefinedMessages <root_api_predefined_messages>` — Готовые сообщения для вывода при старте приложения.
|
||||
|
||||
.. rubric:: Внедрение зависимостей
|
||||
|
||||
@@ -84,8 +82,8 @@
|
||||
inject
|
||||
)
|
||||
|
||||
* :ref:`FromDishka <root_dependency_injection>` — Маркер для внедрения зависимостей.
|
||||
* :ref:`inject <root_dependency_injection>` — Декоратор для асинхронного внедрения.
|
||||
* :ref:`FromDishka <root_dependency_injection>` — Маркер аргумента функции как зависимости, которая должна быть инжектирована.
|
||||
* :ref:`inject <root_dependency_injection>` — Декоратор для инжектирования зависимостей, указанных в сигнатуре.
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Наше обязательство
|
||||
------------------
|
||||
|
||||
В целях создания открытой и гостеприимной атмосферы мы, как участники и мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе свободным от преследований для всех, независимо от возраста, телосложения, инвалидности, этнической принадлежности, гендерной идентичности и самовыражения, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы, религии или сексуальной идентичности и ориентации.
|
||||
В целях создания открытой и гостеприимной атмосферы мы, как участники и мейнтейнеры, обязуемся сделать участие в нашем проекте и сообществе свободным от преследований для всех, независимо от возраста, телосложения, инвалидности, этнической принадлежности, уровня опыта, образования, социально-экономического статуса, национальности, внешности, расы или религии.
|
||||
|
||||
-----
|
||||
|
||||
|
||||
+16
-13
@@ -55,7 +55,7 @@
|
||||
|
||||
Поищите ответ в существующих `Issues <https://github.com/koloideal/Argenta/issues>`_. Если вы нашли похожий вопрос, но всё ещё нуждаетесь в разъяснениях, можете написать в нём. Также рекомендуем поискать ответ в интернете.
|
||||
|
||||
Если ответа не нашлось, создайте новый `Issue <https://github.com/koloideal/Argenta/issues/new>`_ и предоставьте как можно больше контекста, включая версии проекта и платформы (CPython, pip и т.д.).
|
||||
Если ответа не нашлось, создайте новый `Issue <https://github.com/koloideal/Argenta/issues/new>`_ и предоставьте как можно больше контекста, включая версии проекта и платформы.
|
||||
|
||||
Мы займемся вашей задачей как можно скорее.
|
||||
|
||||
@@ -87,10 +87,10 @@
|
||||
* Также поищите в интернете (включая `Stack Overflow`), чтобы узнать, обсуждалась ли проблема за пределами `GitHub`.
|
||||
* Соберите информацию об ошибке:
|
||||
* Трассировка стека.
|
||||
* ОС, платформа и версия (Windows, Linux, macOS, x86, ARM).
|
||||
* Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера пакетов и т.д.
|
||||
* Входные данные и полученный результат.
|
||||
* Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на старых версиях?
|
||||
* ОС, платформа и версия (Windows, Linux, macOS, x86, ARM).
|
||||
* Версия интерпретатора, компилятора, SDK, среды выполнения, менеджера пакетов и т.д.
|
||||
* Входные данные и полученный результат.
|
||||
* Можете ли вы надёжно воспроизвести проблему? Воспроизводится ли она на старых версиях?
|
||||
|
||||
.. rubric:: Как мне отправить хороший отчет об ошибке?
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
|
||||
python -m pytest tests
|
||||
|
||||
#. Сделайте коммит, следуя нашему руководству по стилю, и отправьте изменения в ваш форк.
|
||||
#. Сделайте коммит, следуя :ref:`нашему руководству по стилю <styleguide>`, и отправьте изменения в ваш форк.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -209,6 +209,10 @@
|
||||
.. note::
|
||||
|
||||
Мы поддерживаем документацию на двух языках: русском и английском.
|
||||
|
||||
.. important::
|
||||
|
||||
Для инкапсуляции различных команд, необходимых для настройки и запуска проекта мы используем ``just``, он же фигурирует в различных примерах в документации, поэтому рекомендуем вам `установить его <https://github.com/casey/just#installation>`_
|
||||
|
||||
Для улучшения документации вы можете следовать процессу, похожему на внесение вклада в код:
|
||||
|
||||
@@ -220,33 +224,32 @@
|
||||
cd docs
|
||||
|
||||
#. Внесите изменения в **русскую** версию документации (`docs/index.rst` и/или `docs/root/*`).
|
||||
#. Чтобы собрать документацию локально и увидеть изменения, выполните:
|
||||
#. Чтобы собрать документацию локально в режиме автоматического ребилда и увидеть изменения, выполните:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make live-ru
|
||||
just live-ru
|
||||
|
||||
#. Откройте `127.0.0.1:8000` в браузере, чтобы просмотреть сгенерированную документацию.
|
||||
#. После завершения работы над русской версией необходимо создать английский перевод:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make update-langs
|
||||
just update-langs
|
||||
|
||||
#. После обновления шаблона обновите файлы перевода, расположенные в `docs/locales/en/LC_MESSAGES/`.
|
||||
#. Когда изменения будут готовы, сделайте коммит и откройте `Pull Request`. Используйте префикс `docs:` в сообщении коммита.
|
||||
|
||||
-----
|
||||
|
||||
.. _Руководства по стилю:
|
||||
.. _styleguide:
|
||||
|
||||
Руководства по стилю
|
||||
--------------------
|
||||
|
||||
.. _Сообщения коммитов:
|
||||
.. _commits_messages:
|
||||
|
||||
Сообщения коммитов
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
**Сообщения коммитов**
|
||||
|
||||
Мы следуем спецификации `Conventional Commits <https://www.conventionalcommits.org/en/v1.0.0/>`_. Это делает историю проекта более читаемой и позволяет автоматически генерировать журнал изменений.
|
||||
|
||||
|
||||
+19
-4
@@ -1,18 +1,20 @@
|
||||
Тестирование
|
||||
============
|
||||
|
||||
В этом разделе описаны практики тестирования приложений на основе ``Argenta``. Примеры основаны на фактическом публичном API: ``App``, ``Router``, ``Command``, ``Orchestrator``, DI через ``dishka`` и интеграцию в ``argenta.di.integration``.
|
||||
В этом разделе описаны практики тестирования приложений на основе ``Argenta``. Примеры основаны на фактическом публичном API.
|
||||
|
||||
Модульное тестирование хендлеров
|
||||
--------------------------------
|
||||
|
||||
Обработчики в Argenta — обычные функции. Их удобно тестировать как чистые функции, не поднимая весь цикл приложения. Рекомендуются ``unittest`` или ``pytest``.
|
||||
|
||||
Пример с ``unittest`` для простого хендлера без DI:
|
||||
Пример для простого хендлера без DI:
|
||||
|
||||
.. literalinclude:: ../code_snippets/testing/simple_handler_unittest.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
-----
|
||||
|
||||
Тестирование с внедрением зависимостей (DI)
|
||||
-------------------------------------------
|
||||
@@ -22,6 +24,8 @@
|
||||
.. literalinclude:: ../code_snippets/testing/di_handler_unittest.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
-----
|
||||
|
||||
Интеграционное тестирование приложения
|
||||
--------------------------------------
|
||||
@@ -31,11 +35,22 @@
|
||||
.. literalinclude:: ../code_snippets/testing/app_integration_unittest.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
-----
|
||||
|
||||
E2E-тестирование цикла (опционально)
|
||||
E2E-тестирование цикла
|
||||
------------------------------------
|
||||
|
||||
Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк во ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — вынесите конфигурацию в функцию ``main()`` и запускайте модуль в подпроцессе с подготовленным вводом/выводом.
|
||||
Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё же необходимо — пример ниже.
|
||||
|
||||
.. danger ::
|
||||
Обязательно передавайте строковый триггер команды выхода последним элементом в списке, который передаёте в контекстном менеджере при патче ``input`` как аргумент ``side_effects``, иначе тестируемое приложение будет ожидать ввода следующей команды и не сможет корректно завершиться.
|
||||
|
||||
.. literalinclude:: ../code_snippets/testing/app_e2e_test.py
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
-----
|
||||
|
||||
Советы по тестированию
|
||||
----------------------
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||
set shell := ["bash", "-c"]
|
||||
|
||||
# Вывести список всех рецептов
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Запустить тесты через pytest
|
||||
tests:
|
||||
python -m pytest tests
|
||||
|
||||
# Запустить тесты с отчетом о покрытии
|
||||
tests-cov:
|
||||
python -m pytest --cov=argenta tests
|
||||
|
||||
# Отформатировать код (Ruff + isort)
|
||||
format:
|
||||
python -m ruff format ./src
|
||||
python -m isort ./src
|
||||
|
||||
# Проверить типы через mypy (strict)
|
||||
mypy:
|
||||
python -m mypy -p argenta --strict
|
||||
|
||||
# Проверить стиль через wemake-python-styleguide
|
||||
wps:
|
||||
python -m flake8 --format=wemake ./src
|
||||
|
||||
# Запустить линтер Ruff
|
||||
ruff:
|
||||
python -m ruff check ./src
|
||||
|
||||
@@ -45,6 +45,7 @@ exclude = [
|
||||
".__pycache__",
|
||||
"tests"
|
||||
]
|
||||
line-length=110
|
||||
|
||||
[tool.pyright]
|
||||
typeCheckingMode = "strict"
|
||||
@@ -52,6 +53,9 @@ typeCheckingMode = "strict"
|
||||
[tool.mypy]
|
||||
disable_error_code = "import-untyped"
|
||||
|
||||
[tool.isort]
|
||||
line_length=90
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>"
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}$"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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 +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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import polib
|
||||
import deepl
|
||||
import getopt
|
||||
import sys
|
||||
import re
|
||||
|
||||
DEEPL_API_TOKEN = 'ADD YOUR API KEY HERE!'
|
||||
|
||||
global argv
|
||||
global opts
|
||||
global args
|
||||
|
||||
argv = sys.argv[1:]
|
||||
opts, args = getopt.getopt(argv, "f:l:")
|
||||
|
||||
def translate(text, lang):
|
||||
# Define a dictionary to hold the mappings of tokens to placeholders
|
||||
placeholders = {}
|
||||
|
||||
# Use a regular expression to find all the tokens
|
||||
tokens = re.findall(r'%\((.*?)\)s', text)
|
||||
|
||||
# Replace each token with a unique placeholder
|
||||
for i, token in enumerate(tokens):
|
||||
placeholder = f'__PLACEHOLDER_{i}__'
|
||||
placeholders[placeholder] = f'%({token})s'
|
||||
text = text.replace(f'%({token})s', placeholder)
|
||||
|
||||
# Perform the translation
|
||||
translator = deepl.Translator(DEEPL_API_TOKEN)
|
||||
translated_text = str(translator.translate_text(text, target_lang=lang))
|
||||
|
||||
# Replace the placeholders back with the original tokens
|
||||
for placeholder, token in placeholders.items():
|
||||
translated_text = translated_text.replace(placeholder, token)
|
||||
|
||||
return translated_text
|
||||
|
||||
def get_filename():
|
||||
# read arguments from command line
|
||||
for opt, arg in opts:
|
||||
if opt in ['-f']:
|
||||
filename = arg
|
||||
if not filename:
|
||||
print('Please enter the filename of the PO file e.g. /directory/django.po:')
|
||||
filename = input()
|
||||
return filename
|
||||
|
||||
def get_target_language():
|
||||
# read arguments from command line
|
||||
for opt, arg in opts:
|
||||
if opt in ['-l']:
|
||||
lang = arg
|
||||
if not lang:
|
||||
print('Please enter two letter ISO language code e.g. DE:')
|
||||
lang = input()
|
||||
return lang
|
||||
|
||||
def process_file(filename, lang):
|
||||
po = polib.pofile(filename)
|
||||
for entry in po.untranslated_entries():
|
||||
if not entry.msgstr:
|
||||
print(entry.msgid)
|
||||
print('translating...')
|
||||
entry.msgstr = translate(entry.msgid, lang)
|
||||
print(entry.msgstr)
|
||||
print('\n')
|
||||
po.save(filename)
|
||||
|
||||
if __name__ == '__main__':
|
||||
process_file(get_filename(), get_target_language())
|
||||
Reference in New Issue
Block a user