anydi 0.46.0rc1__py3-none-any.whl → 0.47.0rc0__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.
anydi/__init__.py CHANGED
@@ -5,22 +5,24 @@ from ._decorators import injectable, provided, provider, request, singleton, tra
5
5
  from ._module import Module
6
6
  from ._provider import ProviderDef as Provider
7
7
  from ._scope import Scope
8
- from ._typing import InjectMarker
8
+ from ._typing import Inject
9
9
 
10
10
  # Alias for dependency auto marker
11
- auto = InjectMarker()
11
+ # TODO: deprecate it
12
+ auto = Inject()
12
13
 
13
14
 
14
15
  __all__ = [
15
16
  "Container",
17
+ "Inject",
16
18
  "Module",
17
19
  "Provider",
18
20
  "Scope",
19
21
  "auto",
20
22
  "injectable",
23
+ "provided",
21
24
  "provider",
22
25
  "request",
23
26
  "singleton",
24
27
  "transient",
25
- "provided",
26
28
  ]
anydi/_container.py CHANGED
@@ -11,7 +11,7 @@ import uuid
11
11
  from collections import defaultdict
12
12
  from collections.abc import AsyncIterator, Iterable, Iterator
13
13
  from contextvars import ContextVar
14
- from typing import Any, Callable, TypeVar, cast, overload
14
+ from typing import Annotated, Any, Callable, TypeVar, cast, overload
15
15
 
16
16
  from typing_extensions import ParamSpec, Self, get_args, get_origin
17
17
 
@@ -806,36 +806,68 @@ class Container:
806
806
  """Get the injected parameters of a callable object."""
807
807
  injected_params: dict[str, Any] = {}
808
808
  for parameter in get_typed_parameters(call):
809
- interface, should_inject = self._validate_injected_parameter(
809
+ interface, should_inject = self.validate_injected_parameter(
810
810
  parameter, call=call
811
811
  )
812
812
  if should_inject:
813
813
  injected_params[parameter.name] = interface
814
814
  return injected_params
815
815
 
816
- def _validate_injected_parameter(
816
+ @staticmethod
817
+ def _unwrap_injected_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
818
+ if get_origin(parameter.annotation) is not Annotated:
819
+ return parameter
820
+
821
+ origin, *metadata = get_args(parameter.annotation)
822
+
823
+ if not metadata or not is_inject_marker(metadata[-1]):
824
+ return parameter
825
+
826
+ if is_inject_marker(parameter.default):
827
+ raise TypeError(
828
+ "Cannot specify `Inject` in `Annotated` and "
829
+ f"default value together for '{parameter.name}'"
830
+ )
831
+
832
+ if parameter.default is not inspect.Parameter.empty:
833
+ return parameter
834
+
835
+ marker = metadata[-1]
836
+ new_metadata = metadata[:-1]
837
+ if new_metadata:
838
+ new_annotation = Annotated.__class_getitem__((origin, *new_metadata)) # type: ignore
839
+ else:
840
+ new_annotation = origin
841
+ return parameter.replace(annotation=new_annotation, default=marker)
842
+
843
+ def validate_injected_parameter(
817
844
  self, parameter: inspect.Parameter, *, call: Callable[..., Any]
818
845
  ) -> tuple[Any, bool]:
819
846
  """Validate an injected parameter."""
820
- interface, should_inject = parameter.annotation, False
821
- if is_inject_marker(parameter.default):
822
- if parameter.annotation is inspect.Parameter.empty:
823
- raise TypeError(
824
- f"Missing `{type_repr(call)}` "
825
- f"parameter `{parameter.name}` annotation."
826
- )
827
- should_inject = True
847
+ parameter = self._unwrap_injected_parameter(parameter)
848
+ interface = parameter.annotation
828
849
 
829
- return interface, should_inject
850
+ if not is_inject_marker(parameter.default):
851
+ return interface, False
830
852
 
831
- # TODO: temporary disable until strict is enforced
832
- if not self.has_provider_for(interface):
853
+ if interface is inspect.Parameter.empty:
854
+ raise TypeError(
855
+ f"Missing `{type_repr(call)}` parameter `{parameter.name}` annotation."
856
+ )
857
+
858
+ # Set inject marker interface
859
+ parameter.default.interface = interface
860
+
861
+ # TODO: temporary disable until strict is enforced (remove False)
862
+ if False and not self.has_provider_for(interface):
833
863
  raise LookupError(
834
864
  f"`{type_repr(call)}` has an unknown dependency parameter "
835
865
  f"`{parameter.name}` with an annotation of "
836
866
  f"`{type_repr(interface)}`."
837
867
  )
838
868
 
869
+ return interface, True
870
+
839
871
  ############################
840
872
  # Module Methods
841
873
  ############################
anydi/_typing.py CHANGED
@@ -7,9 +7,9 @@ import inspect
7
7
  import re
8
8
  import sys
9
9
  from collections.abc import AsyncIterator, Iterator
10
- from typing import Any, Callable, ForwardRef
10
+ from typing import Any, Callable, ForwardRef, TypeVar
11
11
 
12
- from typing_extensions import Self, get_args, get_origin
12
+ from typing_extensions import get_args, get_origin
13
13
 
14
14
  try:
15
15
  from types import NoneType
@@ -17,6 +17,9 @@ except ImportError:
17
17
  NoneType = type(None)
18
18
 
19
19
 
20
+ T = TypeVar("T")
21
+
22
+
20
23
  def type_repr(obj: Any) -> str:
21
24
  """Get a string representation of a type or object."""
22
25
  if isinstance(obj, str):
@@ -93,48 +96,58 @@ def get_typed_parameters(obj: Callable[..., Any]) -> list[inspect.Parameter]:
93
96
  ]
94
97
 
95
98
 
96
- class _InjectMarker:
97
- """A marker object for declaring injectable dependencies."""
99
+ class _Sentinel:
100
+ __slots__ = ("_name",)
98
101
 
99
- __slots__ = ()
102
+ def __init__(self, name: str) -> None:
103
+ self._name = name
100
104
 
101
- def __call__(self) -> Self:
102
- return self
105
+ def __repr__(self) -> str:
106
+ return f"<{self._name}>"
103
107
 
108
+ def __eq__(self, other: object) -> bool:
109
+ return self is other
104
110
 
105
- def InjectMarker() -> Any:
106
- return _InjectMarker()
111
+ def __hash__(self) -> int:
112
+ return id(self)
107
113
 
108
114
 
109
- def is_inject_marker(obj: Any) -> bool:
110
- return isinstance(obj, _InjectMarker)
115
+ NOT_SET = _Sentinel("NOT_SET")
111
116
 
112
117
 
113
- class Event:
114
- """Represents an event object."""
118
+ class InjectMarker:
119
+ """A marker object for declaring injectable dependencies."""
115
120
 
116
- __slots__ = ()
121
+ __slots__ = ("_interface",)
117
122
 
123
+ def __init__(self, interface: Any = NOT_SET) -> None:
124
+ self._interface = interface
118
125
 
119
- def is_event_type(obj: Any) -> bool:
120
- """Checks if an object is an event type."""
121
- return inspect.isclass(obj) and issubclass(obj, Event)
126
+ @property
127
+ def interface(self) -> Any:
128
+ if self._interface is NOT_SET:
129
+ raise TypeError("Interface is not set.")
130
+ return self._interface
122
131
 
132
+ @interface.setter
133
+ def interface(self, interface: Any) -> None:
134
+ self._interface = interface
123
135
 
124
- class _Sentinel:
125
- __slots__ = ("_name",)
126
136
 
127
- def __init__(self, name: str) -> None:
128
- self._name = name
137
+ def is_inject_marker(obj: Any) -> bool:
138
+ return isinstance(obj, InjectMarker)
129
139
 
130
- def __repr__(self) -> str:
131
- return f"<{self._name}>"
132
140
 
133
- def __eq__(self, other: object) -> bool:
134
- return self is other
141
+ def Inject() -> Any:
142
+ return InjectMarker()
135
143
 
136
- def __hash__(self) -> int:
137
- return id(self)
138
144
 
145
+ class Event:
146
+ """Represents an event object."""
139
147
 
140
- NOT_SET = _Sentinel("NOT_SET")
148
+ __slots__ = ()
149
+
150
+
151
+ def is_event_type(obj: Any) -> bool:
152
+ """Checks if an object is an event type."""
153
+ return inspect.isclass(obj) and issubclass(obj, Event)
@@ -46,11 +46,11 @@ class ViewSignature(BaseViewSignature):
46
46
  self.response_arg = name
47
47
  continue
48
48
 
49
- interface, should_inject = container._validate_injected_parameter(
49
+ interface, should_inject = container.validate_injected_parameter(
50
50
  arg, call=self.view_func
51
51
  )
52
52
  if should_inject:
53
- self.dependencies.append((name, arg.annotation))
53
+ self.dependencies.append((name, interface))
54
54
  continue
55
55
 
56
56
  func_param = self._get_param_type(name, arg)
anydi/ext/fastapi.py CHANGED
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Iterator
6
- from typing import Any, cast
6
+ from typing import Annotated, Any, cast
7
7
 
8
8
  from fastapi import Depends, FastAPI, params
9
9
  from fastapi.dependencies.models import Dependant
@@ -11,9 +11,8 @@ from fastapi.routing import APIRoute
11
11
  from starlette.requests import Request
12
12
 
13
13
  from anydi._container import Container
14
- from anydi._typing import get_typed_parameters
14
+ from anydi._typing import InjectMarker, get_typed_parameters
15
15
 
16
- from ._utils import HasInterface, patch_call_parameter
17
16
  from .starlette.middleware import RequestScopedMiddleware
18
17
 
19
18
  __all__ = ["RequestScopedMiddleware", "install", "get_container", "Inject"]
@@ -41,7 +40,7 @@ def install(app: FastAPI, container: Container) -> None:
41
40
  if not call:
42
41
  continue # pragma: no cover
43
42
  for parameter in get_typed_parameters(call):
44
- patch_call_parameter(container, call, parameter)
43
+ container.validate_injected_parameter(parameter, call=call)
45
44
 
46
45
 
47
46
  def get_container(request: Request) -> Container:
@@ -49,20 +48,19 @@ def get_container(request: Request) -> Container:
49
48
  return cast(Container, request.app.state.container)
50
49
 
51
50
 
52
- class Resolver(params.Depends, HasInterface):
53
- """Parameter dependency class for injecting dependencies using AnyDI."""
54
-
51
+ class _Inject(params.Depends, InjectMarker):
55
52
  def __init__(self) -> None:
56
53
  super().__init__(dependency=self._dependency, use_cache=True)
57
- HasInterface.__init__(self)
54
+ InjectMarker.__init__(self)
58
55
 
59
- async def _dependency(self, container: Container = Depends(get_container)) -> Any:
56
+ async def _dependency(
57
+ self, container: Annotated[Container, Depends(get_container)]
58
+ ) -> Any:
60
59
  return await container.aresolve(self.interface)
61
60
 
62
61
 
63
- def Inject() -> Any: # noqa
64
- """Decorator for marking a function parameter as requiring injection."""
65
- return Resolver()
62
+ def Inject() -> Any:
63
+ return _Inject()
66
64
 
67
65
 
68
66
  def _iter_dependencies(dependant: Dependant) -> Iterator[Dependant]:
anydi/ext/faststream.py CHANGED
@@ -9,9 +9,7 @@ from faststream import ContextRepo
9
9
  from faststream.broker.core.usecase import BrokerUsecase
10
10
 
11
11
  from anydi import Container
12
- from anydi._typing import get_typed_parameters
13
-
14
- from ._utils import HasInterface, patch_call_parameter
12
+ from anydi._typing import InjectMarker, get_typed_parameters
15
13
 
16
14
 
17
15
  def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
@@ -26,7 +24,7 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
26
24
  for handler in _get_broken_handlers(broker):
27
25
  call = handler._original_call # noqa
28
26
  for parameter in get_typed_parameters(call):
29
- patch_call_parameter(container, call, parameter)
27
+ container.validate_injected_parameter(parameter, call=call)
30
28
 
31
29
 
32
30
  def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
@@ -43,17 +41,17 @@ def get_container(broker: BrokerUsecase[Any, Any]) -> Container:
43
41
  return cast(Container, getattr(broker, "_container")) # noqa
44
42
 
45
43
 
46
- class Resolver(Depends, HasInterface):
44
+ class _Inject(Depends, InjectMarker):
47
45
  """Parameter dependency class for injecting dependencies using AnyDI."""
48
46
 
49
47
  def __init__(self) -> None:
50
48
  super().__init__(dependency=self._dependency, use_cache=True, cast=True)
51
- HasInterface.__init__(self)
49
+ InjectMarker.__init__(self)
52
50
 
53
51
  async def _dependency(self, context: ContextRepo) -> Any:
54
52
  container = get_container(context.get("broker"))
55
53
  return await container.aresolve(self.interface)
56
54
 
57
55
 
58
- def Inject() -> Any: # noqa
59
- return Resolver()
56
+ def Inject() -> Any:
57
+ return _Inject()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.46.0rc1
3
+ Version: 0.47.0rc0
4
4
  Summary: Dependency Injection library
5
5
  Project-URL: Repository, https://github.com/antonrh/anydi
6
6
  Author-email: Anton Ruhlov <antonruhlov@gmail.com>
@@ -1,19 +1,18 @@
1
- anydi/__init__.py,sha256=GNKdoiDe1XNdMIGvI8UP43QSLOBbMQYyFpiicLlgZxI,544
1
+ anydi/__init__.py,sha256=KjjYm-1yAFxiPYaMs1WRJMtxE0q_vdX7ZRLQR1fFGs8,567
2
2
  anydi/_async.py,sha256=KhRd3RmZFcwNDzrMm8ctA1gwrg-6m_7laECTYsZdF5k,1445
3
- anydi/_container.py,sha256=EC06Uur6GOahFF5mhluGYX95woOnKv2Hcr7j1rs9xu8,31262
3
+ anydi/_container.py,sha256=Ug0TVOuNQ1cpwUQiHSNtAiZB-qJHv8ECd89WcK6ZwWg,32350
4
4
  anydi/_context.py,sha256=_Xy8cTpRskb4cxTd-Fe-5NnIZyBe1DnovkofhdeUfmw,3020
5
5
  anydi/_decorators.py,sha256=F3yBeGQSz1EsulZaEvYn3cd6FEjJRMoyA6u1QCbEwcs,2813
6
6
  anydi/_module.py,sha256=QPvP27JndZkwl-FYUZWscJm6yfkNzjwoFGURyDhb6Pc,2582
7
7
  anydi/_provider.py,sha256=ig2ecn-STmFGcpkLE5A5OM35XHtU2NsxFVrGp2CvuvM,2123
8
8
  anydi/_scan.py,sha256=nOpspmceVucdwf8nUv1QVFsz2sRVWCVUb0QTH9EbWr4,3653
9
9
  anydi/_scope.py,sha256=PFHjPb2-n0vhRo9mvD_craTFfoJBzR3y-N3_0apL5Q0,258
10
- anydi/_typing.py,sha256=zSyE4JAnaBsJLZ3Zb3o9o02LgKePwBWzMwczdhmyG3U,3711
10
+ anydi/_typing.py,sha256=7Nhg4ezPgvFmdGOd2SI_9y5iQv8j-QkNOohXRGNAaIE,4057
11
11
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  anydi/testing.py,sha256=ex9grqKpQmmJWwhIVnzq6aHaUAGLu2-7fwLyYTUuKHE,5678
13
13
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- anydi/ext/_utils.py,sha256=EjOhBtze0tqWMjpTiBCSzaOyecHCRWygMDZbj4ZyAEA,2213
15
- anydi/ext/fastapi.py,sha256=QIC45I3sfE_cNVL27cilw7lwLEZhHPhxj0mtFdtKlIc,2484
16
- anydi/ext/faststream.py,sha256=Ewfj9hXbCSr5x7QWoLEORmc3uPcbr7PEcCcG1wM-6XM,1946
14
+ anydi/ext/fastapi.py,sha256=L9VGPHGy23se1sflmJqTE7LNfQuElVdYEogoT1f5-4A,2324
15
+ anydi/ext/faststream.py,sha256=Dy81Vf34CP6pEIbZ-41vh_-Dn6Qc-rcf14U5poebjxI,1905
17
16
  anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
18
17
  anydi/ext/pytest_plugin.py,sha256=IoP6XKuGLGLd2Xlpfttc3mI4pxCm2WQLE7x_a7asbv4,4732
19
18
  anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
@@ -24,11 +23,11 @@ anydi/ext/django/apps.py,sha256=YLL1uU6dSQ3uf3GB0VHzPW_eLuB1j9h6pv8EdAutMqo,2725
24
23
  anydi/ext/django/middleware.py,sha256=5OUdp0OWRozyW338Sq04BDhacaFlyUTTOduS_7EwCTA,854
25
24
  anydi/ext/django/ninja/__init__.py,sha256=4J0zoHPK9itbTVrjjvLX6Ftrsb2ND8bITqNDIJzEhks,520
26
25
  anydi/ext/django/ninja/_operation.py,sha256=wk5EOjLY3KVIHk9jMCGsFsja9-dQmMOpLpHXciqxQdk,2680
27
- anydi/ext/django/ninja/_signature.py,sha256=mseRTLZZdwdXCG-jdTe1l4WRacbKoa3kfEGWQ1aq3QY,2364
26
+ anydi/ext/django/ninja/_signature.py,sha256=p7JtyMdFhX4fWQOvAhvZNss6iURNERcdsTsQADTHkMY,2358
28
27
  anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
28
  anydi/ext/starlette/middleware.py,sha256=MxnzshAs-CMvjJp0r457k52MzBL8O4KAuClnF6exBdU,803
30
- anydi-0.46.0rc1.dist-info/METADATA,sha256=-ElS0CFkUCYs244HuKXQcy8z0e-lifWx8tGgLo9gp2c,4960
31
- anydi-0.46.0rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
32
- anydi-0.46.0rc1.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
33
- anydi-0.46.0rc1.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
34
- anydi-0.46.0rc1.dist-info/RECORD,,
29
+ anydi-0.47.0rc0.dist-info/METADATA,sha256=MB9wf9GrUWqVQ-SlB4Y3jdz8Fx4WYS6XNA9L298u0lc,4960
30
+ anydi-0.47.0rc0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ anydi-0.47.0rc0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
32
+ anydi-0.47.0rc0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
33
+ anydi-0.47.0rc0.dist-info/RECORD,,
anydi/ext/_utils.py DELETED
@@ -1,77 +0,0 @@
1
- """AnyDI FastAPI extension."""
2
-
3
- from __future__ import annotations
4
-
5
- import inspect
6
- import logging
7
- from typing import Annotated, Any, Callable
8
-
9
- from typing_extensions import get_args, get_origin
10
-
11
- from anydi import Container
12
- from anydi._typing import _InjectMarker
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- class HasInterface(_InjectMarker):
18
- __slots__ = ("_interface",)
19
-
20
- def __init__(self, interface: Any = None) -> None:
21
- self._interface = interface
22
-
23
- @property
24
- def interface(self) -> Any:
25
- if self._interface is None:
26
- raise TypeError("Interface is not set.")
27
- return self._interface
28
-
29
- @interface.setter
30
- def interface(self, interface: Any) -> None:
31
- self._interface = interface
32
-
33
-
34
- def patch_annotated_parameter(parameter: inspect.Parameter) -> inspect.Parameter:
35
- """Patch an annotated parameter to resolve the default value."""
36
- if not (
37
- get_origin(parameter.annotation) is Annotated
38
- and parameter.default is inspect.Parameter.empty
39
- ):
40
- return parameter
41
-
42
- tp_origin, *tp_metadata = get_args(parameter.annotation)
43
- default = tp_metadata[-1]
44
-
45
- if not isinstance(default, HasInterface):
46
- return parameter
47
-
48
- if (num := len(tp_metadata[:-1])) == 0:
49
- interface = tp_origin
50
- elif num == 1:
51
- interface = Annotated[tp_origin, tp_metadata[0]]
52
- elif num == 2:
53
- interface = Annotated[tp_origin, tp_metadata[0], tp_metadata[1]]
54
- elif num == 3:
55
- interface = Annotated[
56
- tp_origin,
57
- tp_metadata[0],
58
- tp_metadata[1],
59
- tp_metadata[2],
60
- ]
61
- else:
62
- raise TypeError("Too many annotated arguments.") # pragma: no cover
63
- return parameter.replace(annotation=interface, default=default)
64
-
65
-
66
- def patch_call_parameter(
67
- container: Container, call: Callable[..., Any], parameter: inspect.Parameter
68
- ) -> None:
69
- """Patch a parameter to inject dependencies using AnyDI."""
70
- parameter = patch_annotated_parameter(parameter)
71
-
72
- interface, should_inject = container._validate_injected_parameter(
73
- parameter, call=call
74
- ) # noqa
75
- if should_inject:
76
- parameter.default.interface = interface
77
- return None