docs and fix

This commit is contained in:
2025-10-21 22:56:29 +03:00
parent 90e80d3454
commit f38da15bdb
25 changed files with 215 additions and 45 deletions
@@ -0,0 +1,26 @@
import sqlite3
from sqlite3 import Connection
from typing import Iterable
from dishka import Provider, Scope, provide
from argenta import App, Orchestrator
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
conn = sqlite3.connect(":memory:")
yield conn
conn.close()
# 2. Создаем и настраиваем App
app = App()
# ... здесь можно добавить роутеры ...
# 3. Создаем Orchestrator, передавая наш провайдер
orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
# 4. Запускаем приложение
if __name__ == "__main__":
orchestrator.start_polling(app)
+1 -1
View File
@@ -46,6 +46,6 @@ AutoCompleter
``AutoCompleter`` передается как аргумент при инициализации `App`. ``AutoCompleter`` передается как аргумент при инициализации `App`.
.. literalinclude:: ../../../code_snippets/autocompleter_example_sample.py .. literalinclude:: ../../../code_snippets/autocompleter_snippet.py
:language: python :language: python
:linenos: :linenos:
+1 -1
View File
@@ -70,6 +70,6 @@ Dividing Lines
Пример конфигурации Пример конфигурации
-------------------- --------------------
.. literalinclude:: ../../../code_snippets/dividing_lines_example_sample.py .. literalinclude:: ../../../code_snippets/dividing_lines_snippet.py
:language: python :language: python
:linenos: :linenos:
+89 -2
View File
@@ -1,6 +1,93 @@
.. _root_api_orchestrator_argparser: .. _root_api_orchestrator_argparser:
Argparser Argparser
**************** ==========
Объект ``ArgParser`` в ``Argenta`` предназначен для разбора и обработки **аргументов командной строки**, которые передаются вашему приложению при его запуске. Важно не путать их с командами, которые пользователь вводит в интерактивном режиме работы приложения. `ArgParser` позволяет вашему приложению получать внешнюю конфигурацию в момент старта, например, путь к файлу настроек, флаги для отладки или режим запуска.
-----
Инициализация
-------------
.. code-block:: python
def __init__(self, processed_args: list[argenta.orchestrator.InputArgument] = []) -> None
Создает экземпляр парсера аргументов командной строки.
* ``processed_args``: Список аргументов, которые будут обрабатываться и парситься при запуске приложения.
Основные методы
---------------
.. py:method:: parse(self) -> dict[str, str | list[str]]
Основной метод, который выполняет разбор списка `processed_args`. Он анализирует аргументы и преобразует их в структурированный словарь.
* Аргументы, имеющие вид флага (например, `--verbose`), получают значение `True`.
* Аргументы, за которыми следует значение (например, `--config settings.yaml`), сохраняются как пара "ключ-значение".
* Если один и тот же аргумент встречается несколько раз, его значения могут быть собраны в список.
:returns: Словарь, где ключи — это имена аргументов, а значения — их соответствующие значения.
.. py:method:: get(self, key: str, default: str | None = None) -> str | None
Удобный метод для получения значения конкретного аргумента по его имени (ключу). Если аргумент не был найден, возвращается значение `default`.
:param key: Имя аргумента (например, `"--config"`).
:param default: Значение, которое будет возвращено, если аргумент `key` отсутствует. По умолчанию `None`.
:returns: Значение аргумента или значение по умолчанию.
.. py:method:: all_args(self) -> list[str]
Возвращает список всех имен (ключей) аргументов, которые были распознаны парсером после вызова метода `parse()`.
Назначение и интеграция
------------------------
`ArgParser` является неотъемлемой частью `Orchestrator`. При создании `Orchestrator` ему передается экземпляр `ArgParser`, который обычно инициализируется с `sys.argv[1:]`.
После этого `Orchestrator` помещает `ArgParser` в DI-контейнер. Это означает, что любой ваш сервис или обработчик команды может запросить `ArgParser` через внедрение зависимостей и получить доступ к стартовым аргументам приложения. Это предпочтительный способ для конфигурации компонентов на основе параметров запуска.
Пример использования
--------------------
.. code-block:: python
import sys
from argenta.orchestrator import Orchestrator
from argenta.orchestrator.argparser import ArgParser
from argenta.app import App
from dishka import Provider, Scope, provide
# 1. Создаем парсер на основе реальных аргументов командной строки
# Например, если запустить: python main.py --config prod.json --debug
arg_parser = ArgParser(processed_args=sys.argv[1:])
# 2. Определяем сервис, который будет использовать эти аргументы
class Settings:
def __init__(self, config_path: str, is_debug: bool):
self.config_path = config_path
self.is_debug = is_debug
class SettingsProvider(Provider):
@provide(scope=Scope.APP)
def get_settings(self, parser: ArgParser) -> Settings:
# Запрашиваем ArgParser из DI и используем его для конфигурации
path = parser.get('--config', default='default.json')
debug_mode = bool(parser.get('--debug'))
return Settings(config_path=path, is_debug=debug_mode)
# 3. Создаем Orchestrator, передавая ему парсер и провайдер
app = App()
orchestrator = Orchestrator(
arg_parser=arg_parser,
custom_providers=[SettingsProvider()]
)
# 4. Запускаем приложение
if __name__ == "__main__":
orchestrator.start_polling(app)
nu
+63 -1
View File
@@ -1,7 +1,69 @@
.. _root_api_orchestrator_index: .. _root_api_orchestrator_index:
Orchestrator Orchestrator
**************** ====================
Объект ``Orchestrator`` в ``Argenta`` представляет собой высокоуровневый компонент, который стоит над ``App`` и отвечает за "оркестрацию" всего приложения. Его главная задача — инициализация и конфигурация сложных систем, таких как внедрение зависимостей (``di``), парсинг аргументов командной строки при запуске, и запуск основного цикла приложения.
В то время как ``App`` отвечает за интерактивную сессию (ввод команд, роутинг), ``Orchestrator`` подготавливает окружение, в котором ``App`` будет работать. Он является точкой входа для приложений.
-----
Инициализация
-------------
.. code-block:: rust
:linenos:
DEFAULT_ARGPARSER: ArgParser = ArgParser(processed_args=[])
.. code-block:: python
:linenos:
def __init__(self, arg_parser: ArgParser = DEFAULT_ARGPARSER,
custom_providers: list[Provider] = [],
auto_inject_handlers: bool = True) -> None
Создает и конфигурирует экземпляр ``Orchestrator``.
* ``arg_parser``: Экземпляр :class:`argenta.orchestrator.argparser.ArgParser`, который отвечает за парсинг аргументов, переданных скрипту при запуске из командной строки (не путать с командами, вводимыми в интерактивном режиме).
* ``custom_providers``: Список пользовательских провайдеров ``dishka.Provider``. Это основной механизм для добавления ваших собственных сервисов (например, подключений к базе данных, клиентов API) в контейнер внедрения зависимостей.
* ``auto_inject_handlers``: Если **True** (по умолчанию), ``dishka`` будет автоматически инспектировать сигнатуры обработчиков команд и внедрять в них требуемые зависимости из контейнера.
-----
Основные методы
--------------
.. py:method:: start_polling(self, app: App) -> None
Это главный метод, который запускает все приложение. Он выполняет следующие шаги:
1. **Настройка DI**: На основе системного провайдера (``SystemProvider``, который предоставляет ``ArgParser``) и списка ``custom_providers`` создается ``ioc`` - контейнер и настраивается внедрение зависимостей.
3. **Запуск основного цикла**: Запускает бесконечный цикл ожидания и обработки пользовательского ввода.
:param app: Экземпляр класса :class:`~argenta.app.models.App`, который будет запущен.
-----
Назначение и использование
--------------------------
``Orchestrator`` абстрагирует от вас всю сложность, связанную с настройкой внедрения зависимостей и парсингом стартовых аргументов. Типичный сценарий использования ``Argenta`` выглядит следующим образом:
1. Создать экземпляр ``App`` и настроить его (добавить роутеры).
2. Создать экземпляр ``Orchestrator``, передав в него необходимые DI-провайдеры и, возможно, парсер аргументов.
3. Вызвать ``orchestrator.start_polling(app)``, чтобы запустить приложение.
Такой подход позволяет сохранить код чистым и разделяет ответственность: ``App`` отвечает за логику интерактивной сессии, а ``Orchestrator`` — за подготовку и запуск окружения.
Пример использования
--------------------
.. literalinclude:: ../../../code_snippets/orchestrator_snippet.py
:language: python
.. toctree:: .. toctree::
:hidden: :hidden:
+7 -7
View File
@@ -13,6 +13,7 @@ Router
------------- -------------
.. code-block:: python .. code-block:: python
:linenos:
__init__(self, title: str | None = None, __init__(self, title: str | None = None,
disable_redirect_stdout: bool = False) -> None disable_redirect_stdout: bool = False) -> None
@@ -38,6 +39,7 @@ Router
**Пример использования:** **Пример использования:**
.. literalinclude:: ../../code_snippets/router_snippet.py .. literalinclude:: ../../code_snippets/router_snippet.py
:linenos:
:language: python :language: python
----- -----
@@ -62,7 +64,7 @@ Router
.. py:exception:: TriggerContainSpacesException .. py:exception:: TriggerContainSpacesException
Выбрасывается, если триггер команды, передаваемый в `Command`, содержит пробелы. Триггеры команд должны быть одним словом. Выбрасывается, если триггер команды, передаваемый в ``Command``, содержит пробелы. Триггеры команд должны быть одним словом.
**Неправильно:** ``Command("add user")`` **Неправильно:** ``Command("add user")``
**Правильно:** ``Command("add-user")`` **Правильно:** ``Command("add-user")``
@@ -74,16 +76,14 @@ Router
**Пример, вызывающий исключение:** **Пример, вызывающий исключение:**
.. code-block:: python .. code-block:: python
:linenos:
Command("send", flags=[ Command("send", flags=[
Flag("message", short_name="m"), Flag("recipient"),
Flag("recipient", short_name="m") # Ошибка: short_name 'm' повторяется Flag("recipient")
]) ])
.. py:exception:: RequiredArgumentNotPassedException .. py:exception:: RequiredArgumentNotPassedException
Это исключение выбрасывается на этапе выполнения, а не регистрации. Оно возникает, если обработчик команды ожидает обязательный аргумент, который не был предоставлен при вызове. Это исключение возникает, если какой-либо обработчик команды не ожидает обязательный аргумент, которым является объект ответа(``Response``).
.. note::
Это исключение, как правило, должно перехватываться и обрабатываться внутри `Orchestrator` для вывода пользователю сообщения об ошибке, а не приводить к падению всего приложения.
+4 -4
View File
@@ -18,19 +18,19 @@
.. note:: .. note::
``argenta.di.FromDishka`` это алиас к ``dishka.FromDishka``, они полностью взаимозаменяемы. ``argenta.di.FromDishka`` это алиас к ``dishka.FromDishka``, они полностью взаимозаменяемы.
.. literalinclude:: ../code_snippets/dependency_injection_example_sample.py .. literalinclude:: ../code_snippets/dependency_injection_snippet.py
:language: python :language: python
:linenos: :linenos:
``Argenta`` -> ``dishka`` зарезолвит тайпхинты и внедрит зависимость с возвращаемым типом ``Connection``, прежде чем использовать зависимость её нужно создать, для этого нужно создать соответствующий провайдер. ``Argenta`` -> ``dishka`` зарезолвит тайпхинты и внедрит зависимость с возвращаемым типом ``Connection``, прежде чем использовать зависимость её нужно создать, для этого нужно создать соответствующий провайдер.
.. literalinclude:: ../code_snippets/dependency_injection_example_sample2.py .. literalinclude:: ../code_snippets/dependency_injection_snippet2.py
:language: python :language: python
:linenos: :linenos:
После создания провайдера, его нужно зарегистрировать в оркестраторе. После создания провайдера, его нужно зарегистрировать в оркестраторе.
.. literalinclude:: ../code_snippets/dependency_injection_example_sample3.py .. literalinclude:: ../code_snippets/dependency_injection_snippet3.py
:language: python :language: python
:linenos: :linenos:
@@ -49,7 +49,7 @@
Краткий сэмпл кода, который получает объект ``ArgSpace`` и выводит в консоль аргумент с именем "type": Краткий сэмпл кода, который получает объект ``ArgSpace`` и выводит в консоль аргумент с именем "type":
.. literalinclude:: ../code_snippets/dependency_injection_example_sample4.py .. literalinclude:: ../code_snippets/dependency_injection_snippet4.py
:language: python :language: python
:linenos: :linenos:
+6 -6
View File
@@ -17,7 +17,7 @@
Краткий сэмпл кода, переопределяющего хэндлер ввода Краткий сэмпл кода, переопределяющего хэндлер ввода
пустой команды пустой команды
.. literalinclude:: ../code_snippets/error_handling_example_sample.py .. literalinclude:: ../code_snippets/error_handling_snippet.py
:language: python :language: python
:linenos: :linenos:
@@ -46,7 +46,7 @@
Сэмпл кода, переопределяющего хэндлер ввода команды с некорректным синтаксисом: Сэмпл кода, переопределяющего хэндлер ввода команды с некорректным синтаксисом:
.. literalinclude:: ../code_snippets/error_handling_example_sample2.py .. literalinclude:: ../code_snippets/error_handling_snippet2.py
:language: python :language: python
:linenos: :linenos:
@@ -73,7 +73,7 @@
Сэмпл кода, переопределяющего хэндлер ввода команды с повторяющимися флагами: Сэмпл кода, переопределяющего хэндлер ввода команды с повторяющимися флагами:
.. literalinclude:: ../code_snippets/error_handling_example_sample3.py .. literalinclude:: ../code_snippets/error_handling_snippet3.py
:language: python :language: python
:linenos: :linenos:
@@ -96,7 +96,7 @@
Сэмпл кода, переопределяющего хэндлер ввода пустой команды: Сэмпл кода, переопределяющего хэндлер ввода пустой команды:
.. literalinclude:: ../code_snippets/error_handling_example_sample4.py .. literalinclude:: ../code_snippets/error_handling_snippet4.py
:language: python :language: python
:linenos: :linenos:
@@ -120,7 +120,7 @@
Сэмпл кода, переопределяющего хэндлер ввода неизвестной команды: Сэмпл кода, переопределяющего хэндлер ввода неизвестной команды:
.. literalinclude:: ../code_snippets/error_handling_example_sample5.py .. literalinclude:: ../code_snippets/error_handling_snippet5.py
:language: python :language: python
:linenos: :linenos:
@@ -143,6 +143,6 @@
Сэмпл кода, переопределяющего хэндлер ввода команды выхода: Сэмпл кода, переопределяющего хэндлер ввода команды выхода:
.. literalinclude:: ../code_snippets/error_handling_example_sample6.py .. literalinclude:: ../code_snippets/error_handling_snippet6.py
:language: python :language: python
:linenos: :linenos:
+1 -7
View File
@@ -4,14 +4,8 @@ from argenta import App, Orchestrator
from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter from argenta.app import PredefinedMessages, DynamicDividingLine, AutoCompleter
from argenta.orchestrator import ArgParser from argenta.orchestrator import ArgParser
from argenta.orchestrator.argparser import BooleanArgument, ValueArgument from argenta.orchestrator.argparser import BooleanArgument, ValueArgument
from dishka import Provider, provide, Scope # type: ignore
class temProvider(Provider):
@provide(scope=Scope.APP)
def get_apace(self) -> int:
return 1234
arg_parser: ArgParser = ArgParser( arg_parser: ArgParser = ArgParser(
processed_args=[ processed_args=[
BooleanArgument(name="repeat", is_deprecated=True), BooleanArgument(name="repeat", is_deprecated=True),
@@ -22,7 +16,7 @@ app: App = App(
dividing_line=DynamicDividingLine(), dividing_line=DynamicDividingLine(),
autocompleter=AutoCompleter(), autocompleter=AutoCompleter(),
) )
orchestrator: Orchestrator = Orchestrator(arg_parser, custom_providers=[temProvider()]) orchestrator: Orchestrator = Orchestrator(arg_parser)
def main(): def main():
+6 -11
View File
@@ -1,7 +1,9 @@
from argenta.command import Command, PredefinedFlags, Flags, Flag, PossibleValues from argenta.command import Flag, PossibleValues
from argenta.orchestrator.argparser import ArgSpace
from argenta.response import Response from argenta.response import Response
from argenta import Router from argenta import Router
from argenta.router.defaults import system_router from argenta.router.defaults import system_router
from dishka import FromDishka
work_router: Router = Router(title="Work points:", disable_redirect_stdout=True) work_router: Router = Router(title="Work points:", disable_redirect_stdout=True)
@@ -9,16 +11,9 @@ work_router: Router = Router(title="Work points:", disable_redirect_stdout=True)
flag = Flag("csdv", possible_values=PossibleValues.NEITHER) flag = Flag("csdv", possible_values=PossibleValues.NEITHER)
@work_router.command( @work_router.command("get")
Command( def command_help(response: Response, argspace: FromDishka[ArgSpace]):
"get", print(argspace.all_arguments)
description="Get Help",
aliases=["help", "Get_help"],
flags=Flags([PredefinedFlags.PORT, PredefinedFlags.HOST]),
)
)
def command_help(response: Response):
response.update_data({"data": [_ for _ in range(9999999)]})
@system_router.command("run") @system_router.command("run")
+1 -1
View File
@@ -15,4 +15,4 @@ class SystemProvider(Provider):
@provide(scope=Scope.APP) @provide(scope=Scope.APP)
def get_argspace(self) -> ArgSpace: def get_argspace(self) -> ArgSpace:
return self._arg_parser.parse_args() return self._arg_parser.parsed_argspace
+9 -5
View File
@@ -58,8 +58,16 @@ class ArgParser:
self.epilog: str = epilog self.epilog: str = epilog
self.processed_args: list[ValueArgument | BooleanArgument] = processed_args self.processed_args: list[ValueArgument | BooleanArgument] = processed_args
self._core: ArgumentParser = ArgumentParser(prog=name, description=description, epilog=epilog) 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)
def _register_args(self, processed_args: list[ValueArgument | BooleanArgument]) -> None:
for arg in processed_args: for arg in processed_args:
if isinstance(arg, BooleanArgument): if isinstance(arg, BooleanArgument):
_ = self._core.add_argument(arg.string_entity, _ = self._core.add_argument(arg.string_entity,
@@ -74,7 +82,3 @@ class ArgParser:
choices=arg.possible_values, choices=arg.possible_values,
required=arg.is_required, required=arg.is_required,
deprecated=arg.is_deprecated) deprecated=arg.is_deprecated)
def parse_args(self) -> ArgSpace:
return ArgSpace.from_namespace(namespace=self._core.parse_args(),
processed_args=self.processed_args)
+2
View File
@@ -23,6 +23,8 @@ class Orchestrator:
self._custom_providers: list[Provider] = custom_providers self._custom_providers: list[Provider] = custom_providers
self._auto_inject_handlers: bool = auto_inject_handlers self._auto_inject_handlers: bool = auto_inject_handlers
self._arg_parser._parse_args()
def start_polling(self, app: App) -> None: def start_polling(self, app: App) -> None:
""" """
Public. Starting the user input processing cycle Public. Starting the user input processing cycle