Skip to content

Commit df1412c

Browse files
committed
Create a new minimal templated exception helper
1 parent dade604 commit df1412c

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

taskiq/error.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Minimal exception templating used by taskiq exceptions."""
2+
3+
from string import Formatter
4+
5+
6+
class Error(Exception):
7+
"""Base templated exception compatible with taskiq needs."""
8+
9+
__template__ = "Exception occurred"
10+
11+
@classmethod
12+
def _collect_annotations(cls) -> dict[str, object]:
13+
"""Collect all annotated fields from the class hierarchy."""
14+
annotations: dict[str, object] = {}
15+
for class_ in reversed(cls.__mro__):
16+
annotations.update(getattr(class_, "__annotations__", {}))
17+
return annotations
18+
19+
@classmethod
20+
def _format_fields(cls, names: set[str]) -> str:
21+
"""Format field names in a deterministic error message."""
22+
return ", ".join(f"'{name}'" for name in sorted(names))
23+
24+
@classmethod
25+
def _template_fields(cls, template: str) -> set[str]:
26+
"""Extract plain field names used in a format template."""
27+
fields: set[str] = set()
28+
for _, field_name, _, _ in Formatter().parse(template):
29+
if not field_name:
30+
continue
31+
field = field_name.split(".", maxsplit=1)[0].split("[", maxsplit=1)[0]
32+
fields.add(field)
33+
return fields
34+
35+
def __init__(self, **kwargs: object) -> None:
36+
annotations = self._collect_annotations()
37+
undeclared = set(kwargs) - set(annotations)
38+
if undeclared:
39+
raise TypeError(f"Undeclared arguments: {self._format_fields(undeclared)}")
40+
41+
missing = {
42+
field
43+
for field in annotations
44+
if field not in kwargs and not hasattr(type(self), field)
45+
}
46+
if missing:
47+
raise TypeError(f"Missing arguments: {self._format_fields(missing)}")
48+
49+
for key, value in kwargs.items():
50+
setattr(self, key, value)
51+
52+
template = getattr(type(self), "__template__", self.__template__)
53+
missing_annotations = self._template_fields(template) - set(annotations)
54+
if missing_annotations:
55+
raise ValueError(
56+
f"Fields must be annotated: {self._format_fields(missing_annotations)}",
57+
)
58+
59+
payload = {field: getattr(self, field) for field in annotations}
60+
super().__init__(template.format(**payload))
61+
62+
def __repr__(self) -> str:
63+
"""Represent exception with all declared fields."""
64+
annotations = self._collect_annotations()
65+
module = type(self).__module__
66+
qualname = type(self).__qualname__
67+
if not annotations:
68+
return f"{module}.{qualname}()"
69+
args = ", ".join(f"{field}={getattr(self, field)!r}" for field in annotations)
70+
return f"{module}.{qualname}({args})"

0 commit comments

Comments
 (0)