mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-14 19:00:23 +00:00
Improve filters factory resolve error (#718)
This commit is contained in:
parent
275bd509a1
commit
45a1fb2749
4 changed files with 74 additions and 33 deletions
10
CHANGES/717.feature
Normal file
10
CHANGES/717.feature
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
Improved description of filters resolving error.
|
||||||
|
For example when you try to pass wrong type of argument to the filter but don't know why filter is not resolved now you can get error like this:
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
aiogram.exceptions.FiltersResolveError: Unknown keyword filters: {'content_types'}
|
||||||
|
Possible cases:
|
||||||
|
- 1 validation error for ContentTypesFilter
|
||||||
|
content_types
|
||||||
|
Invalid content types {'42'} is not allowed here (type=value_error)
|
||||||
|
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional
|
||||||
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
from ...exceptions import FiltersResolveError
|
||||||
from ...types import TelegramObject
|
from ...types import TelegramObject
|
||||||
from ..filters.base import BaseFilter
|
from ..filters.base import BaseFilter
|
||||||
from .bases import (
|
from .bases import (
|
||||||
|
|
@ -108,11 +109,13 @@ class TelegramEventObserver:
|
||||||
if not full_config:
|
if not full_config:
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
validation_errors = []
|
||||||
for bound_filter in self._resolve_filters_chain():
|
for bound_filter in self._resolve_filters_chain():
|
||||||
# Try to initialize filter.
|
# Try to initialize filter.
|
||||||
try:
|
try:
|
||||||
f = bound_filter(**full_config)
|
f = bound_filter(**full_config)
|
||||||
except ValidationError:
|
except ValidationError as e:
|
||||||
|
validation_errors.append(e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Clean full config to prevent to re-initialize another filter
|
# Clean full config to prevent to re-initialize another filter
|
||||||
|
|
@ -123,7 +126,16 @@ class TelegramEventObserver:
|
||||||
filters.append(f)
|
filters.append(f)
|
||||||
|
|
||||||
if full_config:
|
if full_config:
|
||||||
raise ValueError(f"Unknown keyword filters: {set(full_config.keys())}")
|
possible_cases = []
|
||||||
|
for error in validation_errors:
|
||||||
|
for sum_error in error.errors():
|
||||||
|
if sum_error["loc"][0] in full_config:
|
||||||
|
possible_cases.append(error)
|
||||||
|
break
|
||||||
|
|
||||||
|
raise FiltersResolveError(
|
||||||
|
unresolved_fields=set(full_config.keys()), possible_cases=possible_cases
|
||||||
|
)
|
||||||
|
|
||||||
return filters
|
return filters
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,37 @@
|
||||||
from typing import Optional
|
from textwrap import indent
|
||||||
|
from typing import List, Optional, Set
|
||||||
|
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from aiogram.methods import TelegramMethod
|
from aiogram.methods import TelegramMethod
|
||||||
from aiogram.methods.base import TelegramType
|
from aiogram.methods.base import TelegramType
|
||||||
|
|
||||||
|
|
||||||
class TelegramAPIError(Exception):
|
class AiogramError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DetailedAiogramError(AiogramError):
|
||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
|
|
||||||
|
def __init__(self, message: str) -> None:
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
message = self.message
|
||||||
|
if self.url:
|
||||||
|
message += f"\n(background on this error at: {self.url})"
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramAPIError(DetailedAiogramError):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
method: TelegramMethod[TelegramType],
|
method: TelegramMethod[TelegramType],
|
||||||
message: str,
|
message: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
super().__init__(message=message)
|
||||||
self.method = method
|
self.method = method
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def render_description(self) -> str:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
message = [self.render_description()]
|
|
||||||
if self.url:
|
|
||||||
message.append(f"(background on this error at: {self.url})")
|
|
||||||
return "\n".join(message)
|
|
||||||
|
|
||||||
|
|
||||||
class TelegramNetworkError(TelegramAPIError):
|
class TelegramNetworkError(TelegramAPIError):
|
||||||
|
|
@ -38,15 +47,14 @@ class TelegramRetryAfter(TelegramAPIError):
|
||||||
message: str,
|
message: str,
|
||||||
retry_after: int,
|
retry_after: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(method=method, message=message)
|
description = f"Flood control exceeded on method {type(method).__name__!r}"
|
||||||
self.retry_after = retry_after
|
if chat_id := getattr(method, "chat_id", None):
|
||||||
|
|
||||||
def render_description(self) -> str:
|
|
||||||
description = f"Flood control exceeded on method {type(self.method).__name__!r}"
|
|
||||||
if chat_id := getattr(self.method, "chat_id", None):
|
|
||||||
description += f" in chat {chat_id}"
|
description += f" in chat {chat_id}"
|
||||||
description += f". Retry in {self.retry_after} seconds."
|
description += f". Retry in {retry_after} seconds."
|
||||||
return description
|
description += f"\nOriginal description: {message}"
|
||||||
|
|
||||||
|
super().__init__(method=method, message=description)
|
||||||
|
self.retry_after = retry_after
|
||||||
|
|
||||||
|
|
||||||
class TelegramMigrateToChat(TelegramAPIError):
|
class TelegramMigrateToChat(TelegramAPIError):
|
||||||
|
|
@ -58,17 +66,13 @@ class TelegramMigrateToChat(TelegramAPIError):
|
||||||
message: str,
|
message: str,
|
||||||
migrate_to_chat_id: int,
|
migrate_to_chat_id: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
description = f"The group has been migrated to a supergroup with id {migrate_to_chat_id}"
|
||||||
|
if chat_id := getattr(method, "chat_id", None):
|
||||||
|
description += f" from {chat_id}"
|
||||||
|
description += f"\nOriginal description: {message}"
|
||||||
super().__init__(method=method, message=message)
|
super().__init__(method=method, message=message)
|
||||||
self.migrate_to_chat_id = migrate_to_chat_id
|
self.migrate_to_chat_id = migrate_to_chat_id
|
||||||
|
|
||||||
def render_description(self) -> str:
|
|
||||||
description = (
|
|
||||||
f"The group has been migrated to a supergroup with id {self.migrate_to_chat_id}"
|
|
||||||
)
|
|
||||||
if chat_id := getattr(self.method, "chat_id", None):
|
|
||||||
description += f" from {chat_id}"
|
|
||||||
return description
|
|
||||||
|
|
||||||
|
|
||||||
class TelegramBadRequest(TelegramAPIError):
|
class TelegramBadRequest(TelegramAPIError):
|
||||||
pass
|
pass
|
||||||
|
|
@ -100,3 +104,17 @@ class RestartingTelegram(TelegramServerError):
|
||||||
|
|
||||||
class TelegramEntityTooLarge(TelegramNetworkError):
|
class TelegramEntityTooLarge(TelegramNetworkError):
|
||||||
url = "https://core.telegram.org/bots/api#sending-files"
|
url = "https://core.telegram.org/bots/api#sending-files"
|
||||||
|
|
||||||
|
|
||||||
|
class FiltersResolveError(DetailedAiogramError):
|
||||||
|
def __init__(self, unresolved_fields: Set[str], possible_cases: List[ValidationError]) -> None:
|
||||||
|
possible_cases_str = "\n".join(
|
||||||
|
" - " + indent(str(e), " " * 4).lstrip() for e in possible_cases
|
||||||
|
)
|
||||||
|
message = f"Unknown keyword filters: {unresolved_fields}"
|
||||||
|
if possible_cases_str:
|
||||||
|
message += f"\n Possible cases:\n{possible_cases_str}"
|
||||||
|
|
||||||
|
super().__init__(message=message)
|
||||||
|
self.unresolved_fields = unresolved_fields
|
||||||
|
self.possible_cases = possible_cases
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from aiogram.dispatcher.event.handler import HandlerObject
|
||||||
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
||||||
from aiogram.dispatcher.filters.base import BaseFilter
|
from aiogram.dispatcher.filters.base import BaseFilter
|
||||||
from aiogram.dispatcher.router import Router
|
from aiogram.dispatcher.router import Router
|
||||||
|
from aiogram.exceptions import FiltersResolveError
|
||||||
from aiogram.types import Chat, Message, User
|
from aiogram.types import Chat, Message, User
|
||||||
|
|
||||||
pytestmark = pytest.mark.asyncio
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
@ -94,15 +95,15 @@ class TestTelegramEventObserver:
|
||||||
assert any(isinstance(item, MyFilter1) for item in resolved)
|
assert any(isinstance(item, MyFilter1) for item in resolved)
|
||||||
|
|
||||||
# Unknown filter
|
# Unknown filter
|
||||||
with pytest.raises(ValueError, match="Unknown keyword filters: {'@bad'}"):
|
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'@bad'}"):
|
||||||
assert observer.resolve_filters({"@bad": "very"})
|
assert observer.resolve_filters({"@bad": "very"})
|
||||||
|
|
||||||
# Unknown filter
|
# Unknown filter
|
||||||
with pytest.raises(ValueError, match="Unknown keyword filters: {'@bad'}"):
|
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'@bad'}"):
|
||||||
assert observer.resolve_filters({"test": "ok", "@bad": "very"})
|
assert observer.resolve_filters({"test": "ok", "@bad": "very"})
|
||||||
|
|
||||||
# Bad argument type
|
# Bad argument type
|
||||||
with pytest.raises(ValueError, match="Unknown keyword filters: {'test'}"):
|
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'test'}"):
|
||||||
assert observer.resolve_filters({"test": ...})
|
assert observer.resolve_filters({"test": ...})
|
||||||
|
|
||||||
def test_register(self):
|
def test_register(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue