Add possibility to include router via string

This commit is contained in:
Alex Root Junior 2019-12-12 00:28:37 +02:00
parent b943ea2207
commit dadedc80a9
6 changed files with 86 additions and 3 deletions

View file

@ -104,7 +104,7 @@ test:
.PHONY: test-coverage .PHONY: test-coverage
test-coverage: test-coverage:
mkdir -p reports/tests/ mkdir -p reports/tests/
$(py) pytest --cov=aiogram --cov-config .coveragerc --html=reports/tests/index.html tests/ $(py) pytest --cov=aiogram --cov-config .coveragerc --html=reports/tests/index.html -p no:warnings tests/
$(py) coverage html -d reports/coverage $(py) coverage html -d reports/coverage
# ================================================================================================= # =================================================================================================

View file

@ -1,8 +1,9 @@
from __future__ import annotations from __future__ import annotations
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional, Union
from ..api.types import Chat, Update, User from ..api.types import Chat, Update, User
from ..utils.imports import import_module
from .event.observer import EventObserver, SkipHandler, TelegramEventObserver from .event.observer import EventObserver, SkipHandler, TelegramEventObserver
from .filters import BUILTIN_FILTERS from .filters import BUILTIN_FILTERS
@ -76,7 +77,13 @@ class Router:
self._parent_router = router self._parent_router = router
def include_router(self, router: Router) -> Router: def include_router(self, router: Union[Router, str]) -> Router:
if isinstance(router, str):
router = import_module(router)
if not isinstance(router, Router):
raise ValueError(
f"router should be instance of Router not {type(router).__class__.__name__}"
)
router.parent_router = self router.parent_router = self
self.sub_routers.append(router) self.sub_routers.append(router)
return router return router

23
aiogram/utils/imports.py Normal file
View file

@ -0,0 +1,23 @@
import importlib
from typing import Any
def import_module(target: str) -> Any:
if not isinstance(target, str):
raise ValueError(f"Target should be string not {type(target).__class__.__name__}")
module_name, _, attr_name = target.partition(":")
if not module_name or not attr_name:
raise ValueError(f'Import string "{target}" must be in format "<module>:<attribute>"')
try:
module = importlib.import_module(module_name)
except ImportError:
raise ValueError(f'Could not import module "{module_name}".')
try:
attribute = getattr(module, attr_name)
except AttributeError:
raise ValueError(f'Module "{module_name}" has no attribute "{attr_name}"')
return attribute

13
handlers.py Normal file
View file

@ -0,0 +1,13 @@
from typing import Any
from aiogram import Router
from aiogram.api.methods import SendMessage
from aiogram.dispatcher.handler.message import MessageHandler
router = Router()
@router.message_handler(commands=["test"])
class MyHandler(MessageHandler):
async def handle(self) -> Any:
return SendMessage(chat_id=self.chat.id, text="<b>PASS</b>")

View file

@ -20,6 +20,8 @@ from aiogram.api.types import (
from aiogram.dispatcher.event.observer import SkipHandler from aiogram.dispatcher.event.observer import SkipHandler
from aiogram.dispatcher.router import Router from aiogram.dispatcher.router import Router
importable_router = Router()
class TestRouter: class TestRouter:
def test_including_routers(self): def test_including_routers(self):
@ -50,6 +52,15 @@ class TestRouter:
assert router3.parent_router is router2 assert router3.parent_router is router2
assert router3.sub_routers == [] assert router3.sub_routers == []
def test_include_router_by_string(self):
router = Router()
router.include_router("tests.test_dispatcher.test_router:importable_router")
def test_include_router_by_string_bad_type(self):
router = Router()
with pytest.raises(ValueError, match=r"router should be instance of Router"):
router.include_router("tests.test_dispatcher.test_router:TestRouter")
def test_observers_config(self): def test_observers_config(self):
router = Router() router = Router()
assert router.update_handler.handlers assert router.update_handler.handlers

View file

@ -0,0 +1,29 @@
import pytest
import aiogram
from aiogram.utils.imports import import_module
class TestImports:
def test_bad_type(self):
with pytest.raises(ValueError, match=r"Target should be string not"):
import_module(42)
@pytest.mark.parametrize("value", ["module", "module:", ":attribute"])
def test_bad_format(self, value):
with pytest.raises(ValueError, match='must be in format "<module>:<attribute>"'):
import_module(value)
@pytest.mark.parametrize("value", ["module", "aiogram.KABOOM", "aiogram.KABOOM.TEST"])
def test_bad_value(self, value):
with pytest.raises(ValueError, match="Could not import module"):
import_module(f"{value}:attribute")
def test_has_no_attribute(self):
with pytest.raises(ValueError, match="has no attribute"):
import_module("aiogram:KABOOM")
def test_imported(self):
value = import_module("aiogram:__version__")
isinstance(value, str)
assert value == aiogram.__version__