Update documentation and code snippets

This commit is contained in:
2025-12-08 19:29:54 +03:00
parent 75b1efb259
commit 183f069766
6 changed files with 179 additions and 4 deletions
+2 -2
View File
@@ -29,8 +29,8 @@ html_static_path = ["_static"]
html_context = { html_context = {
"languages": [ "languages": [
("English", "/en/latest/%s/", "en"), ("English", "/en/latest/%s.html", "en"),
("Русский", "/ru/latest/%s/", "ru"), ("Русский", "/ru/latest/%s.html", "ru"),
] ]
} }
+24
View File
@@ -23,6 +23,7 @@ from argenta.command.exceptions import (
RepeatedInputFlagsException, RepeatedInputFlagsException,
UnprocessedInputFlagException, UnprocessedInputFlagException,
) )
from argenta.router.exceptions import RepeatedAliasNameException, RepeatedTriggerNameException
from argenta.command.models import Command, InputCommand from argenta.command.models import Command, InputCommand
from argenta.response import Response from argenta.response import Response
from argenta.router import Router from argenta.router import Router
@@ -273,6 +274,28 @@ class BaseApp:
self.system_router.command_register_ignore = self._ignore_command_register self.system_router.command_register_ignore = self._ignore_command_register
self.registered_routers.add_registered_router(self.system_router) self.registered_routers.add_registered_router(self.system_router)
def _validate_routers_for_collisions(self) -> None:
"""
Private. Validates that there are no trigger/alias collisions between routers
:return: None
:raises: RepeatedTriggerNameException or RepeatedAliasNameException if collision detected
"""
all_triggers: set[str] = set()
all_aliases: set[str] = set()
for router_entity in self.registered_routers:
trigger_collisions: set[str] = (all_triggers | all_aliases) & router_entity.triggers
if trigger_collisions:
raise RepeatedTriggerNameException()
alias_collisions: set[str] = (all_aliases | all_triggers) & router_entity.aliases
if alias_collisions:
raise RepeatedAliasNameException(alias_collisions)
all_triggers.update(router_entity.triggers)
all_aliases.update(router_entity.aliases)
def _most_similar_command(self, unknown_command: str) -> str | None: def _most_similar_command(self, unknown_command: str) -> str | None:
all_commands = list(self._current_matching_triggers_with_routers.keys()) all_commands = list(self._current_matching_triggers_with_routers.keys())
@@ -344,6 +367,7 @@ class BaseApp:
:return: None :return: None
""" """
self._setup_system_router() self._setup_system_router()
self._validate_routers_for_collisions()
for router_entity in self.registered_routers: for router_entity in self.registered_routers:
router_triggers = router_entity.triggers router_triggers = router_entity.triggers
+3
View File
@@ -80,6 +80,9 @@ class Router:
if command_name.lower() in self.triggers: if command_name.lower() in self.triggers:
raise RepeatedTriggerNameException() raise RepeatedTriggerNameException()
if command_name.lower() in self.aliases:
raise RepeatedAliasNameException({command_name.lower()})
if overlapping := (self.aliases | self.triggers) & set(map(lambda x: x.lower(), command.aliases)): if overlapping := (self.aliases | self.triggers) & set(map(lambda x: x.lower(), command.aliases)):
raise RepeatedAliasNameException(overlapping) raise RepeatedAliasNameException(overlapping)
+7 -1
View File
@@ -1,4 +1,10 @@
__all__ = ["RepeatedFlagNameException", "RequiredArgumentNotPassedException", "TriggerContainSpacesException"] __all__ = [
"RepeatedFlagNameException",
"RepeatedTriggerNameException",
"RepeatedAliasNameException",
"RequiredArgumentNotPassedException",
"TriggerContainSpacesException",
]
from typing import override from typing import override
+84
View File
@@ -221,6 +221,90 @@ def test_overlapping_aliases_raises_exception() -> None:
pass pass
def test_app_detects_trigger_collision_between_routers() -> None:
from argenta.router.exceptions import RepeatedTriggerNameException
app = App()
router1 = Router()
router2 = Router()
@router1.command('hello')
def handler1(_res: Response) -> None:
pass
@router2.command('hello')
def handler2(_res: Response) -> None:
pass
app.include_router(router1)
app.include_router(router2)
with pytest.raises(RepeatedTriggerNameException):
app._pre_cycle_setup()
def test_app_detects_alias_collision_between_routers() -> None:
app = App()
router1 = Router()
router2 = Router()
@router1.command(Command('hello', aliases={'hi'}))
def handler1(_res: Response) -> None:
pass
@router2.command(Command('world', aliases={'hi'}))
def handler2(_res: Response) -> None:
pass
app.include_router(router1)
app.include_router(router2)
with pytest.raises(RepeatedAliasNameException):
app._pre_cycle_setup()
def test_app_detects_trigger_alias_collision_between_routers() -> None:
app = App()
router1 = Router()
router2 = Router()
@router1.command('hello')
def handler1(_res: Response) -> None:
pass
@router2.command(Command('world', aliases={'hello'}))
def handler2(_res: Response) -> None:
pass
app.include_router(router1)
app.include_router(router2)
with pytest.raises(RepeatedAliasNameException):
app._pre_cycle_setup()
def test_app_detects_collision_case_insensitive() -> None:
from argenta.router.exceptions import RepeatedTriggerNameException
app = App()
router1 = Router()
router2 = Router()
@router1.command('Hello')
def handler1(_res: Response) -> None:
pass
@router2.command('hELLo')
def handler2(_res: Response) -> None:
pass
app.include_router(router1)
app.include_router(router2)
with pytest.raises(RepeatedTriggerNameException):
app._pre_cycle_setup()
# ============================================================================ # ============================================================================
# Tests for startup messages # Tests for startup messages
# ============================================================================ # ============================================================================
+59 -1
View File
@@ -9,8 +9,9 @@ from argenta.command.flag.flags import Flags, InputFlags
from argenta.command.flag.models import PossibleValues, ValidationStatus from argenta.command.flag.models import PossibleValues, ValidationStatus
from argenta.response.entity import Response from argenta.response.entity import Response
from argenta.router import Router from argenta.router import Router
from argenta.router.entity import _structuring_input_flags, _validate_func_args # pyright: ignore[reportPrivateUsage] from argenta.router.entity import _structuring_input_flags, _validate_func_args
from argenta.router.exceptions import ( from argenta.router.exceptions import (
RepeatedAliasNameException,
RepeatedFlagNameException, RepeatedFlagNameException,
RepeatedTriggerNameException, RepeatedTriggerNameException,
RequiredArgumentNotPassedException, RequiredArgumentNotPassedException,
@@ -247,3 +248,60 @@ def test_finds_appropriate_handler_executes_handler_with_flags_by_alias(capsys:
output = capsys.readouterr() output = capsys.readouterr()
assert "Hello World!" in output.out assert "Hello World!" in output.out
# ============================================================================
# Tests for alias and trigger collision detection
# ============================================================================
def test_validate_command_raises_error_for_alias_collision_with_existing_trigger() -> None:
router = Router()
@router.command('hello')
def handler(_res: Response) -> None:
pass
with pytest.raises(RepeatedAliasNameException):
@router.command(Command('world', aliases={'hello'}))
def handler2(_res: Response) -> None:
pass
def test_validate_command_raises_error_for_alias_collision_with_existing_alias() -> None:
router = Router()
@router.command(Command('hello', aliases={'hi'}))
def handler(_res: Response) -> None:
pass
with pytest.raises(RepeatedAliasNameException):
@router.command(Command('world', aliases={'hi'}))
def handler2(_res: Response) -> None:
pass
def test_validate_command_raises_error_for_trigger_collision_with_existing_alias() -> None:
router = Router()
@router.command(Command('hello', aliases={'hi'}))
def handler(_res: Response) -> None:
pass
with pytest.raises(RepeatedAliasNameException):
@router.command('hi')
def handler2(_res: Response) -> None:
pass
def test_validate_command_raises_error_for_alias_collision_case_insensitive() -> None:
router = Router()
@router.command(Command('hello', aliases={'Hi'}))
def handler(_res: Response) -> None:
pass
with pytest.raises(RepeatedAliasNameException):
@router.command(Command('world', aliases={'hI'}))
def handler2(_res: Response) -> None:
pass