10 Commits

Author SHA1 Message Date
kolo 87239f1501 v0.2.2 2025-02-13 19:29:58 +03:00
kolo 48c117dd72 Merge branch 'kolo' of github.com:koloideal/Argenta into kolo 2025-02-13 19:26:12 +03:00
kolo 1d782f6213 fix 2025-02-13 19:26:08 +03:00
kolo ddf396a644 Update README.md 2025-02-13 17:34:15 +03:00
kolo c10af04280 Update README.md 2025-02-12 00:17:17 +03:00
kolo 890d863391 first version of docs 2025-02-12 00:13:43 +03:00
kolo 29b184b2ed working 2025-02-11 22:48:36 +03:00
kolo 6d331a57c1 working 2025-02-11 22:09:44 +03:00
kolo b0eb1e3e6c working 2025-02-11 21:36:23 +03:00
kolo 2d088caaaf work on readme 2025-02-11 01:21:27 +03:00
8 changed files with 314 additions and 62 deletions
+1
View File
@@ -4,6 +4,7 @@ argenta/router/__pycache__/
argenta/app/__pycache__/
argenta/__pycache__/
dist
poetry.lock
tests/__pycache__
tests/mock_default_app/handlers/__pycache__/
tests/mock_default_app/__pycache__/
+272 -1
View File
@@ -1,2 +1,273 @@
# Argenta
Python library for creating cli apps
---
## Описание
**Argenta** — это библиотека для создания CLI-приложений на Python. Она предоставляет удобные инструменты для маршрутизации команд и обработки пользовательского ввода.
---
# Установка
```bash
pip install argenta
```
or
```bash
poetry add argenta
```
---
# Быстрый старт
Пример базового CLI-приложения с Argenta:
```python
#routers.py
from argenta.router import Router
router = Router()
@router.command("hello")
def hello():
print("Hello, world!")
@router.unknown_command
def unlnown_command(command):
print(f'Command "{command}" undefined')
```
```python
#main.py
from argenta.app import App
from routers import router
app: App = App()
def main() -> None:
app.include_router(router)
app.start_polling()
if __name__ == '__main__':
main()
```
---
# Техническая документация
---
## declared *classes* :
---
### *class* :: `App`
Класс, определяющий поведение и состояние приложения
#### Конструктор
```python
App(prompt: str = 'Enter a command',
initial_greeting: str = '\nHello, I am Argenta\n',
farewell_message: str = '\nGoodBye\n',
exit_command: str = 'Q',
exit_command_description: str = 'Exit command',
exit_command_title: str = 'System points:',
ignore_exit_command_register: bool = True,
ignore_command_register: bool = False,
line_separate: str = '',
command_group_description_separate: str = '',
repeat_command_groups: bool = True,
print_func: Callable[[str], None] = print)
```
**Аргументы:**
- **name : mean**
- `prompt` (`str`): Сообщение перед вводом команды.
- `initial_greeting` (`str`): Приветственное сообщение при запуске.
- `farewell_message` (`str`): Сообщение при выходе.
- `exit_command` (`str`): Команда выхода (по умолчанию `'Q'`).
- `exit_command_description` (`str`): Описание команды выхода.
- `exit_command_title` (`str`): Заголовок перед списком команд выхода.
- `ignore_exit_command_register` (`bool`): Игнорировать регистр команды выхода.
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
- `line_separate` (`str`): Разделительная строка между командами.
- `command_group_description_separate` (`str`): Разделитель между группами команд.
- `repeat_command_groups` (`bool`): Повторять описание команд перед вводом.
- `print_func` (`Callable[[str], None]`): Функция вывода текста в терминал (по умолчанию `print`).
---
#### **declared *methods***
---
**App().**`start_polling() -> None`
*method mean* **::** запускает жизненный цикл приложения
---
**App().**`include_router(router: Router, is_main: bool = False) -> None`
*param* `router: Router` **::** регистрируемый роутер
*param* `is_main: bool` **::** будет ли являться регистрируемый роутер главным
*example* **::** `True` или `False`
*method mean* **::** регистрирует роутер в приложении
---
**App().**`set_initial_message(message: str) -> None`
*param* `message: str` **::** устанавливаемое приветственное сообщение
*example* **::** `"Hello, I'm a cli example app"`
*method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы
---
**App().**`set_farewell_message(message: str) -> None`
*param* `message: str` **::** устанавливаемое сообщение при выходе
*example* **::** `"GoodBye !"`
*method mean* **::** устанавливает сообщение, которое будет отображено при выходе
---
**App().**`set_description_message_pattern(pattern: str) -> None`
*param* `pattern: str` **::** паттерн описания команды при её выводе в консоль
*example* **::** `"[{command}] *=*=* {description}"`
*method mean* **::** устанавливает приветственное сообщение
---
**App().**`get_main_router() -> Router`
*method mean* **::** возвращает `Router()`, который является главным в приложении
---
**App().**`get_all_app_commands() -> list[str]`
*method mean* **::** возвращает список команд всех зарегистрированных роутеров, сохраняя их регистр
---
#### Примечания
- Среди зарегистрированных в приложении роутеров должен быть один главный, является ли роутер главным
определяется значением аргумента `is_main` равным `True`, в методе `App().include_router()`, который по умолчанию равен
`False`, если в приложении зарегистрирован лишь один роутер, то он неявно устанавливается главным, если
зарегистрировано больше одного роутера, то требуется явное указание главного. При регистрации более одного
главного роутера вызывается исключение `OnlyOneMainRouterIsAllowedException`. При регистрации более одного
роутера и отсутствии указания главного вызывается исключение `MissingMainRouterException`
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
паттерна на места этих ключевых слов будут подставлены соответствующие значения команды, при отсутствии
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
- Команды приложения не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
у __<u>разных</u>__ роутеров
- У главного обработчика должен быть зарегистрирован обработчик неизвестных команд:
```python
router = Router()
@router.unknown_command
def unknown_command(command):
print(f'Command "{command}" undefined')
```
При отсутствии обработчика неизвестных команд у главного роутера будет вызвано исключение
`MissingHandlerForUnknownCommandsException`. При регистрации обработчика неизвестных команд у
__<u>не</u>__ главного роутера будет вызвано исключение `HandlerForUnknownCommandsOnNonMainRouterException`
#### Исключения
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
- `OnlyOneMainRouterIsAllowedException` — Регистрация более одного главного роутера.
- `MissingMainRouterException` — Отсутствует главный роутер.
- `MissingHandlerForUnknownCommandsException` — В основном роутере отсутствует обработчик неизвестных команд.
- `HandlerForUnknownCommandsOnNonMainRouterException` — Обработчик неизвестных команд определён не у основного роутера.
- `NoRegisteredRoutersException` — Отсутствуют зарегистрированные роутеры.
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
- `RepeatedCommandInDifferentRoutersException` — Одна и та же команда зарегистрирована в разных роутерах.
---
### *class* :: `Router`
Класс, который определяет и конфигурирует обработчики команд
#### Конструктор
```python
Router(title: str = 'Commands group title:',
name: str = 'subordinate')
```
**Аргументы:**
- **name : mean**
- `title` (`str`): Заголовок группы команд.
- `name` (`str`): Персональное название роутера
#### **declared *methods***
---
**`@`Router().**`command(command: str, description: str = None)`
*param* `command: str` **::** строковый триггер, который будет выполнять указанные действия
*example* **::** `U` / `update` / `ExaMPLE`
*param* `description: str` **::** описание команды, которое будет выведено в консоль
*example* **::** `description for update command` или `example description`
*method mean* **::** декоратор регистрирует функцию как обработчик команды
---
**`@`Router().**`unknown_command`
*method mean* **::** декоратор регистрирует функцию как обработчик неизвестных команд
---
**Router().**`get_name() -> str`
*method mean* **::** возвращает установленное название роутера
---
**Router().**`get_title() -> str`
*method mean* **::** возвращает установленный заголовок группы команд данного роутера
---
**Router().**`get_router_info() -> dict`
*method mean* **::** возвращает информацию о роутере
---
**Router().**`get_all_commands() -> list[str]`
*method mean* **::** возвращает все зарегистрированные команды для данного роутера
---
#### Исключения
- `InvalidCommandInstanceException` - Переданный объект для регистрации команды не является строкой
- `InvalidDescriptionInstanceException` - Переданный объект для регистрации описания команды не является строкой
- `UnknownCommandHandlerHasAlreadyBeenCreatedException` - Обработчик неизвестных команд уже создан
- `RepeatedCommandException` - Одна и та же команда зарегистрирована в одном роутере
+2 -2
View File
@@ -1,7 +1,7 @@
from .entity import App
from .exceptions import (HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException,
from .exceptions import (HandlerForUnknownCommandsOnNonMainRouterException,
InvalidDescriptionMessagePatternException,
InvalidRouterInstanceException,
OnlyOneMainRouterIsAllowedException,
MissingMainRouterException,
MissingHandlersForUnknownCommandsOnMainRouterException)
MissingHandlerForUnknownCommandsException)
+34 -31
View File
@@ -4,8 +4,8 @@ from .exceptions import (InvalidRouterInstanceException,
InvalidDescriptionMessagePatternException,
OnlyOneMainRouterIsAllowedException,
MissingMainRouterException,
MissingHandlersForUnknownCommandsOnMainRouterException,
HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException,
MissingHandlerForUnknownCommandsException,
HandlerForUnknownCommandsOnNonMainRouterException,
NoRegisteredRoutersException,
NoRegisteredHandlersException,
RepeatedCommandInDifferentRoutersException)
@@ -14,7 +14,7 @@ from .exceptions import (InvalidRouterInstanceException,
class App:
def __init__(self,
prompt: str = 'Enter a command',
initial_greeting: str = '\nHello, I am Argenta\n',
initial_message: str = '\nHello, I am Argenta\n',
farewell_message: str = '\nGoodBye\n',
exit_command: str = 'Q',
exit_command_description: str = 'Exit command',
@@ -32,7 +32,7 @@ class App:
self.exit_command_title = exit_command_title
self.ignore_exit_command_register = ignore_exit_command_register
self.farewell_message = farewell_message
self.initial_greeting = initial_greeting
self.initial_message = initial_message
self.line_separate = line_separate
self.command_group_description_separate = command_group_description_separate
self.ignore_command_register = ignore_command_register
@@ -50,11 +50,10 @@ class App:
self._validate_main_router()
self._validate_all_router_commands()
self.print_func(self.initial_greeting)
self.print_func(self.initial_message)
if not self.repeat_command_groups:
self._print_command_group_description()
if self.repeat_command_groups:
self.print_func(self.prompt)
while True:
@@ -69,6 +68,8 @@ class App:
is_unknown_command: bool = self._check_is_command_unknown(command)
if is_unknown_command:
if not self.repeat_command_groups:
self.print_func(self.prompt)
continue
for router in self._routers:
@@ -76,10 +77,12 @@ class App:
self.print_func(self.line_separate)
self.print_func(self.command_group_description_separate)
if not self.repeat_command_groups:
self.print_func(self.prompt)
def set_initial_greeting(self, greeting: str) -> None:
self.initial_greeting: str = greeting
def set_initial_message(self, message: str) -> None:
self.initial_message: str = message
def set_farewell_message(self, message: str) -> None:
@@ -107,6 +110,27 @@ class App:
return all_commands
def include_router(self, router: Router, is_main: True | False = False) -> None:
if not isinstance(router, Router):
raise InvalidRouterInstanceException()
if is_main:
if not self._app_main_router:
self._app_main_router = router
router.set_router_as_main()
else:
raise OnlyOneMainRouterIsAllowedException(self._app_main_router.get_name())
router.set_ignore_command_register(self.ignore_command_register)
self._routers.append(router)
command_entities: list[dict[str, Callable[[], None] | str]] = router.get_command_entities()
self._registered_router_entities.append({'name': router.get_name(),
'title': router.get_title(),
'entity': router,
'commands': command_entities})
def _validate_number_of_routers(self) -> None:
if not self._routers:
raise NoRegisteredRoutersException()
@@ -128,11 +152,11 @@ class App:
self._app_main_router = router
if not self._app_main_router.unknown_command_func:
raise MissingHandlersForUnknownCommandsOnMainRouterException()
raise MissingHandlerForUnknownCommandsException()
for router in self._routers:
if router.unknown_command_func and self._app_main_router is not router:
raise HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException()
raise HandlerForUnknownCommandsOnNonMainRouterException()
def _validate_all_router_commands(self) -> None:
@@ -197,24 +221,3 @@ class App:
)
self.print_func(self.command_group_description_separate)
def include_router(self, router: Router, is_main: True | False = False) -> None:
if not isinstance(router, Router):
raise InvalidRouterInstanceException()
if is_main:
if not self._app_main_router:
self._app_main_router = router
router.set_router_as_main()
else:
raise OnlyOneMainRouterIsAllowedException(self._app_main_router.get_name())
router.set_ignore_command_register(self.ignore_command_register)
self._routers.append(router)
command_entities: list[dict[str, Callable[[], None] | str]] = router.get_command_entities()
self._registered_router_entities.append({'name': router.get_name(),
'title': router.get_title(),
'entity': router,
'commands': command_entities})
+2 -2
View File
@@ -28,13 +28,13 @@ class MissingMainRouterException(Exception):
"One of the registered routers must be the main one")
class MissingHandlersForUnknownCommandsOnMainRouterException(Exception):
class MissingHandlerForUnknownCommandsException(Exception):
def __str__(self):
return ("Missing Handlers For Unknown Commands On The Main Router\n"
"The main router must have a declared handler for unknown commands")
class HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException(Exception):
class HandlerForUnknownCommandsOnNonMainRouterException(Exception):
def __str__(self):
return '\nThe handler for unknown commands can only be declared for the main router'
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "argenta"
version = "0.2.1"
version = "0.2.2"
description = "python library for creating cli apps"
authors = [
{name = "kolo",email = "kolo.is.main@gmail.com"}
-23
View File
@@ -1,23 +0,0 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="argenta",
version="0.2.1",
author="kolo",
author_email="kolo.is.main@gmail.com",
description="Python library for creating CLI apps",
long_description=long_description,
long_description_content_type="text/markdown",
packages=find_packages(),
install_requires=[
"requests",
],
classifiers=[
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
],
python_requires='>=3.11',
)
+1 -1
View File
@@ -21,7 +21,7 @@ def main():
app.include_router(work_router, is_main=True)
app.include_router(settings_router)
app.set_initial_greeting(initial_greeting)
app.set_initial_message(initial_greeting)
app.set_farewell_message(goodbye_message)
app.set_description_message_pattern('[bold red][{command}][/bold red] [blue]*=*=*[/blue] [bold yellow italic]{description}')