Быстрый старт

В этом руководстве мы рассмотрим два примера создания CLI-приложения с помощью Argenta:

  • Простой пример: минимальное приложение для быстрого знакомства с основными компонентами.

  • Пример средней сложности: приложение «Калькулятор» с использованием и настройкой флагов.

  • Более сложный пример: полнофункциональное приложение «Менеджер задач» с внедрением зависимостей и бизнес-логикой.

Простой пример

Установка

pip install argenta

Этот пример демонстрирует абсолютный минимум, необходимый для создания и запуска приложения. Вы можете скопировать этот код, запустить его и сразу увидеть результат.

 1from argenta import App, Command, Orchestrator, Router, Response
 2from argenta.command import Flag
 3
 4# 1. Create app and orchestrator instances
 5app = App(
 6    prompt=">> ",
 7    initial_message="Simple App",
 8    farewell_message="Goodbye!",
 9    repeat_command_groups_printing=False,
10)
11orchestrator = Orchestrator()
12
13# 2. Create router for grouping commands
14main_router = Router(title="Main commands")
15
16
17# 3. Define command and its handler
18@main_router.command(Command("hello", description="Prints greeting message", flags=Flag("name")))
19def hello_handler(response: Response):
20    """This handler will be called for 'hello' command."""
21    name = response.input_flags.get_flag_by_name("name")
22    if name:
23        print(f"Hello, {name.input_value}!")
24    else:
25        print("Hello, world!")
26
27
28# 4. Include router to application
29app.include_router(main_router)
30
31# 5. Start application
32if __name__ == "__main__":
33    orchestrator.start_polling(app)

Запуск

Сохраните код в файл (например, main.py) и запустите:

python main.py

Результат

Simple App Example

Промежуточный пример: Калькулятор с флагами

Прежде чем перейти к сложному примеру с DI, рассмотрим промежуточный вариант — калькулятор, который использует флаги для управления поведением.

 1import operator
 2import re
 3
 4from argenta import App, Orchestrator, Response, Router
 5from argenta.app import DynamicDividingLine
 6from argenta.command import Command, Flag, Flags
 7from argenta.response.status import ResponseStatus
 8
 9router = Router("Calculator")
10
11operations = {"mul": operator.mul, "sub": operator.sub, "add": operator.add}
12
13
14@router.command(
15    Command(
16        "calc",
17        description="Calculator with two numbers",
18        flags=Flags(
19            [
20                Flag("a", possible_values=re.compile(r"^\d{,5}$")),  # First number
21                Flag("b", possible_values=re.compile(r"^\d{,5}$")),  # Second number
22                Flag(
23                    "operation", possible_values=["add", "sub", "mul"]
24                ),  # Operation: add, sub, mul
25            ]
26        ),
27    )
28)
29def calc_handler(response: Response):
30    # Get flag values
31    a_flag = response.input_flags.get_flag_by_name("a")
32    b_flag = response.input_flags.get_flag_by_name("b")
33    op_flag = response.input_flags.get_flag_by_name("op")
34
35    # Check that all flags are provided
36    if response.status != ResponseStatus.ALL_FLAGS_VALID or not all([a_flag, b_flag, op_flag]):
37        print("Error: must specify --a, --b and --op")
38        return
39
40    a = float(a_flag.input_value)
41    b = float(b_flag.input_value)
42    operation = op_flag.input_value
43
44    try:
45        result = operations[operation](a, b)
46    except ZeroDivisionError:
47        print("Can't divide by zero")
48    else:
49        print(f"Result: {result}")
50
51
52app = App(
53    initial_message="Calculator",
54    repeat_command_groups_printing=False,
55    prompt=">> ",
56    dividing_line=DynamicDividingLine("~"),
57)
58orchestrator = Orchestrator()
59
60
61def main():
62    app.include_router(router)
63    orchestrator.start_polling(app)
64
65
66if __name__ == "__main__":
67    main()

Запуск:

Сохраните код в файл calculator.py и запустите:

python calculator.py

Использование:

calc --a 10 --b 5 --operation add
calc --a 10 --b 5 --operation mul

Этот пример показывает, как работать с флагами без использования DI. Теперь перейдём к более сложному примеру.


Сложный пример: Менеджер задач с DI

В этом руководстве мы создадим полнофункциональное CLI-приложение «Менеджер задач», которое продемонстрирует работу с внедрением зависимостей.

  1. Установка

pip install argenta
  1. Определение моделей данных и репозитория

Сначала определим модели данных для задачи и репозиторий для их хранения.

 1from dataclasses import dataclass
 2from typing import Literal
 3
 4Priority = Literal["low", "medium", "high"]
 5
 6
 7@dataclass
 8class Task:
 9    description: str
10    priority: Priority = "medium"
11
12
13class TaskRepository:
14    def __init__(self):
15        self._tasks: list[Task] = []
16
17    def add_task(self, task: Task):
18        self._tasks.append(task)
19
20    def get_all_tasks(self) -> list[Task]:
21        return self._tasks
  1. Создание провайдера для DI

Чтобы Argenta могла внедрять TaskRepository в наши обработчики, мы создадим провайдер для dishka.

1from dishka import Provider, Scope, provide
2
3from .repository import TaskRepository
4
5
6class TaskProvider(Provider):
7    @provide(scope=Scope.APP)
8    def get_repository(self) -> TaskRepository:
9        return TaskRepository()
  1. Создание обработчиков команд

Теперь создадим обработчики для команд add-task и list-tasks. Обратите внимание, как мы используем флаги и внедряем TaskRepository.

 1from typing import cast
 2
 3from argenta import Command, Response, Router
 4from argenta.command.flag import Flag, ValidationStatus
 5from argenta.command import Flags
 6from argenta.di import FromDishka
 7
 8from .repository import Priority, Task, TaskRepository
 9
10router = Router(title="Task Manager")
11
12
13@router.command(
14    Command(
15        "add-task",
16        description="Add a new task",
17        flags=Flags(
18            [
19                Flag("description"),
20                Flag("priority", possible_values=["low", "medium", "high"]),
21            ]
22        ),
23    )
24)
25def add_task(response: Response, repo: FromDishka[TaskRepository]):
26    description_flag = response.input_flags.get_flag_by_name("description")
27
28    if not description_flag or not description_flag.status == ValidationStatus.VALID:
29        print("Error: --description flag is required.")
30        return
31
32    task_description = description_flag.input_value or ""
33
34    priority_flag = response.input_flags.get_flag_by_name("priority")
35
36    if priority_flag and priority_flag.status == ValidationStatus.VALID:
37        priority_value = priority_flag.input_value
38    else:
39        priority_value = "medium"
40
41    priority = cast(Priority, priority_value)
42
43    task = Task(description=task_description, priority=priority)
44    repo.add_task(task)
45
46    print(f"Added task: '{task.description}' with priority '{task.priority}'")
47
48
49@router.command(Command("list-tasks", description="List all tasks"))
50def list_tasks(response: Response, repo: FromDishka[TaskRepository]):
51    tasks = repo.get_all_tasks()
52
53    if not tasks:
54        print("No tasks found.")
55        return
56
57    print("Tasks:")
58    for i, task in enumerate(tasks, 1):
59        print(f"  {i}. {task.description} (Priority: {task.priority})")
  1. Сборка и запуск приложения

Наконец, соберем все вместе: создадим экземпляр App, подключим роутер и провайдер, а затем запустим приложение.

 1from argenta import App, Orchestrator
 2
 3from .handlers import router
 4from .provider import TaskProvider
 5
 6# 1. Create app and orchestrator instances
 7app = App(
 8    initial_message="Task Manager",
 9    prompt="Enter a command: ",
10)
11orchestrator = Orchestrator(custom_providers=[TaskProvider()])
12
13# 2. Include router with our commands
14app.include_router(router)
15
16# 3. Start polling via orchestrator
17if __name__ == "__main__":
18    orchestrator.start_polling(app)
  1. Результат

Теперь вы можете запустить main.py и взаимодействовать с вашим новым CLI-приложением.

Task Manager Example