mirror of
https://github.com/koloideal/Argenta.git
synced 2026-08-08 08:51:13 +03:00
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:
@@ -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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
my_project/
|
||||
├── src/
|
||||
│ └── my_project/
|
||||
│ └── application/
|
||||
│ ├── __init__.py
|
||||
│ ├── __main__.py
|
||||
│ ├── routers.py
|
||||
│ └── handlers/
|
||||
│ ├── __init__.py
|
||||
│ └── hello_world_handler.py
|
||||
└── .gitignore
|
||||
@@ -56,6 +56,7 @@ Argenta предназначена для создания приложений,
|
||||
|
||||
root/redirect_stdout
|
||||
root/dependency_injection
|
||||
root/cli
|
||||
root/testing
|
||||
|
||||
.. toctree::
|
||||
|
||||
@@ -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:"
|
||||
@@ -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.dividing_line.models import DynamicDividingLine
|
||||
from argenta.orchestrator import ArgParser
|
||||
from mock.mock_app.routers import work_router
|
||||
from examples.example_app.routers import work_router
|
||||
|
||||
app: App = App(
|
||||
dividing_line=StaticDividingLine('~')
|
||||
)
|
||||
orchestrator: Orchestrator = Orchestrator(arg_parser=ArgParser(processed_args=[]))
|
||||
orchestrator: Orchestrator = Orchestrator()
|
||||
|
||||
|
||||
def main():
|
||||
@@ -1,3 +1,6 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
import typer
|
||||
from typer import Typer
|
||||
|
||||
from .commands import (
|
||||
@@ -9,50 +12,99 @@ from .commands import (
|
||||
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:
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@@ -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)")
|
||||
@@ -39,7 +39,7 @@ def build_handler(entry_point: str, output_name: str | None = None) -> None:
|
||||
"--standalone",
|
||||
"--onefile",
|
||||
f"--output-filename={name}",
|
||||
f"--jobs={os.cpu_count()}",
|
||||
f"--jobs={os.cpu_count() or 1}",
|
||||
"--lto=no",
|
||||
"--include-windows-runtime-dlls=no",
|
||||
]
|
||||
|
||||
@@ -31,6 +31,6 @@ def info_handler() -> None:
|
||||
table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})")
|
||||
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("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17)))
|
||||
|
||||
@@ -3,83 +3,15 @@ __all__ = ["init_handler"]
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
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 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)")
|
||||
from ._templates import (
|
||||
FLAT_HANDLERS_TEMPLATE,
|
||||
FLAT_MAIN_TEMPLATE,
|
||||
GITIGNORE_CONTENT,
|
||||
SRC_HANDLER_TEMPLATE,
|
||||
SRC_MAIN_TEMPLATE,
|
||||
SRC_ROUTERS_TEMPLATE,
|
||||
create_file,
|
||||
)
|
||||
|
||||
|
||||
def init_handler(with_arch: Literal["flat", "src"] = "flat") -> None:
|
||||
|
||||
@@ -4,83 +4,15 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
|
||||
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 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)")
|
||||
from ._templates import (
|
||||
FLAT_HANDLERS_TEMPLATE,
|
||||
FLAT_MAIN_TEMPLATE,
|
||||
GITIGNORE_CONTENT,
|
||||
SRC_HANDLER_TEMPLATE,
|
||||
SRC_MAIN_TEMPLATE,
|
||||
SRC_ROUTERS_TEMPLATE,
|
||||
create_file,
|
||||
)
|
||||
|
||||
|
||||
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():
|
||||
print(f"Error: Directory '{project_name}' already exists.")
|
||||
sys.exit(1)
|
||||
raise SystemExit(1)
|
||||
|
||||
base_dir.mkdir(parents=True)
|
||||
print(f"Initialized project directory: {base_dir}")
|
||||
|
||||
@@ -6,25 +6,53 @@ from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.tree import Tree
|
||||
|
||||
from argenta.app.models import App
|
||||
|
||||
from ..infrastructure.entrypoint_resolver.entity import (
|
||||
CallableEntryPoint,
|
||||
EntryPointAsApp,
|
||||
EntrypointResolver,
|
||||
)
|
||||
from ..infrastructure.entrypoint_resolver.exceptions import (
|
||||
EntrypointError,
|
||||
EntrypointNotAppInstanceError,
|
||||
ResolveFromStringError,
|
||||
)
|
||||
|
||||
|
||||
def routes_handler(entrypoint_path: str) -> None:
|
||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
||||
if not entrypoint_callable_name:
|
||||
raise ResolveFromStringError(
|
||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>"
|
||||
file_path, _, callable_name = entrypoint_path.partition(":")
|
||||
if not callable_name:
|
||||
Console().print(
|
||||
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(
|
||||
entrypoint_callable_name
|
||||
try:
|
||||
app_instance = EntrypointResolver[EntryPointAsApp](file_path).parse_entrypoint_with_type(
|
||||
callable_name
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
console = Console()
|
||||
|
||||
@@ -2,23 +2,33 @@ __all__ = ["run_handler"]
|
||||
|
||||
import os
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
from ..infrastructure.entrypoint_resolver.entity import (
|
||||
CallableEntryPoint,
|
||||
EntrypointResolver,
|
||||
)
|
||||
from ..infrastructure.entrypoint_resolver.exceptions import (
|
||||
EntrypointError,
|
||||
ResolveFromStringError,
|
||||
)
|
||||
|
||||
|
||||
def run_handler(entrypoint_path: str) -> None:
|
||||
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
|
||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
||||
if not entrypoint_callable_name:
|
||||
raise ResolveFromStringError(
|
||||
"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>"
|
||||
file_path, _, callable_name = entrypoint_path.partition(":")
|
||||
if not callable_name:
|
||||
Console().print(
|
||||
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(
|
||||
entrypoint_callable_name
|
||||
try:
|
||||
runner = EntrypointResolver[CallableEntryPoint](file_path).parse_entrypoint_with_type(
|
||||
callable_name
|
||||
)
|
||||
|
||||
runner.instance_object()
|
||||
except (ResolveFromStringError, EntrypointError) as e:
|
||||
Console().print(f"[bold red]Error:[/bold red] {e}")
|
||||
raise SystemExit(1)
|
||||
|
||||
Reference in New Issue
Block a user