python-injection 0.4.2__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of python-injection might be problematic. Click here for more details.

injection/__init__.py CHANGED
@@ -1,17 +1,19 @@
1
- from .core import Module, new_module
1
+ from .core import Injectable, Module, ModulePriorities
2
2
 
3
3
  __all__ = (
4
+ "Injectable",
4
5
  "Module",
6
+ "ModulePriorities",
7
+ "default_module",
5
8
  "get_instance",
6
9
  "inject",
7
10
  "injectable",
8
- "new_module",
9
11
  "singleton",
10
12
  )
11
13
 
12
- _default_module = new_module()
14
+ default_module = Module(f"{__name__}:default_module")
13
15
 
14
- get_instance = _default_module.get_instance
15
- inject = _default_module.inject
16
- injectable = _default_module.injectable
17
- singleton = _default_module.singleton
16
+ get_instance = default_module.get_instance
17
+ inject = default_module.inject
18
+ injectable = default_module.injectable
19
+ singleton = default_module.singleton
injection/common/event.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from contextlib import suppress
2
3
  from dataclasses import dataclass, field
3
4
  from weakref import WeakSet
4
5
 
@@ -17,7 +18,7 @@ class EventListener(ABC):
17
18
  raise NotImplementedError
18
19
 
19
20
 
20
- @dataclass(repr=False, frozen=True, slots=True)
21
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
21
22
  class EventChannel:
22
23
  __listeners: WeakSet[EventListener] = field(default_factory=WeakSet, init=False)
23
24
 
@@ -30,3 +31,9 @@ class EventChannel:
30
31
  def add_listener(self, listener: EventListener):
31
32
  self.__listeners.add(listener)
32
33
  return self
34
+
35
+ def remove_listener(self, listener: EventListener):
36
+ with suppress(KeyError):
37
+ self.__listeners.remove(listener)
38
+
39
+ return self
injection/common/lazy.py CHANGED
@@ -22,7 +22,7 @@ class Lazy(Generic[T]):
22
22
 
23
23
  def __setattr__(self, name: str, value: Any):
24
24
  if self.is_set:
25
- raise TypeError(f"`{repr(self)}` is frozen.") # pragma: no cover
25
+ raise TypeError(f"`{repr(self)}` is frozen.")
26
26
 
27
27
  return super().__setattr__(name, value)
28
28
 
injection/core/module.py CHANGED
@@ -2,14 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import inspect
4
4
  from abc import ABC, abstractmethod
5
+ from collections import ChainMap, OrderedDict
6
+ from contextlib import ContextDecorator, contextmanager
5
7
  from dataclasses import dataclass, field
6
- from functools import singledispatchmethod, wraps
7
- from inspect import Parameter, Signature
8
+ from enum import Enum, auto
9
+ from functools import cached_property, singledispatchmethod, wraps
10
+ from inspect import Signature, get_annotations
11
+ from logging import getLogger
8
12
  from types import MappingProxyType
9
13
  from typing import (
10
14
  Any,
11
15
  Callable,
12
- Generic,
13
16
  Iterable,
14
17
  Iterator,
15
18
  Mapping,
@@ -24,49 +27,145 @@ from typing import (
24
27
 
25
28
  from injection.common.event import Event, EventChannel, EventListener
26
29
  from injection.common.lazy import LazyMapping
27
- from injection.exceptions import NoInjectable
30
+ from injection.exceptions import ModuleError, NoInjectable
28
31
 
29
- __all__ = ("Module", "new_module")
32
+ __all__ = ("Injectable", "Module", "ModulePriorities")
33
+
34
+ _logger = getLogger(__name__)
30
35
 
31
36
  T = TypeVar("T")
32
37
 
33
38
 
34
- @dataclass(repr=False, frozen=True, slots=True)
35
- class Injectable(Generic[T], ABC):
36
- factory: Callable[[], T]
39
+ def _format_type(cls: type) -> str:
40
+ try:
41
+ return f"{cls.__module__}.{cls.__qualname__}"
42
+ except AttributeError:
43
+ return str(cls)
44
+
45
+
46
+ """
47
+ Events
48
+ """
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class ContainerEvent(Event, ABC):
53
+ on_container: Container
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class ContainerDependenciesUpdated(ContainerEvent):
58
+ references: set[type]
59
+
60
+ def __str__(self) -> str:
61
+ length = len(self.references)
62
+ formatted_references = ", ".join(
63
+ f"`{_format_type(reference)}`" for reference in self.references
64
+ )
65
+ return (
66
+ f"{length} container dependenc{'ies' if length > 1 else 'y'} have been "
67
+ f"updated{f': {formatted_references}' if formatted_references else ''}."
68
+ )
69
+
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class ModuleEvent(Event, ABC):
73
+ on_module: Module
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class ModuleEventProxy(ModuleEvent):
78
+ event: Event
79
+
80
+ def __str__(self) -> str:
81
+ return f"`{self.on_module}` has propagated an event: {self.origin}"
82
+
83
+ @cached_property
84
+ def origin(self) -> Event:
85
+ if isinstance(self.event, ModuleEventProxy):
86
+ return self.event.origin
87
+
88
+ return self.event
89
+
90
+ @property
91
+ def is_circular(self) -> bool:
92
+ origin = self.origin
93
+ return isinstance(origin, ModuleEvent) and origin.on_module is self.on_module
94
+
95
+ @property
96
+ def previous_module(self) -> Module | None:
97
+ if isinstance(self.event, ModuleEvent):
98
+ return self.event.on_module
99
+
100
+ return None
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class ModuleAdded(ModuleEvent):
105
+ module_added: Module
106
+
107
+ def __str__(self) -> str:
108
+ return f"`{self.on_module}` now uses `{self.module_added}`."
109
+
110
+
111
+ @dataclass(frozen=True, slots=True)
112
+ class ModuleRemoved(ModuleEvent):
113
+ module_removed: Module
114
+
115
+ def __str__(self) -> str:
116
+ return f"`{self.on_module}` no longer uses `{self.module_removed}`."
117
+
118
+
119
+ @dataclass(frozen=True, slots=True)
120
+ class ModulePriorityUpdated(ModuleEvent):
121
+ module_updated: Module
122
+ priority: ModulePriorities
123
+
124
+ def __str__(self) -> str:
125
+ return (
126
+ f"In `{self.on_module}`, the priority `{self.priority.name}` "
127
+ f"has been applied to `{self.module_updated}`."
128
+ )
129
+
130
+
131
+ """
132
+ Injectables
133
+ """
134
+
135
+
136
+ @runtime_checkable
137
+ class Injectable(Protocol[T]):
138
+ __slots__ = ()
37
139
 
38
140
  @abstractmethod
39
141
  def get_instance(self) -> T:
40
142
  raise NotImplementedError
41
143
 
42
144
 
43
- class NewInjectable(Injectable[T]):
145
+ @dataclass(repr=False, frozen=True, slots=True)
146
+ class _BaseInjectable(Injectable[T], ABC):
147
+ factory: Callable[[], T]
148
+
149
+
150
+ class NewInjectable(_BaseInjectable[T]):
44
151
  __slots__ = ()
45
152
 
46
153
  def get_instance(self) -> T:
47
154
  return self.factory()
48
155
 
49
156
 
50
- class SingletonInjectable(Injectable[T]):
51
- __instance_attribute: str = "_instance"
52
-
53
- __slots__ = (__instance_attribute,)
157
+ class SingletonInjectable(_BaseInjectable[T]):
158
+ @cached_property
159
+ def __instance(self) -> T:
160
+ return self.factory()
54
161
 
55
162
  def get_instance(self) -> T:
56
- cls = type(self)
57
-
58
- try:
59
- instance = getattr(self, cls.__instance_attribute)
60
- except AttributeError:
61
- instance = self.factory()
62
- object.__setattr__(self, cls.__instance_attribute, instance)
163
+ return self.__instance
63
164
 
64
- return instance
65
165
 
66
-
67
- @dataclass(repr=False, frozen=True, slots=True)
68
- class ContainerUpdated(Event):
69
- container: Container
166
+ """
167
+ Container
168
+ """
70
169
 
71
170
 
72
171
  @dataclass(repr=False, frozen=True, slots=True)
@@ -74,57 +173,241 @@ class Container:
74
173
  __data: dict[type, Injectable] = field(default_factory=dict, init=False)
75
174
  __channel: EventChannel = field(default_factory=EventChannel, init=False)
76
175
 
77
- def __getitem__(self, reference: type) -> Injectable:
176
+ def __getitem__(self, reference: type[T]) -> Injectable[T]:
78
177
  cls = origin if (origin := get_origin(reference)) else reference
79
178
 
80
179
  try:
81
180
  return self.__data[cls]
82
181
  except KeyError as exc:
83
- try:
84
- name = f"{cls.__module__}.{cls.__qualname__}"
85
- except AttributeError:
86
- name = repr(reference)
182
+ raise NoInjectable(f"No injectable for `{_format_type(cls)}`.") from exc
183
+
184
+ def set_multiple(self, references: Iterable[type], injectable: Injectable):
185
+ if not isinstance(references, set):
186
+ references = set(references)
187
+
188
+ if references:
189
+ new_values = (
190
+ (self.check_if_exists(reference), injectable)
191
+ for reference in references
192
+ )
193
+ self.__data.update(new_values)
194
+ event = ContainerDependenciesUpdated(self, references)
195
+ self.notify(event)
196
+
197
+ return self
198
+
199
+ def check_if_exists(self, reference: type) -> type:
200
+ if reference in self.__data:
201
+ raise RuntimeError(
202
+ "An injectable already exists for the reference "
203
+ f"class `{_format_type(reference)}`."
204
+ )
205
+
206
+ return reference
207
+
208
+ def add_listener(self, listener: EventListener):
209
+ self.__channel.add_listener(listener)
210
+ return self
211
+
212
+ def notify(self, event: Event):
213
+ self.__channel.dispatch(event)
214
+ return self
215
+
216
+
217
+ """
218
+ Module
219
+ """
220
+
221
+
222
+ class ModulePriorities(Enum):
223
+ HIGH = auto()
224
+ LOW = auto()
225
+
226
+
227
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
228
+ class Module(EventListener):
229
+ """
230
+ Object with isolated injection environment.
231
+
232
+ Modules have been designed to simplify unit test writing. So think carefully before
233
+ instantiating a new one. They could increase complexity unnecessarily if used
234
+ extensively.
235
+ """
236
+
237
+ name: str = field(default=None)
238
+ __container: Container = field(default_factory=Container, init=False)
239
+ __channel: EventChannel = field(default_factory=EventChannel, init=False)
240
+ __modules: OrderedDict[Module, None] = field(
241
+ default_factory=OrderedDict,
242
+ init=False,
243
+ )
244
+
245
+ def __post_init__(self):
246
+ self.__container.add_listener(self)
247
+
248
+ def __getitem__(self, reference: type[T], /) -> Injectable[T]:
249
+ return ChainMap(*self.__modules, self.__container)[reference]
250
+
251
+ def __setitem__(self, on: type | Iterable[type], injectable: Injectable, /):
252
+ references = on if isinstance(on, Iterable) else (on,)
253
+ self.__container.set_multiple(references, injectable)
87
254
 
88
- raise NoInjectable(f"No injectable for `{name}`.") from exc
255
+ def __str__(self) -> str:
256
+ return self.name or object.__str__(self)
89
257
 
90
258
  @property
91
259
  def inject(self) -> InjectDecorator:
260
+ """
261
+ Decorator applicable to a class or function. Inject function dependencies using
262
+ parameter type annotations. If applied to a class, the dependencies resolved
263
+ will be those of the `__init__` method.
264
+ """
265
+
92
266
  return InjectDecorator(self)
93
267
 
94
268
  @property
95
269
  def injectable(self) -> InjectableDecorator:
270
+ """
271
+ Decorator applicable to a class or function. It is used to indicate how the
272
+ injectable will be constructed. At injection time, a new instance will be
273
+ injected each time. Automatically injects constructor dependencies, can be
274
+ disabled with `auto_inject=False`.
275
+ """
276
+
96
277
  return InjectableDecorator(self, NewInjectable)
97
278
 
98
279
  @property
99
280
  def singleton(self) -> InjectableDecorator:
281
+ """
282
+ Decorator applicable to a class or function. It is used to indicate how the
283
+ singleton will be constructed. At injection time, the injected instance will
284
+ always be the same. Automatically injects constructor dependencies, can be
285
+ disabled with `auto_inject=False`.
286
+ """
287
+
100
288
  return InjectableDecorator(self, SingletonInjectable)
101
289
 
102
- def set_multiple(self, references: Iterable[type], injectable: Injectable):
103
- new_values = (
104
- (self.check_if_exists(reference), injectable) for reference in references
105
- )
106
- self.__data.update(new_values)
107
- self.__notify()
290
+ def get_instance(self, reference: type[T]) -> T | None:
291
+ """
292
+ Function used to retrieve an instance associated with the type passed in
293
+ parameter or return `None`.
294
+ """
295
+
296
+ try:
297
+ injectable = self[reference]
298
+ except KeyError:
299
+ return None
300
+
301
+ instance = injectable.get_instance()
302
+ return cast(reference, instance)
303
+
304
+ def use(
305
+ self,
306
+ module: Module,
307
+ priority: ModulePriorities = ModulePriorities.LOW,
308
+ ):
309
+ """
310
+ Function for using another module. Using another module replaces the module's
311
+ dependencies with those of the module used. If the dependency is not found, it
312
+ will be searched for in the module's dependency container.
313
+ """
314
+
315
+ if module is self:
316
+ raise ModuleError("Module can't be used by itself.")
317
+
318
+ if module in self.__modules:
319
+ raise ModuleError(f"`{self}` already uses `{module}`.")
320
+
321
+ self.__modules[module] = None
322
+ self.__move_module(module, priority)
323
+ module.add_listener(self)
324
+ event = ModuleAdded(self, module)
325
+ self.notify(event)
108
326
  return self
109
327
 
110
- def check_if_exists(self, reference: type) -> type:
111
- if reference in self.__data:
112
- raise RuntimeError(
113
- "An injectable already exists for the "
114
- f"reference class `{reference.__name__}`."
115
- )
328
+ def stop_using(self, module: Module):
329
+ """
330
+ Function to remove a module in use.
331
+ """
116
332
 
117
- return reference
333
+ try:
334
+ self.__modules.pop(module)
335
+ except KeyError:
336
+ ...
337
+ else:
338
+ module.remove_listener(self)
339
+ event = ModuleRemoved(self, module)
340
+ self.notify(event)
341
+
342
+ return self
343
+
344
+ @contextmanager
345
+ def use_temporarily(
346
+ self,
347
+ module: Module,
348
+ priority: ModulePriorities = ModulePriorities.LOW,
349
+ ) -> ContextDecorator:
350
+ """
351
+ Context manager or decorator for temporary use of a module.
352
+ """
353
+
354
+ self.use(module, priority)
355
+ yield
356
+ self.stop_using(module)
357
+
358
+ def change_priority(self, module: Module, priority: ModulePriorities):
359
+ """
360
+ Function for changing the priority of a module in use.
361
+ There are two priority values:
362
+
363
+ * **LOW**: The module concerned becomes the least important of the modules used.
364
+ * **HIGH**: The module concerned becomes the most important of the modules used.
365
+ """
366
+
367
+ self.__move_module(module, priority)
368
+ event = ModulePriorityUpdated(self, module, priority)
369
+ self.notify(event)
370
+ return self
118
371
 
119
372
  def add_listener(self, listener: EventListener):
120
373
  self.__channel.add_listener(listener)
121
374
  return self
122
375
 
123
- def __notify(self):
124
- event = ContainerUpdated(self)
376
+ def remove_listener(self, listener: EventListener):
377
+ self.__channel.remove_listener(listener)
378
+ return self
379
+
380
+ def on_event(self, event: Event, /):
381
+ self_event = ModuleEventProxy(self, event)
382
+
383
+ if self_event.is_circular:
384
+ raise ModuleError(
385
+ "Circular dependency between two modules: "
386
+ f"`{self_event.previous_module}` and `{self}`."
387
+ )
388
+
389
+ self.notify(self_event)
390
+
391
+ def notify(self, event: Event):
392
+ _logger.debug(f"{event}")
125
393
  self.__channel.dispatch(event)
126
394
  return self
127
395
 
396
+ def __move_module(self, module: Module, priority: ModulePriorities):
397
+ last = priority == ModulePriorities.LOW
398
+
399
+ try:
400
+ self.__modules.move_to_end(module, last=last)
401
+ except KeyError as exc:
402
+ raise ModuleError(
403
+ f"`{module}` can't be found in the modules used by `{self}`."
404
+ ) from exc
405
+
406
+
407
+ """
408
+ Binder
409
+ """
410
+
128
411
 
129
412
  @dataclass(repr=False, frozen=True, slots=True)
130
413
  class Dependencies:
@@ -150,20 +433,20 @@ class Dependencies:
150
433
  return cls.from_mapping({})
151
434
 
152
435
  @classmethod
153
- def resolve(cls, signature: Signature, container: Container):
154
- dependencies = LazyMapping(cls.__resolver(signature, container))
436
+ def resolve(cls, signature: Signature, module: Module):
437
+ dependencies = LazyMapping(cls.__resolver(signature, module))
155
438
  return cls.from_mapping(dependencies)
156
439
 
157
440
  @classmethod
158
441
  def __resolver(
159
442
  cls,
160
443
  signature: Signature,
161
- container: Container,
444
+ module: Module,
162
445
  ) -> Iterator[tuple[str, Injectable]]:
163
446
  for name, parameter in signature.parameters.items():
164
447
  try:
165
- injectable = container[parameter.annotation]
166
- except NoInjectable:
448
+ injectable = module[parameter.annotation]
449
+ except KeyError:
167
450
  continue
168
451
 
169
452
  yield name, injectable
@@ -186,48 +469,33 @@ class Binder(EventListener):
186
469
  return Arguments(args, kwargs)
187
470
 
188
471
  bound = self.__signature.bind_partial(*args, **kwargs)
189
- arguments = self.__dependencies.arguments | bound.arguments
190
-
191
- positional = []
192
- keywords = {}
472
+ bound.arguments = self.__dependencies.arguments | bound.arguments
473
+ return Arguments(bound.args, bound.kwargs)
193
474
 
194
- for name, parameter in self.__signature.parameters.items():
195
- try:
196
- value = arguments.pop(name)
197
- except KeyError:
198
- continue
199
-
200
- match parameter.kind:
201
- case Parameter.POSITIONAL_ONLY:
202
- positional.append(value)
203
- case Parameter.VAR_POSITIONAL:
204
- positional.extend(value)
205
- case Parameter.VAR_KEYWORD:
206
- keywords.update(value)
207
- case _:
208
- keywords[name] = value
209
-
210
- return Arguments(tuple(positional), keywords)
211
-
212
- def update(self, container: Container):
213
- self.__dependencies = Dependencies.resolve(self.__signature, container)
475
+ def update(self, module: Module):
476
+ self.__dependencies = Dependencies.resolve(self.__signature, module)
214
477
  return self
215
478
 
216
479
  @singledispatchmethod
217
480
  def on_event(self, event: Event, /):
218
- ... # pragma: no cover
481
+ ...
219
482
 
220
483
  @on_event.register
221
- def _(self, event: ContainerUpdated, /):
222
- self.update(event.container)
484
+ def _(self, event: ModuleEvent, /):
485
+ self.update(event.on_module)
486
+
487
+
488
+ """
489
+ Decorators
490
+ """
223
491
 
224
492
 
225
493
  @final
226
494
  @dataclass(repr=False, frozen=True, slots=True)
227
495
  class InjectDecorator:
228
- __container: Container
496
+ __module: Module
229
497
 
230
- def __call__(self, wrapped=None, /):
498
+ def __call__(self, wrapped: Callable[..., Any] = None, /):
231
499
  def decorator(wp):
232
500
  if isinstance(wp, type):
233
501
  return self.__class_decorator(wp)
@@ -237,9 +505,9 @@ class InjectDecorator:
237
505
  return decorator(wrapped) if wrapped else decorator
238
506
 
239
507
  def __decorator(self, function: Callable[..., Any], /) -> Callable[..., Any]:
240
- signature = inspect.signature(function)
241
- binder = Binder(signature).update(self.__container)
242
- self.__container.add_listener(binder)
508
+ signature = inspect.signature(function, eval_str=True)
509
+ binder = Binder(signature).update(self.__module)
510
+ self.__module.add_listener(binder)
243
511
 
244
512
  @wraps(function)
245
513
  def wrapper(*args, **kwargs):
@@ -257,21 +525,27 @@ class InjectDecorator:
257
525
  @final
258
526
  @dataclass(repr=False, frozen=True, slots=True)
259
527
  class InjectableDecorator:
260
- __container: Container
261
- __class: type[Injectable]
528
+ __module: Module
529
+ __class: type[_BaseInjectable]
262
530
 
263
531
  def __repr__(self) -> str:
264
- return f"<{self.__class.__name__} decorator>" # pragma: no cover
265
-
266
- def __call__(self, wrapped=None, /, on=None, auto_inject=True):
532
+ return f"<{self.__class.__qualname__} decorator>"
533
+
534
+ def __call__(
535
+ self,
536
+ wrapped: Callable[..., Any] = None,
537
+ /,
538
+ on: type | Iterable[type] = None,
539
+ auto_inject: bool = True,
540
+ ):
267
541
  def decorator(wp):
268
542
  if auto_inject:
269
- wp = self.__container.inject(wp)
543
+ wp = self.__module.inject(wp)
270
544
 
271
- @lambda fn: fn()
545
+ @lambda function: function()
272
546
  def references():
273
- if isinstance(wp, type):
274
- yield wp
547
+ if reference := self.__get_reference(wp):
548
+ yield reference
275
549
 
276
550
  if on is None:
277
551
  return
@@ -280,56 +554,20 @@ class InjectableDecorator:
280
554
  else:
281
555
  yield on
282
556
 
283
- injectable = self.__class(wp)
284
- self.__container.set_multiple(references, injectable)
285
-
557
+ self.__module[references] = self.__class(wp)
286
558
  return wp
287
559
 
288
560
  return decorator(wrapped) if wrapped else decorator
289
561
 
562
+ @staticmethod
563
+ def __get_reference(wrapped: Callable[..., Any], /) -> type | None:
564
+ if isinstance(wrapped, type):
565
+ return wrapped
290
566
 
291
- @runtime_checkable
292
- class Module(Protocol):
293
- __slots__ = ()
294
-
295
- @abstractmethod
296
- def get_instance(self, *args, **kwargs):
297
- raise NotImplementedError
298
-
299
- @abstractmethod
300
- def inject(self, *args, **kwargs):
301
- raise NotImplementedError
302
-
303
- @abstractmethod
304
- def injectable(self, *args, **kwargs):
305
- raise NotImplementedError
306
-
307
- @abstractmethod
308
- def singleton(self, *args, **kwargs):
309
- raise NotImplementedError
310
-
311
-
312
- @dataclass(repr=False, frozen=True, slots=True)
313
- class InjectionModule:
314
- __container: Container = field(default_factory=Container, init=False)
315
-
316
- @property
317
- def inject(self) -> InjectDecorator:
318
- return self.__container.inject
319
-
320
- @property
321
- def injectable(self) -> InjectableDecorator:
322
- return self.__container.injectable
323
-
324
- @property
325
- def singleton(self) -> InjectableDecorator:
326
- return self.__container.singleton
327
-
328
- def get_instance(self, reference: type[T]) -> T:
329
- instance = self.__container[reference].get_instance()
330
- return cast(reference, instance)
567
+ elif callable(wrapped):
568
+ return_type = get_annotations(wrapped, eval_str=True).get("return")
331
569
 
570
+ if isinstance(return_type, type):
571
+ return return_type
332
572
 
333
- def new_module() -> Module:
334
- module = InjectionModule()
335
- return cast(Module, module)
573
+ return None
injection/exceptions.py CHANGED
@@ -1,4 +1,4 @@
1
- __all__ = ("InjectionError", "NoInjectable")
1
+ __all__ = ("InjectionError", "ModuleError", "NoInjectable")
2
2
 
3
3
 
4
4
  class InjectionError(Exception):
@@ -7,3 +7,7 @@ class InjectionError(Exception):
7
7
 
8
8
  class NoInjectable(KeyError, InjectionError):
9
9
  ...
10
+
11
+
12
+ class ModuleError(InjectionError):
13
+ ...
injection/utils.py CHANGED
@@ -6,6 +6,10 @@ __all__ = ("load_package",)
6
6
 
7
7
 
8
8
  def load_package(package: Package):
9
+ """
10
+ Function for importing all modules in a Python package.
11
+ """
12
+
9
13
  try:
10
14
  path = package.__path__
11
15
  except AttributeError as exc:
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.4.2
3
+ Version: 0.5.1
4
4
  Summary: Fast and easy dependency injection framework.
5
- Home-page: https://github.com/soon-app/python-injection
5
+ Home-page: https://github.com/simplysquare/python-injection
6
6
  License: MIT
7
7
  Keywords: dependencies,inject,injection
8
8
  Author: remimd
@@ -13,10 +13,10 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Dist: frozendict
16
- Project-URL: Repository, https://github.com/soon-app/python-injection
16
+ Project-URL: Repository, https://github.com/simplysquare/python-injection
17
17
  Description-Content-Type: text/markdown
18
18
 
19
- # How to use?
19
+ # Basic usage
20
20
 
21
21
  ## Create an injectable
22
22
 
@@ -108,12 +108,12 @@ class C(B):
108
108
  ## Recipes
109
109
 
110
110
  A recipe is a function that tells the injector how to construct the instance to be injected. It is important to specify
111
- the reference class(es) when defining the recipe.
111
+ the return type annotation when defining the recipe.
112
112
 
113
113
  ```python
114
114
  from injection import singleton
115
115
 
116
- @singleton(on=Singleton)
116
+ @singleton
117
117
  def my_recipe() -> Singleton:
118
118
  """ recipe implementation """
119
119
  ```
@@ -0,0 +1,11 @@
1
+ injection/__init__.py,sha256=4WiI0l-y8fDBm_Rt7-z9oZ5aJKApHvIyEBDhvN03TOA,423
2
+ injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ injection/common/event.py,sha256=0Jvfvws7bx-NxA32JrqeD-9ywBEgSmHtdEHbJNg_hAo,961
4
+ injection/common/lazy.py,sha256=TqFcozHOVY6OY9Y43oCXzNHydTITf3JkM5hzEn7Z4cw,1503
5
+ injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
6
+ injection/core/module.py,sha256=DC-iawfHKnNmNjTP7pIi3Na-v4jU9O2dQoLTrMaGnB0,15910
7
+ injection/exceptions.py,sha256=o_LN7rDeX0fRw2cdQlyxvS4OAO2y9P2aRiiBKXPwbCQ,204
8
+ injection/utils.py,sha256=Y_5EEbcpDNdaCaPq8g1e6lHcHRCTMWsGOnZYuHa46aQ,598
9
+ python_injection-0.5.1.dist-info/METADATA,sha256=DJT_0yMjoi9dZ82cfWIiEYUGKKgRzgspiNK4DyPUodQ,2842
10
+ python_injection-0.5.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
11
+ python_injection-0.5.1.dist-info/RECORD,,
injection/core/module.pyi DELETED
@@ -1,44 +0,0 @@
1
- from abc import abstractmethod
2
- from typing import Any, Callable, Iterable, Protocol, TypeVar, runtime_checkable
3
-
4
- _T = TypeVar("_T")
5
-
6
- @runtime_checkable
7
- class Module(Protocol):
8
- """
9
- Object with isolated injection environment.
10
- """
11
-
12
- @abstractmethod
13
- def get_instance(self, reference: type[_T]) -> _T:
14
- """
15
- Function used to retrieve an instance associated with the type passed in parameter or raise `NoInjectable`
16
- exception.
17
- """
18
- @abstractmethod
19
- def inject(self, wrapped: Callable[..., Any] = ..., /):
20
- """
21
- Decorator applicable to a class or function. Inject function dependencies using parameter type annotations. If
22
- applied to a class, the dependencies resolved will be those of the `__init__` method.
23
-
24
- Doesn't work with type annotations resolved by `__future__` module.
25
- """
26
- @abstractmethod
27
- def injectable(self, *, on: type | Iterable[type] = ..., auto_inject: bool = ...):
28
- """
29
- Decorator applicable to a class or function. It is used to indicate how the injectable will be constructed. At
30
- injection time, a new instance will be injected each time. Automatically injects constructor dependencies, can
31
- be disabled with `auto_inject=False`.
32
- """
33
- @abstractmethod
34
- def singleton(self, *, on: type | Iterable[type] = ..., auto_inject: bool = ...):
35
- """
36
- Decorator applicable to a class or function. It is used to indicate how the singleton will be constructed. At
37
- injection time, the injected instance will always be the same. Automatically injects constructor dependencies,
38
- can be disabled with `auto_inject=False`.
39
- """
40
-
41
- def new_module() -> Module:
42
- """
43
- Function to create a new injection module.
44
- """
injection/utils.pyi DELETED
@@ -1,6 +0,0 @@
1
- from types import ModuleType as Package
2
-
3
- def load_package(package: Package):
4
- """
5
- Function for importing all modules in a Python package.
6
- """
@@ -1,13 +0,0 @@
1
- injection/__init__.py,sha256=kcWDMrMTrUtrn0wDYSD1FbTP8G0WV1IAKhFDfHNI83c,340
2
- injection/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- injection/common/event.py,sha256=ncSFjFWCOsZczpfIkKtvA33hAmyTTcpvVpjCm9CJQmo,762
4
- injection/common/lazy.py,sha256=_mnEq8A2aFhgNYEaz8QNEfRhdlwVclgTGHg6koYeAiU,1523
5
- injection/core/__init__.py,sha256=zuf0ubI2dHnbjn1059eduhS-ACIkkROa6-dhp10krh0,22
6
- injection/core/module.py,sha256=Mj7h-tDoW4VEYkXEeDCWlRHdtEGJHQfPFGS5rh02pW8,9256
7
- injection/core/module.pyi,sha256=PHWQ92faA56FQCwhDzi1K9mZkbKAPXbm2Pru7VX8cTQ,1792
8
- injection/exceptions.py,sha256=bxWdH61sB6gRfAMltt4GdrPHh-1k6QqV8m3vKQN8O_o,144
9
- injection/utils.py,sha256=fSVNgUhsbiWzS8I8db9hWhlX6rv5mDJS-d1Vfb6EBMI,521
10
- injection/utils.pyi,sha256=xpSq678YijDe1SRO9_B2q4SW9WYCYEQgPUhRCTEwVlY,153
11
- python_injection-0.4.2.dist-info/METADATA,sha256=tAPJfNrnnVqoxHlkz47BXZSeypEQXiLgDDJQfjbubzc,2845
12
- python_injection-0.4.2.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
13
- python_injection-0.4.2.dist-info/RECORD,,