python-injection 0.13.1__py3-none-any.whl → 0.14.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
injection/__init__.pyi CHANGED
@@ -74,11 +74,19 @@ class Module:
74
74
  def __contains__(self, cls: _InputType[Any], /) -> bool: ...
75
75
  @property
76
76
  def is_locked(self) -> bool: ...
77
- def inject[**P, T](self, wrapped: Callable[P, T] = ..., /) -> Any:
77
+ def inject[**P, T](
78
+ self,
79
+ wrapped: Callable[P, T] = ...,
80
+ /,
81
+ *,
82
+ threadsafe: bool = ...,
83
+ ) -> Any:
78
84
  """
79
85
  Decorator applicable to a class or function. Inject function dependencies using
80
86
  parameter type annotations. If applied to a class, the dependencies resolved
81
87
  will be those of the `__init__` method.
88
+
89
+ With `threadsafe=True`, the injection logic is wrapped in a `threading.Lock`.
82
90
  """
83
91
 
84
92
  def injectable[**P, T](
@@ -166,6 +174,7 @@ class Module:
166
174
  self,
167
175
  wrapped: Callable[P, T],
168
176
  /,
177
+ threadsafe: bool = ...,
169
178
  ) -> Callable[P, T]: ...
170
179
  async def afind_instance[T](self, cls: _InputType[T]) -> T: ...
171
180
  def find_instance[T](self, cls: _InputType[T]) -> T:
@@ -295,6 +304,8 @@ class Module:
295
304
  Function to unlock the module by deleting cached instances of singletons.
296
305
  """
297
306
 
307
+ @contextmanager
308
+ def load_profile(self, *names: str) -> Iterator[None]: ...
298
309
  async def all_ready(self) -> None: ...
299
310
  def add_logger(self, logger: Logger) -> Self: ...
300
311
  @classmethod
@@ -1,17 +1,7 @@
1
- import asyncio
2
1
  from abc import abstractmethod
3
- from collections.abc import Awaitable, Callable, Coroutine, Generator
2
+ from collections.abc import Awaitable, Callable, Generator
4
3
  from dataclasses import dataclass
5
- from typing import Any, Protocol, runtime_checkable
6
-
7
-
8
- def run_sync[T](coroutine: Coroutine[Any, Any, T]) -> T:
9
- loop = asyncio.get_event_loop()
10
-
11
- try:
12
- return loop.run_until_complete(coroutine)
13
- finally:
14
- coroutine.close()
4
+ from typing import Any, NoReturn, Protocol, runtime_checkable
15
5
 
16
6
 
17
7
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -37,13 +27,13 @@ class Caller[**P, T](Protocol):
37
27
 
38
28
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
39
29
  class AsyncCaller[**P, T](Caller[P, T]):
40
- callable: Callable[P, Coroutine[Any, Any, T]]
30
+ callable: Callable[P, Awaitable[T]]
41
31
 
42
32
  async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
43
33
  return await self.callable(*args, **kwargs)
44
34
 
45
- def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
46
- return run_sync(self.callable(*args, **kwargs))
35
+ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> NoReturn:
36
+ raise RuntimeError("Can't call async callable synchronously.")
47
37
 
48
38
 
49
39
  @dataclass(repr=False, eq=False, frozen=True, slots=True)
@@ -1,56 +1,55 @@
1
- from collections.abc import Callable, Iterator, Mapping
2
- from types import MappingProxyType
1
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
2
+ from functools import partial
3
3
 
4
- from injection._core.common.invertible import Invertible
4
+ from injection._core.common.asynchronous import SimpleAwaitable
5
+ from injection._core.common.invertible import Invertible, SimpleInvertible
5
6
 
6
7
 
7
- class Lazy[T](Invertible[T]):
8
- __slots__ = ("__iterator", "__is_set")
8
+ def lazy[T](factory: Callable[..., T]) -> Invertible[T]:
9
+ def cache() -> Iterator[T]:
10
+ nonlocal factory
11
+ value = factory()
12
+ del factory
9
13
 
10
- __iterator: Iterator[T]
11
- __is_set: bool
14
+ while True:
15
+ yield value
12
16
 
13
- def __init__(self, factory: Callable[..., T]) -> None:
14
- self.__setup_cache(factory)
15
-
16
- def __invert__(self) -> T:
17
- return next(self.__iterator)
18
-
19
- @property
20
- def is_set(self) -> bool:
21
- return self.__is_set
17
+ getter = partial(next, cache())
18
+ return SimpleInvertible(getter)
22
19
 
23
- def __setup_cache(self, factory: Callable[..., T]) -> None:
24
- def infinite_yield() -> Iterator[T]:
25
- nonlocal factory
26
- cached = factory()
27
- self.__is_set = True
28
- del factory
29
20
 
30
- while True:
31
- yield cached
21
+ def alazy[T](factory: Callable[..., Awaitable[T]]) -> Awaitable[T]:
22
+ async def cache() -> AsyncIterator[T]:
23
+ nonlocal factory
24
+ value = await factory()
25
+ del factory
32
26
 
33
- self.__iterator = infinite_yield()
34
- self.__is_set = False
27
+ while True:
28
+ yield value
35
29
 
30
+ getter = partial(anext, cache())
31
+ return SimpleAwaitable(getter)
36
32
 
37
- class LazyMapping[K, V](Mapping[K, V]):
38
- __slots__ = ("__lazy",)
39
33
 
40
- __lazy: Lazy[Mapping[K, V]]
34
+ class Lazy[T](Invertible[T]):
35
+ __slots__ = ("__invertible", "__is_set")
41
36
 
42
- def __init__(self, iterator: Iterator[tuple[K, V]]) -> None:
43
- self.__lazy = Lazy(lambda: MappingProxyType(dict(iterator)))
37
+ __invertible: Invertible[T]
38
+ __is_set: bool
44
39
 
45
- def __getitem__(self, key: K, /) -> V:
46
- return (~self.__lazy)[key]
40
+ def __init__(self, factory: Callable[..., T]) -> None:
41
+ @lazy
42
+ def invertible() -> T:
43
+ value = factory()
44
+ self.__is_set = True
45
+ return value
47
46
 
48
- def __iter__(self) -> Iterator[K]:
49
- yield from ~self.__lazy
47
+ self.__invertible = invertible
48
+ self.__is_set = False
50
49
 
51
- def __len__(self) -> int:
52
- return len(~self.__lazy)
50
+ def __invert__(self) -> T:
51
+ return ~self.__invertible
53
52
 
54
53
  @property
55
54
  def is_set(self) -> bool:
56
- return self.__lazy.is_set
55
+ return self.__is_set
@@ -12,7 +12,7 @@ from typing import (
12
12
  runtime_checkable,
13
13
  )
14
14
 
15
- from injection._core.common.asynchronous import Caller, run_sync
15
+ from injection._core.common.asynchronous import Caller
16
16
  from injection._core.scope import Scope, get_active_scopes, get_scope
17
17
  from injection.exceptions import InjectionError
18
18
 
@@ -138,8 +138,8 @@ class AsyncCMScopedInjectable[T](ScopedInjectable[AsyncContextManager[T], T]):
138
138
  cm = await self.factory.acall()
139
139
  return await scope.aenter(cm)
140
140
 
141
- def build(self, scope: Scope) -> T:
142
- return run_sync(self.abuild(scope))
141
+ def build(self, scope: Scope) -> NoReturn:
142
+ raise RuntimeError("Can't use async context manager synchronously.")
143
143
 
144
144
 
145
145
  class CMScopedInjectable[T](ScopedInjectable[ContextManager[T], T]):
injection/_core/module.py CHANGED
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
4
3
  from abc import ABC, abstractmethod
5
- from collections import OrderedDict
4
+ from collections import OrderedDict, deque
6
5
  from collections.abc import (
7
6
  AsyncIterator,
8
7
  Awaitable,
@@ -12,7 +11,7 @@ from collections.abc import (
12
11
  Iterator,
13
12
  Mapping,
14
13
  )
15
- from contextlib import asynccontextmanager, contextmanager, suppress
14
+ from contextlib import asynccontextmanager, contextmanager, nullcontext, suppress
16
15
  from dataclasses import dataclass, field
17
16
  from enum import StrEnum
18
17
  from functools import partialmethod, singledispatchmethod, update_wrapper
@@ -26,7 +25,7 @@ from inspect import (
26
25
  )
27
26
  from inspect import signature as inspect_signature
28
27
  from logging import Logger, getLogger
29
- from queue import Empty, Queue
28
+ from threading import Lock
30
29
  from types import MethodType
31
30
  from typing import (
32
31
  Any,
@@ -50,7 +49,7 @@ from injection._core.common.asynchronous import (
50
49
  from injection._core.common.event import Event, EventChannel, EventListener
51
50
  from injection._core.common.invertible import Invertible, SimpleInvertible
52
51
  from injection._core.common.key import new_short_key
53
- from injection._core.common.lazy import Lazy, LazyMapping
52
+ from injection._core.common.lazy import Lazy, alazy, lazy
54
53
  from injection._core.common.type import (
55
54
  InputType,
56
55
  TypeInfo,
@@ -296,6 +295,9 @@ class Locator(Broker):
296
295
 
297
296
  async def all_ready(self) -> None:
298
297
  for injectable in self.__injectables:
298
+ if injectable.is_locked:
299
+ continue
300
+
299
301
  await injectable.aget_instance()
300
302
 
301
303
  def add_listener(self, listener: EventListener) -> Self:
@@ -511,7 +513,7 @@ class Module(Broker, EventListener):
511
513
  mode: Mode | ModeStr = Mode.get_default(),
512
514
  ) -> Any:
513
515
  def decorator(wp: type[T]) -> type[T]:
514
- lazy_instance = Lazy(wp)
516
+ lazy_instance = lazy(wp)
515
517
  self.injectable(
516
518
  lambda: ~lazy_instance,
517
519
  ignore_type_hint=True,
@@ -541,13 +543,19 @@ class Module(Broker, EventListener):
541
543
  )
542
544
  return self
543
545
 
544
- def inject[**P, T](self, wrapped: Callable[P, T] | None = None, /) -> Any:
546
+ def inject[**P, T](
547
+ self,
548
+ wrapped: Callable[P, T] | None = None,
549
+ /,
550
+ *,
551
+ threadsafe: bool = False,
552
+ ) -> Any:
545
553
  def decorator(wp: Callable[P, T]) -> Callable[P, T]:
546
554
  if isclass(wp):
547
- wp.__init__ = self.inject(wp.__init__)
555
+ wp.__init__ = self.inject(wp.__init__, threadsafe=threadsafe)
548
556
  return wp
549
557
 
550
- return self.make_injected_function(wp)
558
+ return self.make_injected_function(wp, threadsafe)
551
559
 
552
560
  return decorator(wrapped) if wrapped else decorator
553
561
 
@@ -556,6 +564,7 @@ class Module(Broker, EventListener):
556
564
  self,
557
565
  wrapped: Callable[P, T],
558
566
  /,
567
+ threadsafe: bool = ...,
559
568
  ) -> SyncInjectedFunction[P, T]: ...
560
569
 
561
570
  @overload
@@ -563,12 +572,13 @@ class Module(Broker, EventListener):
563
572
  self,
564
573
  wrapped: Callable[P, Awaitable[T]],
565
574
  /,
575
+ threadsafe: bool = ...,
566
576
  ) -> AsyncInjectedFunction[P, T]: ...
567
577
 
568
- def make_injected_function(self, wrapped, /): # type: ignore[no-untyped-def]
569
- metadata = InjectMetadata(wrapped)
578
+ def make_injected_function(self, wrapped, /, threadsafe=False): # type: ignore[no-untyped-def]
579
+ metadata = InjectMetadata(wrapped, threadsafe)
570
580
 
571
- @metadata.on_setup
581
+ @metadata.task
572
582
  def listen() -> None:
573
583
  metadata.update(self)
574
584
  self.add_listener(metadata)
@@ -646,12 +656,10 @@ class Module(Broker, EventListener):
646
656
 
647
657
  def aget_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
648
658
  if cache:
649
- coroutine = self.aget_instance(cls, default)
650
- return asyncio.ensure_future(coroutine)
659
+ return alazy(lambda: self.aget_instance(cls, default))
651
660
 
652
661
  function = self.make_injected_function(lambda instance=default: instance)
653
- metadata = function.__inject_metadata__
654
- metadata.set_owner(cls)
662
+ metadata = function.__inject_metadata__.set_owner(cls)
655
663
  return SimpleAwaitable(metadata.acall)
656
664
 
657
665
  @overload
@@ -674,11 +682,10 @@ class Module(Broker, EventListener):
674
682
 
675
683
  def get_lazy_instance(self, cls, default=None, *, cache=False): # type: ignore[no-untyped-def]
676
684
  if cache:
677
- return Lazy(lambda: self.get_instance(cls, default))
685
+ return lazy(lambda: self.get_instance(cls, default))
678
686
 
679
687
  function = self.make_injected_function(lambda instance=default: instance)
680
- metadata = function.__inject_metadata__
681
- metadata.set_owner(cls)
688
+ metadata = function.__inject_metadata__.set_owner(cls)
682
689
  return SimpleInvertible(metadata.call)
683
690
 
684
691
  def update[T](self, updater: Updater[T]) -> Self:
@@ -755,6 +762,23 @@ class Module(Broker, EventListener):
755
762
 
756
763
  return self
757
764
 
765
+ def load_profile(self, *names: str) -> ContextManager[None]:
766
+ modules = tuple(self.from_name(name) for name in names)
767
+
768
+ for module in modules:
769
+ module.unlock()
770
+
771
+ self.unlock().init_modules(*modules)
772
+
773
+ del module, modules
774
+
775
+ @contextmanager
776
+ def cleaner() -> Iterator[None]:
777
+ yield
778
+ self.unlock().init_modules()
779
+
780
+ return cleaner()
781
+
758
782
  async def all_ready(self) -> None:
759
783
  for broker in self.__brokers:
760
784
  await broker.all_ready()
@@ -832,10 +856,7 @@ InjectedFunction
832
856
 
833
857
  @dataclass(repr=False, frozen=True, slots=True)
834
858
  class Dependencies:
835
- mapping: Mapping[str, Injectable[Any]]
836
-
837
- def __bool__(self) -> bool:
838
- return bool(self.mapping)
859
+ lazy_mapping: Lazy[Mapping[str, Injectable[Any]]]
839
860
 
840
861
  def __iter__(self) -> Iterator[tuple[str, Any]]:
841
862
  for name, injectable in self.mapping.items():
@@ -849,10 +870,11 @@ class Dependencies:
849
870
 
850
871
  @property
851
872
  def are_resolved(self) -> bool:
852
- if isinstance(self.mapping, LazyMapping) and not self.mapping.is_set:
853
- return False
873
+ return self.lazy_mapping.is_set
854
874
 
855
- return bool(self)
875
+ @property
876
+ def mapping(self) -> Mapping[str, Injectable[Any]]:
877
+ return ~self.lazy_mapping
856
878
 
857
879
  async def aget_arguments(self) -> dict[str, Any]:
858
880
  return {key: value async for key, value in self}
@@ -861,12 +883,13 @@ class Dependencies:
861
883
  return dict(self)
862
884
 
863
885
  @classmethod
864
- def from_mapping(cls, mapping: Mapping[str, Injectable[Any]]) -> Self:
865
- return cls(mapping)
886
+ def from_iterable(cls, iterable: Iterable[tuple[str, Injectable[Any]]]) -> Self:
887
+ lazy_mapping = Lazy(lambda: dict(iterable))
888
+ return cls(lazy_mapping)
866
889
 
867
890
  @classmethod
868
891
  def empty(cls) -> Self:
869
- return cls.from_mapping({})
892
+ return cls.from_iterable(())
870
893
 
871
894
  @classmethod
872
895
  def resolve(
@@ -875,8 +898,8 @@ class Dependencies:
875
898
  module: Module,
876
899
  owner: type | None = None,
877
900
  ) -> Self:
878
- dependencies = LazyMapping(cls.__resolver(signature, module, owner))
879
- return cls.from_mapping(dependencies)
901
+ iterable = cls.__resolver(signature, module, owner)
902
+ return cls.from_iterable(iterable)
880
903
 
881
904
  @classmethod
882
905
  def __resolver(
@@ -916,22 +939,25 @@ class Arguments(NamedTuple):
916
939
  class InjectMetadata[**P, T](Caller[P, T], EventListener):
917
940
  __slots__ = (
918
941
  "__dependencies",
942
+ "__lock",
919
943
  "__owner",
920
- "__setup_queue",
921
944
  "__signature",
945
+ "__tasks",
922
946
  "__wrapped",
923
947
  )
924
948
 
925
949
  __dependencies: Dependencies
950
+ __lock: ContextManager[Any]
926
951
  __owner: type | None
927
- __setup_queue: Queue[Callable[..., Any]] | None
928
952
  __signature: Signature
953
+ __tasks: deque[Callable[..., Any]]
929
954
  __wrapped: Callable[P, T]
930
955
 
931
- def __init__(self, wrapped: Callable[P, T], /) -> None:
956
+ def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None:
932
957
  self.__dependencies = Dependencies.empty()
958
+ self.__lock = Lock() if threadsafe else nullcontext()
933
959
  self.__owner = None
934
- self.__setup_queue = Queue()
960
+ self.__tasks = deque()
935
961
  self.__wrapped = wrapped
936
962
 
937
963
  @property
@@ -964,13 +990,17 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
964
990
  return self.__bind(args, kwargs, additional_arguments)
965
991
 
966
992
  async def acall(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
967
- self.__setup()
968
- arguments = await self.abind(args, kwargs)
993
+ with self.__lock:
994
+ self.__run_tasks()
995
+ arguments = await self.abind(args, kwargs)
996
+
969
997
  return self.wrapped(*arguments.args, **arguments.kwargs)
970
998
 
971
999
  def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
972
- self.__setup()
973
- arguments = self.bind(args, kwargs)
1000
+ with self.__lock:
1001
+ self.__run_tasks()
1002
+ arguments = self.bind(args, kwargs)
1003
+
974
1004
  return self.wrapped(*arguments.args, **arguments.kwargs)
975
1005
 
976
1006
  def set_owner(self, owner: type) -> Self:
@@ -989,14 +1019,9 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
989
1019
  self.__dependencies = Dependencies.resolve(self.signature, module, self.__owner)
990
1020
  return self
991
1021
 
992
- def on_setup[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
1022
+ def task[**_P, _T](self, wrapped: Callable[_P, _T] | None = None, /) -> Any:
993
1023
  def decorator(wp: Callable[_P, _T]) -> Callable[_P, _T]:
994
- queue = self.__setup_queue
995
-
996
- if queue is None:
997
- raise RuntimeError(f"`{self}` is already up.")
998
-
999
- queue.put_nowait(wp)
1024
+ self.__tasks.append(wp)
1000
1025
  return wp
1001
1026
 
1002
1027
  return decorator(wrapped) if wrapped else decorator
@@ -1027,24 +1052,10 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener):
1027
1052
  bound.arguments = bound.arguments | additional_arguments | bound.arguments
1028
1053
  return Arguments(bound.args, bound.kwargs)
1029
1054
 
1030
- def __close_setup_queue(self) -> None:
1031
- self.__setup_queue = None
1032
-
1033
- def __setup(self) -> None:
1034
- if (queue := self.__setup_queue) is None:
1035
- return
1036
-
1037
- while True:
1038
- try:
1039
- task = queue.get_nowait()
1040
- except Empty:
1041
- break
1042
-
1055
+ def __run_tasks(self) -> None:
1056
+ while tasks := self.__tasks:
1057
+ task = tasks.popleft()
1043
1058
  task()
1044
- queue.task_done()
1045
-
1046
- queue.join()
1047
- self.__close_setup_queue()
1048
1059
 
1049
1060
 
1050
1061
  class InjectedFunction[**P, T](ABC):
injection/_core/scope.py CHANGED
@@ -34,12 +34,12 @@ class _ScopeState:
34
34
  default_factory=lambda: ContextVar(f"scope@{new_short_key()}"),
35
35
  init=False,
36
36
  )
37
- __references: set[Scope] = field(
38
- default_factory=set,
37
+ __default: Scope | None = field(
38
+ default=None,
39
39
  init=False,
40
40
  )
41
- __shared_value: Scope | None = field(
42
- default=None,
41
+ __references: set[Scope] = field(
42
+ default_factory=set,
43
43
  init=False,
44
44
  )
45
45
 
@@ -47,8 +47,8 @@ class _ScopeState:
47
47
  def active_scopes(self) -> Iterator[Scope]:
48
48
  yield from self.__references
49
49
 
50
- if shared_value := self.__shared_value:
51
- yield shared_value
50
+ if default := self.__default:
51
+ yield default
52
52
 
53
53
  @contextmanager
54
54
  def bind_contextual_scope(self, scope: Scope) -> Iterator[None]:
@@ -69,15 +69,15 @@ class _ScopeState:
69
69
  "are defined on the same name."
70
70
  )
71
71
 
72
- self.__shared_value = scope
72
+ self.__default = scope
73
73
 
74
74
  try:
75
75
  yield
76
76
  finally:
77
- self.__shared_value = None
77
+ self.__default = None
78
78
 
79
79
  def get_scope(self) -> Scope | None:
80
- return self.__context_var.get(self.__shared_value)
80
+ return self.__context_var.get(self.__default)
81
81
 
82
82
 
83
83
  __SCOPES: Final[defaultdict[str, _ScopeState]] = defaultdict(_ScopeState)
@@ -125,11 +125,8 @@ def _bind_scope(name: str, scope: Scope, shared: bool) -> Iterator[None]:
125
125
  state.bind_shared_scope(scope) if shared else state.bind_contextual_scope(scope)
126
126
  )
127
127
 
128
- try:
129
- with strategy:
130
- yield
131
- finally:
132
- scope.cache.clear()
128
+ with strategy:
129
+ yield
133
130
 
134
131
 
135
132
  @runtime_checkable
@@ -201,9 +198,7 @@ class SyncScope(BaseScope[ExitStack]):
201
198
  return self.delegate.__exit__(exc_type, exc_value, traceback)
202
199
 
203
200
  async def aenter[T](self, context_manager: AsyncContextManager[T]) -> NoReturn:
204
- raise ScopeError(
205
- "Synchronous scope doesn't support asynchronous context manager."
206
- )
201
+ raise ScopeError("Synchronous scope doesn't support async context manager.")
207
202
 
208
203
  def enter[T](self, context_manager: ContextManager[T]) -> T:
209
204
  return self.delegate.enter_context(context_manager)
injection/utils.py CHANGED
@@ -1,5 +1,4 @@
1
- from collections.abc import Callable, Iterable, Iterator
2
- from contextlib import contextmanager
1
+ from collections.abc import Callable, Collection, Iterator
3
2
  from importlib import import_module
4
3
  from importlib.util import find_spec
5
4
  from pkgutil import walk_packages
@@ -18,26 +17,12 @@ def load_profile(*names: str) -> ContextManager[None]:
18
17
  A profile name is equivalent to an injection module name.
19
18
  """
20
19
 
21
- modules = tuple(mod(module_name) for module_name in names)
22
-
23
- for module in modules:
24
- module.unlock()
25
-
26
- target = mod().unlock().init_modules(*modules)
27
-
28
- del module, modules
29
-
30
- @contextmanager
31
- def cleaner() -> Iterator[None]:
32
- yield
33
- target.unlock().init_modules()
34
-
35
- return cleaner()
20
+ return mod().load_profile(*names)
36
21
 
37
22
 
38
23
  def load_modules_with_keywords(
39
24
  *packages: PythonModule | str,
40
- keywords: Iterable[str] | None = None,
25
+ keywords: Collection[str] | None = None,
41
26
  ) -> dict[str, PythonModule]:
42
27
  """
43
28
  Function to import modules from a Python package if one of the keywords is contained in the Python script.
@@ -54,16 +39,16 @@ def load_modules_with_keywords(
54
39
  f"import {injection_package_name}",
55
40
  )
56
41
 
57
- b_keywords = tuple(keyword.encode() for keyword in keywords)
58
-
59
42
  def predicate(module_name: str) -> bool:
60
- if (spec := find_spec(module_name)) and (module_path := spec.origin):
61
- with open(module_path, "rb") as script:
62
- for line in script:
63
- line = b" ".join(line.split(b" ")).strip()
43
+ spec = find_spec(module_name)
44
+
45
+ if spec and (module_path := spec.origin):
46
+ with open(module_path, "r") as file:
47
+ python_script = file.read()
64
48
 
65
- if line and any(keyword in line for keyword in b_keywords):
66
- return True
49
+ return bool(python_script) and any(
50
+ keyword in python_script for keyword in keywords
51
+ )
67
52
 
68
53
  return False
69
54
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.13.1
3
+ Version: 0.14.0
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Project-URL: Repository, https://github.com/100nm/python-injection
6
6
  Author: remimd
@@ -1,25 +1,25 @@
1
1
  injection/__init__.py,sha256=X0vIAoN4MDlhR7YIkup1qHgqbOkwZ3PWgdSi7h-udaM,1049
2
- injection/__init__.pyi,sha256=LXBL7_ihiw6NyU94xWGdXkWwxvzRBoLYBSOv88PV2WM,9640
2
+ injection/__init__.pyi,sha256=f3EhwOaJCnf37YZQdANvbUsAq360zn-62zFLNLJxvoQ,9916
3
3
  injection/exceptions.py,sha256=T__732aXxWWUz0sKc39ySteyolCS5tpqQC0oCnzUF2E,917
4
4
  injection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- injection/utils.py,sha256=SZBzmVAwRL-kilS2eRt9MCeNAL4zeaj_7Rm_2NLBzg0,3175
5
+ injection/utils.py,sha256=s4wwe98owmgVIkxzAg-mu9yvFISWOzz1AvF7eF7-2xY,2752
6
6
  injection/_core/__init__.py,sha256=XERocCxCZBxPGIaOR37yeiQOZyvjHQ6a4rgRmlkUSuU,1367
7
7
  injection/_core/descriptors.py,sha256=7fSHlgAqmgR_Uta8KocBapOt1Xyj2dI7RY9ZdoStTzw,726
8
8
  injection/_core/hook.py,sha256=Qv505pr3kjOE6UitftlLh9JKX9OCNqZBRtHbFha1gqM,3130
9
- injection/_core/injectables.py,sha256=SApYnP6vG3b1qi_KJx6hkJyPoyTmZfooxqPpOsxWSVI,4482
10
- injection/_core/module.py,sha256=wOoHW_bImPSwoxi3i8_rFXP_HWFJ_S78UuY_5EIilFQ,30595
11
- injection/_core/scope.py,sha256=TKOoxCmi2FIoXis0irReVNayDTte4KjLrTq8ZDcrOSk,5583
9
+ injection/_core/injectables.py,sha256=GIumNp0TXf8Voxe1sCPhcqq2gyw4E_hl7I45IJ_tyHE,4512
10
+ injection/_core/module.py,sha256=kotXKuOFv8Gt31J1R8nSgVnu-83_MhIV4B5Etu8_kzo,30968
11
+ injection/_core/scope.py,sha256=uZps_kQsXDL9tXRzF1ECK8ua3cdEXiIAxrrhPt8JNqg,5461
12
12
  injection/_core/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- injection/_core/common/asynchronous.py,sha256=LlYMde_55osS3r8Sc3Fh5lgPp5UWmIfvyBzUeIDmMiM,1698
13
+ injection/_core/common/asynchronous.py,sha256=9bQDVRE6eqo9K0d5H9RzyFalf0WGoGP7cDrKDGbvZPI,1500
14
14
  injection/_core/common/event.py,sha256=XjzV8gxtGlGvzZs_ykvoC60qmdpd3RN08Eiqz5QUwes,1236
15
15
  injection/_core/common/invertible.py,sha256=YZlAdh6bNJgf1-74TRjwJTm8xrlgY95ZhOUGLSJ4XcY,482
16
16
  injection/_core/common/key.py,sha256=ghkZD-Y8Moz6SEPNgMh3xgsZUjDVq-XYAmXaCu5VuCA,80
17
- injection/_core/common/lazy.py,sha256=ZKx2O9CCFsF9F0SLM4zb7jSLksJUv-FBLCPlWltMN5k,1398
17
+ injection/_core/common/lazy.py,sha256=6xh5h0lmaNvl32V0WoX4VCTsNJ3zUJdWVqpLJ_YeIIU,1363
18
18
  injection/_core/common/type.py,sha256=QbBBhJp7i1p6gLzWX0TgofvfG7yDH-gHfEQcssVZeHo,2186
19
19
  injection/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  injection/integrations/fastapi.py,sha256=YHSs85_3m6TUVtOwUcV157b3UZJQIw_aXWAg199a-YE,594
21
21
  injection/testing/__init__.py,sha256=ALcKuDYNdslmpgqotZzSWrXAW0kNNFUs9nzfO1ZgIGc,783
22
22
  injection/testing/__init__.pyi,sha256=6rv5NOYHEaiKMd82E1IIc8lFlLV9ttY57DLiMqGTXt8,482
23
- python_injection-0.13.1.dist-info/METADATA,sha256=y4ls59BE2tC-G8V8chV5dMl4um6zcLtYOejDr03Z3jQ,2996
24
- python_injection-0.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- python_injection-0.13.1.dist-info/RECORD,,
23
+ python_injection-0.14.0.dist-info/METADATA,sha256=A25sC1rCj-HHSw-Sf57IIyHLhiPuDXUt53BdLBpKPnY,2996
24
+ python_injection-0.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ python_injection-0.14.0.dist-info/RECORD,,