wireup 2.2.1__tar.gz → 2.2.2__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.
- {wireup-2.2.1 → wireup-2.2.2}/PKG-INFO +5 -4
- {wireup-2.2.1 → wireup-2.2.2}/pyproject.toml +3 -2
- {wireup-2.2.1 → wireup-2.2.2}/wireup/__init__.py +2 -1
- {wireup-2.2.1 → wireup-2.2.2}/wireup/_annotations.py +9 -3
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/django/apps.py +1 -1
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/container/__init__.py +8 -1
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/service_registry.py +2 -2
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/util.py +71 -19
- {wireup-2.2.1 → wireup-2.2.2}/readme.md +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/_decorators.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/_discovery.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/errors.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/__init__.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/aiohttp.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/click.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/django/__init__.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/fastapi.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/flask.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/integration/starlette.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/__init__.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/_exit_stack.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/container/async_container.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/container/base_container.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/container/sync_container.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/factory_compiler.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/override_manager.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/parameter.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/ioc/types.py +0 -0
- {wireup-2.2.1 → wireup-2.2.2}/wireup/py.typed +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: wireup
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Python Dependency Injection Library
|
|
5
|
-
Home-page: https://github.com/maldoinc/wireup
|
|
6
5
|
License: MIT
|
|
7
6
|
Keywords: flask,django,injector,dependency injection,dependency injection container,dependency injector
|
|
8
7
|
Author: Aldo Mateli
|
|
@@ -26,14 +25,16 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
26
25
|
Classifier: Programming Language :: Python :: 3.11
|
|
27
26
|
Classifier: Programming Language :: Python :: 3.12
|
|
28
27
|
Classifier: Programming Language :: Python :: 3.13
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
29
29
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
30
30
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
31
31
|
Classifier: Typing :: Typed
|
|
32
32
|
Provides-Extra: eval-type
|
|
33
|
-
Requires-Dist: eval-type-backport (>=0.2.0,<0.3.0) ; extra == "eval-type"
|
|
33
|
+
Requires-Dist: eval-type-backport (>=0.2.0,<0.3.0) ; (python_version < "3.11") and (extra == "eval-type")
|
|
34
34
|
Requires-Dist: typing_extensions (>=4.7,<5.0)
|
|
35
35
|
Project-URL: Changelog, https://github.com/maldoinc/wireup/releases
|
|
36
36
|
Project-URL: Documentation, https://maldoinc.github.io/wireup/
|
|
37
|
+
Project-URL: Homepage, https://github.com/maldoinc/wireup
|
|
37
38
|
Project-URL: Repository, https://github.com/maldoinc/wireup
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "wireup"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.2"
|
|
4
4
|
description = "Python Dependency Injection Library"
|
|
5
5
|
authors = ["Aldo Mateli <aldo.mateli@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -33,6 +33,7 @@ classifiers = [
|
|
|
33
33
|
"Programming Language :: Python :: 3.11",
|
|
34
34
|
"Programming Language :: Python :: 3.12",
|
|
35
35
|
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Programming Language :: Python :: 3.14",
|
|
36
37
|
"Programming Language :: Python :: 3 :: Only",
|
|
37
38
|
"Typing :: Typed",
|
|
38
39
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -46,7 +47,7 @@ Changelog = "https://github.com/maldoinc/wireup/releases"
|
|
|
46
47
|
[tool.poetry.dependencies]
|
|
47
48
|
python = "^3.8"
|
|
48
49
|
typing_extensions = "^4.7"
|
|
49
|
-
eval-type-backport = { version = "^0.2.0", optional = true }
|
|
50
|
+
eval-type-backport = { version = "^0.2.0", optional = true, python = "<3.11" }
|
|
50
51
|
|
|
51
52
|
[tool.poetry.extras]
|
|
52
53
|
eval-type = ["eval-type-backport"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from wireup._annotations import Inject, Injected, abstract, service
|
|
1
|
+
from wireup._annotations import Inject, Injected, abstract, injectable, service
|
|
2
2
|
from wireup._decorators import inject_from_container
|
|
3
3
|
from wireup.ioc.container import (
|
|
4
4
|
create_async_container,
|
|
@@ -21,5 +21,6 @@ __all__ = [
|
|
|
21
21
|
"create_async_container",
|
|
22
22
|
"create_sync_container",
|
|
23
23
|
"inject_from_container",
|
|
24
|
+
"injectable",
|
|
24
25
|
"service",
|
|
25
26
|
]
|
|
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
|
|
|
23
23
|
|
|
24
24
|
def Inject( # noqa: N802
|
|
25
25
|
*,
|
|
26
|
+
config: str | None = None,
|
|
26
27
|
param: str | None = None,
|
|
27
28
|
expr: str | None = None,
|
|
28
29
|
qualifier: Qualifier | None = None,
|
|
@@ -41,6 +42,8 @@ def Inject( # noqa: N802
|
|
|
41
42
|
"""
|
|
42
43
|
res: InjectableType
|
|
43
44
|
|
|
45
|
+
if config:
|
|
46
|
+
res = ParameterWrapper(config)
|
|
44
47
|
if param:
|
|
45
48
|
res = ParameterWrapper(param)
|
|
46
49
|
elif expr:
|
|
@@ -89,7 +92,7 @@ class AbstractDeclaration:
|
|
|
89
92
|
|
|
90
93
|
|
|
91
94
|
@overload
|
|
92
|
-
def
|
|
95
|
+
def injectable(
|
|
93
96
|
obj: None = None,
|
|
94
97
|
*,
|
|
95
98
|
qualifier: Qualifier | None = None,
|
|
@@ -99,7 +102,7 @@ def service(
|
|
|
99
102
|
|
|
100
103
|
|
|
101
104
|
@overload
|
|
102
|
-
def
|
|
105
|
+
def injectable(
|
|
103
106
|
obj: T,
|
|
104
107
|
*,
|
|
105
108
|
qualifier: Qualifier | None = None,
|
|
@@ -108,7 +111,7 @@ def service(
|
|
|
108
111
|
pass
|
|
109
112
|
|
|
110
113
|
|
|
111
|
-
def
|
|
114
|
+
def injectable(
|
|
112
115
|
obj: T | None = None,
|
|
113
116
|
*,
|
|
114
117
|
qualifier: Qualifier | None = None,
|
|
@@ -126,6 +129,9 @@ def service(
|
|
|
126
129
|
return _service_decorator if obj is None else _service_decorator(obj)
|
|
127
130
|
|
|
128
131
|
|
|
132
|
+
service = injectable
|
|
133
|
+
|
|
134
|
+
|
|
129
135
|
def abstract(cls: type[T]) -> type[T]:
|
|
130
136
|
"""Mark the decorated class as an abstract service."""
|
|
131
137
|
cls.__wireup_registration__ = AbstractDeclaration(cls) # type: ignore[attr-defined]
|
|
@@ -137,7 +137,7 @@ class WireupConfig(AppConfig):
|
|
|
137
137
|
injected_names = {
|
|
138
138
|
name: self.container.params.get(param.annotation.param)
|
|
139
139
|
if isinstance(param.annotation, ParameterWrapper)
|
|
140
|
-
else get_request_container().
|
|
140
|
+
else get_request_container()._synchronous_get(param.klass, qualifier=param.qualifier_value)
|
|
141
141
|
for name, param in names_to_inject.items()
|
|
142
142
|
if param.annotation
|
|
143
143
|
}
|
|
@@ -92,7 +92,9 @@ def _merge_definitions(
|
|
|
92
92
|
def create_sync_container(
|
|
93
93
|
service_modules: list[ModuleType] | None = None,
|
|
94
94
|
services: list[Any] | None = None,
|
|
95
|
+
injectables: list[Any] | None = None,
|
|
95
96
|
parameters: dict[str, Any] | None = None,
|
|
97
|
+
config: dict[str, Any] | None = None,
|
|
96
98
|
) -> SyncContainer:
|
|
97
99
|
"""Create a Wireup container.
|
|
98
100
|
|
|
@@ -104,7 +106,12 @@ def create_sync_container(
|
|
|
104
106
|
request parameters via the `Inject(param="name")` syntax.
|
|
105
107
|
:raises WireupError: Raised if the dependencies cannot be fully resolved.
|
|
106
108
|
"""
|
|
107
|
-
return _create_container(
|
|
109
|
+
return _create_container(
|
|
110
|
+
SyncContainer,
|
|
111
|
+
service_modules=service_modules,
|
|
112
|
+
services=[*(services or []), *(injectables or [])],
|
|
113
|
+
parameters=parameters,
|
|
114
|
+
)
|
|
108
115
|
|
|
109
116
|
|
|
110
117
|
def create_async_container(
|
|
@@ -80,7 +80,7 @@ def _function_get_unwrapped_return_type(fn: Callable[..., T]) -> type[T] | None:
|
|
|
80
80
|
return fn
|
|
81
81
|
|
|
82
82
|
if ret := fn.__annotations__.get("return"):
|
|
83
|
-
ret = ensure_is_type(ret,
|
|
83
|
+
ret = ensure_is_type(ret, globalns_supplier=lambda: get_globals(fn))
|
|
84
84
|
if not ret:
|
|
85
85
|
return None
|
|
86
86
|
|
|
@@ -225,7 +225,7 @@ class ServiceRegistry:
|
|
|
225
225
|
) -> None:
|
|
226
226
|
"""Init and collect all the necessary dependencies to initialize the specified target."""
|
|
227
227
|
for name, parameter in inspect.signature(target).parameters.items():
|
|
228
|
-
annotated_param = param_get_annotation(parameter,
|
|
228
|
+
annotated_param = param_get_annotation(parameter, globalns_supplier=lambda: get_globals(target))
|
|
229
229
|
|
|
230
230
|
if not annotated_param:
|
|
231
231
|
msg = f"Wireup dependencies must have types. Please add a type to the '{name}' parameter in {target}."
|
|
@@ -3,15 +3,18 @@ from __future__ import annotations
|
|
|
3
3
|
import functools
|
|
4
4
|
import importlib
|
|
5
5
|
import inspect
|
|
6
|
+
import sys
|
|
6
7
|
import types
|
|
7
8
|
import typing
|
|
8
9
|
from inspect import Parameter
|
|
9
|
-
from typing import Any, Sequence, TypeVar
|
|
10
|
+
from typing import Any, Sequence, TypeVar, cast
|
|
10
11
|
|
|
11
12
|
from wireup.errors import WireupError
|
|
12
13
|
from wireup.ioc.types import AnnotatedParameter, AnyCallable, InjectableType
|
|
13
14
|
|
|
15
|
+
T = TypeVar("T")
|
|
14
16
|
_OPTIONAL_UNION_ARG_COUNT = 2
|
|
17
|
+
_eval_type = cast("Callable[..., Any]", typing._eval_type) # type: ignore[attr-defined]
|
|
15
18
|
|
|
16
19
|
if typing.TYPE_CHECKING:
|
|
17
20
|
from collections.abc import Callable
|
|
@@ -43,14 +46,18 @@ def _get_wireup_annotation(metadata: Sequence[Any]) -> InjectableType | None:
|
|
|
43
46
|
return annotations[0]
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def param_get_annotation(
|
|
49
|
+
def param_get_annotation(
|
|
50
|
+
parameter: Parameter,
|
|
51
|
+
*,
|
|
52
|
+
globalns_supplier: Callable[[], dict[str, Any]],
|
|
53
|
+
) -> AnnotatedParameter | None:
|
|
47
54
|
"""Get the annotation injection type from a signature's Parameter.
|
|
48
55
|
|
|
49
56
|
Returns the first injectable annotation for an Annotated type or the default value.
|
|
50
57
|
Also handles Optional types by marking them as optional in the AnnotatedParameter.
|
|
51
58
|
Supports both Annotated[Optional[T], ...] and Optional[Annotated[T, ...]] patterns.
|
|
52
59
|
"""
|
|
53
|
-
resolved_type: type[Any] | None = ensure_is_type(parameter.annotation,
|
|
60
|
+
resolved_type: type[Any] | None = ensure_is_type(parameter.annotation, globalns_supplier=globalns_supplier)
|
|
54
61
|
|
|
55
62
|
if resolved_type is Parameter.empty:
|
|
56
63
|
resolved_type = None
|
|
@@ -78,10 +85,24 @@ def param_get_annotation(parameter: Parameter, *, globalns: dict[str, Any]) -> A
|
|
|
78
85
|
return AnnotatedParameter(klass=inner_type, annotation=annotation)
|
|
79
86
|
|
|
80
87
|
|
|
88
|
+
def _type_get_globals(typ: type) -> dict[str, Any]:
|
|
89
|
+
"""
|
|
90
|
+
Merge globals from parent classes with the child class's module globals,
|
|
91
|
+
with child class globals taking precedence.
|
|
92
|
+
"""
|
|
93
|
+
merged_globals: dict[str, Any] = {}
|
|
94
|
+
|
|
95
|
+
for base_class in reversed(typ.__mro__[:-1]):
|
|
96
|
+
base_globals = importlib.import_module(base_class.__module__).__dict__
|
|
97
|
+
merged_globals.update(base_globals)
|
|
98
|
+
|
|
99
|
+
return merged_globals
|
|
100
|
+
|
|
101
|
+
|
|
81
102
|
def get_globals(obj: type[Any] | Callable[..., Any]) -> dict[str, Any]:
|
|
82
103
|
"""Return the globals for the given object."""
|
|
83
104
|
if isinstance(obj, type):
|
|
84
|
-
return
|
|
105
|
+
return _type_get_globals(obj)
|
|
85
106
|
|
|
86
107
|
# Unwrap nested functools.partial to get the underlying function
|
|
87
108
|
while isinstance(obj, functools.partial):
|
|
@@ -90,31 +111,62 @@ def get_globals(obj: type[Any] | Callable[..., Any]) -> dict[str, Any]:
|
|
|
90
111
|
return obj.__globals__
|
|
91
112
|
|
|
92
113
|
|
|
93
|
-
|
|
114
|
+
def _eval_type_native(
|
|
115
|
+
value: typing.ForwardRef,
|
|
116
|
+
globalns: dict[str, Any] | None = None,
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""Evaluate a ForwardRef using the native typing._eval_type function.
|
|
119
|
+
|
|
120
|
+
This function handles version-specific differences in typing._eval_type.
|
|
121
|
+
"""
|
|
122
|
+
if sys.version_info >= (3, 14):
|
|
123
|
+
res = _eval_type(value, globalns, None, type_params=())
|
|
124
|
+
|
|
125
|
+
return type(None) if res is None else res
|
|
126
|
+
|
|
127
|
+
return _eval_type(value, globalns, None)
|
|
94
128
|
|
|
95
129
|
|
|
96
|
-
def ensure_is_type(value: type[T] | str,
|
|
130
|
+
def ensure_is_type(value: type[T] | str, globalns_supplier: Callable[[], dict[str, Any]]) -> type[T] | None:
|
|
97
131
|
"""Ensure the given value represents a type.
|
|
98
132
|
|
|
99
|
-
If it is a string it will be evaluated
|
|
133
|
+
If it is a string it will be evaluated, first trying the native typing._eval_type,
|
|
134
|
+
and falling back to eval_type_backport if needed.
|
|
135
|
+
|
|
136
|
+
This approach ensures compatibility with Python 3.14+ where eval_type_backport
|
|
137
|
+
cannot be imported due to ForwardRef subclassing restrictions.
|
|
100
138
|
"""
|
|
101
|
-
if isinstance(value, str):
|
|
139
|
+
if not isinstance(value, str):
|
|
140
|
+
return value
|
|
141
|
+
|
|
142
|
+
forward_ref = typing.ForwardRef(value)
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
# First, try using the native typing._eval_type then fall back to eval_type_backport.
|
|
146
|
+
# For a more complete solution see the pydantic implementation below.
|
|
147
|
+
# See: https://github.com/pydantic/pydantic/blob/f42171c760d43b9522fde513ae6e209790f7fefb/pydantic/_internal/_typing_extra.py#L485-L512
|
|
148
|
+
return _eval_type_native(forward_ref, globalns=globalns_supplier()) # type:ignore[no-any-return]
|
|
149
|
+
except TypeError as eval_type_error:
|
|
102
150
|
try:
|
|
103
151
|
import eval_type_backport
|
|
104
|
-
|
|
105
|
-
return eval_type_backport.eval_type_backport( # type:ignore[no-any-return]
|
|
106
|
-
eval_type_backport.ForwardRef(value), globalns=globalns, try_default=False
|
|
107
|
-
)
|
|
108
|
-
except NameError:
|
|
109
|
-
return None
|
|
110
|
-
except ImportError as e:
|
|
152
|
+
except ImportError as import_error:
|
|
111
153
|
msg = (
|
|
112
|
-
"
|
|
154
|
+
f"Error evaluating type annotation '{value}'. "
|
|
155
|
+
f"Got: TypeError: {eval_type_error}.\n"
|
|
156
|
+
"Tip: Try using the eval_type_backport package to resolve stringified types.\n"
|
|
113
157
|
"See: https://maldoinc.github.io/wireup/latest/future_annotations/"
|
|
114
158
|
)
|
|
115
|
-
raise WireupError(msg) from
|
|
159
|
+
raise WireupError(msg) from import_error
|
|
160
|
+
|
|
161
|
+
return eval_type_backport.eval_type_backport(forward_ref, globalns=globalns_supplier(), try_default=False) # type:ignore[no-any-return]
|
|
116
162
|
|
|
117
|
-
|
|
163
|
+
except NameError as e:
|
|
164
|
+
msg = (
|
|
165
|
+
f"Error evaluating type annotation '{value}'. Got NameError: {e!s}. "
|
|
166
|
+
"Make sure the type is correctly imported and not inside a TYPE_CHECKING block.\n"
|
|
167
|
+
"See: https://maldoinc.github.io/wireup/latest/future_annotations/"
|
|
168
|
+
)
|
|
169
|
+
raise WireupError(msg) from e
|
|
118
170
|
|
|
119
171
|
|
|
120
172
|
def unwrap_optional_type(type_: Any) -> Any:
|
|
@@ -182,7 +234,7 @@ def get_inject_annotated_parameters(target: AnyCallable) -> dict[str, AnnotatedP
|
|
|
182
234
|
return {
|
|
183
235
|
name: param
|
|
184
236
|
for name, parameter in inspect.signature(target).parameters.items()
|
|
185
|
-
if (param := param_get_annotation(parameter,
|
|
237
|
+
if (param := param_get_annotation(parameter, globalns_supplier=lambda: get_globals(target)))
|
|
186
238
|
and isinstance(param.annotation, InjectableType)
|
|
187
239
|
}
|
|
188
240
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|