This commit is contained in:
2025-10-21 10:33:46 +03:00
parent 9ac24926af
commit bf6fe0f9ee
10 changed files with 232 additions and 25 deletions
@@ -0,0 +1,10 @@
from argenta import App
from argenta.app.autocompleter import AutoCompleter
# Настройка автодополнения с сохранением истории в файл
my_autocompleter = AutoCompleter(history_filename="argenta_history.txt")
# Передача настроенного автокомплитера в приложение
app = App(autocompleter=my_autocompleter)
# ... остальная логика приложения
+49 -2
View File
@@ -1,4 +1,51 @@
.. _root_api_app_autocompleter: .. _root_api_app_autocompleter:
Autocompleter AutoCompleter
**************** =====================
Объект ``AutoCompleter`` является компонентом ``Argenta``, отвечающим за интерактивное автодополнение команд. Его основная задача — улучшить опыт взаимодействия пользователя с командной строкой, предоставляя подсказки и автоматически завершая ввод на основе ранее введенных команд. Это значительно ускоряет работу и снижает вероятность опечаток.
``AutoCompleter`` использует ``pyreadline3`` для имплементации ``readline GNU`` на ``Windows`` для управления историей команд и реализации логики автодополнения.
-----
Инициализация
-------------
.. code:: python
__init__(self, history_filename: str | None = None,
autocomplete_button: str = "tab") -> None
Создает и настраивает экземпляр ``AutoCompleter``.
* ``history_filename``: Имя файла для сохранения и загрузки истории команд. Если указано, история будет персистентной между сессиями приложения. Если **None**, история будет храниться только в памяти текущей сессии.
* ``autocomplete_button``: Название клавиши, которая активирует автодополнение. По умолчанию используется клавиша **"tab"**.
-----
Назначение и возможности
-------------------------
``AutoCompleter`` обладает следующими возможностями:
* **Автодополнение по истории**: Основная логика автодополнения основана на истории команд, которые пользователь вводил ранее. Когда пользователь начинает вводить команду и нажимает клавишу автодополнения (по умолчанию **Tab**), система ищет в истории все команды, начинающиеся с введенного текста.
* **Завершение по общему префиксу**: Если найдено несколько команд с общим префиксом, автодокомплитер подставит только общую часть. Например, если в истории есть команды ``show_users`` и ``show_profile``, при вводе ``sho`` и нажатии **Tab** будет подставлено ``show_``.
* **Персистентная история**: При указании параметра ``history_filename`` в конструкторе, вся история команд сохраняется в файл при выходе из приложения и загружается при следующем запуске. Это делает автодополнение со временем все более "умным" и персонализированным.
* **Очистка истории**: При сохранении истории ``AutoCompleter`` автоматически удаляет дубликаты и команды, которые больше не зарегистрированы в приложении, поддерживая актуальность и чистоту файла истории.
* **Настройка клавиши активации**: Вы можете изменить клавишу, отвечающую за автодополнение, через параметр ``autocomplete_button``.
-----
Пример использования
--------------------
``AutoCompleter`` передается как аргумент при инициализации `App`.
.. literalinclude:: ../../../code_snippets/autocompleter_example_sample.py
:language: python
:linenos:
+79 -2
View File
@@ -1,4 +1,81 @@
.. _root_api_app_dividing_lines: .. _root_api_app_dividing_lines:
DividingLines Dividing Lines
**************** ==============
Разделительные линии в ``Argenta`` играют важную роль в визуальном оформлении консольного интерфейса. Они используются для структурирования вывода, отделения блоков информации друг от друга (например, вывода команды от следующего приглашения к вводу).
Библиотека предлагает два подхода к управлению разделительными линиями: статический и динамический, каждый из которых имеет свои преимущества и сценарии использования.
----
Класс ``StaticDividingLine``
--------------------------
``StaticDividingLine`` — это класс, который создает разделительную линию **фиксированной** длины. Длина и символ-заполнитель задаются при инициализации объекта. Этот тип линии полезен, когда вам нужен предсказуемый и унифицированный внешний вид интерфейса, независимо от содержимого вывода.
.. code-block:: python
:linenos:
def __init__(self, unit_part: str = "-", *,
length: int = 25) -> None
Создает экземпляр статической разделительной линии.
* ``unit_part``: Символ, который будет использоваться для построения линии. Учитывается только первый символ строки. По умолчанию: ``-``.
* ``length``: Целое число, определяющее фиксированную длину линии в символах. Является keyword-only аргументом. По умолчанию: ``25``.
-----
Класс ``DynamicDividingLine``
---------------------------
``DynamicDividingLine`` представляет собой более "умный" подход. Этот класс создает линию, длина которой **динамически** подстраивается под самую длинную строку, выведенную в консоль, в рамках выполнения одной команды. Это достигается за счет механизма перехвата `stdout`. В результате разделительные линии всегда идеально обрамляют выводимый контент, что выглядит очень аккуратно.
.. code-block:: python
:linenos:
__init__(self, unit_part: str = "-") -> None
Создает экземпляр динамической разделительной линии.
* ``unit_part``: Символ, который будет использоваться для построения линии. По умолчанию: ``-``.
Длина для этой линии не задается при инициализации, так как она вычисляется автоматически во время выполнения.
.. warning::
Обязательно почитайте про нюансы использования динамических линий и перехвата ``stdout`` в :ref:`этом разделе<root_redirect_stdout>`.
Назначение и использование
--------------------------
Выбор между статической и динамической линией зависит от ваших потребностей в конкретном приложении или даже для конкретного `Router`.
* **`StaticDividingLine`** идеально подходит, если:
* Вам нужен строгий, консистентный дизайн.
* Вы используете роутеры с отключенным перехватом `stdout` (`disable_redirect_stdout=True`), так как в этом случае динамическое вычисление длины невозможно.
* **`DynamicDividingLine`** (используется по умолчанию) является предпочтительным выбором, если:
* Вы хотите, чтобы интерфейс выглядел аккуратно и адаптивно.
* Вывод ваших команд имеет разную длину, и вы хотите, чтобы рамки всегда соответствовали контенту.
Тип разделительной линии для всего приложения задается при инициализации `App` через параметр `dividing_line`.
Пример конфигурации
--------------------
.. code-block:: python
from argenta.app import App
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
# Создание статической линии из символов "=" длиной 40
static_line = StaticDividingLine(unit_part="=", length=40)
# Создание динамической линии из символов "*"
dynamic_line = DynamicDividingLine(unit_part="*")
# Приложение со статической линией
app_with_static_line = App(dividing_line=static_line)
# Приложение с динамической линией (поведение по умолчанию, но с кастомным символом)
app_with_dynamic_line = App(dividing_line=dynamic_line)
+59 -14
View File
@@ -10,7 +10,8 @@ App
Инициализация Инициализация
------------- -------------
.. code:: rust .. code-block:: rust
:linenos:
AVAILABLE_DIVIDING_LINES: TypeAlias = StaticDividingLine | DynamicDividingLine AVAILABLE_DIVIDING_LINES: TypeAlias = StaticDividingLine | DynamicDividingLine
DEFAULT_DIVIDING_LINE: StaticDividingLine = StaticDividingLine() DEFAULT_DIVIDING_LINE: StaticDividingLine = StaticDividingLine()
@@ -19,7 +20,8 @@ App
DEFAULT_AUTOCOMPLETER: AutoCompleter = AutoCompleter() DEFAULT_AUTOCOMPLETER: AutoCompleter = AutoCompleter()
DEFAULT_EXIT_COMMAND: Command = Command("Q", description="Exit command") DEFAULT_EXIT_COMMAND: Command = Command("Q", description="Exit command")
.. code:: python .. code-block:: python
:linenos:
def __init__(self, *, prompt: str = "What do you want to do?\n\n", def __init__(self, *, prompt: str = "What do you want to do?\n\n",
initial_message: str = "Argenta\n", initial_message: str = "Argenta\n",
@@ -33,7 +35,7 @@ App
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER, autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
print_func: Printer = DEFAULT_PRINT_FUNC) -> None print_func: Printer = DEFAULT_PRINT_FUNC) -> None
Создает и настраивает экземпляр приложения `Argenta`. Создает и настраивает экземпляр приложения ``Argenta``.
* ``prompt``: Строка-приглашение, которая отображается перед вводом каждой команды. По умолчанию: **"What do you want to do?\\n\\n"**. * ``prompt``: Строка-приглашение, которая отображается перед вводом каждой команды. По умолчанию: **"What do you want to do?\\n\\n"**.
* ``initial_message``: Приветственное сообщение, которое выводится при запуске приложения. * ``initial_message``: Приветственное сообщение, которое выводится при запуске приложения.
@@ -47,55 +49,98 @@ App
* ``autocompleter``: Объект, отвечающий за логику автодополнения команд. * ``autocompleter``: Объект, отвечающий за логику автодополнения команд.
* ``print_func``: Функция, используемая для вывода всех системных сообщений. По умолчанию используется ``rich.console.Console().print``. * ``print_func``: Функция, используемая для вывода всех системных сообщений. По умолчанию используется ``rich.console.Console().print``.
-----
Основные методы Основные методы
--------------- ---------------
.. py:method:: include_router(self, router: Router) -> None - .. py:method:: include_router(self, router: Router) -> None
Регистрирует один `Router` в приложении. Все команды, определенные в этом роутере, становятся доступными для вызова. Регистрирует один ``Router`` в приложении. Все команды, определенные в этом роутере, становятся доступными для вызова.
:param router: Объект роутера, который нужно зарегистрировать. :param router: Объект роутера, который нужно зарегистрировать.
.. py:method:: include_routers(self, *routers: Router) -> None - .. py:method:: include_routers(self, *routers: Router) -> None
Регистрирует несколько роутеров одновременно. Является удобной оберткой над `include_router`. Регистрирует несколько роутеров одновременно. Является удобной оберткой над ``include_router``.
:param routers: Последовательность объектов `Router` для регистрации. :param routers: Последовательность объектов ``Router`` для регистрации.
.. py:method:: add_message_on_startup(self, message: str) -> None - .. py:method:: add_message_on_startup(self, message: str) -> None
Добавляет дополнительное текстовое сообщение, которое будет выведено на экран при запуске приложения, сразу после `initial_message`. Добавляет дополнительное текстовое сообщение, которое будет выведено на экран при запуске приложения, сразу после `initial_message`.
:param message: Строка с сообщением. :param message: Строка с сообщением.
-----
Методы установки обработчиков Методы установки обработчиков
------------------------------- -------------------------------
`App` позволяет гибко настраивать реакцию на различные события, такие как ошибки ввода или ввод неизвестной команды. ``App`` позволяет гибко настраивать реакцию на различные события, такие как ошибки ввода или ввод неизвестной команды.
.. hint::
Подробнее о исключениях и их обработке в соответствующем :ref:`разделе документации <root_error_handling>`.
-----
.. py:method:: set_description_message_pattern(self, handler: DescriptionMessageGenerator) -> None .. py:method:: set_description_message_pattern(self, handler: DescriptionMessageGenerator) -> None
Устанавливает пользовательский шаблон для форматирования строки, описывающей доступную команду (триггер + описание). Устанавливает пользовательский шаблон для форматирования строки, описывающей доступную команду (триггер + описание).
``DescriptionMessageGenerator`` -> ``Callable[[str, str], str]``
Где первый аргумент - это триггер команды, а второй - ее описание, возвращает строку, которая является форматированной строкой.
------
.. py:method:: set_incorrect_input_syntax_handler(self, handler: NonStandardBehaviorHandler[str]) -> None .. py:method:: set_incorrect_input_syntax_handler(self, handler: NonStandardBehaviorHandler[str]) -> None
Устанавливает обработчик, который вызывается при некорректном синтаксисе флагов в введенной команде. Устанавливает обработчик, который вызывается при некорректном синтаксисе флагов в введенной команде.
``NonStandardBehaviorHandler[str]`` -> ``Callable[[str], None]``
Где первый и единственный аргумент - это необработанная строка пользовательского ввода.
------
.. py:method:: set_repeated_input_flags_handler(self, handler: NonStandardBehaviorHandler[str]) -> None .. py:method:: set_repeated_input_flags_handler(self, handler: NonStandardBehaviorHandler[str]) -> None
Устанавливает обработчик для ситуации, когда пользователь вводит один и тот же флаг несколько раз. Устанавливает обработчик для ситуации, когда пользователь вводит один и тот же флаг несколько раз.
``NonStandardBehaviorHandler[str]`` -> ``Callable[[str], None]``
Где первый и единственный аргумент - это необработанная строка пользовательского ввода.
------
.. py:method:: set_unknown_command_handler(self, handler: NonStandardBehaviorHandler[InputCommand]) -> None .. py:method:: set_unknown_command_handler(self, handler: NonStandardBehaviorHandler[InputCommand]) -> None
Устанавливает обработчик, который срабатывает, если введенная команда не была найдена ни в одном из зарегистрированных роутеров. Устанавливает обработчик, который срабатывает, если введенная команда не была найдена ни в одном из зарегистрированных роутеров.
``NonStandardBehaviorHandler[InputCommand]`` -> ``Callable[[InputCommand], None]``
Где первый и единственный аргумент - это распаршенный в объект InputCommand ввод пользователя.
-----
.. py:method:: set_empty_command_handler(self, handler: EmptyCommandHandler) -> None .. py:method:: set_empty_command_handler(self, handler: EmptyCommandHandler) -> None
Устанавливает обработчик для случая, когда пользователь отправляет пустую строку вместо команды. Устанавливает обработчик для случая, когда пользователь отправляет пустую строку вместо команды.
``EmptyCommandHandler`` -> ``Callable[[], None]``
Не принимает и не возвращает ничего.
-----
.. py:method:: set_exit_command_handler(self, handler: NonStandardBehaviorHandler[Response]) -> None .. py:method:: set_exit_command_handler(self, handler: NonStandardBehaviorHandler[Response]) -> None
Позволяет переопределить стандартное поведение при вызове команды выхода. По умолчанию просто выводится `farewell_message`. Позволяет переопределить стандартное поведение при вызове команды выхода. По умолчанию просто выводится ``farewell_message``.
``NonStandardBehaviorHandler[Response]`` -> ``Callable[[Response], None]``
Где первый и единственный аргумент - это объект ответа, ``Response``.
.. toctree:: .. toctree::
:hidden: :hidden:
+4
View File
@@ -20,16 +20,19 @@
.. literalinclude:: ../code_snippets/dependency_injection_example_sample.py .. literalinclude:: ../code_snippets/dependency_injection_example_sample.py
:language: python :language: python
:linenos:
``Argenta`` -> ``dishka`` зарезолвит тайпхинты и внедрит зависимость с возвращаемым типом ``Connection``, прежде чем использовать зависимость её нужно создать, для этого нужно создать соответствующий провайдер. ``Argenta`` -> ``dishka`` зарезолвит тайпхинты и внедрит зависимость с возвращаемым типом ``Connection``, прежде чем использовать зависимость её нужно создать, для этого нужно создать соответствующий провайдер.
.. literalinclude:: ../code_snippets/dependency_injection_example_sample2.py .. literalinclude:: ../code_snippets/dependency_injection_example_sample2.py
:language: python :language: python
:linenos:
После создания провайдера, его нужно зарегистрировать в оркестраторе. После создания провайдера, его нужно зарегистрировать в оркестраторе.
.. literalinclude:: ../code_snippets/dependency_injection_example_sample3.py .. literalinclude:: ../code_snippets/dependency_injection_example_sample3.py
:language: python :language: python
:linenos:
Как это работает? Как это работает?
----------------- -----------------
@@ -48,6 +51,7 @@
.. literalinclude:: ../code_snippets/dependency_injection_example_sample4.py .. literalinclude:: ../code_snippets/dependency_injection_example_sample4.py
:language: python :language: python
:linenos:
Обмен данными между хендлерами Обмен данными между хендлерами
------------------------------ ------------------------------
+8 -2
View File
@@ -18,7 +18,8 @@
пустой команды пустой команды
.. literalinclude:: ../code_snippets/error_handling_example_sample.py .. literalinclude:: ../code_snippets/error_handling_example_sample.py
:language: python :language: python
:linenos:
.. _possible_errors: .. _possible_errors:
@@ -47,6 +48,7 @@
.. literalinclude:: ../code_snippets/error_handling_example_sample2.py .. literalinclude:: ../code_snippets/error_handling_example_sample2.py
:language: python :language: python
:linenos:
--------------- ---------------
@@ -72,7 +74,8 @@
Сэмпл кода, переопределяющего хэндлер ввода команды с повторяющимися флагами: Сэмпл кода, переопределяющего хэндлер ввода команды с повторяющимися флагами:
.. literalinclude:: ../code_snippets/error_handling_example_sample3.py .. literalinclude:: ../code_snippets/error_handling_example_sample3.py
:language: python :language: python
:linenos:
--------------- ---------------
@@ -95,6 +98,7 @@
.. literalinclude:: ../code_snippets/error_handling_example_sample4.py .. literalinclude:: ../code_snippets/error_handling_example_sample4.py
:language: python :language: python
:linenos:
--------------- ---------------
@@ -118,6 +122,7 @@
.. literalinclude:: ../code_snippets/error_handling_example_sample5.py .. literalinclude:: ../code_snippets/error_handling_example_sample5.py
:language: python :language: python
:linenos:
--------------- ---------------
@@ -140,3 +145,4 @@
.. literalinclude:: ../code_snippets/error_handling_example_sample6.py .. literalinclude:: ../code_snippets/error_handling_example_sample6.py
:language: python :language: python
:linenos:
+10
View File
@@ -5,6 +5,8 @@
Флаги (или параметры) — это специальные аргументы, которые конечный юзер может добавлять к командам, чтобы управлять их поведением. Флаги (или параметры) — это специальные аргументы, которые конечный юзер может добавлять к командам, чтобы управлять их поведением.
-----
Синтаксис флагов Синтаксис флагов
----------------- -----------------
@@ -17,6 +19,8 @@
То есть, у флага обязательно должен быть префикс, который может быть одним, двум или трем минусам. После префикса следует имя флага, без То есть, у флага обязательно должен быть префикс, который может быть одним, двум или трем минусам. После префикса следует имя флага, без
пробела, после, через пробел, идёт значение флага, если оно есть. пробела, после, через пробел, идёт значение флага, если оно есть.
-----
Два типа флагов Два типа флагов
--------------- ---------------
@@ -27,6 +31,8 @@
При регистрации флага вы можете указать допустимые для него значения, по умолчанию любое введённое значение для флага будет валидным. Допустимые значения можно указать различными способами: При регистрации флага вы можете указать допустимые для него значения, по умолчанию любое введённое значение для флага будет валидным. Допустимые значения можно указать различными способами:
-----
Ограничение по списку возможных значений Ограничение по списку возможных значений
---------------------------------------- ----------------------------------------
@@ -37,6 +43,7 @@
Предположим, у вас есть флаг ``--format``, который может принимать только значения ``json`` или ``xml``. Предположим, у вас есть флаг ``--format``, который может принимать только значения ``json`` или ``xml``.
.. code-block:: bash .. code-block:: bash
:linenos:
# Эта команда сработает # Эта команда сработает
export --format json export --format json
@@ -44,6 +51,8 @@
# А эта вызовет ошибку валидации, так как "csv" нет в списке разрешённых # А эта вызовет ошибку валидации, так как "csv" нет в списке разрешённых
export --format csv export --format csv
-----
Проверка с помощью регулярных выражений Проверка с помощью регулярных выражений
----------------------------------------- -----------------------------------------
@@ -54,6 +63,7 @@
Допустим, флаг ``--email`` должен принимать только корректные email-адреса. Допустим, флаг ``--email`` должен принимать только корректные email-адреса.
.. code-block:: bash .. code-block:: bash
:linenos:
# Сработает, так как значение соответствует формату email # Сработает, так как значение соответствует формату email
send --email "user@example.com" send --email "user@example.com"
+1
View File
@@ -41,3 +41,4 @@
.. literalinclude:: ../code_snippets/overriding_format_example_sample.py .. literalinclude:: ../code_snippets/overriding_format_example_sample.py
:language: python :language: python
:linenos:
+2
View File
@@ -13,11 +13,13 @@
.. literalinclude:: ../code_snippets/quickstart_example_routers.py .. literalinclude:: ../code_snippets/quickstart_example_routers.py
:language: python :language: python
:linenos:
3. **Определение приложения и оркестратора**. Для запуска приложения необходимо вызвать ``.include_router()`` у созданного приложения и передать ему раннее созданный роутер, после этого необходимо вызвать ``.start_polling()`` у созданного оркестратора и передать ему созданное приложение. 3. **Определение приложения и оркестратора**. Для запуска приложения необходимо вызвать ``.include_router()`` у созданного приложения и передать ему раннее созданный роутер, после этого необходимо вызвать ``.start_polling()`` у созданного оркестратора и передать ему созданное приложение.
.. literalinclude:: ../code_snippets/quickstart_example_main.py .. literalinclude:: ../code_snippets/quickstart_example_main.py
:language: python :language: python
:linenos:
4. **Запуск приложения**. Запускаем приложение как обычный скрипт. 4. **Запуск приложения**. Запускаем приложение как обычный скрипт.
+6 -1
View File
@@ -11,16 +11,19 @@
----- -----
Механизм перехвата ``stdout`` Механизм перехвата ``stdout``
----------------------------- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
По умолчанию ``Argenta`` перехватывает весь текст, который выводится в ``stdout`` внутри обработчика команды (``handler``). Это делается для реализации **динамической длины разделителя**. Система анализирует весь выведенный текст, находит самую длинную строку и использует её длину для отрисовки верхней и нижней разделительных линий. Это создает аккуратный и визуально согласованный интерфейс, где вывод команды "обернут" в рамку, идеально подогнанную под его содержимое. По умолчанию ``Argenta`` перехватывает весь текст, который выводится в ``stdout`` внутри обработчика команды (``handler``). Это делается для реализации **динамической длины разделителя**. Система анализирует весь выведенный текст, находит самую длинную строку и использует её длину для отрисовки верхней и нижней разделительных линий. Это создает аккуратный и визуально согласованный интерфейс, где вывод команды "обернут" в рамку, идеально подогнанную под его содержимое.
-----
Побочные эффекты перехвата ``stdout`` Побочные эффекты перехвата ``stdout``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Главный побочный эффект этого механизма проявляется при использовании функций, которые одновременно ожидают ввод от пользователя и выводят текст-приглашение. Классический пример — стандартная функция ``input()``. Главный побочный эффект этого механизма проявляется при использовании функций, которые одновременно ожидают ввод от пользователя и выводят текст-приглашение. Классический пример — стандартная функция ``input()``.
.. code-block:: python .. code-block:: python
:linenos:
# Внутри обработчика команды # Внутри обработчика команды
user_name = input("Введите ваше имя: ") user_name = input("Введите ваше имя: ")
@@ -42,6 +45,7 @@
.. literalinclude:: ../code_snippets/redirect_stdout_example_sample.py .. literalinclude:: ../code_snippets/redirect_stdout_example_sample.py
:language: python :language: python
:linenos:
В этом случае ``input()`` будет работать как обычно, и пользователь сразу увидит приглашение "Как вас зовут?". В этом случае ``input()`` будет работать как обычно, и пользователь сразу увидит приглашение "Как вас зовут?".
@@ -67,6 +71,7 @@
.. literalinclude:: ../code_snippets/redirect_stdout_example_sample2.py .. literalinclude:: ../code_snippets/redirect_stdout_example_sample2.py
:language: python :language: python
:linenos:
----- -----