38 Commits

Author SHA1 Message Date
kolo 3f7c577c29 0.3.1 2025-02-28 16:19:22 +03:00
kolo b72fcc6a11 fix 2025-02-27 01:16:39 +03:00
kolo 158e5eb75a fix 2025-02-25 00:37:11 +03:00
kolo 46cb2f70c5 fix 2025-02-24 19:04:20 +03:00
kolo ef242a5732 fix 2025-02-23 18:53:11 +03:00
kolo f87f102ced v0.3.0 2025-02-23 13:26:12 +03:00
kolo ddf2a2fb10 v0.3.0 2025-02-23 13:25:51 +03:00
kolo eac5358ead work on v0.3.0 2025-02-22 12:14:25 +03:00
kolo 76c18ddbff v0.3.0-alpha 2025-02-21 18:05:40 +03:00
kolo 4eeb4eb182 work on v0.3.0 2025-02-21 17:50:53 +03:00
kolo 905698384a work on v0.3.0 2025-02-21 01:46:03 +03:00
kolo 79ccfbb3b1 work on v0.3.0 2025-02-20 22:10:29 +03:00
kolo a63c46a78b work on v0.3.0 2025-02-19 23:49:38 +03:00
kolo a3a7cbf2e6 work on v0.3.0 2025-02-18 23:35:36 +03:00
kolo a9e545f3d8 work on v0.3.0 2025-02-17 20:57:15 +03:00
kolo 37b62fd69b fix 2025-02-17 00:33:05 +03:00
kolo 0ae86d0b2b work on v0.3.0 2025-02-15 12:05:42 +03:00
kolo ebfd5a80b3 work on v0.3.0 2025-02-13 23:26:01 +03:00
kolo 250704fc88 some fix 2025-02-13 19:31:30 +03:00
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
kolo b4b7d5442c v0.2.1 2025-02-09 22:47:53 +03:00
kolo 684760121c some fix 2025-02-09 13:42:06 +03:00
kolo dfc3c472ce v0.2.0 2025-02-09 13:41:11 +03:00
kolo 8a3f742636 v0.2.0 2025-02-09 02:37:44 +03:00
kolo 213525915d remove tests for .gitignore 2025-02-08 22:42:35 +03:00
kolo 6174de3bc0 fix 2025-02-08 00:58:21 +03:00
kolo c78609caca v0.1.3 2025-02-08 00:57:21 +03:00
kolo 3f88c5278e configure tests for development 2025-02-07 19:02:44 +03:00
kolo 93438f8e45 v0.1.2 2025-02-07 18:35:08 +03:00
47 changed files with 1274 additions and 197 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
.venv .venv
.idea .idea
argenta/router/__pycache__/
argenta/app/__pycache__/
argenta/__pycache__/
dist dist
poetry.lock
*__pycache__/
+272 -1
View File
@@ -1,2 +1,273 @@
# Argenta # 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
View File
@@ -1,2 +0,0 @@
from .router import *
from .app import *
+3
View File
@@ -0,0 +1,3 @@
from .entity import App
from .exceptions import (InvalidDescriptionMessagePatternException,
InvalidRouterInstanceException)
+175 -77
View File
@@ -1,68 +1,113 @@
from typing import Callable from typing import Callable
from inspect import getfullargspec
from ..command.entity import Command
from ..router.entity import Router from ..router.entity import Router
from ..command.input_comand.entity import InputCommand
from ..command.input_comand.exceptions import (IncorrectInputFlagException,
InvalidInputFlagsHandlerHasBeenAlreadyCreatedException,
IncorrectNumberArgsHandlerException,
UnknownCommandHandlerHasBeenAlreadyCreatedException)
from .exceptions import (InvalidRouterInstanceException, from .exceptions import (InvalidRouterInstanceException,
InvalidDescriptionMessagePatternException, InvalidDescriptionMessagePatternException,
OnlyOneMainRouterIsAllowedException, NoRegisteredRoutersException,
MissingMainRouterException, NoRegisteredHandlersException,
MissingHandlersForUnknownCommandsOnMainRouterException, RepeatedCommandInDifferentRoutersException)
HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException)
class App: class App:
def __init__(self, def __init__(self,
prompt: str = 'Enter a command', prompt: str = 'Enter a command',
exit_command: str = 'q', initial_message: str = '\nHello, I am Argenta\n',
farewell_message: str = '\nGoodBye\n',
invalid_input_flags_message: str = 'Invalid input flags',
exit_command: str = 'Q',
exit_command_description: str = 'Exit command',
exit_command_title: str = 'System points:',
ignore_exit_command_register: bool = True, ignore_exit_command_register: bool = True,
initial_greeting: str = 'Hello', ignore_command_register: bool = False,
goodbye_message: str = 'GoodBye', line_separate: str = '',
line_separate: str = '\n', command_group_description_separate: str = '',
command_group_description_separate: str = '\n', repeat_command_groups: bool = True,
print_func: Callable[[str], None] = print) -> None: print_func: Callable[[str], None] = print) -> None:
self.prompt = prompt self.prompt = prompt
self.print_func = print_func self.print_func = print_func
self.exit_command = exit_command self.exit_command = exit_command
self.exit_command_description = exit_command_description
self.exit_command_title = exit_command_title
self.ignore_exit_command_register = ignore_exit_command_register self.ignore_exit_command_register = ignore_exit_command_register
self.goodbye_message = goodbye_message self.farewell_message = farewell_message
self.initial_greeting = initial_greeting self.initial_message = initial_message
self.invalid_input_flags_message = invalid_input_flags_message
self.line_separate = line_separate self.line_separate = line_separate
self.command_group_description_separate = command_group_description_separate self.command_group_description_separate = command_group_description_separate
self.ignore_command_register = ignore_command_register
self.repeat_command_groups = repeat_command_groups
self.routers: list[Router] = [] self._routers: list[Router] = []
self.registered_commands: list[dict[str, str | list[dict[str, Callable[[], None] | str]] | Router]] = [] self._invalid_input_flags_handler: Callable[[str], None] | None = None
self.main_app_router: Router | None = None self._unknown_command_handler: Callable[[Command], None] | None = None
self._description_message_pattern = '[{command}] *=*=* {description}' self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = []
self._app_main_router: Router | None = None
self._description_message_pattern: str = '[{command}] *=*=* {description}'
def start_polling(self) -> None: def start_polling(self) -> None:
self.print_func(self.initial_greeting) self._validate_number_of_routers()
self.validate_main_router() self._validate_included_routers()
self._validate_all_router_commands()
while True: self.print_func(self.initial_message)
self.print_command_group_description()
if not self.repeat_command_groups:
self._print_command_group_description()
self.print_func(self.prompt) self.print_func(self.prompt)
command: str = input() while True:
if self.repeat_command_groups:
self._print_command_group_description()
self.print_func(self.prompt)
self.checking_command_for_exit_command(command) raw_command: str = input()
self.print_func(self.line_separate)
is_unknown_command: bool = self.check_is_command_unknown(command) try:
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
if is_unknown_command: except IncorrectInputFlagException:
self.print_func(self.line_separate)
if self._invalid_input_flags_handler:
self._invalid_input_flags_handler(raw_command)
else:
self.print_func(f'Incorrect flag syntax: "{raw_command}"')
self.print_func(self.line_separate)
if not self.repeat_command_groups:
self.print_func(self.prompt)
continue continue
for router in self.routers: self._checking_command_for_exit_command(input_command.get_string_entity())
router.input_command_handler(command) self.print_func(self.line_separate)
is_unknown_command: bool = self._check_is_command_unknown(input_command)
if is_unknown_command:
if not self.repeat_command_groups:
self.print_func(self.prompt)
continue
for router in self._routers:
router.input_command_handler(input_command)
self.print_func(self.line_separate) self.print_func(self.line_separate)
self.print_func(self.command_group_description_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: def set_initial_message(self, message: str) -> None:
self.initial_greeting = greeting self.initial_message: str = message
def set_goodbye_message(self, message: str) -> None: def set_farewell_message(self, message: str) -> None:
self.goodbye_message = message self.farewell_message: str = message
def set_description_message_pattern(self, pattern: str) -> None: def set_description_message_pattern(self, pattern: str) -> None:
@@ -71,75 +116,128 @@ class App:
description='description') description='description')
except KeyError: except KeyError:
raise InvalidDescriptionMessagePatternException(pattern) raise InvalidDescriptionMessagePatternException(pattern)
self._description_message_pattern = pattern self._description_message_pattern: str = pattern
def validate_main_router(self): def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
if not self.main_app_router: if self._invalid_input_flags_handler:
raise MissingMainRouterException() raise InvalidInputFlagsHandlerHasBeenAlreadyCreatedException()
else:
if not self.main_app_router.unknown_command_func: args = getfullargspec(handler).args
raise MissingHandlersForUnknownCommandsOnMainRouterException() if len(args) != 1:
raise IncorrectNumberArgsHandlerException()
for router in self.routers: else:
if router.unknown_command_func and self.main_app_router is not router: self._invalid_input_flags_handler = handler
raise HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException()
def checking_command_for_exit_command(self, command: str): def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
if self._unknown_command_handler:
raise UnknownCommandHandlerHasBeenAlreadyCreatedException()
else:
args = getfullargspec(handler).args
if len(args) != 1:
raise IncorrectNumberArgsHandlerException()
else:
self._unknown_command_handler = handler
def get_all_app_commands(self) -> list[str]:
all_commands: list[str] = []
for router in self._routers:
all_commands.extend(router.get_all_commands())
return all_commands
def include_router(self, router: Router) -> None:
if not isinstance(router, Router):
raise InvalidRouterInstanceException()
router.set_ignore_command_register(self.ignore_command_register)
self._routers.append(router)
command_entities: list[dict[str, Callable[[], None] | Command]] = 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()
def _validate_included_routers(self) -> None:
for router in self._routers:
if not router.get_command_entities():
raise NoRegisteredHandlersException(router.get_name())
def _validate_all_router_commands(self) -> None:
for idx in range(len(self._registered_router_entities)):
current_router: Router = self._registered_router_entities[idx]['entity']
routers_without_current_router = self._registered_router_entities.copy()
routers_without_current_router.pop(idx)
current_router_all_commands: list[str] = current_router.get_all_commands()
for router_entity in routers_without_current_router:
if len(set(current_router_all_commands).intersection(set(router_entity['entity'].get_all_commands()))) > 0:
raise RepeatedCommandInDifferentRoutersException()
if self.ignore_command_register:
if len(set([x.lower() for x in current_router_all_commands]).intersection(set([x.lower() for x in router_entity['entity'].get_all_commands()]))) > 0:
raise RepeatedCommandInDifferentRoutersException()
def _checking_command_for_exit_command(self, command: str):
if command.lower() == self.exit_command.lower(): if command.lower() == self.exit_command.lower():
if self.ignore_exit_command_register: if self.ignore_exit_command_register:
self.print_func(self.goodbye_message) self.print_func(self.farewell_message)
exit(0) exit(0)
else: else:
if command == self.exit_command: if command == self.exit_command:
self.print_func(self.goodbye_message) self.print_func(self.farewell_message)
exit(0) exit(0)
def check_is_command_unknown(self, command: str): def _check_is_command_unknown(self, command: Command):
registered_commands = self.registered_commands registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = self._registered_router_entities
for router in registered_commands: for router_entity in registered_router_entities:
for command_entity in router['commands']: for command_entity in router_entity['commands']:
if command_entity['command'].lower() == command.lower(): if command_entity['command'].get_string_entity().lower() == command.get_string_entity().lower():
if router['router'].ignore_command_register: if self.ignore_command_register:
return False return False
else: else:
if command_entity['command'] == command: if command_entity['command'].get_string_entity() == command.get_string_entity():
return False return False
self.main_app_router.unknown_command_handler(command)
if self._unknown_command_handler:
self._unknown_command_handler(command)
else:
print(f"Unknown command: {command.get_string_entity()}")
self.print_func(self.line_separate) self.print_func(self.line_separate)
self.print_func(self.command_group_description_separate) self.print_func(self.command_group_description_separate)
return True return True
def print_command_group_description(self): def _print_command_group_description(self):
for router in self.registered_commands: for router_entity in self._registered_router_entities:
self.print_func(router['name']) self.print_func(router_entity['title'])
for command_entity in router['commands']: for command_entity in router_entity['commands']:
self.print_func(self._description_message_pattern.format( self.print_func(self._description_message_pattern.format(
command=command_entity['command'], command=command_entity['command'].get_string_entity(),
description=command_entity['description'] description=command_entity['command'].get_description()
) )
) )
self.print_func(self.command_group_description_separate) self.print_func(self.command_group_description_separate)
self.print_func(self.exit_command_title)
def include_router(self, router: Router, is_main: bool = False) -> None: self.print_func(self._description_message_pattern.format(
if not isinstance(router, Router): command=self.exit_command,
raise InvalidRouterInstanceException() description=self.exit_command_description
)
if is_main: )
if not self.main_app_router: self.print_func(self.command_group_description_separate)
self.main_app_router = router
router.set_router_as_main()
else:
raise OnlyOneMainRouterIsAllowedException(router)
self.routers.append(router)
registered_commands: list[dict[str, Callable[[], None] | str]] = router.get_registered_commands()
self.registered_commands.append({'name': router.get_name(),
'router': router,
'commands': registered_commands})
+8 -17
View File
@@ -13,27 +13,18 @@ class InvalidDescriptionMessagePatternException(Exception):
f"Your pattern: {self.pattern}") f"Your pattern: {self.pattern}")
class OnlyOneMainRouterIsAllowedException(Exception): class NoRegisteredRoutersException(Exception):
def __init__(self, existing_main_router):
self.existing_main_router = existing_main_router
def __str__(self): def __str__(self):
return ("Only One Main Router Allowed\n" return "No Registered Router Found"
f"Existing main router is: {self.existing_main_router}")
class MissingMainRouterException(Exception): class NoRegisteredHandlersException(Exception):
def __init__(self, router_name):
self.router_name = router_name
def __str__(self): def __str__(self):
return ("Missing Main Router\n" return f"No Registered Handlers Found For '{self.router_name}'"
"One of the registered routers must be the main one")
class MissingHandlersForUnknownCommandsOnMainRouterException(Exception): class RepeatedCommandInDifferentRoutersException(Exception):
def __str__(self): def __str__(self):
return ("Missing Handlers For Unknown Commands On The Main Router\n" return "Commands in different handlers cannot be repeated"
"The main router must have a declared handler for unknown commands")
class HandlerForUnknownCommandsCanOnlyBeDeclaredForMainRouterException(Exception):
def __str__(self):
return '\nThe handler for unknown commands can only be declared for the main router'
View File
+60
View File
@@ -0,0 +1,60 @@
from .params.flag.entity import Flag
from .params.flag.flags_group.entity import FlagsGroup
from .exceptions import (InvalidCommandInstanceException,
InvalidDescriptionInstanceException,
InvalidFlagsInstanceException)
from .params.flag.input_flag.entity import InputFlag
class Command:
def __init__(self, command: str,
description: str | None = None,
flags: Flag | FlagsGroup | None = None):
self._command = command
self._description = description
self._flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup([flags]) if isinstance(flags, Flag) else flags
self._input_flags: InputFlag | FlagsGroup | None = None
def get_string_entity(self):
return self._command
def get_description(self):
if not self._description:
description = f'description for "{self._command}" command'
return description
else:
return self._description
def get_flags(self):
return self._flags
def set_command(self, command: str):
self._command = command
def validate_commands_params(self):
if not isinstance(self._command, str):
raise InvalidCommandInstanceException(self._command)
if not isinstance(self._description, str):
raise InvalidDescriptionInstanceException()
if not any([(isinstance(self._flags, Flag), isinstance(self._flags, FlagsGroup)), not self._flags]):
raise InvalidFlagsInstanceException
def validate_input_flag(self, flag: InputFlag):
registered_flags: FlagsGroup | Flag | None = self._flags
if registered_flags:
if isinstance(registered_flags, Flag):
if registered_flags.get_string_entity() == flag.get_string_entity():
is_valid = registered_flags.validate_input_flag_value(flag.get_value())
if is_valid:
return True
else:
for registered_flag in registered_flags:
if registered_flag.get_string_entity() == flag.get_string_entity():
is_valid = registered_flag.validate_input_flag_value(flag.get_value())
if is_valid:
return True
return False
+13
View File
@@ -0,0 +1,13 @@
class InvalidCommandInstanceException(Exception):
def __str__(self):
return "Invalid Command Instance"
class InvalidDescriptionInstanceException(Exception):
def __str__(self):
return "Invalid Description Instance"
class InvalidFlagsInstanceException(Exception):
def __str__(self):
return "Invalid Flags Instance"
+66
View File
@@ -0,0 +1,66 @@
from ..input_comand.exceptions import IncorrectInputFlagException, RepeatedInputFlagsException
from ..entity import Command
from ..params.flag.flags_group.entity import FlagsGroup
from ..params.flag.input_flag.entity import InputFlag
from typing import Generic, TypeVar
T = TypeVar('T')
class InputCommand(Command, Generic[T]):
def set_input_flags(self, input_flags: FlagsGroup):
self._input_flags = input_flags
def get_input_flags(self) -> FlagsGroup:
return self._input_flags
@staticmethod
def parse(raw_command: str) -> 'InputCommand[T]':
list_of_tokens = raw_command.split()
command = list_of_tokens[0]
list_of_tokens.pop(0)
flags: FlagsGroup = FlagsGroup()
current_flag_name = None
current_flag_value = None
for _ in list_of_tokens:
if _.startswith('-'):
flag_prefix_last_symbol_index = _.rfind('-')
if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3:
raise IncorrectInputFlagException()
else:
current_flag_name = _
else:
if not current_flag_name:
raise IncorrectInputFlagException()
else:
current_flag_value = _
if current_flag_name and current_flag_value:
flag_prefix_last_symbol_index = current_flag_name.rfind('-')
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index]
flag_name = current_flag_name[flag_prefix_last_symbol_index:]
input_flag = InputFlag(flag_name=flag_name,
flag_prefix=flag_prefix)
input_flag.set_value(current_flag_value)
all_flags = [x.get_string_entity() for x in flags.get_flags()]
if input_flag.get_string_entity() not in all_flags:
flags.add_flag(input_flag)
else:
raise RepeatedInputFlagsException(input_flag)
current_flag_name = None
current_flag_value = None
if any([current_flag_name, current_flag_value]):
raise IncorrectInputFlagException()
if len(flags.get_flags()) == 0:
return InputCommand(command=command)
else:
input_command = InputCommand(command=command)
input_command.set_input_flags(flags)
return input_command
@@ -0,0 +1,39 @@
from ..params.flag.input_flag.entity import InputFlag
class InvalidInputFlagException(Exception):
def __init__(self, flag: InputFlag):
self.flag = flag
def __str__(self):
return ("Invalid Input Flags\n"
f"Unknown or invalid input flag: '{self.flag.get_string_entity()} {self.flag.get_value()}'")
class IncorrectInputFlagException(Exception):
def __str__(self):
return "Incorrect Input Flags"
class RepeatedInputFlagsException(Exception):
def __init__(self, flag: InputFlag):
self.flag = flag
def __str__(self):
return ("Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'")
class InvalidInputFlagsHandlerHasBeenAlreadyCreatedException(Exception):
def __str__(self):
return "Invalid Input Flags Handler has already been created"
class UnknownCommandHandlerHasBeenAlreadyCreatedException(Exception):
def __str__(self):
return "Unknown Command Handler has already been created"
class IncorrectNumberArgsHandlerException(Exception):
def __str__(self):
return "Incorrect Input Flags Handler has incorrect number of arguments"
View File
+45
View File
@@ -0,0 +1,45 @@
from typing import Literal
class Flag:
def __init__(self, flag_name: str,
flag_prefix: Literal['-', '--', '---'] = '-',
ignore_flag_value_register: bool = False,
possible_flag_values: list[str] = False):
self._flag_name = flag_name
self._flag_prefix = flag_prefix
self.possible_flag_values = possible_flag_values
self.ignore_flag_value_register = ignore_flag_value_register
self._value = None
def get_string_entity(self):
string_entity: str = self._flag_prefix + self._flag_name
return string_entity
def get_flag_name(self):
return self._flag_name
def get_flag_prefix(self):
return self._flag_prefix
def get_value(self):
return self._value
def set_value(self, value):
self._value = value
def validate_input_flag_value(self, input_flag_value: str):
if self.possible_flag_values:
if self.ignore_flag_value_register:
if input_flag_value.lower() in [x.lower() for x in self.possible_flag_values]:
return True
else:
return False
else:
if input_flag_value in self.possible_flag_values:
return True
else:
return False
else:
return True
@@ -0,0 +1,22 @@
from argenta.command.params.flag.entity import Flag
from argenta.command.params.flag.input_flag.entity import InputFlag
class FlagsGroup:
def __init__(self, flags: list[Flag | InputFlag] = None):
self._flags: list[Flag | InputFlag] = [] if not flags else flags
def get_flags(self):
return self._flags
def add_flag(self, flag: Flag | InputFlag):
self._flags.append(flag)
def add_flags(self, flags: list[Flag | InputFlag]):
self._flags.extend(flags)
def __iter__(self):
return iter(self._flags)
def __next__(self):
return next(iter(self))
@@ -0,0 +1,11 @@
from ...flag.entity import Flag
class InputFlag(Flag):
def set_value(self, value: str):
self._value = value
def get_value(self) -> str:
return self._value
+2
View File
@@ -0,0 +1,2 @@
from .entity import Router
from .exceptions import InvalidDescriptionInstanceException
+112 -50
View File
@@ -1,70 +1,132 @@
from typing import Callable, Any from typing import Callable, Any
from ..router.exceptions import (InvalidCommandInstanceException, from inspect import getfullargspec
UnknownCommandHandlerHasAlreadyBeenCreatedException, from ..command.entity import Command
InvalidDescriptionInstanceException) from ..command.input_comand.entity import InputCommand
from ..command.input_comand.exceptions import InvalidInputFlagException
from ..command.params.flag.flags_group.entity import FlagsGroup
from ..router.exceptions import (RepeatedCommandException, RepeatedFlagNameException,
CurrentCommandDoesNotProcessFlagsException,
TooManyTransferredArgsException,
RequiredArgumentNotPassedException)
class Router: class Router:
def __init__(self, def __init__(self,
name: str, title: str = 'Commands group title:',
ignore_command_register: bool = False): name: str = 'subordinate'):
self.ignore_command_register: bool = ignore_command_register self.title = title
self._name = name self.name = name
self.processed_commands: list[dict[str, Callable[[], None] | str]] = [] self._command_entities: list[dict[str, Callable[[], None] | Command]] = []
self.unknown_command_func: Callable[[str], None] | None = None self._ignore_command_register: bool = False
self._is_main_router: bool = False
self._unknown_command_handler: Callable[[str], None] | None = None
self._not_valid_flag_handler: Callable[[str], None] | None = None
def command(self, command: str, description: str) -> Callable[[Any], Any]: def command(self, command: Command) -> Callable[[Any], Any]:
if not isinstance(command, str): command.validate_commands_params()
raise InvalidCommandInstanceException() self._validate_command(command)
if not isinstance(description, str):
raise InvalidDescriptionInstanceException() def command_decorator(func):
else: Router._validate_func_args(command, func)
def command_decorator(func): self._command_entities.append({'handler_func': func,
self.processed_commands.append({'func': func, 'command': command})
'command': command, def wrapper(*args, **kwargs):
'description': description}) return func(*args, **kwargs)
def wrapper(*args, **kwargs): return wrapper
return func(*args, **kwargs)
return wrapper return command_decorator
return command_decorator
def unknown_command(self, func): def input_command_handler(self, input_command: InputCommand):
if self.unknown_command_func is not None: input_command_name: str = input_command.get_string_entity()
raise UnknownCommandHandlerHasAlreadyBeenCreatedException() for command_entity in self._command_entities:
if input_command_name.lower() == command_entity['command'].get_string_entity().lower():
self.unknown_command_func = func if command_entity['command'].get_flags():
if input_command.get_input_flags():
def wrapper(*args, **kwargs): for flag in input_command.get_input_flags():
return func(*args, **kwargs) is_valid = command_entity['command'].validate_input_flag(flag)
return wrapper if not is_valid:
raise InvalidInputFlagException(flag)
return command_entity['handler_func'](input_command.get_input_flags())
def input_command_handler(self, input_command): else:
for command_entity in self.processed_commands: return command_entity['handler_func'](FlagsGroup(None))
if input_command.lower() == command_entity['command'].lower():
if self.ignore_command_register:
return command_entity['func']()
else: else:
if input_command == command_entity['command']: if input_command.get_input_flags():
return command_entity['func']() raise CurrentCommandDoesNotProcessFlagsException()
else:
def unknown_command_handler(self, unknown_command): return command_entity['handler_func']()
self.unknown_command_func(unknown_command)
def set_router_as_main(self): def _validate_command(self, command: Command):
self._is_main_router = True command_name: str = command.get_string_entity()
if command_name in self.get_all_commands():
raise RepeatedCommandException()
if self._ignore_command_register:
if command_name.lower() in [x.lower() for x in self.get_all_commands()]:
raise RepeatedCommandException()
flags: FlagsGroup = command.get_flags()
if flags:
flags_name: list = [x.get_string_entity().lower() for x in flags]
if len(set(flags_name)) < len(flags_name):
raise RepeatedFlagNameException()
def get_registered_commands(self) -> list[dict[str, Callable[[], None] | str]]: @staticmethod
return self.processed_commands def _validate_func_args(command: Command, func: Callable):
registered_args = command.get_flags()
transferred_args = getfullargspec(func).args
if registered_args and transferred_args:
if len(transferred_args) != 1:
raise TooManyTransferredArgsException()
elif registered_args and not transferred_args:
raise RequiredArgumentNotPassedException()
elif not registered_args and transferred_args:
raise TooManyTransferredArgsException()
def set_ignore_command_register(self, ignore_command_register: bool):
self._ignore_command_register = ignore_command_register
def get_command_entities(self) -> list[dict[str, Callable[[], None] | Command]]:
return self._command_entities
def get_name(self) -> str: def get_name(self) -> str:
return self._name return self.name
def get_title(self) -> str:
return self.title
def get_router_info(self) -> dict:
return {
'title': self.title,
'name': self.name,
'ignore_command_register': self._ignore_command_register,
'attributes': {
'command_entities': self._command_entities,
'unknown_command_func': self._unknown_command_handler
}
}
def get_all_commands(self) -> list[str]:
all_commands: list[str] = []
for command_entity in self._command_entities:
all_commands.append(command_entity['command'].get_string_entity())
return all_commands
def get_all_flags(self) -> list[FlagsGroup]:
all_flags: list[FlagsGroup] = []
for command_entity in self._command_entities:
all_flags.append(command_entity['command'].get_flags())
return all_flags
+22 -7
View File
@@ -1,13 +1,28 @@
class InvalidCommandInstanceException(Exception):
def __str__(self):
return "Invalid Command Instance"
class InvalidDescriptionInstanceException(Exception): class InvalidDescriptionInstanceException(Exception):
def __str__(self): def __str__(self):
return "Invalid Description Instance" return "Invalid Description Instance"
class UnknownCommandHandlerHasAlreadyBeenCreatedException(Exception): class RepeatedCommandException(Exception):
def __str__(self): def __str__(self):
return "Only one unknown command handler can be declared" return "Commands in handler cannot be repeated"
class RepeatedFlagNameException(Exception):
def __str__(self):
return "Repeated flag name in register command"
class CurrentCommandDoesNotProcessFlagsException(Exception):
def __str__(self):
return "Current command does not process flags"
class TooManyTransferredArgsException(Exception):
def __str__(self):
return "Too many transferred arguments"
class RequiredArgumentNotPassedException(Exception):
def __str__(self):
return "Required argument not passed"
Generated
-7
View File
@@ -1,7 +0,0 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
package = []
[metadata]
lock-version = "2.1"
python-versions = ">=3.11"
content-hash = "f5666f5625d676c506924a57dc0520a1f3ed2b2c774baed3dc85353594f8473d"
+25 -4
View File
@@ -1,17 +1,38 @@
[project] [project]
name = "argenta" name = "argenta"
version = "0.1.1" version = "0.3.1"
description = "python library for creating cli apps" description = "python library for creating custom shells"
authors = [ authors = [
{name = "kolo",email = "kolo.is.main@gmail.com"} {name = "kolo",email = "kolo.is.main@gmail.com"}
] ]
license = {text = "MIT"} license = {text = "MIT"}
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [] # no dependencies
]
[tool.ruff]
exclude = [
".idea",
"venv",
".git",
"poetry.lock",
".__pycache__",
"tests"
]
[build-system] [build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"] requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.group.dev.dependencies]
art = "^6.4"
rich = "^13.9.4"
numpy = "^2.2.2"
word2number = "^1.1"
numexpr = "^2.10.2"
requests = "^2.32.3"
tqdm = "^4.67.1"
setuptools = "^75.8.0"
-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.1.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',
)
-5
View File
@@ -1,5 +0,0 @@
from argenta.app.entity import *
def test():
assert App().exit_command == 'q'
View File
@@ -0,0 +1,75 @@
import os
import shutil
import time
from zipfile import ZipFile
import requests
from ..local_data_func.get_script_release_tag import get_script_tag
from tqdm import tqdm
class UpdateScript:
GITHUB_REPO = "https://api.github.com/repos/koloideal/WordMath"
@staticmethod
def get_latest_release() -> dict:
response = requests.get(f"{UpdateScript.GITHUB_REPO}/releases/latest")
data = response.json()
return {'tag': data['tag_name'],
'url': data['zipball_url']}
@staticmethod
def download_new_release(zip_url):
response = requests.get(zip_url, stream=True)
response.raise_for_status()
total_size = int(response.headers.get("content-length", 0))
block_size = 1024
with tqdm(total=total_size, unit="B", unit_scale=True) as progress_bar:
with open('new_release.zip', "wb") as file:
for data in response.iter_content(block_size):
progress_bar.update(len(data))
file.write(data)
time.sleep(0.3)
with ZipFile('new_release.zip') as zip_file:
zip_file.extractall("new_release")
@staticmethod
def upgrade_script():
excluded_files = ['venv', '.venv', '.git', 'new_release']
for obj in os.listdir():
if obj not in excluded_files:
if os.path.isfile(obj):
os.remove(obj)
elif os.path.isdir(obj):
shutil.rmtree(obj)
new_release = os.listdir('new_release')
new_release_name = new_release[0] if new_release[0].startswith('koloideal-WordMath') else None
path = f'new_release/{new_release_name}'
all_files = os.listdir(path)
for file in all_files:
shutil.move(path + '/' + file, './' + file)
shutil.rmtree('new_release')
@staticmethod
def start_update() -> bool:
existing_release_tag: str = get_script_tag()
latest_release: dict = UpdateScript.get_latest_release()
latest_release_tag = latest_release['tag']
latest_release_url = latest_release['url']
if latest_release_tag != existing_release_tag:
#UpdateScript.download_new_release(latest_release_url)
#UpdateScript.upgrade_script()
return latest_release_tag
else:
return False
@@ -0,0 +1,68 @@
from numpy import ndarray
from word2number import w2n
from ..local_data_func.get_operator_synonyms import get_operator_synonyms
import numexpr
def word2num_math(string: str) -> ndarray | ZeroDivisionError | OverflowError:
operator_synonyms: dict[str, list[str]] = get_operator_synonyms()
def variables_to_operator(synonym):
for key in operator_synonyms.keys():
if synonym in operator_synonyms[key]:
return key
action = {
"plus": '+',
"minus": '-',
"divide": '/',
"multiply": '*',
"degree": '**',
}
result_string: str = ''
all_variables_of_operators: list[str] = [x for l in operator_synonyms.values() for x in l]
operators = {}
while True:
is_clear = True
number_of_operator = 1
for word in string.split():
try:
if word in all_variables_of_operators:
ope_index = string.index(word)
if ope_index in operators.keys():
is_clear = True
break
else:
raise ValueError
except ValueError:
continue
else:
is_clear = False
operators[number_of_operator] = variables_to_operator(word)
number_of_operator += 1
string = string.replace(word, "&&", 1)
if is_clear:
break
num_of_ope = 1
for number in string.split("&&"):
try:
int_num = w2n.word_to_num(number.strip())
except ValueError:
return ValueError("Invalid input expression")
if num_of_ope in operators.keys():
result_string += f'{int_num} {action[operators[num_of_ope]]} '
num_of_ope += 1
else:
result_string += f'{int_num}'
try:
result = numexpr.evaluate(result_string)
except ZeroDivisionError:
return ZeroDivisionError('Except divide by zero')
except OverflowError:
return OverflowError('Too big result')
else:
return result
View File
@@ -0,0 +1,10 @@
from rich.console import Console
console = Console()
def help_command():
console.print("[italic bold]The main functionality of the script is to convert an expression from a string "
"to a mathematical one and then calculate this expression. "
"Project GitHub: https://github.com/koloideal/WordMath[/italic bold]")
@@ -0,0 +1,20 @@
from rich.console import Console
from ...business_logic.word2num_math import word2num_math
console = Console()
print_line_separator = lambda: console.print('\n[bold blue]--------------------------------------[/bold blue]\n')
def start_solving_command():
while True:
console.print(
"\n[italic]Enter a string expression or [bold italic green] Q [/bold italic green] for exit:[/italic]")
string_expression = input()
if string_expression.lower() == 'q':
break
else:
print_line_separator()
console.print(
f'[bold green]Answer:[/bold green] [bold blue]{word2num_math(string_expression)}[/bold blue]')
print_line_separator()
@@ -0,0 +1,26 @@
import requests
from rich.console import Console
from ...business_logic.script_updater import UpdateScript
console = Console()
print_line_separator = lambda: console.print('\n[bold green]--------------------------------------[/bold green]\n')
def upgrade_command():
try:
requests.get('https://ya.ru')
except requests.exceptions.ConnectionError:
console.print('[bold red]No internet connection[/bold red]')
else:
latest_tag = UpdateScript.start_update()
if latest_tag:
print_line_separator()
console.print(f"[bold yellow]The newest version ({latest_tag}) of the script has been successfully installed![/bold yellow]")
print_line_separator()
console.print("[bold yellow]Rerun the script for the changes to take effect[/bold yellow]")
print_line_separator()
exit(0)
else:
console.print('[bold red]You have the latest version installed[/bold red]')
+44
View File
@@ -0,0 +1,44 @@
from rich.console import Console
from argenta.command.entity import Command
from argenta.command.params.flag.entity import Flag
from argenta.command.params.flag.flags_group.entity import FlagsGroup
from argenta.router import Router
from ..handlers.handlers_implementation.help_command import help_command
work_router: Router = Router(title='Work points:')
settings_router: Router = Router(title='Settings points:')
console = Console()
flagi = FlagsGroup(flags=[
Flag(flag_name='host',
flag_prefix='--', ),
Flag(flag_name='port',
flag_prefix='--', )
])
@work_router.command(Command(command='0', description='Get Help'))
def command_help():
print('Help command')
'''flags = args.get_flags()
for flag in flags:
print(f'name: "{flag.get_string_entity()}", value: "{flag.get_value()}"')'''
#help_command()
@work_router.command(Command(command='P', description='Start Solving', flags=flagi))
def command_start_solving(argrrtrts: FlagsGroup | None):
print('Solving...')
flags = argrrtrts.get_flags()
for flag in flags:
print(f'name: "{flag.get_string_entity()}", value: "{flag.get_value()}"')
#start_solving_command()
@settings_router.command(Command(command='G', description='Update WordMath'))
def command_update():
print('uefi')
# upgrade_command()
@@ -0,0 +1,44 @@
{
"minus": [
"without",
"lacking",
"deprived_of",
"bereft_of",
"destitute_of",
"minus"
],
"plus": [
"added_to",
"add",
"coupled_with",
"with_the_addition_of",
"plus"
],
"divide": [
"separate",
"part",
"split",
"divide",
"divide_by"
],
"multiply": [
"extend",
"expand",
"spread",
"build_up",
"accumulate",
"augment",
"multiply",
"times"
],
"degree": [
"stage",
"extent",
"grade",
"proportion",
"gradation",
"to_the_power_of",
"degree",
"to_the"
]
}
@@ -0,0 +1,3 @@
{
"tag": "v4.1.1"
}
@@ -0,0 +1,8 @@
import json
def get_operator_synonyms() -> dict[str, list[str]]:
with open("tests/mock_app/local_data/operator_synonyms.json", "r", encoding="utf-8") as file:
operator_synonyms: dict = json.load(file)
return operator_synonyms
@@ -0,0 +1,8 @@
import json
def get_script_tag() -> str:
with open("tests/mock_app/local_data/script_release_tag.json", "r", encoding="utf-8") as file:
script_release_tag: str = json.load(file)['tag']
return script_release_tag
+34
View File
@@ -0,0 +1,34 @@
from tests.mock_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
from art import text2art
from rich.console import Console
app: App = App(prompt='[italic white bold]What do you want to do(enter number of action)?',
line_separate='[bold green]\n---------------------------------------------\n',
print_func=Console().print,
command_group_description_separate='',
repeat_command_groups=False)
def main():
ascii_name: str = text2art('WordMath', font='nancyj')
initial_greeting: str = f'[bold red]\n\n{ascii_name}'
ascii_goodbye_message: str = text2art('GoodBye', font='small')
goodbye_message: str = f'[bold red]\n{ascii_goodbye_message}{' '*12}made by kolo\n'
app.include_router(work_router)
app.include_router(settings_router)
app.set_initial_message(initial_greeting)
app.set_farewell_message(goodbye_message)
app.set_invalid_input_flags_handler(lambda raw_command: print(f"Invalid input flags: {raw_command}"))
app.set_description_message_pattern('[bold red][{command}][/bold red] [blue]*=*=*[/blue] [bold yellow italic]{description}')
app.start_polling()
if __name__ == "__main__":
main()
View File
@@ -0,0 +1,29 @@
from pprint import pprint
from rich.console import Console
from argenta.router import Router
work_router: Router = Router(name='work')
settings_router: Router = Router(name='settings')
console = Console()
@work_router.command(command='a')
def command_help():
console.print('[bold red]command help [/bold red]')
@work_router.command(command='B', description='tester')
def command_start_solving():
console.print('[bold red]command start [/bold red]')
@settings_router.command(command='b')
def command_settings():
console.print('[bold red]command settings [/bold red]')
@work_router.unknown_command
def command_unknown_command(command):
console.print(f'[bold red]Unknown command: [/bold red]{command}')
+17
View File
@@ -0,0 +1,17 @@
from pprint import pprint
from tests.mock_default_app.handlers.routers import work_router, settings_router
from argenta.app.entity import App
app: App = App(ignore_command_register=False,
line_separate='\n-------------------------------\n')
def main():
app.include_router(work_router, is_main=True)
app.include_router(settings_router)
app.start_polling()
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
class Entity:
pass
class Person(Entity):
pass
a: Entity = Entity()
print(a)
a = Person()