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 ...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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue