docs: add CLI documentation section with translations and code snippets; refactor mock/ to examples/, improve CLI module (bug fixes, DX, error handling)

This commit is contained in:
2026-07-25 19:22:04 +03:00
parent 311b312ab4
commit 6b9bea155f
21 changed files with 824 additions and 219 deletions
+22
View File
@@ -0,0 +1,22 @@
from argenta import App, Orchestrator
from argenta.command import Router, Command, Response
router = Router(title="Example")
@router.command(Command("hello", description="Say hello"))
def hello_handler(response: Response):
print("Hello, world!")
def create_app() -> App:
app = App()
app.include_router(router)
return app
def main() -> None:
orchestrator = Orchestrator()
orchestrator.run_repl(create_app())
if __name__ == "__main__":
main()
+16
View File
@@ -0,0 +1,16 @@
from argenta import App, Orchestrator
from argenta.command import Router, Command, Response
router = Router(title="Example")
@router.command(Command("hello", description="Say hello"))
def hello_handler(response: Response):
print("Hello, world!")
app = App()
app.include_router(router)
orchestrator = Orchestrator()
if __name__ == "__main__":
orchestrator.run_repl(app)
@@ -0,0 +1,4 @@
my_project/
├── main.py
├── handlers.py
└── .gitignore
+11
View File
@@ -0,0 +1,11 @@
my_project/
├── src/
│ └── my_project/
│ └── application/
│ ├── __init__.py
│ ├── __main__.py
│ ├── routers.py
│ └── handlers/
│ ├── __init__.py
│ └── hello_world_handler.py
└── .gitignore
+1
View File
@@ -56,6 +56,7 @@ Argenta предназначена для создания приложений,
root/redirect_stdout root/redirect_stdout
root/dependency_injection root/dependency_injection
root/cli
root/testing root/testing
.. toctree:: .. toctree::
+265
View File
@@ -0,0 +1,265 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2025, kolo
# This file is distributed under the same license as the Argenta package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: Argenta \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-25 18:00+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n"
#: ../../root/cli.rst:3
msgid "Командная строка"
msgstr "Command Line Interface"
#: ../../root/cli.rst:5
msgid ""
"Помимо библиотеки, ``Argenta`` поставляется с собственным CLI-инструментом, "
"который помогает создавать проекты, запускать приложения, инспектировать "
"маршруты и собирать бинарники."
msgstr ""
"In addition to the library, ``Argenta`` ships with its own CLI tool that "
"helps scaffold projects, run applications, inspect routes, and build binaries."
#: ../../root/cli.rst:8
msgid "Установка"
msgstr "Installation"
#: ../../root/cli.rst:10
msgid "CLI доступен как опциональная зависимость:"
msgstr "The CLI is available as an optional dependency:"
#: ../../root/cli.rst:16
msgid ""
"После установки команда ``argenta`` доступна в терминале:"
msgstr ""
"After installation, the ``argenta`` command is available in the terminal:"
#: ../../root/cli.rst:22
msgid ""
"Если вы устанавливали ``argenta`` без extras, CLI-инструмент не будет "
"доступен. Установите с ``[cli]`` для доступа к команде ``argenta``."
msgstr ""
"If you installed ``argenta`` without extras, the CLI tool will not be "
"available. Install with ``[cli]`` to access the ``argenta`` command."
#: ../../root/cli.rst:25
msgid "Флаг ``--version``"
msgstr "The ``--version`` flag"
#: ../../root/cli.rst:27
msgid "Показать установленную версию ``Argenta``:"
msgstr "Show the installed ``Argenta`` version:"
#: ../../root/cli.rst:35
msgid "Создание проектов"
msgstr "Scaffolding Projects"
#: ../../root/cli.rst:38
msgid "Команда ``new``"
msgstr "The ``new`` command"
#: ../../root/cli.rst:40
msgid "Создаёт новую директорию проекта с boilerplate-кодом."
msgstr "Creates a new project directory with boilerplate code."
#: ../../root/cli.rst:44
msgid "``project_name`` — имя директории проекта (обязательный аргумент)."
msgstr "``project_name`` — project directory name (required argument)."
#: ../../root/cli.rst:45
msgid "``--with-arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``."
msgstr "``--with-arch`` — project architecture: ``flat`` (default) or ``src``."
#: ../../root/cli.rst:53
msgid "При архитектуре ``flat`` создаётся следующая структура:"
msgstr "With the ``flat`` architecture, the following structure is created:"
#: ../../root/cli.rst:59
msgid "При архитектуре ``src``:"
msgstr "With the ``src`` architecture:"
#: ../../root/cli.rst:67
msgid "Команда ``init``"
msgstr "The ``init`` command"
#: ../../root/cli.rst:69
msgid ""
"Инициализирует boilerplate в текущей директории. Удобно, когда проект уже "
"существует и нужно добавить структуру Argenta."
msgstr ""
"Initializes boilerplate in the current directory. Useful when the project "
"already exists and you need to add Argenta structure."
#: ../../root/cli.rst:75
msgid ""
"Команда ``init`` не перезаписывает существующие файлы — они будут пропущены."
msgstr ""
"The ``init`` command does not overwrite existing files — they will be skipped."
#: ../../root/cli.rst:80
msgid "Запуск приложений"
msgstr "Running Applications"
#: ../../root/cli.rst:83
msgid "Команда ``run``"
msgstr "The ``run`` command"
#: ../../root/cli.rst:85
msgid ""
"Запускает оркестратор ``Argenta`` из callable-ентрипойнта. Это альтернатива "
"прямому вызову ``python main.py``, но с автоматической настройкой окружения."
msgstr ""
"Starts the ``Argenta`` orchestrator from a callable entrypoint. This is an "
"alternative to directly calling ``python main.py``, but with automatic "
"environment setup."
#: ../../root/cli.rst:89
msgid ""
"``entrypoint`` — путь к callable в формате "
"``<path/to/file.py>:<callable>`` или ``<path.to.module>:<callable>``."
msgstr ""
"``entrypoint`` — path to a callable in the format "
"``<path/to/file.py>:<callable>`` or ``<path.to.module>:<callable>``."
#: ../../root/cli.rst:99
msgid ""
"Команда ``run`` устанавливает переменную окружения "
"``RUN_FROM_ARGENTA_RUNNER=1``, что отключает парсинг аргументов командной "
"строки в ``ArgParser``. Это позволяет запустить REPL без конфликтов с "
"CLI-аргументами ``argenta``."
msgstr ""
"The ``run`` command sets the ``RUN_FROM_ARGENTA_RUNNER=1`` environment "
"variable, which disables command-line argument parsing in ``ArgParser``. "
"This allows starting the REPL without conflicts with ``argenta`` CLI arguments."
#: ../../root/cli.rst:104
msgid "Инспекция маршрутов"
msgstr "Inspecting Routes"
#: ../../root/cli.rst:107
msgid "Команда ``routes``"
msgstr "The ``routes`` command"
#: ../../root/cli.rst:109
msgid ""
"Отображает все зарегистрированные роутеры, команды, алиасы и флаги в виде "
"дерева. Принимает как инстанс ``App``, так и callable, возвращающий ``App``."
msgstr ""
"Displays all registered routers, commands, aliases, and flags as a tree. "
"Accepts either an ``App`` instance or a callable returning ``App``."
#: ../../root/cli.rst:113
msgid ""
"``entrypoint`` — путь к ``App`` или callable в формате "
"``<path/to/file.py>:<app_or_callable>``."
msgstr ""
"``entrypoint`` — path to an ``App`` or callable in the format "
"``<path/to/file.py>:<app_or_callable>``."
#: ../../root/cli.rst:125
msgid "Если передан инстанс ``App``:"
msgstr "If an ``App`` instance is passed:"
#: ../../root/cli.rst:131
msgid ""
"Если передан callable (фабрика), он будет вызван, и результат будет "
"использован для отображения маршрутов:"
msgstr ""
"If a callable (factory) is passed, it will be called, and the result will "
"be used to display routes:"
#: ../../root/cli.rst:139
msgid ""
"При использовании callable-ентрипойнта (например, ``create_app``) REPL не "
"запускается — фабрика вызывается, и маршруты считываются из возвращённого "
"``App``. Это полезно, когда роутеры подключаются внутри функции, а не на "
"уровне модуля."
msgstr ""
"When using a callable entrypoint (e.g., ``create_app``), the REPL is not "
"started — the factory is called, and routes are read from the returned "
"``App``. This is useful when routers are registered inside a function "
"rather than at the module level."
#: ../../root/cli.rst:144
msgid "Сборка бинарников"
msgstr "Building Binaries"
#: ../../root/cli.rst:147
msgid "Команда ``build``"
msgstr "The ``build`` command"
#: ../../root/cli.rst:149
msgid "Компилирует проект в standalone-бинарник с помощью `Nuitka <https://nuitka.net/>`_."
msgstr "Compiles a project into a standalone binary using `Nuitka <https://nuitka.net/>`_."
#: ../../root/cli.rst:153
msgid ""
"``entrypoint`` — путь к callable в формате "
"``<path/to/file.py>:<callable>``."
msgstr ""
"``entrypoint`` — path to a callable in the format "
"``<path/to/file.py>:<callable>``."
#: ../../root/cli.rst:154
msgid ""
"``--output`` / ``-o`` — имя выходного бинарника (по умолчанию — имя файла "
"или пакета)."
msgstr ""
"``--output`` / ``-o`` — output binary name (defaults to the file or package name)."
#: ../../root/cli.rst:166
msgid ""
"Для использования команды ``build`` необходимо установить ``Nuitka``:"
msgstr ""
"To use the ``build`` command, you must install ``Nuitka``:"
#: ../../root/cli.rst:174
msgid "Информация об окружении"
msgstr "Environment Information"
#: ../../root/cli.rst:177
msgid "Команда ``info``"
msgstr "The ``info`` command"
#: ../../root/cli.rst:179
msgid ""
"Отображает версию ``Argenta``, версию Python, платформу и ссылку на "
"документацию."
msgstr ""
"Displays the ``Argenta`` version, Python version, platform, and a link to "
"the documentation."
#: ../../root/cli.rst:187
msgid "Формат ентрипойнтов"
msgstr "Entrypoint Format"
#: ../../root/cli.rst:190
msgid ""
"Все команды, принимающие ентрипойнт (``run``, ``routes``, ``build``), "
"используют единый формат:"
msgstr ""
"All commands that accept an entrypoint (``run``, ``routes``, ``build``) "
"use a unified format:"
#: ../../root/cli.rst:196
msgid ""
"Поддерживаются как пути к файлам, так и dotted-модули. Если передан путь к "
"директории с ``__main__.py``, он будет разрешён автоматически."
msgstr ""
"Both file paths and dotted modules are supported. If a directory path "
"containing ``__main__.py`` is passed, it will be resolved automatically."
#: ../../root/cli.rst:199
msgid "Примеры валидных ентрипойнтов:"
msgstr "Examples of valid entrypoints:"
+244
View File
@@ -0,0 +1,244 @@
.. _root_cli:
Командная строка
==================
Помимо библиотеки, ``Argenta`` поставляется с собственным CLI-инструментом, который помогает создавать проекты, запускать приложения, инспектировать маршруты и собирать бинарники.
Установка
---------
CLI доступен как опциональная зависимость:
.. code-block:: shell
pip install argenta[cli]
После установки команда ``argenta`` доступна в терминале:
.. code-block:: shell
argenta --help
.. image:: _static/cli/help.png
:alt: Argenta CLI help
.. note::
Если вы устанавливали ``argenta`` без extras, CLI-инструмент не будет доступен. Установите с ``[cli]`` для доступа к команде ``argenta``.
Флаг ``--version``
~~~~~~~~~~~~~~~~~~~
Показать установленную версию ``Argenta``:
.. code-block:: shell
argenta --version
.. code-block:: shell
argenta -v
-----
Создание проектов
-----------------
Команда ``new``
~~~~~~~~~~~~~~~~
Создаёт новую директорию проекта с boilerplate-кодом.
.. code-block:: shell
argenta new <project_name> [--with-arch flat|src]
* ``project_name`` — имя директории проекта (обязательный аргумент).
* ``--with-arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``.
**Примеры:**
.. code-block:: shell
argenta new my-app
argenta new my-app --with-arch src
При архитектуре ``flat`` создаётся следующая структура:
.. literalinclude:: ../code_snippets/cli/flat_structure.txt
:language: text
При архитектуре ``src``:
.. literalinclude:: ../code_snippets/cli/src_structure.txt
:language: text
.. image:: _static/cli/new_command.png
:alt: argenta new command output
Команда ``init``
~~~~~~~~~~~~~~~~~
Инициализирует boilerplate в текущей директории. Удобно, когда проект уже существует и нужно добавить структуру Argenta.
.. code-block:: shell
argenta init [--with-arch flat|src]
* ``--with-arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``.
**Примеры:**
.. code-block:: shell
argenta init
argenta init --with-arch src
.. note::
Команда ``init`` не перезаписывает существующие файлы — они будут пропущены.
-----
Запуск приложений
-----------------
Команда ``run``
~~~~~~~~~~~~~~~~
Запускает оркестратор ``Argenta`` из callable-ентрипойнта. Это альтернатива прямому вызову ``python main.py``, но с автоматической настройкой окружения.
.. code-block:: shell
argenta run <entrypoint>
* ``entrypoint`` — путь к callable в формате ``<path/to/file.py>:<callable>`` или ``<path.to.module>:<callable>``.
**Примеры:**
.. code-block:: shell
argenta run app/main.py:main
argenta run my_project.application:main
.. image:: _static/cli/run_command.png
:alt: argenta run command output
.. note::
Команда ``run`` устанавливает переменную окружения ``RUN_FROM_ARGENTA_RUNNER=1``, что отключает парсинг аргументов командной строки в ``ArgParser``. Это позволяет запустить REPL без конфликтов с CLI-аргументами ``argenta``.
-----
Инспекция маршрутов
-------------------
Команда ``routes``
~~~~~~~~~~~~~~~~~~~
Отображает все зарегистрированные роутеры, команды, алиасы и флаги в виде дерева. Принимает как инстанс ``App``, так и callable, возвращающий ``App``.
.. code-block:: shell
argenta routes <entrypoint>
* ``entrypoint`` — путь к ``App`` или callable в формате ``<path/to/file.py>:<app_or_callable>``.
**Примеры:**
.. code-block:: shell
argenta routes app/main.py:app
argenta routes app/main.py:create_app
Если передан инстанс ``App``:
.. literalinclude:: ../code_snippets/cli/app_instance.py
:language: python
:linenos:
Если передан callable (фабрика), он будет вызван, и результат будет использован для отображения маршрутов:
.. literalinclude:: ../code_snippets/cli/app_factory.py
:language: python
:linenos:
.. image:: _static/cli/routes_command.png
:alt: argenta routes command output
.. note::
При использовании callable-ентрипойнта (например, ``create_app``) REPL не запускается — фабрика вызывается, и маршруты считываются из возвращённого ``App``. Это полезно, когда роутеры подключаются внутри функции, а не на уровне модуля.
-----
Сборка бинарников
-----------------
Команда ``build``
~~~~~~~~~~~~~~~~~~
Компилирует проект в standalone-бинарник с помощью `Nuitka <https://nuitka.net/>`_.
.. code-block:: shell
argenta build <entrypoint> [--output <name>]
* ``entrypoint`` — путь к callable в формате ``<path/to/file.py>:<callable>``.
* ``--output`` / ``-o`` — имя выходного бинарника (по умолчанию — имя файла или пакета).
**Примеры:**
.. code-block:: shell
argenta build app/main.py:main
argenta build app/main.py:main --output myapp
argenta build app/__main__.py:main -o myapp
.. warning::
Для использования команды ``build`` необходимо установить ``Nuitka``:
.. code-block:: shell
pip install nuitka
.. image:: _static/cli/build_command.png
:alt: argenta build command output
-----
Информация об окружении
-----------------------
Команда ``info``
~~~~~~~~~~~~~~~~~
Отображает версию ``Argenta``, версию Python, платформу и ссылку на документацию.
.. code-block:: shell
argenta info
.. image:: _static/cli/info_command.png
:alt: argenta info command output
-----
Формат ентрипойнтов
-------------------
Все команды, принимающие ентрипойнт (``run``, ``routes``, ``build``), используют единый формат:
.. code-block:: text
<path/to/file.py>:<object_name>
<path.to.module>:<object_name>
Поддерживаются как пути к файлам, так и dotted-модули. Если передан путь к директории с ``__main__.py``, он будет разрешён автоматически.
**Примеры валидных ентрипойнтов:**
.. code-block:: text
app/main.py:main
app/main.py:app
app/main.py:create_app
my_project.application:main
my_project/application/__main__.py:main
@@ -4,12 +4,12 @@ from argenta import App, Orchestrator
from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter
from argenta.app.dividing_line.models import DynamicDividingLine from argenta.app.dividing_line.models import DynamicDividingLine
from argenta.orchestrator import ArgParser from argenta.orchestrator import ArgParser
from mock.mock_app.routers import work_router from examples.example_app.routers import work_router
app: App = App( app: App = App(
dividing_line=StaticDividingLine('~') dividing_line=StaticDividingLine('~')
) )
orchestrator: Orchestrator = Orchestrator(arg_parser=ArgParser(processed_args=[])) orchestrator: Orchestrator = Orchestrator()
def main(): def main():
+94 -42
View File
@@ -1,3 +1,6 @@
from importlib.metadata import version
import typer
from typer import Typer from typer import Typer
from .commands import ( from .commands import (
@@ -9,50 +12,99 @@ from .commands import (
run_handler, run_handler,
) )
app = Typer(
name="argenta",
help="Argenta CLI — scaffold, run, inspect, and build CLI apps.",
no_args_is_help=True,
)
def _version_callback(value: bool) -> None:
if value:
typer.echo(f"argenta {version('argenta')}")
raise typer.Exit()
@app.callback()
def _root(
version_flag: bool = typer.Option(
None,
"--version",
"-v",
callback=_version_callback,
is_eager=True,
help="Show Argenta version and exit.",
),
) -> None:
"""Argenta CLI — scaffold, run, inspect, and build CLI apps."""
@app.command(
"run",
help="Start the orchestrator REPL from a callable entrypoint.",
short_help="Start the orchestrator REPL",
epilog="Example: argenta run app/main.py:main",
)
def _run(entrypoint_path: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<callable>")) -> None:
run_handler(entrypoint_path)
@app.command(
"init",
help="Scaffold a flat or src boilerplate in the current project directory.",
short_help="Initialize architecture in existing project",
epilog="Run from the project root. Example: argenta init --with-arch src",
)
def _init(with_arch: str = typer.Option("flat", "--with-arch", help="Architecture: flat or src")) -> None:
init_handler(with_arch=with_arch) # type: ignore[arg-type]
@app.command(
"new",
help="Create a new project directory with a flat or src boilerplate.",
short_help="Create a new project with boilerplate",
epilog="Example: argenta new my-app --with-arch src",
)
def _new(
project_name: str = typer.Argument(help="Name of the new project directory"),
with_arch: str = typer.Option("flat", "--with-arch", help="Architecture: flat or src"),
) -> None:
new_handler(project_name=project_name, with_arch=with_arch) # type: ignore[arg-type]
@app.command(
"routes",
help="Display all registered routes, commands, aliases, and flags. Accepts an App instance or a callable returning App.",
short_help="Show registered routes and commands",
epilog="Examples:\n argenta routes app/main.py:app\n argenta routes app/main.py:create_app",
)
def _routes(entrypoint_path: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<app_or_callable>")) -> None:
routes_handler(entrypoint_path)
@app.command(
name="info",
help="Display Argenta version, Python version, and platform info.",
short_help="Show Argenta version and environment info",
)
def _info() -> None:
info_handler()
@app.command(
name="build",
help="Compile a project entrypoint into a standalone binary using Nuitka.",
short_help="Build a standalone binary",
epilog="Example: argenta build app/main.py:main --output myapp",
)
def _build(
entry_point: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<callable>"),
output_name: str | None = typer.Option(None, "--output", "-o", help="Output binary name"),
) -> None:
build_handler(entry_point=entry_point, output_name=output_name)
def main() -> None: def main() -> None:
app = Typer()
app.command(
"run",
help="Command to start the orchestrator repl; the path to the callable object is required",
short_help="Start the orchestrator REPL",
epilog="Example: run app/main.py:main",
)(run_handler)
app.command(
"init",
help="Creates a flat/src boilerplate architecture in an existing project",
short_help="Initialize architecture in existing project",
epilog="Make sure you are in the project root before running this command.",
)(init_handler)
app.command(
"new",
help="Creates a project and in it flat/src boilerplate architecture",
short_help="Create a new project with boilerplate",
epilog="This will create a new directory with the project structure.",
)(new_handler)
app.command(
"routes",
help="Creates a project and in it flat/src boilerplate architecture",
short_help="Create a new project with boilerplate",
epilog="This will create a new directory with the project structure.",
)(routes_handler)
app.command(
name="info",
help="Displays information about the installed Argenta package and environment",
short_help="Show Argenta version and environment info",
epilog="Uses metadata to retrieve the installed package version.",
)(info_handler)
app.command(
name="build",
help="Compiles the project into a standalone binary using Nuitka",
short_help="Build a standalone binary",
)(build_handler)
app() app()
+88
View File
@@ -0,0 +1,88 @@
__all__ = [
"GITIGNORE_CONTENT",
"FLAT_MAIN_TEMPLATE",
"FLAT_HANDLERS_TEMPLATE",
"SRC_MAIN_TEMPLATE",
"SRC_ROUTERS_TEMPLATE",
"SRC_HANDLER_TEMPLATE",
"create_file",
]
from pathlib import Path
GITIGNORE_CONTENT = """
__pycache__/
*.py[cod]
.env
.venv/
env/
"""
FLAT_MAIN_TEMPLATE = """
from argenta import Orchestrator, App
from handlers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
FLAT_HANDLERS_TEMPLATE = """
from argenta import Router, Response
router = Router("Hello command")
@router.command("hello")
def hello_handler(response: Response):
print("Hello world!")
"""
SRC_MAIN_TEMPLATE = """
from argenta import Orchestrator, App
from .routers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
SRC_ROUTERS_TEMPLATE = """
from argenta import Router
from .handlers.hello_world_handler import hello_handler
router = Router()
router.command("hello")(hello_handler)
"""
SRC_HANDLER_TEMPLATE = """
from argenta import Response
def hello_handler(response: Response) -> None:
print("Hello world!")
"""
def create_file(path: Path, content: str) -> None:
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content.strip(), encoding="utf-8")
else:
print(f"Skipped: {path} (already exists)")
+1 -1
View File
@@ -39,7 +39,7 @@ def build_handler(entry_point: str, output_name: str | None = None) -> None:
"--standalone", "--standalone",
"--onefile", "--onefile",
f"--output-filename={name}", f"--output-filename={name}",
f"--jobs={os.cpu_count()}", f"--jobs={os.cpu_count() or 1}",
"--lto=no", "--lto=no",
"--include-windows-runtime-dlls=no", "--include-windows-runtime-dlls=no",
] ]
+1 -1
View File
@@ -31,6 +31,6 @@ def info_handler() -> None:
table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})") table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})")
table.add_row("Docs", "https://argenta.readthedocs.io") table.add_row("Docs", "https://argenta.readthedocs.io")
console.print(f"[bold red]{text2art("Argenta", font='tarty1')}[/bold red]") console.print(f"[bold red]{text2art('Argenta', font='tarty1')}[/bold red]")
console.print(Padding(table, pad=(2, 5))) console.print(Padding(table, pad=(2, 5)))
console.print(Padding("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17))) console.print(Padding("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17)))
+9 -77
View File
@@ -3,83 +3,15 @@ __all__ = ["init_handler"]
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
GITIGNORE_CONTENT = """ from ._templates import (
__pycache__/ FLAT_HANDLERS_TEMPLATE,
*.py[cod] FLAT_MAIN_TEMPLATE,
.env GITIGNORE_CONTENT,
.venv/ SRC_HANDLER_TEMPLATE,
env/ SRC_MAIN_TEMPLATE,
""" SRC_ROUTERS_TEMPLATE,
create_file,
FLAT_MAIN_TEMPLATE = """ )
from argenta import Orchestrator, App
from handlers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
FLAT_HANDLERS_TEMPLATE = """
from argenta import Router, Response
router = Router("Hello command")
@router.command("hello")
def start_handler(response: Response):
print("Hello world!")
"""
SRC_MAIN_TEMPLATE = """
from argenta import Orchestrator, App
from .routers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
SRC_ROUTERS_TEMPLATE = """
from argenta import Router
from .handlers.hello_world_handler import hello_handler
router = Router()
router.command('hello')(hello_handler)
"""
SRC_HANDLER_TEMPLATE = """
from argenta import Response
def hello_handler(response: Response) -> None:
print("Hello world!")
"""
def create_file(path: Path, content: str) -> None:
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content.strip(), encoding="utf-8")
else:
print(f"Skipped: {path} (already exists)")
def init_handler(with_arch: Literal["flat", "src"] = "flat") -> None: def init_handler(with_arch: Literal["flat", "src"] = "flat") -> None:
+10 -78
View File
@@ -4,83 +4,15 @@ import sys
from pathlib import Path from pathlib import Path
from typing import Literal from typing import Literal
from ._templates import (
GITIGNORE_CONTENT = """ FLAT_HANDLERS_TEMPLATE,
__pycache__/ FLAT_MAIN_TEMPLATE,
*.py[cod] GITIGNORE_CONTENT,
.env SRC_HANDLER_TEMPLATE,
.venv/ SRC_MAIN_TEMPLATE,
env/ SRC_ROUTERS_TEMPLATE,
""" create_file,
)
FLAT_MAIN_TEMPLATE = """
from argenta import Orchestrator, App
from handlers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
FLAT_HANDLERS_TEMPLATE = """
from argenta import Router, Response
router = Router("Hello command")
@router.command("hello")
def start_handler(response: Response):
print("Hello world!")
"""
SRC_MAIN_TEMPLATE = """
from argenta import Orchestrator, App
from .routers import router
def main():
app = App()
app.include_router(router)
orchestrator = Orchestrator()
orchestrator.run_repl(app)
if __name__ == "__main__":
main()
"""
SRC_ROUTERS_TEMPLATE = """
from argenta import Router
from .handlers.hello_world_handler import hello_handler
router = Router()
router.command('hello')(hello_handler)
"""
SRC_HANDLER_TEMPLATE = """
from argenta import Response
def hello_handler(response: Response) -> None:
print("Hello world!")
"""
def create_file(path: Path, content: str) -> None:
if not path.exists():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content.strip(), encoding="utf-8")
else:
print(f"Skipped: {path} (already exists)")
def new_handler(project_name: str, with_arch: Literal["flat", "src"] = "flat") -> None: def new_handler(project_name: str, with_arch: Literal["flat", "src"] = "flat") -> None:
@@ -88,7 +20,7 @@ def new_handler(project_name: str, with_arch: Literal["flat", "src"] = "flat") -
if base_dir.exists(): if base_dir.exists():
print(f"Error: Directory '{project_name}' already exists.") print(f"Error: Directory '{project_name}' already exists.")
sys.exit(1) raise SystemExit(1)
base_dir.mkdir(parents=True) base_dir.mkdir(parents=True)
print(f"Initialized project directory: {base_dir}") print(f"Initialized project directory: {base_dir}")
+37 -9
View File
@@ -6,25 +6,53 @@ from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
from rich.tree import Tree from rich.tree import Tree
from argenta.app.models import App
from ..infrastructure.entrypoint_resolver.entity import ( from ..infrastructure.entrypoint_resolver.entity import (
CallableEntryPoint,
EntryPointAsApp, EntryPointAsApp,
EntrypointResolver, EntrypointResolver,
)
from ..infrastructure.entrypoint_resolver.exceptions import (
EntrypointError,
EntrypointNotAppInstanceError,
ResolveFromStringError, ResolveFromStringError,
) )
def routes_handler(entrypoint_path: str) -> None: def routes_handler(entrypoint_path: str) -> None:
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":") file_path, _, callable_name = entrypoint_path.partition(":")
if not entrypoint_callable_name: if not callable_name:
raise ResolveFromStringError( Console().print(
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>" f'[bold red]Error:[/bold red] "{entrypoint_path}" must be in format '
f'"<path/to/file.py>:<app_object>" or "<path.to.module>:<app_object>"'
) )
raise SystemExit(1)
app_instance = EntrypointResolver[EntryPointAsApp](entrypoint_path).parse_entrypoint_with_type( try:
entrypoint_callable_name app_instance = EntrypointResolver[EntryPointAsApp](file_path).parse_entrypoint_with_type(
) callable_name
)
app = app_instance.instance_object app = app_instance.instance_object
except EntrypointNotAppInstanceError:
try:
callable_entrypoint = EntrypointResolver[CallableEntryPoint](
file_path
).parse_entrypoint_with_type(
callable_name
)
except (ResolveFromStringError, EntrypointError) as e:
Console().print(f"[bold red]Error:[/bold red] {e}")
raise SystemExit(1)
app = callable_entrypoint.instance_object()
if not isinstance(app, App):
Console().print(
f"[bold red]Error:[/bold red] callable must return an App instance, got {type(app).__name__}"
)
raise SystemExit(1)
except (ResolveFromStringError, EntrypointError) as e:
Console().print(f"[bold red]Error:[/bold red] {e}")
raise SystemExit(1)
routers = app.registered_routers routers = app.registered_routers
console = Console() console = Console()
+19 -9
View File
@@ -2,23 +2,33 @@ __all__ = ["run_handler"]
import os import os
from rich.console import Console
from ..infrastructure.entrypoint_resolver.entity import ( from ..infrastructure.entrypoint_resolver.entity import (
CallableEntryPoint, CallableEntryPoint,
EntrypointResolver, EntrypointResolver,
)
from ..infrastructure.entrypoint_resolver.exceptions import (
EntrypointError,
ResolveFromStringError, ResolveFromStringError,
) )
def run_handler(entrypoint_path: str) -> None: def run_handler(entrypoint_path: str) -> None:
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1" os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":") file_path, _, callable_name = entrypoint_path.partition(":")
if not entrypoint_callable_name: if not callable_name:
raise ResolveFromStringError( Console().print(
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name> or <path.to.module>:<object_name>" f'[bold red]Error:[/bold red] "{entrypoint_path}" must be in format '
f'"<path/to/file.py>:<callable>" or "<path.to.module>:<callable>"'
) )
raise SystemExit(1)
runner = EntrypointResolver[CallableEntryPoint](entrypoint_path).parse_entrypoint_with_type( try:
entrypoint_callable_name runner = EntrypointResolver[CallableEntryPoint](file_path).parse_entrypoint_with_type(
) callable_name
)
runner.instance_object() runner.instance_object()
except (ResolveFromStringError, EntrypointError) as e:
Console().print(f"[bold red]Error:[/bold red] {e}")
raise SystemExit(1)