ref: typehints, enum instead of raw string, abc and other (#1)

Full code coverage with annotations, fixing errors in various linters: ruff, wps, etc. Fixing errors in type checkers: ty, mypy, pyright. Formatting and bringing code to a consistent style, applying best practices in various aspects.
This commit is contained in:
kolo
2025-10-08 13:37:31 +03:00
committed by GitHub
parent 22f1171192
commit 73303b1c08
45 changed files with 983 additions and 996 deletions
+10 -1
View File
@@ -1,3 +1,12 @@
__all__ = ["App"]
__all__ = [
"App",
"PredefinedMessages",
"DynamicDividingLine",
"StaticDividingLine",
"AutoCompleter"
]
from argenta.app.models import App
from argenta.app.defaults import PredefinedMessages
from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine
from argenta.app.autocompleter.entity import AutoCompleter
+18 -19
View File
@@ -13,10 +13,10 @@ class AutoCompleter:
:param autocomplete_button: the button for auto-completion
:return: None
"""
self.history_filename = history_filename
self.autocomplete_button = autocomplete_button
self.history_filename: str | None = history_filename
self.autocomplete_button: str = autocomplete_button
def _complete(self, text, state) -> str | None:
def _complete(self, text: str, state: int) -> str | None:
"""
Private. Auto-completion function
:param text: part of the command being entered
@@ -24,7 +24,7 @@ class AutoCompleter:
:return: the desired candidate as str or None
"""
matches: list[str] = sorted(
cmd for cmd in self.get_history_items() if cmd.startswith(text)
cmd for cmd in _get_history_items() if cmd.startswith(text)
)
if len(matches) > 1:
common_prefix = matches[0]
@@ -38,7 +38,7 @@ class AutoCompleter:
i += 1
common_prefix = common_prefix[:i]
if state == 0:
readline.insert_text(common_prefix[len(text) :])
readline.insert_text(common_prefix[len(text) :])
readline.redisplay()
return None
elif len(matches) == 1:
@@ -54,10 +54,10 @@ class AutoCompleter:
"""
if self.history_filename:
if os.path.exists(self.history_filename):
readline.read_history_file(self.history_filename)
readline.read_history_file(self.history_filename)
else:
for line in all_commands:
readline.add_history(line)
readline.add_history(line)
readline.set_completer(self._complete)
readline.set_completer_delims(readline.get_completer_delims().replace(" ", ""))
@@ -69,7 +69,7 @@ class AutoCompleter:
:return: None
"""
if self.history_filename:
readline.write_history_file(self.history_filename)
readline.write_history_file(self.history_filename)
with open(self.history_filename, "r") as history_file:
raw_history = history_file.read()
pretty_history: list[str] = []
@@ -77,15 +77,14 @@ class AutoCompleter:
if line.split()[0] in all_commands:
pretty_history.append(line)
with open(self.history_filename, "w") as history_file:
history_file.write("\n".join(pretty_history))
_ = history_file.write("\n".join(pretty_history))
@staticmethod
def get_history_items() -> list[str] | list[Never]:
"""
Private. Returns a list of all commands entered by the user
:return: all commands entered by the user as list[str] | list[Never]
"""
return [
readline.get_history_item(i)
for i in range(1, readline.get_current_history_length() + 1)
]
def _get_history_items() -> list[str] | list[Never]:
"""
Private. Returns a list of all commands entered by the user
:return: all commands entered by the user as list[str] | list[Never]
"""
return [
readline.get_history_item(i)
for i in range(1, readline.get_current_history_length() + 1)
]
-1
View File
@@ -5,7 +5,6 @@ class PredefinedMessages(StrEnum):
"""
Public. A dataclass with predetermined messages for quick use
"""
USAGE = "[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]"
HELP = "[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]"
AUTOCOMPLETE = "[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>"
+5 -5
View File
@@ -8,7 +8,7 @@ class BaseDividingLine(ABC):
:param unit_part: the single part of the dividing line
:return: None
"""
self._unit_part = unit_part
self._unit_part: str = unit_part
def get_unit_part(self) -> str:
"""
@@ -22,7 +22,7 @@ class BaseDividingLine(ABC):
class StaticDividingLine(BaseDividingLine):
def __init__(self, unit_part: str = "-", length: int = 25) -> None:
def __init__(self, unit_part: str = "-", *, length: int = 25) -> None:
"""
Public. The static dividing line
:param unit_part: the single part of the dividing line
@@ -30,9 +30,9 @@ class StaticDividingLine(BaseDividingLine):
:return: None
"""
super().__init__(unit_part)
self.length = length
self.length: int = length
def get_full_static_line(self, is_override: bool) -> str:
def get_full_static_line(self, *, is_override: bool) -> str:
"""
Private. Returns the full line of the dividing line
:param is_override: has the default text layout been redefined
@@ -53,7 +53,7 @@ class DynamicDividingLine(BaseDividingLine):
"""
super().__init__(unit_part)
def get_full_dynamic_line(self, length: int, is_override: bool) -> str:
def get_full_dynamic_line(self, *, length: int, is_override: bool) -> str:
"""
Private. Returns the full line of the dividing line
:param length: the length of the dividing line
+128 -117
View File
@@ -1,29 +1,37 @@
from typing import Callable
from rich.console import Console
from rich.markup import escape
from art import text2art
from contextlib import redirect_stdout
import io
import re
from contextlib import redirect_stdout
from typing import Never, TypeAlias
from art import text2art # pyright: ignore[reportMissingTypeStubs, reportUnknownVariableType]
from rich.console import Console
from rich.markup import escape
from argenta.command.models import Command, InputCommand
from argenta.router import Router
from argenta.router.defaults import system_router
from argenta.app.autocompleter import AutoCompleter
from argenta.app.dividing_line.models import StaticDividingLine, DynamicDividingLine
from argenta.command.exceptions import (
UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException,
BaseInputCommandException,
from argenta.app.dividing_line.models import DynamicDividingLine, StaticDividingLine
from argenta.app.protocols import (
DescriptionMessageGenerator,
EmptyCommandHandler,
NonStandardBehaviorHandler,
Printer,
)
from argenta.app.registered_routers.entity import RegisteredRouters
from argenta.command.exceptions import (
EmptyInputCommandException,
InputCommandException,
RepeatedInputFlagsException,
UnprocessedInputFlagException,
)
from argenta.command.models import Command, InputCommand
from argenta.response import Response
from argenta.router import Router
from argenta.router.defaults import system_router
Matches: TypeAlias = list[str] | list[Never]
class BaseApp:
def __init__(self, prompt: str,
def __init__(self, *, prompt: str,
initial_message: str,
farewell_message: str,
exit_command: Command,
@@ -33,57 +41,44 @@ class BaseApp:
repeat_command_groups: bool,
override_system_messages: bool,
autocompleter: AutoCompleter,
print_func: Callable[[str], None]) -> None:
self._prompt = prompt
self._print_func = print_func
self._exit_command = exit_command
self._system_router_title = system_router_title
self._dividing_line = dividing_line
self._ignore_command_register = ignore_command_register
self._repeat_command_groups_description = repeat_command_groups
self._override_system_messages = override_system_messages
self._autocompleter = autocompleter
print_func: Printer) -> None:
self._prompt: str = prompt
self._print_func: Printer = print_func
self._exit_command: Command = exit_command
self._system_router_title: str | None = system_router_title
self._dividing_line: StaticDividingLine | DynamicDividingLine = dividing_line
self._ignore_command_register: bool = ignore_command_register
self._repeat_command_groups_description: bool = repeat_command_groups
self._override_system_messages: bool = override_system_messages
self._autocompleter: AutoCompleter = autocompleter
self._farewell_message = farewell_message
self._initial_message = initial_message
self._farewell_message: str = farewell_message
self._initial_message: str = initial_message
self._description_message_gen: Callable[[str, str], str] = lambda command, description: f"{command} *=*=* {description}"
self._description_message_gen: DescriptionMessageGenerator = lambda command, description: f"{command} *=*=* {description}"
self._registered_routers: RegisteredRouters = RegisteredRouters()
self._messages_on_startup: list[str] = []
self._matching_lower_triggers_with_routers: dict[str, Router] = {}
self._matching_default_triggers_with_routers: dict[str, Router] = {}
if self._ignore_command_register:
self._current_matching_triggers_with_routers: dict[str, Router] = self._matching_lower_triggers_with_routers
else:
self._current_matching_triggers_with_routers: dict[str, Router] = self._matching_default_triggers_with_routers
self._current_matching_triggers_with_routers: dict[str, Router] = self._matching_lower_triggers_with_routers if self._ignore_command_register else self._matching_default_triggers_with_routers
self._incorrect_input_syntax_handler: Callable[[str], None] = (
lambda raw_command: print_func(f"Incorrect flag syntax: {raw_command}")
)
self._repeated_input_flags_handler: Callable[[str], None] = (
lambda raw_command: print_func(f"Repeated input flags: {raw_command}")
)
self._empty_input_command_handler: Callable[[], None] = lambda: print_func(
"Empty input command"
)
self._unknown_command_handler: Callable[[InputCommand], None] = (
lambda command: print_func(f"Unknown command: {command.get_trigger()}")
)
self._exit_command_handler: Callable[[Response], None] = (
lambda response: print_func(self._farewell_message)
)
self._incorrect_input_syntax_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(f"Incorrect flag syntax: {_}")
self._repeated_input_flags_handler: NonStandardBehaviorHandler[str] = lambda _: print_func(f"Repeated input flags: {_}")
self._empty_input_command_handler: EmptyCommandHandler = lambda: print_func("Empty input command")
self._unknown_command_handler: NonStandardBehaviorHandler[InputCommand] = lambda _: print_func(f"Unknown command: {_.trigger}")
self._exit_command_handler: NonStandardBehaviorHandler[Response] = lambda _: print_func(self._farewell_message)
def set_description_message_pattern(self, _: Callable[[str, str], str]) -> None:
def set_description_message_pattern(self, _: DescriptionMessageGenerator, /) -> None:
"""
Public. Sets the output pattern of the available commands
:param _: output pattern of the available commands
:return: None
"""
self._description_message_gen: Callable[[str, str], str] = _
self._description_message_gen = _
def set_incorrect_input_syntax_handler(self, _: Callable[[str], None]) -> None:
def set_incorrect_input_syntax_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for incorrect flags when entering a command
:param _: handler for incorrect flags when entering a command
@@ -91,7 +86,7 @@ class BaseApp:
"""
self._incorrect_input_syntax_handler = _
def set_repeated_input_flags_handler(self, _: Callable[[str], None]) -> None:
def set_repeated_input_flags_handler(self, _: NonStandardBehaviorHandler[str], /) -> None:
"""
Public. Sets the handler for repeated flags when entering a command
:param _: handler for repeated flags when entering a command
@@ -99,7 +94,7 @@ class BaseApp:
"""
self._repeated_input_flags_handler = _
def set_unknown_command_handler(self, _: Callable[[str], None]) -> None:
def set_unknown_command_handler(self, _: NonStandardBehaviorHandler[InputCommand], /) -> None:
"""
Public. Sets the handler for unknown commands when entering a command
:param _: handler for unknown commands when entering a command
@@ -107,7 +102,7 @@ class BaseApp:
"""
self._unknown_command_handler = _
def set_empty_command_handler(self, _: Callable[[], None]) -> None:
def set_empty_command_handler(self, _: EmptyCommandHandler, /) -> None:
"""
Public. Sets the handler for empty commands when entering a command
:param _: handler for empty commands when entering a command
@@ -115,7 +110,7 @@ class BaseApp:
"""
self._empty_input_command_handler = _
def set_exit_command_handler(self, _: Callable[[], None]) -> None:
def set_exit_command_handler(self, _: NonStandardBehaviorHandler[Response], /) -> None:
"""
Public. Sets the handler for exit command when entering a command
:param _: handler for exit command when entering a command
@@ -131,11 +126,12 @@ class BaseApp:
for registered_router in self._registered_routers:
if registered_router.title:
self._print_func(registered_router.title)
for command_handler in registered_router.get_command_handlers():
for command_handler in registered_router.command_handlers:
handled_command = command_handler.handled_command
self._print_func(
self._description_message_gen(
command_handler.get_handled_command().get_trigger(),
command_handler.get_handled_command().get_description(),
handled_command.trigger,
handled_command.description,
)
)
self._print_func("")
@@ -146,16 +142,7 @@ class BaseApp:
:param text: framed text
:return: None
"""
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(
self._dividing_line.get_full_static_line(self._override_system_messages)
)
print(text.strip("\n"))
self._print_func(
self._dividing_line.get_full_static_line(self._override_system_messages)
)
elif isinstance(self._dividing_line, DynamicDividingLine):
if isinstance(self._dividing_line, DynamicDividingLine):
clear_text = re.sub(r"\u001b\[[0-9;]*m", "", text)
max_length_line = max([len(line) for line in clear_text.split("\n")])
max_length_line = (
@@ -168,15 +155,27 @@ class BaseApp:
self._print_func(
self._dividing_line.get_full_dynamic_line(
max_length_line, self._override_system_messages
length=max_length_line, is_override=self._override_system_messages
)
)
print(text.strip("\n"))
self._print_func(
self._dividing_line.get_full_dynamic_line(
max_length_line, self._override_system_messages
length=max_length_line, is_override=self._override_system_messages
)
)
elif isinstance(self._dividing_line, StaticDividingLine): # pyright: ignore[reportUnnecessaryIsInstance]
self._print_func(
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
print(text.strip("\n"))
self._print_func(
self._dividing_line.get_full_static_line(is_override=self._override_system_messages)
)
else:
raise NotImplementedError
def _is_exit_command(self, command: InputCommand) -> bool:
"""
@@ -184,20 +183,21 @@ class BaseApp:
:param command: command to check
:return: is it an exit command or not as bool
"""
trigger = command.trigger
exit_trigger = self._exit_command.trigger
if self._ignore_command_register:
if (
command.get_trigger().lower()
== self._exit_command.get_trigger().lower()
trigger.lower() == exit_trigger.lower()
):
return True
elif command.get_trigger().lower() in [
x.lower() for x in self._exit_command.get_aliases()
elif trigger.lower() in [
x.lower() for x in self._exit_command.aliases
]:
return True
else:
if command.get_trigger() == self._exit_command.get_trigger():
if trigger == exit_trigger:
return True
elif command.get_trigger() in self._exit_command.get_aliases():
elif trigger in self._exit_command.aliases:
return True
return False
@@ -207,7 +207,7 @@ class BaseApp:
:param command: command to check
:return: is it an unknown command or not as bool
"""
input_command_trigger = command.get_trigger()
input_command_trigger = command.trigger
if self._ignore_command_register:
if input_command_trigger.lower() in list(self._current_matching_triggers_with_routers.keys()):
return False
@@ -217,7 +217,7 @@ class BaseApp:
return True
def _error_handler(
self, error: BaseInputCommandException, raw_command: str
self, error: InputCommandException, raw_command: str
) -> None:
"""
Private. Handles parsing errors of the entered command
@@ -240,23 +240,25 @@ class BaseApp:
system_router.title = self._system_router_title
@system_router.command(self._exit_command)
def exit_command(response: Response) -> None:
def _(response: Response) -> None:
self._exit_command_handler(response)
if system_router not in self._registered_routers.get_registered_routers():
system_router.set_command_register_ignore(self._ignore_command_register)
if system_router not in self._registered_routers.registered_routers:
system_router.command_register_ignore = self._ignore_command_register
self._registered_routers.add_registered_router(system_router)
def _most_similar_command(self, unknown_command: str) -> str | None:
all_commands = list(self._current_matching_triggers_with_routers.keys())
matches: list[str] | list = sorted(
matches_startswith_unknown_command: Matches = sorted(
cmd for cmd in all_commands if cmd.startswith(unknown_command)
)
if not matches:
matches: list[str] | list = sorted(
cmd for cmd in all_commands if unknown_command.startswith(cmd)
)
matches_startswith_cmd: Matches = sorted(
cmd for cmd in all_commands if unknown_command.startswith(cmd)
)
matches: Matches = matches_startswith_unknown_command or matches_startswith_cmd
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
@@ -272,9 +274,9 @@ class BaseApp:
self._prompt = f"[italic dim bold]{self._prompt}"
self._initial_message = ("\n" + f"[bold red]{text2art(self._initial_message, font='tarty1')}" + "\n")
self._farewell_message = (
"[bold red]\n\n"
+ text2art(self._farewell_message, font="chanky")
+ "\n[/bold red]\n"
"[bold red]\n\n" +
str(text2art(self._farewell_message, font="chanky")) + # pyright: ignore[reportUnknownArgumentType]
"\n[/bold red]\n" +
"[red i]github.com/koloideal/Argenta[/red i] | [red bold i]made by kolo[/red bold i]\n"
)
self._description_message_gen = lambda command, description: (
@@ -287,7 +289,7 @@ class BaseApp:
self._empty_input_command_handler = lambda: self._print_func("[red bold]Empty input command")
def unknown_command_handler(command: InputCommand) -> None:
cmd_trg: str = command.get_trigger()
cmd_trg: str = command.trigger
mst_sim_cmd: str | None = self._most_similar_command(cmd_trg)
first_part_of_text = f"[red]Unknown command:[/red] [blue]{escape(cmd_trg)}[/blue]"
second_part_of_text = (
@@ -299,7 +301,7 @@ class BaseApp:
self._unknown_command_handler = unknown_command_handler
def pre_cycle_setup(self) -> None:
def _pre_cycle_setup(self) -> None:
"""
Private. Configures various aspects of the application before the start of the cycle
:return: None
@@ -307,8 +309,8 @@ class BaseApp:
self._setup_system_router()
for router_entity in self._registered_routers:
router_triggers = router_entity.get_triggers()
router_aliases = router_entity.get_aliases()
router_triggers = router_entity.triggers
router_aliases = router_entity.aliases
combined = router_triggers + router_aliases
for trigger in combined:
@@ -337,20 +339,28 @@ class BaseApp:
self._print_command_group_description()
AVAILABLE_DIVIDING_LINES: TypeAlias = StaticDividingLine | DynamicDividingLine
DEFAULT_DIVIDING_LINE: StaticDividingLine = StaticDividingLine()
DEFAULT_PRINT_FUNC: Printer = Console().print
DEFAULT_AUTOCOMPLETER: AutoCompleter = AutoCompleter()
DEFAULT_EXIT_COMMAND: Command = Command("Q", description="Exit command")
class App(BaseApp):
def __init__(
self,
prompt: str = "What do you want to do?\n",
self, *,
prompt: str = "What do you want to do?\n\n",
initial_message: str = "Argenta\n",
farewell_message: str = "\nSee you\n",
exit_command: Command = Command("Q", "Exit command"),
exit_command: Command = DEFAULT_EXIT_COMMAND,
system_router_title: str | None = "System points:",
ignore_command_register: bool = True,
dividing_line: StaticDividingLine | DynamicDividingLine = StaticDividingLine(),
dividing_line: AVAILABLE_DIVIDING_LINES = DEFAULT_DIVIDING_LINE,
repeat_command_groups: bool = True,
override_system_messages: bool = False,
autocompleter: AutoCompleter = AutoCompleter(),
print_func: Callable[[str], None] = Console().print,
autocompleter: AutoCompleter = DEFAULT_AUTOCOMPLETER,
print_func: Printer = DEFAULT_PRINT_FUNC,
) -> None:
"""
Public. The essence of the application itself.
@@ -387,7 +397,7 @@ class App(BaseApp):
Private. Starts the user input processing cycle
:return: None
"""
self.pre_cycle_setup()
self._pre_cycle_setup()
while True:
if self._repeat_command_groups_description:
self._print_command_group_description()
@@ -396,11 +406,11 @@ class App(BaseApp):
try:
input_command: InputCommand = InputCommand.parse(raw_command=raw_command)
except BaseInputCommandException as error:
with redirect_stdout(io.StringIO()) as f:
except InputCommandException as error:
with redirect_stdout(io.StringIO()) as stderr:
self._error_handler(error, raw_command)
res: str = f.getvalue()
self._print_framed_text(res)
stderr_result: str = stderr.getvalue()
self._print_framed_text(stderr_result)
continue
if self._is_exit_command(input_command):
@@ -409,29 +419,30 @@ class App(BaseApp):
return
if self._is_unknown_command(input_command):
with redirect_stdout(io.StringIO()) as f:
with redirect_stdout(io.StringIO()) as stdout:
self._unknown_command_handler(input_command)
res: str = f.getvalue()
self._print_framed_text(res)
stdout_res: str = stdout.getvalue()
self._print_framed_text(stdout_res)
continue
processing_router = self._current_matching_triggers_with_routers[input_command.get_trigger().lower()]
processing_router = self._current_matching_triggers_with_routers[input_command.trigger.lower()]
if processing_router.disable_redirect_stdout:
if isinstance(self._dividing_line, StaticDividingLine):
self._print_func(self._dividing_line.get_full_static_line(self._override_system_messages))
self._print_func(self._dividing_line.get_full_static_line(is_override=self._override_system_messages))
processing_router.finds_appropriate_handler(input_command)
self._print_func(self._dividing_line.get_full_static_line(self._override_system_messages))
self._print_func(self._dividing_line.get_full_static_line(is_override=self._override_system_messages))
else:
self._print_func(StaticDividingLine(self._dividing_line.get_unit_part()).get_full_static_line(self._override_system_messages))
dividing_line_unit_part: str = self._dividing_line.get_unit_part()
self._print_func(StaticDividingLine(dividing_line_unit_part).get_full_static_line(is_override=self._override_system_messages))
processing_router.finds_appropriate_handler(input_command)
self._print_func(StaticDividingLine(self._dividing_line.get_unit_part()).get_full_static_line(self._override_system_messages))
self._print_func(StaticDividingLine(dividing_line_unit_part).get_full_static_line(is_override=self._override_system_messages))
else:
with redirect_stdout(io.StringIO()) as f:
with redirect_stdout(io.StringIO()) as stdout:
processing_router.finds_appropriate_handler(input_command)
res: str = f.getvalue()
if res:
self._print_framed_text(res)
stdout_result: str = stdout.getvalue()
if stdout_result:
self._print_framed_text(stdout_result)
def include_router(self, router: Router) -> None:
"""
@@ -439,7 +450,7 @@ class App(BaseApp):
:param router: registered router
:return: None
"""
router.set_command_register_ignore(self._ignore_command_register)
router.command_register_ignore = self._ignore_command_register
self._registered_routers.add_registered_router(router)
def include_routers(self, *routers: Router) -> None:
+22
View File
@@ -0,0 +1,22 @@
from typing import Protocol, TypeVar
T = TypeVar('T', contravariant=True) # noqa: WPS111
class NonStandardBehaviorHandler(Protocol[T]):
def __call__(self, __param: T) -> None:
raise NotImplementedError
class EmptyCommandHandler(Protocol):
def __call__(self) -> None:
raise NotImplementedError
class Printer(Protocol):
def __call__(self, __text: str) -> None:
raise NotImplementedError
class DescriptionMessageGenerator(Protocol):
def __call__(self, __first_param: str, __second_param: str) -> str:
raise NotImplementedError
+7 -14
View File
@@ -1,34 +1,27 @@
from typing import Iterator
from typing import Iterator, Optional
from argenta.router import Router
class RegisteredRouters:
def __init__(self, registered_routers: list[Router] | None = None) -> None:
def __init__(self, registered_routers: Optional[list[Router]] = None) -> None:
"""
Private. Combines registered routers
:param registered_routers: list of the registered routers
:return: None
"""
self._registered_routers = registered_routers if registered_routers else []
self.registered_routers: list[Router] = registered_routers if registered_routers else []
def get_registered_routers(self) -> list[Router]:
"""
Private. Returns the registered routers
:return: registered routers as list[Router]
"""
return self._registered_routers
def add_registered_router(self, router: Router) -> None:
def add_registered_router(self, router: Router, /) -> None:
"""
Private. Adds a new registered router
:param router: registered router
:return: None
"""
self._registered_routers.append(router)
self.registered_routers.append(router)
def __iter__(self) -> Iterator[Router]:
return iter(self._registered_routers)
return iter(self.registered_routers)
def __next__(self) -> Router:
return next(iter(self._registered_routers))
return next(iter(self.registered_routers))