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

Simple App Example

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.

  1. Installation

pip install argenta
  1. 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
  1. 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()
  1. 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})")
  1. 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)
  1. Result

Now you can run main.py and interact with your new CLI application.

Task Manager Example