anydi 0.46.0__tar.gz → 0.47.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 (96) hide show
  1. {anydi-0.46.0 → anydi-0.47.0}/PKG-INFO +1 -1
  2. {anydi-0.46.0 → anydi-0.47.0}/anydi/__init__.py +5 -3
  3. {anydi-0.46.0 → anydi-0.47.0}/anydi/_container.py +46 -14
  4. {anydi-0.46.0 → anydi-0.47.0}/anydi/_typing.py +41 -28
  5. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/ninja/_signature.py +2 -2
  6. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/fastapi.py +10 -12
  7. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/faststream.py +6 -8
  8. {anydi-0.46.0 → anydi-0.47.0}/pyproject.toml +2 -2
  9. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/api/router.py +4 -4
  10. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/starlette/app.py +3 -3
  11. {anydi-0.46.0 → anydi-0.47.0}/tests/test_container.py +45 -43
  12. {anydi-0.46.0 → anydi-0.47.0}/uv.lock +1 -1
  13. anydi-0.46.0/anydi/ext/_utils.py +0 -77
  14. {anydi-0.46.0 → anydi-0.47.0}/.editorconfig +0 -0
  15. {anydi-0.46.0 → anydi-0.47.0}/.github/workflows/ci.yml +0 -0
  16. {anydi-0.46.0 → anydi-0.47.0}/.gitignore +0 -0
  17. {anydi-0.46.0 → anydi-0.47.0}/.readthedocs.yaml +0 -0
  18. {anydi-0.46.0 → anydi-0.47.0}/LICENSE +0 -0
  19. {anydi-0.46.0 → anydi-0.47.0}/Makefile +0 -0
  20. {anydi-0.46.0 → anydi-0.47.0}/README.md +0 -0
  21. {anydi-0.46.0 → anydi-0.47.0}/anydi/_async.py +0 -0
  22. {anydi-0.46.0 → anydi-0.47.0}/anydi/_context.py +0 -0
  23. {anydi-0.46.0 → anydi-0.47.0}/anydi/_decorators.py +0 -0
  24. {anydi-0.46.0 → anydi-0.47.0}/anydi/_module.py +0 -0
  25. {anydi-0.46.0 → anydi-0.47.0}/anydi/_provider.py +0 -0
  26. {anydi-0.46.0 → anydi-0.47.0}/anydi/_scan.py +0 -0
  27. {anydi-0.46.0 → anydi-0.47.0}/anydi/_scope.py +0 -0
  28. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/__init__.py +0 -0
  29. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/__init__.py +0 -0
  30. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/_container.py +0 -0
  31. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/_settings.py +0 -0
  32. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/_utils.py +0 -0
  33. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/apps.py +0 -0
  34. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/middleware.py +0 -0
  35. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/ninja/__init__.py +0 -0
  36. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/django/ninja/_operation.py +0 -0
  37. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/pydantic_settings.py +0 -0
  38. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/pytest_plugin.py +0 -0
  39. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/starlette/__init__.py +0 -0
  40. {anydi-0.46.0 → anydi-0.47.0}/anydi/ext/starlette/middleware.py +0 -0
  41. {anydi-0.46.0 → anydi-0.47.0}/anydi/py.typed +0 -0
  42. {anydi-0.46.0 → anydi-0.47.0}/anydi/testing.py +0 -0
  43. {anydi-0.46.0 → anydi-0.47.0}/docs/examples/basic.md +0 -0
  44. {anydi-0.46.0 → anydi-0.47.0}/docs/extensions/django.md +0 -0
  45. {anydi-0.46.0 → anydi-0.47.0}/docs/extensions/fastapi.md +0 -0
  46. {anydi-0.46.0 → anydi-0.47.0}/docs/extensions/faststream.md +0 -0
  47. {anydi-0.46.0 → anydi-0.47.0}/docs/extensions/pydantic_settings.md +0 -0
  48. {anydi-0.46.0 → anydi-0.47.0}/docs/index.md +0 -0
  49. {anydi-0.46.0 → anydi-0.47.0}/docs/usage.md +0 -0
  50. {anydi-0.46.0 → anydi-0.47.0}/mkdocs.yml +0 -0
  51. {anydi-0.46.0 → anydi-0.47.0}/tests/__init__.py +0 -0
  52. {anydi-0.46.0 → anydi-0.47.0}/tests/conftest.py +0 -0
  53. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/__init__.py +0 -0
  54. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/__init__.py +0 -0
  55. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/api/__init__.py +0 -0
  56. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/api/test_router.py +0 -0
  57. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/api/urls.py +0 -0
  58. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/conftest.py +0 -0
  59. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/container.py +0 -0
  60. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/scan/__init__.py +0 -0
  61. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/services.py +0 -0
  62. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/settings.py +0 -0
  63. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/test_views.py +0 -0
  64. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/urls.py +0 -0
  65. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/django/views.py +0 -0
  66. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fastapi/__init__.py +0 -0
  67. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fastapi/app.py +0 -0
  68. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fastapi/conftest.py +0 -0
  69. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fastapi/test_ext.py +0 -0
  70. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fastapi/test_routes.py +0 -0
  71. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/faststream/__init__.py +0 -0
  72. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/faststream/test_ext.py +0 -0
  73. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/faststream/test_subscribers.py +0 -0
  74. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/fixtures.py +0 -0
  75. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/starlette/__init__.py +0 -0
  76. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/starlette/conftest.py +0 -0
  77. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/starlette/test_routes.py +0 -0
  78. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/test_pydantic.py +0 -0
  79. {anydi-0.46.0 → anydi-0.47.0}/tests/ext/test_pytest_plugin.py +0 -0
  80. {anydi-0.46.0 → anydi-0.47.0}/tests/fixtures.py +0 -0
  81. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/__init__.py +0 -0
  82. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/__init__.py +0 -0
  83. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a1/__init__.py +0 -0
  84. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a1/handlers.py +0 -0
  85. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a2/__init__.py +0 -0
  86. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a2/a21/__init__.py +0 -0
  87. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a2/a21/handlers.py +0 -0
  88. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a3/__init__.py +0 -0
  89. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/a/a3/handlers.py +0 -0
  90. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/b/__init__.py +0 -0
  91. {anydi-0.46.0 → anydi-0.47.0}/tests/scan_app/b/handlers.py +0 -0
  92. {anydi-0.46.0 → anydi-0.47.0}/tests/test_decorators.py +0 -0
  93. {anydi-0.46.0 → anydi-0.47.0}/tests/test_module.py +0 -0
  94. {anydi-0.46.0 → anydi-0.47.0}/tests/test_scan.py +0 -0
  95. {anydi-0.46.0 → anydi-0.47.0}/tests/test_testing.py +0 -0
  96. {anydi-0.46.0 → anydi-0.47.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.46.0
3
+ Version: 0.47.0
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>
@@ -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
  ]
@@ -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
  ############################
@@ -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)
@@ -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]:
@@ -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
  [project]
2
2
  name = "anydi"
3
- version = "0.46.0"
3
+ version = "0.47.0"
4
4
  description = "Dependency Injection library"
5
5
  authors = [{ name = "Anton Ruhlov", email = "antonruhlov@gmail.com" }]
6
6
  requires-python = "~=3.9"
@@ -137,7 +137,7 @@ omit = [
137
137
  ]
138
138
 
139
139
  [tool.bumpversion]
140
- current_version = "0.46.0"
140
+ current_version = "0.47.0"
141
141
  parse = """(?x)
142
142
  (?P<major>0|[1-9]\\d*)\\.
143
143
  (?P<minor>0|[1-9]\\d*)\\.
@@ -1,9 +1,9 @@
1
- from typing import Any
1
+ from typing import Annotated, Any
2
2
 
3
3
  from django.http import HttpRequest
4
4
  from ninja import Router
5
5
 
6
- import anydi
6
+ from anydi import Inject
7
7
 
8
8
  from tests.ext.django.services import HelloService
9
9
 
@@ -14,7 +14,7 @@ router = Router()
14
14
  def say_hello(
15
15
  request: HttpRequest,
16
16
  name: str,
17
- hello_service: HelloService = anydi.auto,
17
+ hello_service: Annotated[HelloService, Inject()],
18
18
  ) -> Any:
19
19
  assert hello_service.started
20
20
  return hello_service.say_hello(name)
@@ -24,7 +24,7 @@ def say_hello(
24
24
  async def say_hello_async(
25
25
  request: HttpRequest,
26
26
  name: str,
27
- hello_service: HelloService = anydi.auto,
27
+ hello_service: HelloService = Inject(),
28
28
  ) -> Any:
29
29
  assert hello_service.started
30
30
  return await hello_service.say_hello_async(name)
@@ -6,7 +6,7 @@ from starlette.requests import Request
6
6
  from starlette.responses import JSONResponse
7
7
  from starlette.routing import Route
8
8
 
9
- from anydi import auto
9
+ from anydi import Inject
10
10
  from anydi.ext.starlette.middleware import RequestScopedMiddleware
11
11
  from anydi.testing import TestContainer
12
12
 
@@ -28,8 +28,8 @@ def mail_service() -> MailService:
28
28
  @container.inject
29
29
  async def send_email(
30
30
  request: Request,
31
- user_service: UserService = auto,
32
- mail_service: MailService = auto,
31
+ user_service: UserService = Inject(),
32
+ mail_service: MailService = Inject(),
33
33
  ) -> JSONResponse:
34
34
  data = await request.json()
35
35
  message = cast(str, data.get("message"))
@@ -9,7 +9,7 @@ from typing import Annotated, Any, Callable, Union
9
9
  import pytest
10
10
  from typing_extensions import Self
11
11
 
12
- from anydi import Container, Provider, Scope, auto, request, singleton, transient
12
+ from anydi import Container, Inject, Provider, Scope, request, singleton, transient
13
13
  from anydi._provider import ProviderKind
14
14
  from anydi._typing import Event
15
15
 
@@ -1403,25 +1403,53 @@ class TestContainer:
1403
1403
 
1404
1404
 
1405
1405
  class TestContainerInjector:
1406
- def test_inject(self, container: Container) -> None:
1406
+ def test_inject_using_inject_marker(self, container: Container) -> None:
1407
1407
  @container.provider(scope="singleton")
1408
- def ident_provider() -> str:
1409
- return "1000"
1408
+ def message() -> str:
1409
+ return "test"
1410
+
1411
+ @container.inject
1412
+ def func(message: str = Inject()) -> str:
1413
+ return message
1410
1414
 
1415
+ result = func()
1416
+
1417
+ assert result == "test"
1418
+
1419
+ def test_inject_using_inject_annotated_marker(self, container: Container) -> None:
1411
1420
  @container.provider(scope="singleton")
1412
- def service_provider(ident: str) -> Service:
1413
- return Service(ident=ident)
1421
+ def message() -> str:
1422
+ return "test"
1414
1423
 
1415
1424
  @container.inject
1416
- def func(name: str, service: Service = auto) -> str:
1417
- return f"{name} = {service.ident}"
1425
+ def func(message: Annotated[str, Inject()]) -> str:
1426
+ return message
1418
1427
 
1419
- result = func(name="service ident")
1428
+ result = func() # type: ignore
1420
1429
 
1421
- assert result == "service ident = 1000"
1430
+ assert result == "test"
1431
+
1432
+ def test_inject_using_inject_annotated_with_marker(
1433
+ self, container: Container
1434
+ ) -> None:
1435
+ @container.provider(scope="singleton")
1436
+ def message() -> str:
1437
+ return "test"
1438
+
1439
+ with pytest.raises(
1440
+ TypeError,
1441
+ match=(
1442
+ "Cannot specify `Inject` in `Annotated` and default value "
1443
+ "together for 'message'"
1444
+ ),
1445
+ ):
1446
+
1447
+ @container.inject
1448
+ def func(message: Annotated[str, Inject()] = Inject()) -> str:
1449
+ return message
1422
1450
 
1423
1451
  def test_inject_missing_annotation(self, container: Container) -> None:
1424
- def handler(name=auto) -> str: # type: ignore
1452
+ def handler(name=Inject()) -> str: # type: ignore
1425
1453
  return name # type: ignore
1426
1454
 
1427
1455
  with pytest.raises(
@@ -1433,7 +1461,7 @@ class TestContainerInjector:
1433
1461
  def test_inject_unknown_dependency(self) -> None:
1434
1462
  container = Container()
1435
1463
 
1436
- def handler(message: str = auto) -> None:
1464
+ def handler(message: str = Inject()) -> None:
1437
1465
  pass
1438
1466
 
1439
1467
  with pytest.raises(
@@ -1445,32 +1473,6 @@ class TestContainerInjector:
1445
1473
  ):
1446
1474
  container.inject(handler)
1447
1475
 
1448
- def test_inject_auto_marker(self, container: Container) -> None:
1449
- @container.provider(scope="singleton")
1450
- def message() -> str:
1451
- return "test"
1452
-
1453
- @container.inject
1454
- def func(message: str = auto) -> str:
1455
- return message
1456
-
1457
- result = func()
1458
-
1459
- assert result == "test"
1460
-
1461
- def test_inject_auto_marker_call(self, container: Container) -> None:
1462
- @container.provider(scope="singleton")
1463
- def message() -> str:
1464
- return "test"
1465
-
1466
- @container.inject
1467
- def func(message: str = auto()) -> str:
1468
- return message
1469
-
1470
- result = func()
1471
-
1472
- assert result == "test"
1473
-
1474
1476
  def test_inject_class(self, container: Container) -> None:
1475
1477
  @container.provider(scope="singleton")
1476
1478
  def ident_provider() -> str:
@@ -1482,7 +1484,7 @@ class TestContainerInjector:
1482
1484
 
1483
1485
  @container.inject
1484
1486
  class Handler:
1485
- def __init__(self, name: str, service: Service = auto) -> None:
1487
+ def __init__(self, name: str, service: Service = Inject()) -> None:
1486
1488
  self.name = name
1487
1489
  self.service = service
1488
1490
 
@@ -1510,7 +1512,7 @@ class TestContainerInjector:
1510
1512
  await container.astart()
1511
1513
 
1512
1514
  @container.inject
1513
- async def func(name: str, service: Service = auto) -> str:
1515
+ async def func(name: str, service: Service = Inject()) -> str:
1514
1516
  return f"{name} = {service.ident}"
1515
1517
 
1516
1518
  result = await func(name="service ident")
@@ -1528,8 +1530,8 @@ class TestContainerInjector:
1528
1530
 
1529
1531
  def sum_handler(
1530
1532
  value1: int,
1531
- value2: Annotated[int, "value1"] = auto,
1532
- value3: Annotated[int, "value2"] = auto,
1533
+ value2: Annotated[int, "value1"] = Inject(),
1534
+ value3: Annotated[int, "value2"] = Inject(),
1533
1535
  ) -> int:
1534
1536
  return value1 + value2 + value3
1535
1537
 
@@ -1542,7 +1544,7 @@ class TestContainerInjector:
1542
1544
  def message() -> str:
1543
1545
  return "hello"
1544
1546
 
1545
- def handler(message: str = auto) -> str:
1547
+ def handler(message: str = Inject()) -> str:
1546
1548
  return message
1547
1549
 
1548
1550
  _ = container.run(handler)
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "anydi"
16
- version = "0.46.0"
16
+ version = "0.47.0"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "anyio" },
@@ -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
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
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