Improve filters factory resolve error (#718)

This commit is contained in:
Alex Root Junior 2021-10-06 00:10:46 +03:00 committed by GitHub
parent 275bd509a1
commit 45a1fb2749
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 33 deletions

10
CHANGES/717.feature Normal file
View 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)

View file

@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional
from pydantic import ValidationError
from ...exceptions import FiltersResolveError
from ...types import TelegramObject
from ..filters.base import BaseFilter
from .bases import (
@ -108,11 +109,13 @@ class TelegramEventObserver:
if not full_config:
return filters
validation_errors = []
for bound_filter in self._resolve_filters_chain():
# Try to initialize filter.
try:
f = bound_filter(**full_config)
except ValidationError:
except ValidationError as e:
validation_errors.append(e)
continue
# Clean full config to prevent to re-initialize another filter
@ -123,7 +126,16 @@ class TelegramEventObserver:
filters.append(f)
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

View file

@ -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.base import TelegramType
class TelegramAPIError(Exception):
class AiogramError(Exception):
pass
class DetailedAiogramError(AiogramError):
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__(
self,
method: TelegramMethod[TelegramType],
message: str,
) -> None:
super().__init__(message=message)
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):
@ -38,15 +47,14 @@ class TelegramRetryAfter(TelegramAPIError):
message: str,
retry_after: int,
) -> None:
super().__init__(method=method, message=message)
self.retry_after = retry_after
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"Flood control exceeded on method {type(method).__name__!r}"
if chat_id := getattr(method, "chat_id", None):
description += f" in chat {chat_id}"
description += f". Retry in {self.retry_after} seconds."
return description
description += f". Retry in {retry_after} seconds."
description += f"\nOriginal description: {message}"
super().__init__(method=method, message=description)
self.retry_after = retry_after
class TelegramMigrateToChat(TelegramAPIError):
@ -58,17 +66,13 @@ class TelegramMigrateToChat(TelegramAPIError):
message: str,
migrate_to_chat_id: int,
) -> 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)
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):
pass
@ -100,3 +104,17 @@ class RestartingTelegram(TelegramServerError):
class TelegramEntityTooLarge(TelegramNetworkError):
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

View file

@ -9,6 +9,7 @@ from aiogram.dispatcher.event.handler import HandlerObject
from aiogram.dispatcher.event.telegram import TelegramEventObserver
from aiogram.dispatcher.filters.base import BaseFilter
from aiogram.dispatcher.router import Router
from aiogram.exceptions import FiltersResolveError
from aiogram.types import Chat, Message, User
pytestmark = pytest.mark.asyncio
@ -94,15 +95,15 @@ class TestTelegramEventObserver:
assert any(isinstance(item, MyFilter1) for item in resolved)
# 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"})
# 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"})
# 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": ...})
def test_register(self):