Quick Start¶
In this guide, we will look at two examples of creating a CLI application with Argenta:
Simple example: a minimal application for quick introduction to the main components.
Medium-complexity example: the Calculator app using and setting flags.
More complex example: a full-featured “Task Manager” application with dependency injection and business logic.
Simple Example¶
Installation
pip install argenta
This example demonstrates the absolute minimum required to create and run an application. You can copy this code, run it, and immediately see the result.
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)
Running
Save the code to a file (for example, main.py) and run:
python main.py
Result
Intermediate Example: Calculator with Flags¶
Before moving to a complex example with DI, let’s consider an intermediate option — a calculator that uses flags to control behavior.
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()
Running:
Save the code to a file calculator.py and run:
python calculator.py
Usage:
calc --a 10 --b 5 --operation add
calc --a 10 --b 5 --operation mul
This example shows how to work with flags without using DI. Now let’s move on to a more complex example.
Complex Example: Task Manager with DI¶
In this guide, we will create a full-featured CLI application “Task Manager” that will demonstrate working with dependency injection.
Installation
pip install argenta
Defining Data Models and Repository
First, let’s define data models for tasks and a repository to store them.
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
Creating a Provider for DI
To allow Argenta to inject TaskRepository into our handlers, we will create a provider for 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()
Creating Command Handlers
Now let’s create handlers for the add-task and list-tasks commands. Notice how we use flags and inject 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})")
Building and Running the Application
Finally, let’s put it all together: create an App instance, connect the router and provider, and then run the application.
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)
Result
Now you can run main.py and interact with your new CLI application.