anydi 0.39.3__tar.gz → 0.41.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 (87) hide show
  1. {anydi-0.39.3 → anydi-0.41.0}/.github/workflows/ci.yml +1 -1
  2. {anydi-0.39.3 → anydi-0.41.0}/Makefile +4 -4
  3. {anydi-0.39.3 → anydi-0.41.0}/PKG-INFO +2 -2
  4. {anydi-0.39.3 → anydi-0.41.0}/anydi/_container.py +29 -24
  5. {anydi-0.39.3 → anydi-0.41.0}/anydi/_context.py +10 -10
  6. {anydi-0.39.3 → anydi-0.41.0}/anydi/_types.py +3 -3
  7. {anydi-0.39.3 → anydi-0.41.0}/anydi/_utils.py +1 -1
  8. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/ninja/_signature.py +3 -2
  9. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/faststream.py +2 -2
  10. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/pytest_plugin.py +6 -2
  11. {anydi-0.39.3 → anydi-0.41.0}/docs/examples/basic.md +2 -2
  12. {anydi-0.39.3 → anydi-0.41.0}/docs/extensions/fastapi.md +3 -3
  13. {anydi-0.39.3 → anydi-0.41.0}/docs/usage.md +9 -10
  14. {anydi-0.39.3 → anydi-0.41.0}/pyproject.toml +25 -15
  15. {anydi-0.39.3 → anydi-0.41.0}/tests/test_container.py +7 -24
  16. {anydi-0.39.3 → anydi-0.41.0}/uv.lock +839 -811
  17. {anydi-0.39.3 → anydi-0.41.0}/.editorconfig +0 -0
  18. {anydi-0.39.3 → anydi-0.41.0}/.gitignore +0 -0
  19. {anydi-0.39.3 → anydi-0.41.0}/.readthedocs.yaml +0 -0
  20. {anydi-0.39.3 → anydi-0.41.0}/LICENSE +0 -0
  21. {anydi-0.39.3 → anydi-0.41.0}/README.md +0 -0
  22. {anydi-0.39.3 → anydi-0.41.0}/anydi/__init__.py +0 -0
  23. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/__init__.py +0 -0
  24. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/_utils.py +0 -0
  25. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/__init__.py +0 -0
  26. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/_container.py +0 -0
  27. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/_settings.py +0 -0
  28. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/_utils.py +0 -0
  29. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/apps.py +0 -0
  30. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/middleware.py +0 -0
  31. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/ninja/__init__.py +0 -0
  32. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/django/ninja/_operation.py +0 -0
  33. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/fastapi.py +0 -0
  34. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/pydantic_settings.py +0 -0
  35. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/starlette/__init__.py +0 -0
  36. {anydi-0.39.3 → anydi-0.41.0}/anydi/ext/starlette/middleware.py +0 -0
  37. {anydi-0.39.3 → anydi-0.41.0}/anydi/py.typed +0 -0
  38. {anydi-0.39.3 → anydi-0.41.0}/docs/extensions/django.md +0 -0
  39. {anydi-0.39.3 → anydi-0.41.0}/docs/extensions/faststream.md +0 -0
  40. {anydi-0.39.3 → anydi-0.41.0}/docs/extensions/pydantic_settings.md +0 -0
  41. {anydi-0.39.3 → anydi-0.41.0}/docs/index.md +0 -0
  42. {anydi-0.39.3 → anydi-0.41.0}/mkdocs.yml +0 -0
  43. {anydi-0.39.3 → anydi-0.41.0}/tests/__init__.py +0 -0
  44. {anydi-0.39.3 → anydi-0.41.0}/tests/conftest.py +0 -0
  45. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/__init__.py +0 -0
  46. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/__init__.py +0 -0
  47. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/api/__init__.py +0 -0
  48. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/api/router.py +0 -0
  49. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/api/test_router.py +0 -0
  50. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/api/urls.py +0 -0
  51. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/conftest.py +0 -0
  52. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/container.py +0 -0
  53. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/scan/__init__.py +0 -0
  54. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/services.py +0 -0
  55. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/settings.py +0 -0
  56. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/test_views.py +0 -0
  57. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/urls.py +0 -0
  58. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/django/views.py +0 -0
  59. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/__init__.py +0 -0
  60. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/app.py +0 -0
  61. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/conftest.py +0 -0
  62. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/test_auto_register.py +0 -0
  63. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/test_ext.py +0 -0
  64. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fastapi/test_routes.py +0 -0
  65. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/faststream/__init__.py +0 -0
  66. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/faststream/test_ext.py +0 -0
  67. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/faststream/test_subscribers.py +0 -0
  68. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/fixtures.py +0 -0
  69. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/starlette/__init__.py +0 -0
  70. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/starlette/app.py +0 -0
  71. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/starlette/conftest.py +0 -0
  72. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/starlette/test_routes.py +0 -0
  73. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/test_pydantic.py +0 -0
  74. {anydi-0.39.3 → anydi-0.41.0}/tests/ext/test_pytest_plugin.py +0 -0
  75. {anydi-0.39.3 → anydi-0.41.0}/tests/fixtures.py +0 -0
  76. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/__init__.py +0 -0
  77. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/__init__.py +0 -0
  78. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a1/__init__.py +0 -0
  79. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a1/handlers.py +0 -0
  80. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a2/__init__.py +0 -0
  81. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a2/a21/__init__.py +0 -0
  82. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a2/a21/handlers.py +0 -0
  83. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a3/__init__.py +0 -0
  84. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/a/a3/handlers.py +0 -0
  85. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/b/__init__.py +0 -0
  86. {anydi-0.39.3 → anydi-0.41.0}/tests/scan_app/b/handlers.py +0 -0
  87. {anydi-0.39.3 → anydi-0.41.0}/tests/test_utils.py +0 -0
@@ -26,7 +26,7 @@ jobs:
26
26
  - name: Static type checking
27
27
  run: uv run mypy anydi tests
28
28
  - name: Code formatting
29
- run: uv run ruff check anydi tests && uv run ruff format anydi tests --check
29
+ run: uv run ruff check && uv run ruff format --check
30
30
 
31
31
  tests:
32
32
  name: Run unit tests ${{ matrix.name }}
@@ -6,12 +6,12 @@ help:
6
6
 
7
7
  lint: ## Run code linters
8
8
  uv run mypy anydi tests
9
- uv run ruff check anydi tests
10
- uv run ruff format anydi tests --check
9
+ uv run ruff check
10
+ uv run ruff format --check
11
11
 
12
12
  fmt: ## Run code formatters
13
- uv run ruff check anydi tests --fix
14
- uv run ruff format anydi tests
13
+ uv run ruff check --fix
14
+ uv run ruff format
15
15
 
16
16
  test: ## Run unit tests
17
17
  uv run pytest -vv tests --ignore=tests/ext/test_pytest_plugin.py --cov=anydi -p no:anydi -p no:testanydi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.39.3
3
+ Version: 0.41.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>
@@ -29,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
29
  Classifier: Typing :: Typed
30
30
  Requires-Python: ~=3.9
31
31
  Requires-Dist: anyio>=3.7.1
32
- Requires-Dist: typing-extensions<5,>=4.12.1
32
+ Requires-Dist: typing-extensions<5,>=4.13.2
33
33
  Requires-Dist: wrapt<2,>=1.17.0
34
34
  Description-Content-Type: text/markdown
35
35
 
@@ -14,7 +14,7 @@ from collections import defaultdict
14
14
  from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
15
15
  from contextvars import ContextVar
16
16
  from types import ModuleType
17
- from typing import Annotated, Any, Callable, TypeVar, Union, cast, overload
17
+ from typing import Annotated, Any, Callable, TypeVar, cast, overload
18
18
 
19
19
  from typing_extensions import Concatenate, ParamSpec, Self, final, get_args, get_origin
20
20
 
@@ -94,18 +94,18 @@ class Container:
94
94
  testing: bool = False,
95
95
  logger: logging.Logger | None = None,
96
96
  ) -> None:
97
- self._providers: dict[type[Any], Provider] = {}
97
+ self._providers: dict[Any, Provider] = {}
98
98
  self._strict = strict
99
- self._default_scope = default_scope
99
+ self._default_scope: Scope = default_scope
100
100
  self._testing = testing
101
101
  self._logger = logger or logging.getLogger(__name__)
102
- self._resources: dict[str, list[type[Any]]] = defaultdict(list)
102
+ self._resources: dict[str, list[Any]] = defaultdict(list)
103
103
  self._singleton_context = InstanceContext()
104
104
  self._request_context_var: ContextVar[InstanceContext | None] = ContextVar(
105
105
  "request_context", default=None
106
106
  )
107
- self._override_instances: dict[type[Any], Any] = {}
108
- self._unresolved_interfaces: set[type[Any]] = set()
107
+ self._override_instances: dict[Any, Any] = {}
108
+ self._unresolved_interfaces: set[Any] = set()
109
109
  self._inject_cache: dict[Callable[..., Any], Callable[..., Any]] = {}
110
110
 
111
111
  # Register providers
@@ -364,8 +364,8 @@ class Container:
364
364
  )
365
365
 
366
366
  unresolved_parameter = None
367
- parameters = []
368
- scopes = {}
367
+ parameters: list[inspect.Parameter] = []
368
+ scopes: dict[Scope, Provider] = {}
369
369
 
370
370
  for parameter in signature.parameters.values():
371
371
  if parameter.annotation is inspect.Parameter.empty:
@@ -490,7 +490,10 @@ class Container:
490
490
  call = interface
491
491
  if inspect.isclass(call) and not is_builtin_type(call):
492
492
  # Try to get defined scope
493
- scope = getattr(interface, "__scope__", parent_scope)
493
+ if hasattr(call, "__scope__"):
494
+ scope = getattr(call, "__scope__")
495
+ else:
496
+ scope = parent_scope
494
497
  return self._register_provider(call, scope, interface, **defaults)
495
498
  raise
496
499
 
@@ -510,9 +513,11 @@ class Container:
510
513
  def _parameter_has_default(
511
514
  self, parameter: inspect.Parameter, /, **defaults: Any
512
515
  ) -> bool:
513
- return (defaults and parameter.name in defaults) or (
514
- not self.strict and parameter.default is not inspect.Parameter.empty
516
+ has_default_in_kwargs = parameter.name in defaults if defaults else False
517
+ has_non_strict_default = not self.strict and (
518
+ parameter.default is not inspect.Parameter.empty
515
519
  )
520
+ return has_default_in_kwargs or has_non_strict_default
516
521
 
517
522
  ############################
518
523
  # Instance Methods
@@ -522,7 +527,7 @@ class Container:
522
527
  def resolve(self, interface: type[T]) -> T: ...
523
528
 
524
529
  @overload
525
- def resolve(self, interface: T) -> T: ...
530
+ def resolve(self, interface: T) -> T: ... # type: ignore
526
531
 
527
532
  def resolve(self, interface: type[T]) -> T:
528
533
  """Resolve an instance by interface."""
@@ -942,7 +947,7 @@ class Container:
942
947
 
943
948
  def _get_injected_params(self, call: Callable[..., Any]) -> dict[str, Any]:
944
949
  """Get the injected parameters of a callable object."""
945
- injected_params = {}
950
+ injected_params: dict[str, Any] = {}
946
951
  for parameter in get_typed_parameters(call):
947
952
  if not is_marker(parameter.default):
948
953
  continue
@@ -1007,6 +1012,10 @@ class Container:
1007
1012
  scope=decorator_args.scope,
1008
1013
  override=decorator_args.override,
1009
1014
  )(obj)
1015
+ else:
1016
+ raise TypeError(
1017
+ "The module must be a callable, a module type, or a module instance."
1018
+ )
1010
1019
 
1011
1020
  ############################
1012
1021
  # Scanner Methods
@@ -1022,10 +1031,10 @@ class Container:
1022
1031
  """Scan packages or modules for decorated members and inject dependencies."""
1023
1032
  dependencies: list[ScannedDependency] = []
1024
1033
 
1025
- if isinstance(packages, Iterable) and not isinstance(packages, str):
1026
- scan_packages: Iterable[ModuleType | str] = packages
1034
+ if isinstance(packages, ModuleType | str):
1035
+ scan_packages: Iterable[ModuleType | str] = [packages]
1027
1036
  else:
1028
- scan_packages = cast(Iterable[Union[ModuleType, str]], [packages])
1037
+ scan_packages = packages
1029
1038
 
1030
1039
  for package in scan_packages:
1031
1040
  dependencies.extend(self._scan_package(package, tags=tags))
@@ -1117,19 +1126,19 @@ class Container:
1117
1126
 
1118
1127
  def transient(target: T) -> T:
1119
1128
  """Decorator for marking a class as transient scope."""
1120
- setattr(target, "__scope__", "transient")
1129
+ target.__scope__ = "transient"
1121
1130
  return target
1122
1131
 
1123
1132
 
1124
1133
  def request(target: T) -> T:
1125
1134
  """Decorator for marking a class as request scope."""
1126
- setattr(target, "__scope__", "request")
1135
+ target.__scope__ = "request"
1127
1136
  return target
1128
1137
 
1129
1138
 
1130
1139
  def singleton(target: T) -> T:
1131
1140
  """Decorator for marking a class as singleton scope."""
1132
- setattr(target, "__scope__", "singleton")
1141
+ target.__scope__ = "singleton"
1133
1142
  return target
1134
1143
 
1135
1144
 
@@ -1141,11 +1150,7 @@ def provider(
1141
1150
  def decorator(
1142
1151
  target: Callable[Concatenate[M, P], T],
1143
1152
  ) -> Callable[Concatenate[M, P], T]:
1144
- setattr(
1145
- target,
1146
- "__provider__",
1147
- ProviderDecoratorArgs(scope=scope, override=override),
1148
- )
1153
+ target.__provider__ = ProviderDecoratorArgs(scope=scope, override=override) # type: ignore
1149
1154
  return target
1150
1155
 
1151
1156
  return decorator
@@ -16,17 +16,17 @@ class InstanceContext:
16
16
  __slots__ = ("_instances", "_stack", "_async_stack", "_lock", "_async_lock")
17
17
 
18
18
  def __init__(self) -> None:
19
- self._instances: dict[type[Any], Any] = {}
19
+ self._instances: dict[Any, Any] = {}
20
20
  self._stack = contextlib.ExitStack()
21
21
  self._async_stack = contextlib.AsyncExitStack()
22
22
  self._lock = threading.RLock()
23
23
  self._async_lock = AsyncRLock()
24
24
 
25
- def get(self, interface: type[Any]) -> Any | None:
25
+ def get(self, interface: Any) -> Any | None:
26
26
  """Get an instance from the context."""
27
27
  return self._instances.get(interface)
28
28
 
29
- def set(self, interface: type[Any], value: Any) -> None:
29
+ def set(self, interface: Any, value: Any) -> None:
30
30
  """Set an instance in the context."""
31
31
  self._instances[interface] = value
32
32
 
@@ -38,16 +38,16 @@ class InstanceContext:
38
38
  """Enter the context asynchronously."""
39
39
  return await self._async_stack.enter_async_context(cm)
40
40
 
41
- def __setitem__(self, interface: type[Any], value: Any) -> None:
41
+ def __setitem__(self, interface: Any, value: Any) -> None:
42
42
  self._instances[interface] = value
43
43
 
44
- def __getitem__(self, interface: type[Any]) -> Any:
44
+ def __getitem__(self, interface: Any) -> Any:
45
45
  return self._instances[interface]
46
46
 
47
- def __contains__(self, interface: type[Any]) -> bool:
47
+ def __contains__(self, interface: Any) -> bool:
48
48
  return interface in self._instances
49
49
 
50
- def __delitem__(self, interface: type[Any]) -> None:
50
+ def __delitem__(self, interface: Any) -> None:
51
51
  self._instances.pop(interface, None)
52
52
 
53
53
  def __enter__(self) -> Self:
@@ -78,9 +78,9 @@ class InstanceContext:
78
78
  exc_tb: TracebackType | None,
79
79
  ) -> bool:
80
80
  """Exit the context asynchronously."""
81
- return await run_async(
82
- self.__exit__, exc_type, exc_val, exc_tb
83
- ) or await self._async_stack.__aexit__(exc_type, exc_val, exc_tb)
81
+ sync_exit = await run_async(self.__exit__, exc_type, exc_val, exc_tb)
82
+ async_exit = await self._async_stack.__aexit__(exc_type, exc_val, exc_tb)
83
+ return bool(sync_exit) or bool(async_exit)
84
84
 
85
85
  async def aclose(self) -> None:
86
86
  """Close the scoped context asynchronously."""
@@ -8,7 +8,7 @@ from functools import cached_property
8
8
  from types import ModuleType
9
9
  from typing import Annotated, Any, Callable, NamedTuple, Union
10
10
 
11
- import wrapt
11
+ import wrapt # type: ignore
12
12
  from typing_extensions import Literal, Self, TypeAlias
13
13
 
14
14
  Scope = Literal["transient", "singleton", "request"]
@@ -43,9 +43,9 @@ def is_event_type(obj: Any) -> bool:
43
43
  return inspect.isclass(obj) and issubclass(obj, Event)
44
44
 
45
45
 
46
- class InstanceProxy(wrapt.ObjectProxy): # type: ignore[misc]
46
+ class InstanceProxy(wrapt.ObjectProxy): # type: ignore
47
47
  def __init__(self, wrapped: Any, *, interface: type[Any]) -> None:
48
- super().__init__(wrapped)
48
+ super().__init__(wrapped) # type: ignore
49
49
  self._self_interface = interface
50
50
 
51
51
  @property
@@ -12,7 +12,7 @@ from collections.abc import AsyncIterator, Iterator
12
12
  from types import TracebackType
13
13
  from typing import Any, Callable, ForwardRef, TypeVar
14
14
 
15
- import anyio
15
+ import anyio.to_thread
16
16
  from typing_extensions import ParamSpec, Self, get_args, get_origin
17
17
 
18
18
  try:
@@ -53,8 +53,9 @@ class ViewSignature(BaseViewSignature):
53
53
  func_param = self._get_param_type(name, arg)
54
54
  self.params.append(func_param)
55
55
 
56
- if hasattr(view_func, "_ninja_contribute_args"):
57
- for p_name, p_type, p_source in view_func._ninja_contribute_args: # noqa
56
+ ninja_contribute_args = getattr(view_func, "_ninja_contribute_args", None)
57
+ if ninja_contribute_args is not None:
58
+ for p_name, p_type, p_source in ninja_contribute_args:
58
59
  self.params.append(
59
60
  FuncParam(p_name, p_source.alias or p_name, p_source, p_type, False)
60
61
  )
@@ -30,8 +30,8 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
30
30
 
31
31
 
32
32
  def _get_broken_handlers(broker: BrokerUsecase[Any, Any]) -> list[Any]:
33
- if hasattr(broker, "handlers"):
34
- return [handler.calls[0][0] for handler in broker.handlers.values()]
33
+ if (handlers := getattr(broker, "handlers", None)) is not None:
34
+ return [handler.calls[0][0] for handler in handlers.values()]
35
35
  # faststream > 0.5.0
36
36
  return [
37
37
  subscriber.calls[0].handler
@@ -6,7 +6,6 @@ from collections.abc import Iterator
6
6
  from typing import Any, Callable, cast
7
7
 
8
8
  import pytest
9
- from _pytest.python import async_warn_and_skip
10
9
  from anyio.pytest_plugin import extract_backend_and_options, get_runner
11
10
 
12
11
  from anydi import Container
@@ -117,7 +116,12 @@ def _anydi_ainject(
117
116
 
118
117
  # Skip if the anyio backend is not available
119
118
  if "anyio_backend" not in request.fixturenames:
120
- async_warn_and_skip(request.node.nodeid)
119
+ msg = (
120
+ "To run async test functions with `anyio`, "
121
+ "please configure the `anyio` pytest plugin.\n"
122
+ "See: https://anyio.readthedocs.io/en/stable/testing.html"
123
+ )
124
+ pytest.fail(msg, pytrace=False)
121
125
 
122
126
  async def _awrapper() -> None:
123
127
  # Setup the container
@@ -86,9 +86,9 @@ from app.models import User
86
86
  from app.repositories import UserRepository
87
87
 
88
88
 
89
- @dataclass
90
89
  class UserService:
91
- user_repository: UserRepository
90
+ def __init__(self, user_repository: UserRepository) -> None:
91
+ self.user_repository = user_repository
92
92
 
93
93
  def get_users(self) -> List[User]:
94
94
  return self.user_repository.all()
@@ -144,15 +144,15 @@ from anydi import Container
144
144
  from anydi.ext.fastapi import Inject, RequestScopedMiddleware
145
145
 
146
146
 
147
- @dataclass
147
+ @dataclass(kw_only=True)
148
148
  class User:
149
149
  id: str
150
150
  email: str
151
151
 
152
152
 
153
- @dataclass
154
153
  class UserService:
155
- request: Request
154
+ def __init__(self, request: Request) -> None:
155
+ self.request = request
156
156
 
157
157
  async def get_user(self, user_id: str) -> User:
158
158
  # Use request headers, IP, etc., from the Request object as needed
@@ -153,22 +153,21 @@ assert not container.is_resolved(int)
153
153
  Consider a scenario with class dependencies:
154
154
 
155
155
  ```python
156
- from dataclasses import dataclass
157
-
158
156
  class Database:
159
157
  def connect(self) -> None:
160
158
  print("connect")
161
159
  def disconnect(self) -> None:
162
160
  print("disconnect")
163
161
 
164
- @dataclass
162
+
165
163
  class Repository:
166
- db: Database
164
+ def __init__(self, db: Database) -> None:
165
+ self.db = db
167
166
 
168
167
 
169
- @dataclass
170
168
  class Service:
171
- repo: Repository
169
+ def __init__(self, repo: Repository) -> None:
170
+ self.repo = repo
172
171
  ```
173
172
 
174
173
  You can instantiate these classes without manually registering each one:
@@ -746,17 +745,17 @@ class Item:
746
745
  name: str
747
746
 
748
747
 
749
- @dataclass(kw_only=True)
750
748
  class Repository:
751
- items: list[Item] = field(default_factory=list)
749
+ def __init__(self) -> None:
750
+ self.items: list[Item] = []
752
751
 
753
752
  def all(self) -> list[Item]:
754
753
  return self.items
755
754
 
756
755
 
757
- @dataclass(kw_only=True)
758
756
  class Service:
759
- repo: Repository
757
+ def __init__(self, repo: Repository) -> None:
758
+ self.repo = repo
760
759
 
761
760
  def get_items(self) -> list[Item]:
762
761
  return self.repo.all()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anydi"
3
- version = "0.39.3"
3
+ version = "0.41.0"
4
4
  description = "Dependency Injection library"
5
5
  authors = [{ name = "Anton Ruhlov", email = "antonruhlov@gmail.com" }]
6
6
  requires-python = "~=3.9"
@@ -37,7 +37,7 @@ classifiers = [
37
37
  "Programming Language :: Python :: 3 :: Only",
38
38
  ]
39
39
  dependencies = [
40
- "typing-extensions>=4.12.1,<5",
40
+ "typing-extensions>=4.13.2,<5",
41
41
  "anyio>=3.7.1",
42
42
  "wrapt>=1.17.0,<2",
43
43
  ]
@@ -51,11 +51,12 @@ anydi = "anydi.ext.pytest_plugin"
51
51
  [dependency-groups]
52
52
  dev = [
53
53
  "mypy[faster-cache]>=1.15.0,<2",
54
- "ruff>=0.9.4,<1.0",
55
- "pytest>=8.3.1,<9",
54
+ "ruff>=0.11.10,<1.0",
55
+ "pytest>=8.4.0,<9",
56
56
  "pytest-cov>=6.0.0,<7",
57
- "fastapi>=0.100.0,<0.101",
58
- "httpx>=0.26.0,<0.27",
57
+ "starlette>=0.37.2",
58
+ "fastapi>=0.100.0",
59
+ "httpx>=0.26.0",
59
60
  "django~=4.2",
60
61
  "django-stubs>=5.1.1,<6",
61
62
  "django-ninja>=1.1.0,<2",
@@ -63,7 +64,8 @@ dev = [
63
64
  "faststream>=0.5.10,<0.6",
64
65
  "redis>=5.0.4,<6",
65
66
  "pydantic-settings>=2.4.0,<3",
66
- "bump-my-version>=0.31.1",
67
+ "bump-my-version>=1.1.4",
68
+ "pyright>=1.1.401",
67
69
  ]
68
70
  docs = [
69
71
  "mkdocs>=1.4.2,<2",
@@ -77,6 +79,10 @@ build-backend = "hatchling.build"
77
79
  [tool.ruff]
78
80
  line-length = 88
79
81
  target-version = "py39"
82
+ include = [
83
+ "anydi/**/*.py",
84
+ "tests/**/*.py",
85
+ ]
80
86
 
81
87
  [tool.ruff.lint]
82
88
  select = ["A", "B", "C", "E", "F", "I", "W", "TID252", "T20", "UP", "FURB", "PT"]
@@ -89,15 +95,19 @@ forced-separate = ["tests", "app"]
89
95
  [tool.ruff.lint.pydocstyle]
90
96
  convention = "google"
91
97
 
98
+ [tool.pyright]
99
+ include = [
100
+ "anydi/**/*.py",
101
+ "tests/**/*.py",
102
+ ]
103
+ strict = [
104
+ "anydi/*.py",
105
+ ]
106
+ typeCheckingMode = "standard"
107
+
92
108
  [tool.mypy]
93
109
  strict = true
94
-
95
- [[tool.mypy.overrides]]
96
- module = [
97
- "django.*",
98
- "wrapt.*",
99
- ]
100
- ignore_missing_imports = true
110
+ warn_unused_ignores = false
101
111
 
102
112
  [[tool.mypy.overrides]]
103
113
  module = [
@@ -136,7 +146,7 @@ omit = [
136
146
  ]
137
147
 
138
148
  [tool.bumpversion]
139
- current_version = "0.39.3"
149
+ current_version = "0.41.0"
140
150
  parse = """(?x)
141
151
  (?P<major>0|[1-9]\\d*)\\.
142
152
  (?P<minor>0|[1-9]\\d*)\\.
@@ -1557,30 +1557,6 @@ class TestContainerInjector:
1557
1557
 
1558
1558
  assert result == "service ident = 1000"
1559
1559
 
1560
- def test_inject_dataclass(self, container: Container) -> None:
1561
- @container.provider(scope="singleton")
1562
- def ident_provider() -> str:
1563
- return "1000"
1564
-
1565
- @container.provider(scope="singleton")
1566
- def service_provider(ident: str) -> Service:
1567
- return Service(ident=ident)
1568
-
1569
- @container.inject
1570
- @dataclass
1571
- class Handler:
1572
- name: str
1573
- service: Service = auto
1574
-
1575
- def handle(self) -> str:
1576
- return f"{self.name} = {self.service.ident}"
1577
-
1578
- handler = Handler(name="service ident")
1579
-
1580
- result = handler.handle()
1581
-
1582
- assert result == "service ident = 1000"
1583
-
1584
1560
  async def test_inject_with_sync_and_async_resources(
1585
1561
  self, container: Container
1586
1562
  ) -> None:
@@ -1894,6 +1870,13 @@ class TestContainerModule:
1894
1870
  Annotated[str, "dep2"],
1895
1871
  ]
1896
1872
 
1873
+ def test_register_module_invlid_path(self, container: Container) -> None:
1874
+ with pytest.raises(
1875
+ TypeError,
1876
+ match="The module must be a callable, a module type, or a module instance.",
1877
+ ):
1878
+ container.register_module("anydi.Container")
1879
+
1897
1880
 
1898
1881
  class TestContainerScanning:
1899
1882
  def test_scan(self, container: Container) -> None: