python-injection 0.17.3__tar.gz → 0.18.0__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 (28) hide show
  1. {python_injection-0.17.3 → python_injection-0.18.0}/PKG-INFO +5 -2
  2. {python_injection-0.17.3 → python_injection-0.18.0}/README.md +2 -1
  3. python_injection-0.18.0/injection/entrypoint.py +147 -0
  4. {python_injection-0.17.3 → python_injection-0.18.0}/injection/loaders.py +2 -2
  5. {python_injection-0.17.3 → python_injection-0.18.0}/pyproject.toml +12 -3
  6. {python_injection-0.17.3 → python_injection-0.18.0}/.gitignore +0 -0
  7. {python_injection-0.17.3 → python_injection-0.18.0}/injection/__init__.py +0 -0
  8. {python_injection-0.17.3 → python_injection-0.18.0}/injection/__init__.pyi +0 -0
  9. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/__init__.py +0 -0
  10. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/__init__.py +0 -0
  11. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/asynchronous.py +0 -0
  12. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/event.py +0 -0
  13. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/invertible.py +0 -0
  14. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/key.py +0 -0
  15. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/lazy.py +0 -0
  16. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/common/type.py +0 -0
  17. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/descriptors.py +0 -0
  18. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/injectables.py +0 -0
  19. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/module.py +0 -0
  20. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/scope.py +0 -0
  21. {python_injection-0.17.3 → python_injection-0.18.0}/injection/_core/slots.py +0 -0
  22. {python_injection-0.17.3 → python_injection-0.18.0}/injection/exceptions.py +0 -0
  23. {python_injection-0.17.3 → python_injection-0.18.0}/injection/ext/__init__.py +0 -0
  24. {python_injection-0.17.3 → python_injection-0.18.0}/injection/ext/fastapi.py +0 -0
  25. {python_injection-0.17.3 → python_injection-0.18.0}/injection/ext/fastapi.pyi +0 -0
  26. {python_injection-0.17.3 → python_injection-0.18.0}/injection/py.typed +0 -0
  27. {python_injection-0.17.3 → python_injection-0.18.0}/injection/testing/__init__.py +0 -0
  28. {python_injection-0.17.3 → python_injection-0.18.0}/injection/testing/__init__.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-injection
3
- Version: 0.17.3
3
+ Version: 0.18.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
@@ -19,6 +19,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
19
  Classifier: Topic :: Software Development :: Testing
20
20
  Classifier: Typing :: Typed
21
21
  Requires-Python: <4,>=3.12
22
+ Provides-Extra: async
23
+ Requires-Dist: anyio; extra == 'async'
22
24
  Description-Content-Type: text/markdown
23
25
 
24
26
  # python-injection
@@ -85,6 +87,7 @@ if __name__ == "__main__":
85
87
  * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
86
88
  * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
87
89
  * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
88
- * [**Utils**](https://github.com/100nm/python-injection/tree/prod/documentation/utils.md)
90
+ * [**Loaders**](https://github.com/100nm/python-injection/tree/prod/documentation/loaders.md)
91
+ * [**Entrypoint**](https://github.com/100nm/python-injection/tree/prod/documentation/entrypoint.md)
89
92
  * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
90
93
  * [**Concrete example**](https://github.com/100nm/python-injection-example)
@@ -62,6 +62,7 @@ if __name__ == "__main__":
62
62
  * [**Scoped dependencies**](https://github.com/100nm/python-injection/tree/prod/documentation/scoped-dependencies.md)
63
63
  * [**Testing**](https://github.com/100nm/python-injection/tree/prod/documentation/testing.md)
64
64
  * [**Advanced usage**](https://github.com/100nm/python-injection/tree/prod/documentation/advanced-usage.md)
65
- * [**Utils**](https://github.com/100nm/python-injection/tree/prod/documentation/utils.md)
65
+ * [**Loaders**](https://github.com/100nm/python-injection/tree/prod/documentation/loaders.md)
66
+ * [**Entrypoint**](https://github.com/100nm/python-injection/tree/prod/documentation/entrypoint.md)
66
67
  * [**Integrations**](https://github.com/100nm/python-injection/tree/prod/documentation/integrations.md)
67
68
  * [**Concrete example**](https://github.com/100nm/python-injection-example)
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Iterator
5
+ from contextlib import asynccontextmanager, contextmanager
6
+ from dataclasses import dataclass, field
7
+ from functools import wraps
8
+ from types import MethodType
9
+ from types import ModuleType as PythonModule
10
+ from typing import Any, Self, final, overload
11
+
12
+ from injection import Module, mod
13
+ from injection.loaders import PythonModuleLoader
14
+
15
+ __all__ = ("AsyncEntrypoint", "Entrypoint", "autocall", "entrypointmaker")
16
+
17
+ type AsyncEntrypoint[**P, T] = Entrypoint[P, Coroutine[Any, Any, T]]
18
+ type EntrypointDecorator[**P, T1, T2] = Callable[[Callable[P, T1]], Callable[P, T2]]
19
+ type EntrypointSetupMethod[*Ts, **P, T1, T2] = Callable[
20
+ [Entrypoint[P, T1], *Ts],
21
+ Entrypoint[P, T2],
22
+ ]
23
+
24
+
25
+ def autocall[**P, T](wrapped: Callable[P, T] | None = None, /) -> Any:
26
+ def decorator(wp: Callable[P, T]) -> Callable[P, T]:
27
+ wp() # type: ignore[call-arg]
28
+ return wp
29
+
30
+ return decorator(wrapped) if wrapped else decorator
31
+
32
+
33
+ @overload
34
+ def entrypointmaker[*Ts, **P, T1, T2](
35
+ wrapped: EntrypointSetupMethod[*Ts, P, T1, T2],
36
+ /,
37
+ *,
38
+ module: Module | None = ...,
39
+ ) -> EntrypointDecorator[P, T1, T2]: ...
40
+
41
+
42
+ @overload
43
+ def entrypointmaker[*Ts, **P, T1, T2](
44
+ wrapped: None = ...,
45
+ /,
46
+ *,
47
+ module: Module | None = ...,
48
+ ) -> Callable[
49
+ [EntrypointSetupMethod[*Ts, P, T1, T2]],
50
+ EntrypointDecorator[P, T1, T2],
51
+ ]: ...
52
+
53
+
54
+ def entrypointmaker[*Ts, **P, T1, T2](
55
+ wrapped: EntrypointSetupMethod[*Ts, P, T1, T2] | None = None,
56
+ /,
57
+ *,
58
+ module: Module | None = None,
59
+ ) -> Any:
60
+ def decorator(
61
+ wp: EntrypointSetupMethod[*Ts, P, T1, T2],
62
+ ) -> EntrypointDecorator[P, T1, T2]:
63
+ return Entrypoint._make_decorator(wp, module)
64
+
65
+ return decorator(wrapped) if wrapped else decorator
66
+
67
+
68
+ @final
69
+ @dataclass(repr=False, eq=False, frozen=True, slots=True)
70
+ class Entrypoint[**P, T]:
71
+ function: Callable[P, T]
72
+ module: Module = field(default_factory=mod)
73
+
74
+ def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> T:
75
+ return self.function(*args, **kwargs)
76
+
77
+ def async_to_sync[_T](
78
+ self: AsyncEntrypoint[P, _T],
79
+ run: Callable[[Coroutine[Any, Any, _T]], _T] = asyncio.run,
80
+ /,
81
+ ) -> Entrypoint[P, _T]:
82
+ function = self.function
83
+
84
+ @wraps(function)
85
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> _T:
86
+ return run(function(*args, **kwargs))
87
+
88
+ return self.__recreate(wrapper)
89
+
90
+ def decorate(
91
+ self,
92
+ decorator: Callable[[Callable[P, T]], Callable[P, T]],
93
+ /,
94
+ ) -> Self:
95
+ return self.__recreate(decorator(self.function))
96
+
97
+ def inject(self) -> Self:
98
+ return self.decorate(self.module.make_injected_function)
99
+
100
+ def load_modules(
101
+ self,
102
+ /,
103
+ loader: PythonModuleLoader,
104
+ *packages: PythonModule | str,
105
+ ) -> Self:
106
+ return self.setup(lambda: loader.load(*packages))
107
+
108
+ def setup(self, function: Callable[..., Any], /) -> Self:
109
+ @contextmanager
110
+ def decorator() -> Iterator[Any]:
111
+ yield function()
112
+
113
+ return self.decorate(decorator())
114
+
115
+ def async_setup[_T](
116
+ self: AsyncEntrypoint[P, _T],
117
+ function: Callable[..., Awaitable[Any]],
118
+ /,
119
+ ) -> AsyncEntrypoint[P, _T]:
120
+ @asynccontextmanager
121
+ async def decorator() -> AsyncIterator[Any]:
122
+ yield await function()
123
+
124
+ return self.decorate(decorator())
125
+
126
+ def __recreate[**_P, _T](
127
+ self: Entrypoint[Any, Any],
128
+ function: Callable[_P, _T],
129
+ /,
130
+ ) -> Entrypoint[_P, _T]:
131
+ return type(self)(function, self.module)
132
+
133
+ @classmethod
134
+ def _make_decorator[*Ts, _T](
135
+ cls,
136
+ setup_method: EntrypointSetupMethod[*Ts, P, T, _T],
137
+ /,
138
+ module: Module | None = None,
139
+ ) -> EntrypointDecorator[P, T, _T]:
140
+ module = module or mod()
141
+ setup_method = module.make_injected_function(setup_method)
142
+
143
+ def decorator(function: Callable[P, T]) -> Callable[P, _T]:
144
+ self = cls(function, module)
145
+ return MethodType(setup_method, self)().function
146
+
147
+ return decorator
@@ -57,7 +57,7 @@ class PythonModuleLoader:
57
57
 
58
58
  def load(self, *packages: PythonModule | str) -> Self:
59
59
  modules = itertools.chain.from_iterable(
60
- self.__iter_modules(package) for package in packages
60
+ self.__iter_modules_from(package) for package in packages
61
61
  )
62
62
  self.__modules.update(modules)
63
63
  return self
@@ -67,7 +67,7 @@ class PythonModuleLoader:
67
67
  module_name in modules for modules in (self.__modules, self._sys_modules)
68
68
  )
69
69
 
70
- def __iter_modules(
70
+ def __iter_modules_from(
71
71
  self,
72
72
  package: PythonModule | str,
73
73
  ) -> Iterator[tuple[str, PythonModule | None]]:
@@ -9,11 +9,15 @@ bench = [
9
9
  "types-tabulate",
10
10
  ]
11
11
  dev = [
12
- "anyio",
13
12
  "hatch",
14
13
  "mypy",
15
14
  "ruff",
16
15
  ]
16
+ doc = [
17
+ "fastapi",
18
+ "typer",
19
+ "uvloop",
20
+ ]
17
21
  test = [
18
22
  "fastapi",
19
23
  "httpx",
@@ -25,7 +29,7 @@ test = [
25
29
 
26
30
  [project]
27
31
  name = "python-injection"
28
- version = "0.17.3"
32
+ version = "0.18.0"
29
33
  description = "Fast and easy dependency injection framework."
30
34
  license = { text = "MIT" }
31
35
  readme = "README.md"
@@ -48,6 +52,11 @@ classifiers = [
48
52
  ]
49
53
  dependencies = []
50
54
 
55
+ [project.optional-dependencies]
56
+ async = [
57
+ "anyio",
58
+ ]
59
+
51
60
  [project.urls]
52
61
  Repository = "https://github.com/100nm/python-injection"
53
62
 
@@ -115,5 +124,5 @@ ignore = ["N818"]
115
124
  fixable = ["ALL"]
116
125
 
117
126
  [tool.uv]
118
- default-groups = ["bench", "dev", "test"]
127
+ default-groups = ["bench", "dev", "doc", "test"]
119
128
  package = true