Skip to content

Commit 13bbd83

Browse files
authored
feat: ✨ Python interface
1 parent 22b66a3 commit 13bbd83

File tree

6 files changed

+170
-130
lines changed

6 files changed

+170
-130
lines changed

conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

3-
from injection import Module, default_module
3+
from injection import default_module
4+
from injection.core import Module
45
from tests.helpers import EventHistory
56

67

injection/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from .core import Injectable, Module, ModulePriorities
1+
from .core import Module, ModulePriorities
22

33
__all__ = (
4-
"Injectable",
54
"Module",
65
"ModulePriorities",
76
"default_module",

injection/__init__.pyi

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from collections.abc import Callable, Iterable
2+
from contextlib import ContextDecorator
3+
from enum import Enum
4+
from typing import Any, ContextManager, Final, TypeVar, final
5+
6+
_T = TypeVar("_T")
7+
8+
default_module: Final[Module] = ...
9+
10+
get_instance = default_module.get_instance
11+
inject = default_module.inject
12+
injectable = default_module.injectable
13+
singleton = default_module.singleton
14+
15+
@final
16+
class Module:
17+
"""
18+
Object with isolated injection environment.
19+
20+
Modules have been designed to simplify unit test writing. So think carefully before
21+
instantiating a new one. They could increase complexity unnecessarily if used
22+
extensively.
23+
"""
24+
25+
def __init__(self, name: str = ...): ...
26+
def __contains__(self, cls: type, /) -> bool: ...
27+
def inject(self, wrapped: Callable[..., Any] = ..., /):
28+
"""
29+
Decorator applicable to a class or function. Inject function dependencies using
30+
parameter type annotations. If applied to a class, the dependencies resolved
31+
will be those of the `__init__` method.
32+
"""
33+
def injectable(
34+
self,
35+
wrapped: Callable[..., Any] = ...,
36+
/,
37+
*,
38+
on: type | Iterable[type] = ...,
39+
auto_inject: bool = ...,
40+
):
41+
"""
42+
Decorator applicable to a class or function. It is used to indicate how the
43+
injectable will be constructed. At injection time, a new instance will be
44+
injected each time. Automatically injects constructor dependencies, can be
45+
disabled with `auto_inject=False`.
46+
"""
47+
def singleton(
48+
self,
49+
wrapped: Callable[..., Any] = ...,
50+
/,
51+
*,
52+
on: type | Iterable[type] = ...,
53+
auto_inject: bool = ...,
54+
):
55+
"""
56+
Decorator applicable to a class or function. It is used to indicate how the
57+
singleton will be constructed. At injection time, the injected instance will
58+
always be the same. Automatically injects constructor dependencies, can be
59+
disabled with `auto_inject=False`.
60+
"""
61+
def get_instance(self, cls: type[_T]) -> _T | None:
62+
"""
63+
Function used to retrieve an instance associated with the type passed in
64+
parameter or return `None`.
65+
"""
66+
def use(self, module: Module, priority: ModulePriorities = ...):
67+
"""
68+
Function for using another module. Using another module replaces the module's
69+
dependencies with those of the module used. If the dependency is not found, it
70+
will be searched for in the module's dependency container.
71+
"""
72+
def stop_using(self, module: Module):
73+
"""
74+
Function to remove a module in use.
75+
"""
76+
def use_temporarily(
77+
self,
78+
module: Module,
79+
priority: ModulePriorities = ...,
80+
) -> ContextManager | ContextDecorator:
81+
"""
82+
Context manager or decorator for temporary use of a module.
83+
"""
84+
def change_priority(self, module: Module, priority: ModulePriorities):
85+
"""
86+
Function for changing the priority of a module in use.
87+
There are two priority values:
88+
89+
* **LOW**: The module concerned becomes the least important of the modules used.
90+
* **HIGH**: The module concerned becomes the most important of the modules used.
91+
"""
92+
def unlock(self):
93+
"""
94+
Function to unlock the module by deleting cached instances of singletons.
95+
"""
96+
97+
@final
98+
class ModulePriorities(Enum):
99+
HIGH = ...
100+
LOW = ...

injection/core/module.py

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,6 @@ def get_default(cls):
261261

262262
@dataclass(repr=False, eq=False, frozen=True, slots=True)
263263
class Module(EventListener):
264-
"""
265-
Object with isolated injection environment.
266-
267-
Modules have been designed to simplify unit test writing. So think carefully before
268-
instantiating a new one. They could increase complexity unnecessarily if used
269-
extensively.
270-
"""
271-
272264
name: str = field(default=None)
273265
__channel: EventChannel = field(default_factory=EventChannel, init=False)
274266
__container: Container = field(default_factory=Container, init=False)
@@ -298,34 +290,14 @@ def __str__(self) -> str:
298290

299291
@property
300292
def inject(self) -> InjectDecorator:
301-
"""
302-
Decorator applicable to a class or function. Inject function dependencies using
303-
parameter type annotations. If applied to a class, the dependencies resolved
304-
will be those of the `__init__` method.
305-
"""
306-
307293
return InjectDecorator(self)
308294

309295
@property
310296
def injectable(self) -> InjectableDecorator:
311-
"""
312-
Decorator applicable to a class or function. It is used to indicate how the
313-
injectable will be constructed. At injection time, a new instance will be
314-
injected each time. Automatically injects constructor dependencies, can be
315-
disabled with `auto_inject=False`.
316-
"""
317-
318297
return InjectableDecorator(self, NewInjectable)
319298

320299
@property
321300
def singleton(self) -> InjectableDecorator:
322-
"""
323-
Decorator applicable to a class or function. It is used to indicate how the
324-
singleton will be constructed. At injection time, the injected instance will
325-
always be the same. Automatically injects constructor dependencies, can be
326-
disabled with `auto_inject=False`.
327-
"""
328-
329301
return InjectableDecorator(self, SingletonInjectable)
330302

331303
@property
@@ -338,11 +310,6 @@ def __brokers(self) -> Iterator[Container | Module]:
338310
yield self.__container
339311

340312
def get_instance(self, cls: type[_T]) -> _T | None:
341-
"""
342-
Function used to retrieve an instance associated with the type passed in
343-
parameter or return `None`.
344-
"""
345-
346313
try:
347314
injectable = self[cls]
348315
except KeyError:
@@ -360,12 +327,6 @@ def use(
360327
module: Module,
361328
priority: ModulePriorities = ModulePriorities.get_default(),
362329
):
363-
"""
364-
Function for using another module. Using another module replaces the module's
365-
dependencies with those of the module used. If the dependency is not found, it
366-
will be searched for in the module's dependency container.
367-
"""
368-
369330
if module is self:
370331
raise ModuleError("Module can't be used by itself.")
371332

@@ -382,10 +343,6 @@ def use(
382343
return self
383344

384345
def stop_using(self, module: Module):
385-
"""
386-
Function to remove a module in use.
387-
"""
388-
389346
event = ModuleRemoved(self, module)
390347

391348
with suppress(KeyError):
@@ -401,23 +358,11 @@ def use_temporarily(
401358
module: Module,
402359
priority: ModulePriorities = ModulePriorities.get_default(),
403360
) -> ContextManager | ContextDecorator:
404-
"""
405-
Context manager or decorator for temporary use of a module.
406-
"""
407-
408361
self.use(module, priority)
409362
yield
410363
self.stop_using(module)
411364

412365
def change_priority(self, module: Module, priority: ModulePriorities):
413-
"""
414-
Function for changing the priority of a module in use.
415-
There are two priority values:
416-
417-
* **LOW**: The module concerned becomes the least important of the modules used.
418-
* **HIGH**: The module concerned becomes the most important of the modules used.
419-
"""
420-
421366
event = ModulePriorityUpdated(self, module, priority)
422367

423368
with self.notify(event):
@@ -426,10 +371,6 @@ def change_priority(self, module: Module, priority: ModulePriorities):
426371
return self
427372

428373
def unlock(self):
429-
"""
430-
Function to unlock the module by deleting cached instances of singletons.
431-
"""
432-
433374
for broker in self.__brokers:
434375
broker.unlock()
435376

@@ -581,8 +522,7 @@ def wrapper(*args, **kwargs):
581522
return wrapper
582523

583524
def __class_decorator(self, cls: type, /) -> type:
584-
init_function = type.__getattribute__(cls, "__init__")
585-
type.__setattr__(cls, "__init__", self.__decorator(init_function))
525+
cls.__init__ = self.__decorator(cls.__init__)
586526
return cls
587527

588528
def __new_binder(self, function: Callable[..., Any]) -> Binder:
@@ -605,6 +545,7 @@ def __call__(
605545
self,
606546
wrapped: Callable[..., Any] = None,
607547
/,
548+
*,
608549
on: type | Iterable[type] = None,
609550
auto_inject: bool = True,
610551
):
@@ -632,7 +573,7 @@ def classes():
632573
return decorator(wrapped) if wrapped else decorator
633574

634575
@staticmethod
635-
def __get_target_class(wrapped: Callable[..., Any], /) -> type | None:
576+
def __get_target_class(wrapped: Callable[..., Any]) -> type | None:
636577
if isinstance(wrapped, type):
637578
return wrapped
638579

0 commit comments

Comments
 (0)