mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 10:05:28 +03:00
docs
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
|
||||
def empty_command_handler():
|
||||
print("Empty command handler called")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_empty_command_handler(empty_command_handler)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
|
||||
def incorrect_input_syntax_handler(raw_command: str):
|
||||
print(f"Incorrect input syntax for command: {raw_command}")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_incorrect_input_syntax_handler(incorrect_input_syntax_handler)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
|
||||
def repeated_input_flags_handler(raw_command: str):
|
||||
print(f"Repeated input flags: {raw_command}")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_repeated_input_flags_handler(repeated_input_flags_handler)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
|
||||
def empty_command_handler():
|
||||
print("Empty input command")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_empty_command_handler(empty_command_handler)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from argenta import App
|
||||
from argenta.command import InputCommand
|
||||
|
||||
|
||||
def unknown_command_handler(command: InputCommand):
|
||||
print(f"Unknown input command with trigger: {command.trigger}")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_unknown_command_handler(unknown_command_handler)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from argenta import App, Response
|
||||
|
||||
|
||||
def exit_command_handler(response: Response):
|
||||
print("Exit command handler")
|
||||
|
||||
|
||||
app: App = App()
|
||||
app.set_exit_command_handler(exit_command_handler)
|
||||
|
||||
@@ -12,12 +12,13 @@ orchestrator = Orchestrator()
|
||||
# 2. Создание роутера для группировки команд
|
||||
main_router = Router(title="Основные команды")
|
||||
|
||||
|
||||
# 3. Определение команды и её обработчика
|
||||
@main_router.command(Command(
|
||||
"hello",
|
||||
description="Печатает приветственное сообщение",
|
||||
flags=Flag("name")
|
||||
))
|
||||
@main_router.command(
|
||||
Command(
|
||||
"hello", description="Печатает приветственное сообщение", flags=Flag("name")
|
||||
)
|
||||
)
|
||||
def hello_handler(response: Response):
|
||||
"""Этот обработчик будет вызван для команды 'hello'."""
|
||||
name = response.input_flags.get_flag_by_name("name")
|
||||
@@ -26,6 +27,7 @@ def hello_handler(response: Response):
|
||||
else:
|
||||
print("Привет, мир!")
|
||||
|
||||
|
||||
# 4. Подключение роутера к приложению
|
||||
app.include_router(main_router)
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from typing import cast
|
||||
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
from argenta.command.flag.models import ValidationStatus
|
||||
from argenta.command.flag import ValidationStatus, Flag, Flags
|
||||
from argenta.di import FromDishka
|
||||
|
||||
from .repository import Priority, Task, TaskRepository
|
||||
@@ -24,14 +23,12 @@ router = Router(title="Task Manager")
|
||||
)
|
||||
def add_task(response: Response, repo: FromDishka[TaskRepository]):
|
||||
description_flag = response.input_flags.get_flag_by_name("description")
|
||||
|
||||
if not description_flag or not description_flag.input_value:
|
||||
if not description_flag or not description_flag.status == ValidationStatus.VALID:
|
||||
print("Error: --description flag is required.")
|
||||
return
|
||||
task_description = description_flag.input_value
|
||||
task_description = description_flag.input_value or ""
|
||||
|
||||
priority_flag = response.input_flags.get_flag_by_name("priority")
|
||||
|
||||
if priority_flag and priority_flag.status == ValidationStatus.VALID:
|
||||
priority_value = priority_flag.input_value
|
||||
else:
|
||||
|
||||
@@ -3,16 +3,16 @@ from argenta import App, Orchestrator
|
||||
from .handlers import router
|
||||
from .provider import TaskProvider
|
||||
|
||||
# 1. Создаем экземпляр приложения
|
||||
# 1. Создаем экземпляр приложения и оркестратора
|
||||
app = App(
|
||||
initial_message="Task Manager",
|
||||
prompt="Enter a command: ",
|
||||
)
|
||||
orchestrator = Orchestrator(custom_providers=[TaskProvider()])
|
||||
|
||||
# 2. Подключаем роутер с нашими командами
|
||||
app.include_router(router)
|
||||
|
||||
# 3. Создаем и запускаем оркестратор
|
||||
# 3. Запускаем поллинг через оркестратор
|
||||
if __name__ == "__main__":
|
||||
orchestrator = Orchestrator(custom_providers=[TaskProvider()])
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
@@ -3,19 +3,23 @@ from argenta.command import Flag
|
||||
from argenta.di import FromDishka
|
||||
|
||||
# 1. Создаём роутер
|
||||
router = Router(title='Authentication')
|
||||
router = Router(title="Authentication")
|
||||
|
||||
|
||||
# 2. Определяем сервис и обработчики
|
||||
def authenticate_user(username: str) -> str:
|
||||
"""Возвращает фиктивный токен для пользователя."""
|
||||
return f"token_for_{username}"
|
||||
|
||||
@router.command(Command('login', flags=Flag('username')))
|
||||
|
||||
@router.command(Command("login", flags=Flag("username")))
|
||||
def login_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
"""Обработчик для команды 'login'. Сохраняет токен в хранилище."""
|
||||
username_flag = response.input_flags.get_flag_by_name('username')
|
||||
username_flag = response.input_flags.get_flag_by_name("username")
|
||||
if not username_flag or not username_flag.input_value:
|
||||
print("[red]Ошибка:[/red] необходимо указать имя пользователя с помощью флага --username.")
|
||||
print(
|
||||
"[red]Ошибка:[/red] необходимо указать имя пользователя с помощью флага --username."
|
||||
)
|
||||
return
|
||||
|
||||
username = username_flag.input_value
|
||||
@@ -25,19 +29,23 @@ def login_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
data_bridge.update({"auth_token": token})
|
||||
print(f"[green]Успешный вход![/green] Пользователь '{username}' аутентифицирован.")
|
||||
|
||||
@router.command('get-profile')
|
||||
|
||||
@router.command("get-profile")
|
||||
def get_profile_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
"""Обработчик для команды 'get-profile'. Использует токен из хранилища."""
|
||||
session_data = data_bridge.get_all()
|
||||
token = session_data.get("auth_token")
|
||||
|
||||
if not token:
|
||||
print("[red]Ошибка:[/red] вы не аутентифицированы. Сначала выполните команду 'login'.")
|
||||
print(
|
||||
"[red]Ошибка:[/red] вы не аутентифицированы. Сначала выполните команду 'login'."
|
||||
)
|
||||
return
|
||||
|
||||
print(f"Загрузка профиля с использованием токена: [yellow]{token}[/yellow]")
|
||||
|
||||
@router.command('logout')
|
||||
|
||||
@router.command("logout")
|
||||
def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
"""Обработчик для команды 'logout'. Очищает токен."""
|
||||
try:
|
||||
@@ -45,4 +53,3 @@ def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
print("[green]Выход выполнен.[/green] Данные сессии очищены.")
|
||||
except KeyError:
|
||||
print("Вы и так не были аутентифицированы.")
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class TestAppIntegration(unittest.TestCase):
|
||||
@self.router.command(Command("HELP", description="Show help"))
|
||||
def help_cmd(response: Response):
|
||||
print("Available commands: HELP")
|
||||
|
||||
_ = help_cmd # appease linter: function is registered via decorator
|
||||
|
||||
self.app.include_router(self.router)
|
||||
@@ -22,5 +23,3 @@ class TestAppIntegration(unittest.TestCase):
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
self.router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
||||
self.assertIn("Available commands:", stdout.getvalue())
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class Service:
|
||||
def hello(self) -> str:
|
||||
return "world"
|
||||
|
||||
|
||||
router = Router(title="DI")
|
||||
|
||||
|
||||
@@ -56,5 +57,3 @@ class TestDIHandler(unittest.TestCase):
|
||||
class _FakeApp:
|
||||
# Minimal stub for setup_dishka; app object is not used in unit tests
|
||||
registered_routers = []
|
||||
|
||||
|
||||
|
||||
@@ -20,5 +20,3 @@ class TestSimpleHandler(unittest.TestCase):
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
||||
self.assertIn("PONG", stdout.getvalue())
|
||||
|
||||
|
||||
|
||||
+8
-3
@@ -6,13 +6,16 @@
|
||||
Argenta
|
||||
=======
|
||||
|
||||
Что это и зачем?
|
||||
----------------
|
||||
|
||||
**Библиотека для построения модульных CLI-приложений с простым и приятным API.**
|
||||
|
||||
Если у вас есть функциональность, которую вы хотите предоставить в виде CLI-приложения, Argenta поможет вам в этом.
|
||||
Основная цель библиотеки — дать разработчикам возможность сосредоточиться на реализации своих идей, предоставляя для этого удобные абстракции.
|
||||
|
||||
.. image:: https://github.com/koloideal/Argenta/blob/main/imgs/mock_app_preview4.png?raw=True
|
||||
:alt: Пример приложения
|
||||
:alt: App example
|
||||
|
||||
Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что при запуске пользователь входит в интерактивную сессию, где ему доступна вся реализованная вами функциональность.
|
||||
|
||||
@@ -22,9 +25,11 @@ Argenta предназначена для создания приложений,
|
||||
|
||||
* **Интерактивные сессии**. В отличие от традиционных CLI-инструментов, Argenta создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение.
|
||||
* **Декларативный синтаксис**. Команды и их обработчики объявляются с помощью простых декораторов, что делает код чистым и интуитивно понятным.
|
||||
* **Встроенное внедрение зависимостей (DI)**. Благодаря интеграции с `dishka <https://dishka.readthedocs.io/en/stable/>`_, вы можете легко внедрять сервисы (например, подключения к БД) прямо в обработчики команд, что упрощает их тестирование и переиспользование.
|
||||
* **Нативный DI**. Благодаря интеграции с `dishka <https://dishka.readthedocs.io/en/stable/>`_, вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование и переиспользование.
|
||||
* **Автоматическая валидация и парсинг**. Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов.
|
||||
* **Гибкая настройка**. Вы можете легко кастомизировать системные сообщения, форматирование вывода и даже перенаправлять стандартный вывод (stdout) в свои обработчики.
|
||||
* **Гибкая настройка**. Вы можете легко кастомизировать системные сообщения, форматирование вывода и т.д.
|
||||
|
||||
-----
|
||||
|
||||
Архитектура и жизненный цикл
|
||||
-----------------------------
|
||||
|
||||
@@ -5,12 +5,18 @@
|
||||
|
||||
В этом руководстве мы рассмотрим два примера создания CLI-приложения с помощью Argenta:
|
||||
|
||||
* **Простой пример**: Быстрое знакомство с основными компонентами, такими как `App`, `Command` и `Router`.
|
||||
* **Простой пример**: Минимальное приложение, быстрое знакомство с основными компонентами.
|
||||
* **Более сложный пример**: Полнофункциональное приложение «Менеджер задач» с внедрением зависимостей и бизнес-логикой.
|
||||
|
||||
Простой пример
|
||||
---------------
|
||||
|
||||
**Установка**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
pip install argenta
|
||||
|
||||
Этот пример демонстрирует абсолютный минимум, необходимый для создания и запуска приложения. Вы можете скопировать этот код, запустить его и сразу увидеть результат.
|
||||
|
||||
.. literalinclude:: ../code_snippets/quickstart/simple_app.py
|
||||
@@ -22,6 +28,8 @@
|
||||
.. image:: https://i.ibb.co/JwK9Vv4j/2025-11-03-135118.png
|
||||
:alt: Simple App Example
|
||||
|
||||
-----
|
||||
|
||||
Более сложный пример: Менеджер задач
|
||||
--------------------------------------
|
||||
|
||||
@@ -43,7 +51,7 @@
|
||||
|
||||
3. **Создание провайдера для DI**
|
||||
|
||||
Чтобы Argenta могла внедрять `TaskRepository` в наши обработчики, мы создадим провайдер для `dishka`.
|
||||
Чтобы Argenta могла внедрять ``TaskRepository`` в наши обработчики, мы создадим провайдер для ``dishka``.
|
||||
|
||||
.. literalinclude:: ../code_snippets/quickstart/task_manager/provider.py
|
||||
:language: python
|
||||
@@ -51,7 +59,7 @@
|
||||
|
||||
4. **Создание обработчиков команд**
|
||||
|
||||
Теперь создадим обработчики для команд `add-task` и `list-tasks`. Обратите внимание, как мы используем флаги и внедряем `TaskRepository`.
|
||||
Теперь создадим обработчики для команд ``add-task`` и ``list-tasks``. Обратите внимание, как мы используем флаги и внедряем ``TaskRepository``.
|
||||
|
||||
.. literalinclude:: ../code_snippets/quickstart/task_manager/handlers.py
|
||||
:language: python
|
||||
@@ -59,7 +67,7 @@
|
||||
|
||||
5. **Сборка и запуск приложения**
|
||||
|
||||
Наконец, соберем все вместе: создадим экземпляр `App`, подключим роутер и провайдер, а затем запустим приложение.
|
||||
Наконец, соберем все вместе: создадим экземпляр ``App``, подключим роутер и провайдер, а затем запустим приложение.
|
||||
|
||||
.. literalinclude:: ../code_snippets/quickstart/task_manager/main.py
|
||||
:language: python
|
||||
@@ -67,7 +75,7 @@
|
||||
|
||||
6. **Результат**
|
||||
|
||||
Теперь вы можете запустить `main.py` и взаимодействовать с вашим новым CLI-приложением.
|
||||
Теперь вы можете запустить ``main.py`` и взаимодействовать с вашим новым CLI-приложением.
|
||||
|
||||
.. image:: https://i.ibb.co/bgsCLZhP/image.png
|
||||
:alt: Task Manager Example
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import polib
|
||||
import deepl
|
||||
import getopt
|
||||
import sys
|
||||
import re
|
||||
|
||||
DEEPL_API_TOKEN = 'ADD YOUR API KEY HERE!'
|
||||
|
||||
global argv
|
||||
global opts
|
||||
global args
|
||||
|
||||
argv = sys.argv[1:]
|
||||
opts, args = getopt.getopt(argv, "f:l:")
|
||||
|
||||
def translate(text, lang):
|
||||
# Define a dictionary to hold the mappings of tokens to placeholders
|
||||
placeholders = {}
|
||||
|
||||
# Use a regular expression to find all the tokens
|
||||
tokens = re.findall(r'%\((.*?)\)s', text)
|
||||
|
||||
# Replace each token with a unique placeholder
|
||||
for i, token in enumerate(tokens):
|
||||
placeholder = f'__PLACEHOLDER_{i}__'
|
||||
placeholders[placeholder] = f'%({token})s'
|
||||
text = text.replace(f'%({token})s', placeholder)
|
||||
|
||||
# Perform the translation
|
||||
translator = deepl.Translator(DEEPL_API_TOKEN)
|
||||
translated_text = str(translator.translate_text(text, target_lang=lang))
|
||||
|
||||
# Replace the placeholders back with the original tokens
|
||||
for placeholder, token in placeholders.items():
|
||||
translated_text = translated_text.replace(placeholder, token)
|
||||
|
||||
return translated_text
|
||||
|
||||
def get_filename():
|
||||
# read arguments from command line
|
||||
for opt, arg in opts:
|
||||
if opt in ['-f']:
|
||||
filename = arg
|
||||
if not filename:
|
||||
print('Please enter the filename of the PO file e.g. /directory/django.po:')
|
||||
filename = input()
|
||||
return filename
|
||||
|
||||
def get_target_language():
|
||||
# read arguments from command line
|
||||
for opt, arg in opts:
|
||||
if opt in ['-l']:
|
||||
lang = arg
|
||||
if not lang:
|
||||
print('Please enter two letter ISO language code e.g. DE:')
|
||||
lang = input()
|
||||
return lang
|
||||
|
||||
def process_file(filename, lang):
|
||||
po = polib.pofile(filename)
|
||||
for entry in po.untranslated_entries():
|
||||
if not entry.msgstr:
|
||||
print(entry.msgid)
|
||||
print('translating...')
|
||||
entry.msgstr = translate(entry.msgid, lang)
|
||||
print(entry.msgstr)
|
||||
print('\n')
|
||||
po.save(filename)
|
||||
|
||||
if __name__ == '__main__':
|
||||
process_file(get_filename(), get_target_language())
|
||||
Reference in New Issue
Block a user