python-injection 0.10.3__tar.gz → 0.10.5__tar.gz

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.
Files changed (23) hide show
  1. {python_injection-0.10.3 → python_injection-0.10.5}/PKG-INFO +1 -4
  2. {python_injection-0.10.3 → python_injection-0.10.5}/README.md +0 -3
  3. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/type.py +11 -3
  4. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/hook.py +11 -3
  5. python_injection-0.10.5/injection/integrations/__init__.py +11 -0
  6. {python_injection-0.10.3 → python_injection-0.10.5}/injection/integrations/blacksheep.py +2 -6
  7. python_injection-0.10.5/injection/integrations/fastapi.py +37 -0
  8. {python_injection-0.10.3 → python_injection-0.10.5}/injection/testing/__init__.py +1 -4
  9. {python_injection-0.10.3 → python_injection-0.10.5}/injection/testing/__init__.pyi +1 -4
  10. {python_injection-0.10.3 → python_injection-0.10.5}/injection/utils.py +6 -8
  11. {python_injection-0.10.3 → python_injection-0.10.5}/pyproject.toml +4 -1
  12. python_injection-0.10.3/injection/integrations/__init__.py +0 -0
  13. {python_injection-0.10.3 → python_injection-0.10.5}/injection/__init__.py +0 -0
  14. {python_injection-0.10.3 → python_injection-0.10.5}/injection/__init__.pyi +0 -0
  15. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/__init__.py +0 -0
  16. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/__init__.py +0 -0
  17. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/event.py +0 -0
  18. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/invertible.py +0 -0
  19. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/lazy.py +0 -0
  20. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/common/threading.py +0 -0
  21. {python_injection-0.10.3 → python_injection-0.10.5}/injection/_core/module.py +0 -0
  22. {python_injection-0.10.3 → python_injection-0.10.5}/injection/exceptions.py +0 -0
  23. {python_injection-0.10.3 → python_injection-0.10.5}/injection/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-injection
3
- Version: 0.10.3
3
+ Version: 0.10.5
4
4
  Summary: Fast and easy dependency injection framework.
5
5
  Home-page: https://github.com/100nm/python-injection
6
6
  License: MIT
@@ -63,7 +63,6 @@ class Printer:
63
63
  self.history.append(message)
64
64
  print(message)
65
65
 
66
-
67
66
  @injectable
68
67
  class Service:
69
68
  def __init__(self, printer: Printer):
@@ -72,12 +71,10 @@ class Service:
72
71
  def hello(self):
73
72
  self.printer.print("Hello world!")
74
73
 
75
-
76
74
  @inject
77
75
  def main(service: Service):
78
76
  service.hello()
79
77
 
80
-
81
78
  if __name__ == "__main__":
82
79
  main()
83
80
  ```
@@ -37,7 +37,6 @@ class Printer:
37
37
  self.history.append(message)
38
38
  print(message)
39
39
 
40
-
41
40
  @injectable
42
41
  class Service:
43
42
  def __init__(self, printer: Printer):
@@ -46,12 +45,10 @@ class Service:
46
45
  def hello(self):
47
46
  self.printer.print("Hello world!")
48
47
 
49
-
50
48
  @inject
51
49
  def main(service: Service):
52
50
  service.hello()
53
51
 
54
-
55
52
  if __name__ == "__main__":
56
53
  main()
57
54
  ```
@@ -1,9 +1,17 @@
1
1
  from collections.abc import Awaitable, Callable, Iterable, Iterator
2
2
  from inspect import iscoroutinefunction, isfunction
3
3
  from types import GenericAlias, UnionType
4
- from typing import Annotated, Any, Union, get_args, get_origin, get_type_hints
5
-
6
- type TypeDef[T] = type[T] | GenericAlias
4
+ from typing import (
5
+ Annotated,
6
+ Any,
7
+ TypeAliasType,
8
+ Union,
9
+ get_args,
10
+ get_origin,
11
+ get_type_hints,
12
+ )
13
+
14
+ type TypeDef[T] = type[T] | TypeAliasType | GenericAlias
7
15
  type InputType[T] = TypeDef[T] | UnionType
8
16
  type TypeInfo[T] = InputType[T] | Callable[..., T] | Iterable[TypeInfo[T]]
9
17
 
@@ -2,7 +2,7 @@ import itertools
2
2
  from collections.abc import Callable, Generator, Iterator
3
3
  from dataclasses import dataclass, field
4
4
  from inspect import isclass, isgeneratorfunction
5
- from typing import Self
5
+ from typing import Any, Self
6
6
 
7
7
  from injection.exceptions import HookError
8
8
 
@@ -48,11 +48,11 @@ class Hook[**P, T]:
48
48
  handler: Callable[P, T],
49
49
  function: HookFunction[P, T],
50
50
  ) -> Callable[P, T]:
51
- if not isgeneratorfunction(function):
51
+ if not cls.__is_generator_function(function):
52
52
  return function # type: ignore[return-value]
53
53
 
54
54
  def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
55
- hook = function(*args, **kwargs)
55
+ hook: HookGenerator[T] = function(*args, **kwargs) # type: ignore[assignment]
56
56
 
57
57
  try:
58
58
  next(hook)
@@ -87,6 +87,14 @@ class Hook[**P, T]:
87
87
 
88
88
  return handler
89
89
 
90
+ @staticmethod
91
+ def __is_generator_function(obj: Any) -> bool:
92
+ for o in obj, getattr(obj, "__call__", None):
93
+ if isgeneratorfunction(o):
94
+ return True
95
+
96
+ return False
97
+
90
98
 
91
99
  def apply_hooks[**P, T](
92
100
  handler: Callable[P, T],
@@ -0,0 +1,11 @@
1
+ from importlib.util import find_spec
2
+ from typing import Literal
3
+
4
+ __all__ = ("_is_installed",)
5
+
6
+
7
+ def _is_installed(package: str, needed_for: object, /) -> Literal[True]:
8
+ if find_spec(package) is None:
9
+ raise RuntimeError(f"To use `{needed_for}`, {package} must be installed.")
10
+
11
+ return True
@@ -1,15 +1,11 @@
1
1
  from typing import Any, override
2
2
 
3
3
  from injection import Module, mod
4
+ from injection.integrations import _is_installed
4
5
 
5
6
  __all__ = ("InjectionServices",)
6
7
 
7
-
8
- try:
9
- import blacksheep # noqa: F401
10
- except ImportError as exc: # pragma: no cover
11
- raise ImportError(f"To use `{__name__}`, blacksheep must be installed.") from exc
12
- else:
8
+ if _is_installed("blacksheep", __name__):
13
9
  from rodi import ContainerProtocol
14
10
 
15
11
 
@@ -0,0 +1,37 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from injection import Module, mod
5
+ from injection.exceptions import InjectionError
6
+ from injection.integrations import _is_installed
7
+
8
+ __all__ = ("Inject",)
9
+
10
+ if _is_installed("fastapi", __name__):
11
+ from fastapi import Depends
12
+
13
+
14
+ def Inject[T](cls: type[T] | Any, /, module: Module | None = None) -> Any: # noqa: N802
15
+ """
16
+ Declare a FastAPI dependency with `python-injection`.
17
+ """
18
+
19
+ dependency: InjectionDependency[T] = InjectionDependency(cls, module or mod())
20
+ return Depends(dependency)
21
+
22
+
23
+ class InjectionDependency[T]:
24
+ __slots__ = ("__call__",)
25
+
26
+ __call__: Callable[[], T]
27
+
28
+ def __init__(self, cls: type[T] | Any, module: Module):
29
+ lazy_instance = module.get_lazy_instance(cls)
30
+ self.__call__ = lambda: self.__ensure(~lazy_instance, cls)
31
+
32
+ @staticmethod
33
+ def __ensure[_T](instance: _T | None, cls: type[_T] | Any) -> _T:
34
+ if instance is None:
35
+ raise InjectionError(f"`{cls}` is an unknown dependency.")
36
+
37
+ return instance
@@ -1,4 +1,3 @@
1
- from contextlib import ContextDecorator
2
1
  from typing import ContextManager, Final
3
2
 
4
3
  from injection import mod
@@ -22,7 +21,5 @@ test_injectable = mod(_TEST_PROFILE_NAME).injectable
22
21
  test_singleton = mod(_TEST_PROFILE_NAME).singleton
23
22
 
24
23
 
25
- def load_test_profile(
26
- *other_profile_names: str,
27
- ) -> ContextManager[None] | ContextDecorator:
24
+ def load_test_profile(*other_profile_names: str) -> ContextManager[None]:
28
25
  return load_profile(_TEST_PROFILE_NAME, *other_profile_names)
@@ -1,4 +1,3 @@
1
- from contextlib import ContextDecorator
2
1
  from typing import ContextManager
3
2
 
4
3
  import injection as _
@@ -9,9 +8,7 @@ test_constant = _.constant
9
8
  test_injectable = _.injectable
10
9
  test_singleton = _.singleton
11
10
 
12
- def load_test_profile(
13
- *additional_names: str,
14
- ) -> ContextManager[None] | ContextDecorator:
11
+ def load_test_profile(*other_profile_names: str) -> ContextManager[None]:
15
12
  """
16
13
  Context manager or decorator for temporary use test module.
17
14
  """
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Callable, Iterator
2
- from contextlib import ContextDecorator, contextmanager
2
+ from contextlib import contextmanager
3
3
  from importlib import import_module
4
4
  from pkgutil import walk_packages
5
5
  from types import ModuleType as PythonModule
@@ -10,11 +10,7 @@ from injection import mod
10
10
  __all__ = ("load_packages", "load_profile")
11
11
 
12
12
 
13
- def load_profile(
14
- name: str,
15
- /,
16
- *other_profile_names: str,
17
- ) -> ContextManager[None] | ContextDecorator:
13
+ def load_profile(name: str, /, *other_profile_names: str) -> ContextManager[None]:
18
14
  """
19
15
  Injection module initialization function based on profile name.
20
16
  A profile name is equivalent to an injection module name.
@@ -27,12 +23,14 @@ def load_profile(
27
23
 
28
24
  target = mod().unlock().init_modules(*modules)
29
25
 
26
+ del module, modules
27
+
30
28
  @contextmanager
31
- def teardown() -> Iterator[None]:
29
+ def cleaner() -> Iterator[None]:
32
30
  yield
33
31
  target.unlock().init_modules()
34
32
 
35
- return teardown()
33
+ return cleaner()
36
34
 
37
35
 
38
36
  def load_packages(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-injection"
3
- version = "0.10.3"
3
+ version = "0.10.5"
4
4
  description = "Fast and easy dependency injection framework."
5
5
  license = "MIT"
6
6
  authors = ["remimd"]
@@ -32,6 +32,8 @@ ruff = "*"
32
32
 
33
33
  [tool.poetry.group.test.dependencies]
34
34
  blacksheep = "*"
35
+ fastapi = "*"
36
+ httpx = "*"
35
37
  pydantic = "*"
36
38
  pytest = "*"
37
39
  pytest-asyncio = "*"
@@ -83,6 +85,7 @@ warn_required_dynamic_aliases = true
83
85
  [tool.pytest.ini_options]
84
86
  python_files = "test_*.py"
85
87
  addopts = "-p no:warnings --tb=short --cov=./ --cov-report term-missing:skip-covered"
88
+ asyncio_default_fixture_loop_scope = "session"
86
89
  asyncio_mode = "auto"
87
90
  testpaths = "**/tests/"
88
91