Быстрый старт¶
В этом руководстве мы рассмотрим два примера создания 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
Результат
Промежуточный пример: Калькулятор с флагами¶
Прежде чем перейти к сложному примеру с 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-приложение «Менеджер задач», которое продемонстрирует работу с внедрением зависимостей.
Установка
pip install argenta
Определение моделей данных и репозитория
Сначала определим модели данных для задачи и репозиторий для их хранения.
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
Создание провайдера для 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()
Создание обработчиков команд
Теперь создадим обработчики для команд 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})")
Сборка и запуск приложения
Наконец, соберем все вместе: создадим экземпляр 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)
Результат
Теперь вы можете запустить main.py и взаимодействовать с вашим новым CLI-приложением.