anydi 0.25.0a2__tar.gz → 0.25.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.
Files changed (27) hide show
  1. {anydi-0.25.0a2 → anydi-0.25.2}/PKG-INFO +1 -1
  2. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_container.py +4 -1
  3. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_utils.py +51 -28
  4. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/_settings.py +1 -1
  5. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/_utils.py +4 -0
  6. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/apps.py +5 -2
  7. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/ninja/_signature.py +2 -2
  8. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/pytest_plugin.py +4 -3
  9. {anydi-0.25.0a2 → anydi-0.25.2}/pyproject.toml +1 -1
  10. {anydi-0.25.0a2 → anydi-0.25.2}/LICENSE +0 -0
  11. {anydi-0.25.0a2 → anydi-0.25.2}/README.md +0 -0
  12. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/__init__.py +0 -0
  13. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_context.py +0 -0
  14. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_logger.py +0 -0
  15. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_module.py +0 -0
  16. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_scanner.py +0 -0
  17. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/_types.py +0 -0
  18. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/__init__.py +0 -0
  19. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/__init__.py +0 -0
  20. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/_container.py +0 -0
  21. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/middleware.py +0 -0
  22. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/ninja/__init__.py +0 -0
  23. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/django/ninja/_operation.py +0 -0
  24. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/fastapi.py +0 -0
  25. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/starlette/__init__.py +0 -0
  26. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/ext/starlette/middleware.py +0 -0
  27. {anydi-0.25.0a2 → anydi-0.25.2}/anydi/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anydi
3
- Version: 0.25.0a2
3
+ Version: 0.25.2
4
4
  Summary: Dependency Injection library
5
5
  Home-page: https://github.com/antonrh/anydi
6
6
  License: MIT
@@ -47,6 +47,7 @@ from ._utils import (
47
47
  get_full_qualname,
48
48
  get_typed_parameters,
49
49
  get_typed_return_annotation,
50
+ has_resource_origin,
50
51
  is_builtin_type,
51
52
  )
52
53
 
@@ -723,7 +724,9 @@ class Container:
723
724
  f"Missing `{get_full_qualname(obj)}` provider return annotation."
724
725
  )
725
726
 
726
- if get_origin(annotation) in (get_origin(Iterator), get_origin(AsyncIterator)):
727
+ origin = get_origin(annotation)
728
+
729
+ if has_resource_origin(origin):
727
730
  args = get_args(annotation)
728
731
  if args:
729
732
  return args[0]
@@ -6,9 +6,9 @@ import builtins
6
6
  import functools
7
7
  import inspect
8
8
  import sys
9
- from typing import Any, Callable, ForwardRef, TypeVar, cast
9
+ from typing import Any, AsyncIterator, Callable, ForwardRef, Iterator, TypeVar, cast
10
10
 
11
- from typing_extensions import Annotated, ParamSpec, get_origin
11
+ from typing_extensions import ParamSpec, get_args, get_origin
12
12
 
13
13
  try:
14
14
  import anyio # noqa
@@ -33,27 +33,28 @@ P = ParamSpec("P")
33
33
 
34
34
  def get_full_qualname(obj: Any) -> str:
35
35
  """Get the fully qualified name of an object."""
36
- origin = get_origin(obj)
37
- if origin is Annotated:
38
- metadata = ", ".join(
39
- [
40
- f'"{arg}"' if isinstance(arg, str) else str(arg)
41
- for arg in obj.__metadata__
42
- ]
43
- )
44
- return f"Annotated[{get_full_qualname(obj.__args__[0])}, {metadata}]]"
45
-
46
36
  qualname = getattr(obj, "__qualname__", None)
47
- module_name = getattr(obj, "__module__", None)
37
+ module = getattr(obj, "__module__", None)
38
+
48
39
  if qualname is None:
49
40
  qualname = type(obj).__qualname__
50
41
 
51
- if module_name is None:
52
- module_name = type(obj).__module__
42
+ if module is None:
43
+ module = type(obj).__module__
53
44
 
54
- if module_name == builtins.__name__:
45
+ if module == builtins.__name__:
55
46
  return qualname
56
- return f"{module_name}.{qualname}"
47
+
48
+ origin = get_origin(obj)
49
+
50
+ if origin:
51
+ args = ", ".join(
52
+ get_full_qualname(arg) if not isinstance(arg, str) else f'"{arg}"'
53
+ for arg in get_args(obj)
54
+ )
55
+ return f"{get_full_qualname(origin)}[{args}]"
56
+
57
+ return f"{module}.{qualname}"
57
58
 
58
59
 
59
60
  def is_builtin_type(tp: type[Any]) -> bool:
@@ -61,17 +62,19 @@ def is_builtin_type(tp: type[Any]) -> bool:
61
62
  return tp.__module__ == builtins.__name__
62
63
 
63
64
 
64
- def make_forwardref(annotation: str, globalns: dict[str, Any]) -> Any:
65
- """Create a forward reference from a string annotation."""
66
- forward_ref = ForwardRef(annotation)
67
- return evaluate_forwardref(forward_ref, globalns, globalns)
68
-
69
-
70
- def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
65
+ def get_typed_annotation(
66
+ annotation: Any,
67
+ globalns: dict[str, Any],
68
+ module: Any = None,
69
+ is_class: bool = False,
70
+ ) -> Any:
71
71
  """Get the typed annotation of a parameter."""
72
72
  if isinstance(annotation, str):
73
- annotation = ForwardRef(annotation)
74
- annotation = evaluate_forwardref(annotation, globalns, globalns)
73
+ if sys.version_info < (3, 9):
74
+ annotation = ForwardRef(annotation)
75
+ else:
76
+ annotation = ForwardRef(annotation, module=module, is_class=is_class)
77
+ annotation = evaluate_forwardref(annotation, globalns, {})
75
78
  return annotation
76
79
 
77
80
 
@@ -82,20 +85,40 @@ def get_typed_return_annotation(obj: Callable[..., Any]) -> Any:
82
85
  if annotation is inspect.Signature.empty:
83
86
  return None
84
87
  globalns = getattr(obj, "__globals__", {})
85
- return get_typed_annotation(annotation, globalns)
88
+ module = getattr(obj, "__module__", None)
89
+ is_class = inspect.isclass(obj)
90
+ return get_typed_annotation(annotation, globalns, module=module, is_class=is_class)
86
91
 
87
92
 
88
93
  def get_typed_parameters(obj: Callable[..., Any]) -> list[inspect.Parameter]:
89
94
  """Get the typed parameters of a callable object."""
90
95
  globalns = getattr(obj, "__globals__", {})
96
+ module = getattr(obj, "__module__", None)
97
+ is_class = inspect.isclass(obj)
91
98
  return [
92
99
  parameter.replace(
93
- annotation=get_typed_annotation(parameter.annotation, globalns)
100
+ annotation=get_typed_annotation(
101
+ parameter.annotation,
102
+ globalns,
103
+ module=module,
104
+ is_class=is_class,
105
+ )
94
106
  )
95
107
  for name, parameter in inspect.signature(obj).parameters.items()
96
108
  ]
97
109
 
98
110
 
111
+ _resource_origins = (
112
+ get_origin(Iterator),
113
+ get_origin(AsyncIterator),
114
+ )
115
+
116
+
117
+ def has_resource_origin(origin: Any) -> bool:
118
+ """Check if the given origin is a resource origin."""
119
+ return origin in _resource_origins
120
+
121
+
99
122
  async def run_async(
100
123
  func: Callable[P, T],
101
124
  /,
@@ -11,7 +11,7 @@ class Settings(TypedDict):
11
11
  STRICT_MODE: bool
12
12
  REGISTER_SETTINGS: bool
13
13
  REGISTER_COMPONENTS: bool
14
- INJECT_URLCONF: str | None
14
+ INJECT_URLCONF: str | Sequence[str] | None
15
15
  MODULES: Sequence[str]
16
16
  SCAN_PACKAGES: Sequence[str]
17
17
  PATCH_NINJA: bool
@@ -94,10 +94,14 @@ def inject_urlpatterns(container: anydi.Container, *, urlconf: str) -> None:
94
94
  """Auto-inject the container into views."""
95
95
  resolver = get_resolver(urlconf)
96
96
  for pattern in iter_urlpatterns(resolver.url_patterns):
97
+ # Skip already injected views
98
+ if hasattr(pattern.callback, "_injected"):
99
+ continue
97
100
  # Skip django-ninja views
98
101
  if pattern.lookup_str.startswith("ninja."):
99
102
  continue # pragma: no cover
100
103
  pattern.callback = container.inject(pattern.callback)
104
+ pattern.callback._injected = True # type: ignore[attr-defined]
101
105
 
102
106
 
103
107
  def iter_urlpatterns(
@@ -33,7 +33,7 @@ class ContainerConfig(AppConfig): # type: ignore[misc]
33
33
  )
34
34
  except ImportError as exc:
35
35
  raise ImproperlyConfigured(
36
- f"Cannot import container getter '{container_factory_path}'."
36
+ f"Cannot import container factory '{container_factory_path}'."
37
37
  ) from exc
38
38
  self.container = container_factory()
39
39
  else:
@@ -75,7 +75,10 @@ class ContainerConfig(AppConfig): # type: ignore[misc]
75
75
 
76
76
  # Auto-injecting the container into views
77
77
  if urlconf := self.settings["INJECT_URLCONF"]:
78
- inject_urlpatterns(self.container, urlconf=urlconf)
78
+ if isinstance(urlconf, str):
79
+ urlconf = [urlconf]
80
+ for u in urlconf:
81
+ inject_urlpatterns(self.container, urlconf=u)
79
82
 
80
83
  # Scan packages
81
84
  for scan_package in self.settings["SCAN_PACKAGES"]:
@@ -11,7 +11,7 @@ from ninja.signature.details import (
11
11
  )
12
12
  from ninja.signature.utils import get_path_param_names, get_typed_signature
13
13
 
14
- from anydi._types import Marker # noqa
14
+ from anydi._types import is_marker # noqa
15
15
 
16
16
 
17
17
  class ViewSignature(BaseViewSignature):
@@ -46,7 +46,7 @@ class ViewSignature(BaseViewSignature):
46
46
  continue
47
47
 
48
48
  # Skip default values that are anydi dependency markers
49
- if isinstance(arg.default, Marker):
49
+ if is_marker(arg.default):
50
50
  self.dependencies.append((name, arg.annotation))
51
51
  continue
52
52
 
@@ -6,6 +6,7 @@ from typing import Any, Callable, Iterator, cast
6
6
  import pytest
7
7
 
8
8
  from anydi import Container
9
+ from anydi._utils import get_typed_parameters
9
10
 
10
11
 
11
12
  def pytest_configure(config: pytest.Config) -> None:
@@ -63,14 +64,14 @@ def _anydi_injected_parameter_iterator(
63
64
  _anydi_unresolved: list[str],
64
65
  ) -> Callable[[], Iterator[tuple[str, Any]]]:
65
66
  def _iterator() -> Iterator[tuple[str, inspect.Parameter]]:
66
- for name, parameter in inspect.signature(request.function).parameters.items():
67
+ for parameter in get_typed_parameters(request.function):
67
68
  if (
68
69
  ((interface := parameter.annotation) is parameter.empty)
69
70
  or interface in _anydi_unresolved
70
- or name in request.node.funcargs
71
+ or parameter.name in request.node.funcargs
71
72
  ):
72
73
  continue
73
- yield name, interface
74
+ yield parameter.name, interface
74
75
 
75
76
  return _iterator
76
77
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "anydi"
3
- version = "0.25.0a2"
3
+ version = "0.25.2"
4
4
  description = "Dependency Injection library"
5
5
  authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
6
6
  license = "MIT"
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