Skip to content

Commit 620787e

Browse files
authored
feat: ✨ More precise exceptions
1 parent 1f4a736 commit 620787e

File tree

6 files changed

+52
-35
lines changed

6 files changed

+52
-35
lines changed

injection/common/formatting.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Any
2+
3+
__all__ = ("format_type",)
4+
5+
6+
def format_type(cls: type | Any) -> str:
7+
try:
8+
return f"{cls.__module__}.{cls.__qualname__}"
9+
except AttributeError:
10+
return str(cls)

injection/common/lazy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from collections.abc import Callable, Iterator, Mapping
12
from types import MappingProxyType, new_class
2-
from typing import Any, Callable, Generic, Iterator, Mapping, TypeVar
3+
from typing import Any, Generic, TypeVar
34

45
__all__ = ("Lazy", "LazyMapping")
56

injection/core/module.py

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,19 @@
22

33
import inspect
44
from abc import ABC, abstractmethod
5-
from collections import ChainMap, OrderedDict
6-
from contextlib import ContextDecorator, contextmanager
5+
from collections import OrderedDict
6+
from collections.abc import Callable, Iterable, Iterator, Mapping
7+
from contextlib import ContextDecorator, contextmanager, suppress
78
from dataclasses import dataclass, field
89
from enum import Enum, auto
910
from functools import cached_property, singledispatchmethod, wraps
1011
from inspect import Signature, get_annotations
1112
from logging import getLogger
1213
from types import MappingProxyType
13-
from typing import (
14-
Any,
15-
Callable,
16-
Iterable,
17-
Iterator,
18-
Mapping,
19-
NamedTuple,
20-
Protocol,
21-
TypeVar,
22-
cast,
23-
final,
24-
runtime_checkable,
25-
)
14+
from typing import Any, NamedTuple, Protocol, TypeVar, cast, final, runtime_checkable
2615

2716
from injection.common.event import Event, EventChannel, EventListener
17+
from injection.common.formatting import format_type
2818
from injection.common.lazy import Lazy, LazyMapping
2919
from injection.exceptions import (
3020
ModuleCircularUseError,
@@ -40,13 +30,6 @@
4030
T = TypeVar("T")
4131

4232

43-
def _format_type(cls: type) -> str:
44-
try:
45-
return f"{cls.__module__}.{cls.__qualname__}"
46-
except AttributeError:
47-
return str(cls)
48-
49-
5033
"""
5134
Events
5235
"""
@@ -64,7 +47,7 @@ class ContainerDependenciesUpdated(ContainerEvent):
6447
def __str__(self) -> str:
6548
length = len(self.references)
6649
formatted_references = ", ".join(
67-
f"`{_format_type(reference)}`" for reference in self.references
50+
f"`{format_type(reference)}`" for reference in self.references
6851
)
6952
return (
7053
f"{length} container dependenc{'ies' if length > 1 else 'y'} have been "
@@ -196,7 +179,7 @@ def __getitem__(self, reference: type[T], /) -> Injectable[T]:
196179
try:
197180
return self.__data[cls]
198181
except KeyError as exc:
199-
raise NoInjectable(f"No injectable for `{_format_type(cls)}`.") from exc
182+
raise NoInjectable(reference) from exc
200183

201184
def set_multiple(self, references: Iterable[type], injectable: Injectable):
202185
references = set(self.__get_origin(reference) for reference in references)
@@ -216,7 +199,7 @@ def check_if_exists(self, reference: type) -> type:
216199
if reference in self.__data:
217200
raise RuntimeError(
218201
"An injectable already exists for the reference "
219-
f"class `{_format_type(reference)}`."
202+
f"class `{format_type(reference)}`."
220203
)
221204

222205
return reference
@@ -266,7 +249,11 @@ def __post_init__(self):
266249
self.__container.add_listener(self)
267250

268251
def __getitem__(self, reference: type[T], /) -> Injectable[T]:
269-
return ChainMap(*self.__modules, self.__container)[reference]
252+
for getter in *self.__modules, self.__container:
253+
with suppress(KeyError):
254+
return getter[reference]
255+
256+
raise NoInjectable(reference)
270257

271258
def __setitem__(self, on: type | Iterable[type], injectable: Injectable, /):
272259
references = on if isinstance(on, Iterable) else (on,)

injection/exceptions.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
1+
from injection.common.formatting import format_type
2+
3+
__all__ = (
4+
"InjectionError",
5+
"NoInjectable",
6+
"ModuleError",
7+
"ModuleCircularUseError",
8+
"ModuleNotUsedError",
9+
)
10+
11+
112
class InjectionError(Exception):
2-
...
13+
__slots__ = ()
314

415

516
class NoInjectable(KeyError, InjectionError):
6-
...
17+
__slots__ = ("__reference",)
18+
19+
def __init__(self, reference: type):
20+
super().__init__(f"No injectable for `{format_type(reference)}`.")
21+
self.__reference = reference
22+
23+
@property
24+
def reference(self) -> type:
25+
return self.__reference
726

827

928
class ModuleError(InjectionError):
10-
...
29+
__slots__ = ()
1130

1231

1332
class ModuleCircularUseError(ModuleError):
14-
...
33+
__slots__ = ()
1534

1635

1736
class ModuleNotUsedError(KeyError, ModuleError):
18-
...
37+
__slots__ = ()

injection/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from importlib import import_module
22
from pkgutil import walk_packages
3-
from types import ModuleType as Package
3+
from types import ModuleType
44

55
__all__ = ("load_package",)
66

77

8-
def load_package(package: Package):
8+
def load_package(package: ModuleType):
99
"""
1010
Function for importing all modules in a Python package.
1111
"""

tests/helpers/event_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
from collections.abc import Iterator
12
from dataclasses import dataclass, field
2-
from typing import Iterator
33

44
from injection.common.event import Event, EventListener
55

0 commit comments

Comments
 (0)