anydi 0.50.0__tar.gz → 0.53.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 (32) hide show
  1. {anydi-0.50.0 → anydi-0.53.0}/PKG-INFO +8 -2
  2. {anydi-0.50.0 → anydi-0.53.0}/README.md +7 -1
  3. {anydi-0.50.0 → anydi-0.53.0}/anydi/_container.py +2 -13
  4. {anydi-0.50.0 → anydi-0.53.0}/anydi/_scan.py +3 -3
  5. {anydi-0.50.0 → anydi-0.53.0}/anydi/_typing.py +5 -43
  6. anydi-0.53.0/anydi/ext/django/__init__.py +8 -0
  7. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/fastapi.py +3 -2
  8. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/faststream.py +3 -2
  9. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/pytest_plugin.py +3 -2
  10. {anydi-0.50.0 → anydi-0.53.0}/pyproject.toml +3 -14
  11. anydi-0.50.0/anydi/ext/django/__init__.py +0 -9
  12. anydi-0.50.0/anydi/ext/django/_container.py +0 -18
  13. anydi-0.50.0/anydi/ext/django/_settings.py +0 -37
  14. anydi-0.50.0/anydi/ext/django/_utils.py +0 -89
  15. anydi-0.50.0/anydi/ext/django/apps.py +0 -89
  16. anydi-0.50.0/anydi/ext/django/middleware.py +0 -29
  17. anydi-0.50.0/anydi/ext/django/ninja/__init__.py +0 -16
  18. anydi-0.50.0/anydi/ext/django/ninja/_operation.py +0 -75
  19. anydi-0.50.0/anydi/ext/django/ninja/_signature.py +0 -68
  20. {anydi-0.50.0 → anydi-0.53.0}/anydi/__init__.py +0 -0
  21. {anydi-0.50.0 → anydi-0.53.0}/anydi/_async.py +0 -0
  22. {anydi-0.50.0 → anydi-0.53.0}/anydi/_context.py +0 -0
  23. {anydi-0.50.0 → anydi-0.53.0}/anydi/_decorators.py +0 -0
  24. {anydi-0.50.0 → anydi-0.53.0}/anydi/_module.py +0 -0
  25. {anydi-0.50.0 → anydi-0.53.0}/anydi/_provider.py +0 -0
  26. {anydi-0.50.0 → anydi-0.53.0}/anydi/_scope.py +0 -0
  27. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/__init__.py +0 -0
  28. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/pydantic_settings.py +0 -0
  29. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/starlette/__init__.py +0 -0
  30. {anydi-0.50.0 → anydi-0.53.0}/anydi/ext/starlette/middleware.py +0 -0
  31. {anydi-0.50.0 → anydi-0.53.0}/anydi/py.typed +0 -0
  32. {anydi-0.50.0 → anydi-0.53.0}/anydi/testing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.50.0
3
+ Version: 0.53.0
4
4
  Summary: Dependency Injection library
5
5
  Keywords: dependency injection,dependencies,di,async,asyncio,application
6
6
  Author: Anton Ruhlov
@@ -140,6 +140,12 @@ anydi.ext.fastapi.install(app, container)
140
140
 
141
141
  ## Django Ninja Example
142
142
 
143
+ ### Install
144
+
145
+ ```sh
146
+ pip install 'anydi-django[ninja]'
147
+ ```
148
+
143
149
  *container.py*
144
150
 
145
151
  ```python
@@ -161,7 +167,7 @@ def get_container() -> Container:
161
167
  ```python
162
168
  INSTALLED_APPS = [
163
169
  ...
164
- "anydi.ext.django",
170
+ "anydi_django",
165
171
  ]
166
172
 
167
173
  ANYDI = {
@@ -105,6 +105,12 @@ anydi.ext.fastapi.install(app, container)
105
105
 
106
106
  ## Django Ninja Example
107
107
 
108
+ ### Install
109
+
110
+ ```sh
111
+ pip install 'anydi-django[ninja]'
112
+ ```
113
+
108
114
  *container.py*
109
115
 
110
116
  ```python
@@ -126,7 +132,7 @@ def get_container() -> Container:
126
132
  ```python
127
133
  INSTALLED_APPS = [
128
134
  ...
129
- "anydi.ext.django",
135
+ "anydi_django",
130
136
  ]
131
137
 
132
138
  ANYDI = {
@@ -25,8 +25,6 @@ from ._scope import ALLOWED_SCOPES, Scope
25
25
  from ._typing import (
26
26
  NOT_SET,
27
27
  Event,
28
- get_typed_annotation,
29
- get_typed_parameters,
30
28
  is_async_context_manager,
31
29
  is_context_manager,
32
30
  is_event_type,
@@ -264,9 +262,7 @@ class Container:
264
262
  self._validate_provider_scope(scope, name, kind)
265
263
 
266
264
  # Get the signature
267
- globalns = getattr(call, "__globals__", {})
268
- module = getattr(call, "__module__", None)
269
- signature = inspect.signature(call, globals=globalns)
265
+ signature = inspect.signature(call, eval_str=True)
270
266
 
271
267
  # Detect the interface
272
268
  if interface is NOT_SET:
@@ -277,9 +273,6 @@ class Container:
277
273
  if interface is inspect.Signature.empty:
278
274
  interface = None
279
275
 
280
- if isinstance(interface, str):
281
- interface = get_typed_annotation(interface, globalns, module)
282
-
283
276
  # If the callable is an iterator, return the actual type
284
277
  if is_iterator_type(interface) or is_iterator_type(get_origin(interface)):
285
278
  if args := get_args(interface):
@@ -320,10 +313,6 @@ class Container:
320
313
  f"are not allowed in the provider `{name}`."
321
314
  )
322
315
 
323
- parameter = parameter.replace(
324
- annotation=get_typed_annotation(parameter.annotation, globalns, module)
325
- )
326
-
327
316
  try:
328
317
  sub_provider = self._get_or_register_provider(parameter.annotation)
329
318
  except LookupError as exc:
@@ -777,7 +766,7 @@ class Container:
777
766
  def _get_injected_params(self, call: Callable[..., Any]) -> dict[str, Any]:
778
767
  """Get the injected parameters of a callable object."""
779
768
  injected_params: dict[str, Any] = {}
780
- for parameter in get_typed_parameters(call):
769
+ for parameter in inspect.signature(call, eval_str=True).parameters.values():
781
770
  interface, should_inject = self.validate_injected_parameter(
782
771
  parameter, call=call
783
772
  )
@@ -9,7 +9,7 @@ from types import ModuleType
9
9
  from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from ._decorators import is_injectable
12
- from ._typing import get_typed_parameters, is_inject_marker
12
+ from ._typing import is_inject_marker
13
13
 
14
14
  if TYPE_CHECKING:
15
15
  from ._container import Container
@@ -103,8 +103,8 @@ class Scanner:
103
103
  # If no tags are passed and not explicitly injectable,
104
104
  # check for parameter markers
105
105
  if not tags:
106
- for param in get_typed_parameters(member):
107
- if is_inject_marker(param.default):
106
+ for parameter in inspect.signature(member).parameters.values():
107
+ if is_inject_marker(parameter.default):
108
108
  return True
109
109
 
110
110
  return False
@@ -3,9 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import inspect
6
- from collections.abc import AsyncIterator, Callable, Iterator
6
+ from collections.abc import AsyncIterator, Iterator
7
7
  from types import NoneType
8
- from typing import Any, ForwardRef
8
+ from typing import Any
9
+
10
+ from typing_extensions import Sentinel
9
11
 
10
12
 
11
13
  def is_context_manager(obj: Any) -> bool:
@@ -28,47 +30,7 @@ def is_iterator_type(tp: Any) -> bool:
28
30
  return tp in (Iterator, AsyncIterator)
29
31
 
30
32
 
31
- def get_typed_annotation(
32
- annotation: Any, globalns: dict[str, Any], module: Any = None
33
- ) -> Any:
34
- """Get the typed annotation of a callable object."""
35
- if isinstance(annotation, str):
36
- ref = ForwardRef(annotation, module=module)
37
- annotation = ref._evaluate(globalns, globalns, recursive_guard=frozenset()) # type: ignore[reportDeprecated]
38
- return annotation
39
-
40
-
41
- def get_typed_parameters(obj: Callable[..., Any]) -> list[inspect.Parameter]:
42
- """Get the typed parameters of a callable object."""
43
- globalns = getattr(obj, "__globals__", {})
44
- module = getattr(obj, "__module__", None)
45
- return [
46
- parameter.replace(
47
- annotation=get_typed_annotation(
48
- parameter.annotation, globalns, module=module
49
- )
50
- )
51
- for parameter in inspect.signature(obj).parameters.values()
52
- ]
53
-
54
-
55
- class _Sentinel:
56
- __slots__ = ("_name",)
57
-
58
- def __init__(self, name: str) -> None:
59
- self._name = name
60
-
61
- def __repr__(self) -> str:
62
- return f"<{self._name}>"
63
-
64
- def __eq__(self, other: object) -> bool:
65
- return self is other
66
-
67
- def __hash__(self) -> int:
68
- return id(self)
69
-
70
-
71
- NOT_SET = _Sentinel("NOT_SET")
33
+ NOT_SET = Sentinel("NOT_SET")
72
34
 
73
35
 
74
36
  class InjectMarker:
@@ -0,0 +1,8 @@
1
+ raise ImportError(
2
+ "The Django extension requires additional dependencies.\n\n"
3
+ "Install one of the following extras:\n"
4
+ " pip install 'anydi-django' # for Django\n"
5
+ " pip install 'anydi-django[ninja]' # for Django Ninja\n\n"
6
+ "Then, instead of importing from 'anydi.ext.django', import directly from:\n"
7
+ " import anydi_django\n"
8
+ )
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import inspect
5
6
  from collections.abc import Iterator
6
7
  from typing import Annotated, Any, cast
7
8
 
@@ -11,7 +12,7 @@ from fastapi.routing import APIRoute
11
12
  from starlette.requests import Request
12
13
 
13
14
  from anydi._container import Container
14
- from anydi._typing import InjectMarker, get_typed_parameters
15
+ from anydi._typing import InjectMarker
15
16
 
16
17
  from .starlette.middleware import RequestScopedMiddleware
17
18
 
@@ -39,7 +40,7 @@ def install(app: FastAPI, container: Container) -> None:
39
40
  call, *params = dependant.cache_key
40
41
  if not call:
41
42
  continue # pragma: no cover
42
- for parameter in get_typed_parameters(call):
43
+ for parameter in inspect.signature(call, eval_str=True).parameters.values():
43
44
  container.validate_injected_parameter(parameter, call=call)
44
45
 
45
46
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import inspect
5
6
  from typing import Any, cast
6
7
 
7
8
  from fast_depends.dependencies import Depends
@@ -9,7 +10,7 @@ from faststream import ContextRepo
9
10
  from faststream.broker.core.usecase import BrokerUsecase
10
11
 
11
12
  from anydi import Container
12
- from anydi._typing import InjectMarker, get_typed_parameters
13
+ from anydi._typing import InjectMarker
13
14
 
14
15
 
15
16
  def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
@@ -23,7 +24,7 @@ def install(broker: BrokerUsecase[Any, Any], container: Container) -> None:
23
24
 
24
25
  for handler in _get_broken_handlers(broker):
25
26
  call = handler._original_call # noqa
26
- for parameter in get_typed_parameters(call):
27
+ for parameter in inspect.signature(call, eval_str=True).parameters.values():
27
28
  container.validate_injected_parameter(parameter, call=call)
28
29
 
29
30
 
@@ -9,7 +9,6 @@ import pytest
9
9
  from anyio.pytest_plugin import extract_backend_and_options, get_runner
10
10
 
11
11
  from anydi import Container
12
- from anydi._typing import get_typed_parameters
13
12
 
14
13
  logger = logging.getLogger(__name__)
15
14
 
@@ -61,7 +60,9 @@ def _anydi_injected_parameter_iterator(
61
60
  )
62
61
 
63
62
  def _iterator() -> Iterator[tuple[str, inspect.Parameter]]:
64
- for parameter in get_typed_parameters(request.function):
63
+ for parameter in inspect.signature(
64
+ request.function, eval_str=True
65
+ ).parameters.values():
65
66
  interface = parameter.annotation
66
67
  if (
67
68
  interface is inspect.Parameter.empty
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anydi"
3
- version = "0.50.0"
3
+ version = "0.53.0"
4
4
  description = "Dependency Injection library"
5
5
  authors = [{ name = "Anton Ruhlov", email = "antonruhlov@gmail.com" }]
6
6
  requires-python = ">=3.10.0, <3.15"
@@ -51,17 +51,13 @@ anydi = "anydi.ext.pytest_plugin"
51
51
  [dependency-groups]
52
52
  dev = [
53
53
  "ruff>=0.14.0",
54
- "pyright>=1.1.406",
54
+ "pyright>=1.1.407",
55
55
  "pytest>=8.4.0,<9",
56
- "pytest-cov>=6.0.0,<7",
56
+ "pytest-cov>=7.0.0",
57
57
  "pytest-mock>=3.14.1",
58
58
  "starlette>=0.37.2",
59
59
  "fastapi>=0.100.0",
60
60
  "httpx>=0.26.0",
61
- "django~=4.2",
62
- "django-stubs>=5.1.1,<6",
63
- "django-ninja>=1.1.0,<2",
64
- "pytest-django>=4.8.0,<5",
65
61
  "faststream>=0.5.10,<0.6",
66
62
  "redis>=5.0.4,<6",
67
63
  "pydantic-settings>=2.4.0,<3",
@@ -116,7 +112,6 @@ addopts = [
116
112
  ]
117
113
  xfail_strict = true
118
114
  junit_family = "xunit2"
119
- DJANGO_SETTINGS_MODULE = "tests.ext.django.settings"
120
115
 
121
116
  [tool.coverage.report]
122
117
  exclude_also = [
@@ -132,9 +127,3 @@ exclude_also = [
132
127
  "if has_signature_eval_str_arg",
133
128
  "if not anyio:",
134
129
  ]
135
-
136
- [tool.coverage.run]
137
- omit = [
138
- "anydi/ext/django/ninja/_operation.py",
139
- "anydi/ext/django/ninja/_signature.py",
140
- ]
@@ -1,9 +0,0 @@
1
- from ._container import container
2
- from ._utils import inject_urlpatterns, register_components, register_settings
3
-
4
- __all__ = [
5
- "container",
6
- "register_components",
7
- "register_settings",
8
- "inject_urlpatterns",
9
- ]
@@ -1,18 +0,0 @@
1
- from typing import cast
2
-
3
- from django.apps.registry import apps
4
- from django.utils.functional import SimpleLazyObject
5
-
6
- import anydi
7
-
8
- from .apps import ContainerConfig
9
-
10
- __all__ = ["container"]
11
-
12
-
13
- def _get_container() -> anydi.Container:
14
- app_config = cast(ContainerConfig, apps.get_app_config(ContainerConfig.label))
15
- return app_config.container
16
-
17
-
18
- container = cast(anydi.Container, SimpleLazyObject(_get_container))
@@ -1,37 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import Sequence
4
-
5
- from django.conf import settings
6
- from typing_extensions import TypedDict
7
-
8
-
9
- class Settings(TypedDict):
10
- CONTAINER_FACTORY: str | None
11
- REGISTER_SETTINGS: bool
12
- REGISTER_COMPONENTS: bool
13
- INJECT_URLCONF: str | Sequence[str] | None
14
- MODULES: Sequence[str]
15
- SCAN_PACKAGES: Sequence[str]
16
- PATCH_NINJA: bool
17
-
18
-
19
- DEFAULTS = Settings(
20
- CONTAINER_FACTORY=None,
21
- REGISTER_SETTINGS=False,
22
- REGISTER_COMPONENTS=False,
23
- MODULES=[],
24
- PATCH_NINJA=False,
25
- INJECT_URLCONF=None,
26
- SCAN_PACKAGES=[],
27
- )
28
-
29
-
30
- def get_settings() -> Settings:
31
- """Get the AnyDI settings from the Django settings."""
32
- return Settings(
33
- **{
34
- **DEFAULTS,
35
- **getattr(settings, "ANYDI", {}),
36
- }
37
- )
@@ -1,89 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from collections.abc import Iterator
4
- from typing import Annotated, Any
5
-
6
- from django.conf import settings
7
- from django.core.cache import BaseCache, caches
8
- from django.db import connections
9
- from django.db.backends.base.base import BaseDatabaseWrapper
10
- from django.urls import URLPattern, URLResolver, get_resolver
11
-
12
- from anydi import Container
13
-
14
-
15
- def register_settings(
16
- container: Container, prefix: str = "django.conf.settings."
17
- ) -> None:
18
- """Register Django settings into the container."""
19
-
20
- # Ensure prefix ends with a dot
21
- if prefix[-1] != ".":
22
- prefix += "."
23
-
24
- for setting_name in dir(settings):
25
- setting_value = getattr(settings, setting_name)
26
- if not setting_name.isupper():
27
- continue
28
-
29
- container.register(
30
- Annotated[type(setting_value), f"{prefix}{setting_name}"],
31
- _get_setting_value(setting_value),
32
- scope="singleton",
33
- )
34
-
35
-
36
- def register_components(container: Container) -> None:
37
- """Register Django components into the container."""
38
-
39
- # Register caches
40
- def _get_cache(cache_name: str) -> Any:
41
- return lambda: caches[cache_name]
42
-
43
- for cache_name in caches:
44
- container.register(
45
- Annotated[BaseCache, cache_name],
46
- _get_cache(cache_name),
47
- scope="singleton",
48
- )
49
-
50
- # Register database connections
51
- def _get_connection(alias: str) -> Any:
52
- return lambda: connections[alias]
53
-
54
- for alias in connections:
55
- container.register(
56
- Annotated[BaseDatabaseWrapper, alias],
57
- _get_connection(alias),
58
- scope="singleton",
59
- )
60
-
61
-
62
- def inject_urlpatterns(container: Container, *, urlconf: str) -> None:
63
- """Auto-inject the container into views."""
64
- resolver = get_resolver(urlconf)
65
- injected_urlpatterns = []
66
- for pattern in iter_urlpatterns(resolver.url_patterns):
67
- # Skip already injected views
68
- if pattern.lookup_str in injected_urlpatterns:
69
- continue
70
- # Skip django-ninja views
71
- if pattern.lookup_str.startswith("ninja."):
72
- continue # pragma: no cover
73
- pattern.callback = container.inject(pattern.callback)
74
- injected_urlpatterns.append(pattern.lookup_str)
75
-
76
-
77
- def iter_urlpatterns(
78
- urlpatterns: list[URLPattern | URLResolver],
79
- ) -> Iterator[URLPattern]:
80
- """Iterate over all views in urlpatterns."""
81
- for url_pattern in urlpatterns:
82
- if isinstance(url_pattern, URLResolver):
83
- yield from iter_urlpatterns(url_pattern.url_patterns)
84
- else:
85
- yield url_pattern
86
-
87
-
88
- def _get_setting_value(value: Any) -> Any:
89
- return lambda: value
@@ -1,89 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import types
4
- from collections.abc import Callable
5
- from typing import cast
6
-
7
- from django.apps import AppConfig
8
- from django.conf import settings
9
- from django.core.exceptions import ImproperlyConfigured
10
- from django.utils.module_loading import import_string
11
-
12
- import anydi
13
- from anydi.testing import TestContainer
14
-
15
- from ._settings import get_settings
16
- from ._utils import inject_urlpatterns, register_components, register_settings
17
-
18
-
19
- class ContainerConfig(AppConfig):
20
- name = "anydi.ext.django"
21
- label = "anydi_django"
22
-
23
- def __init__(self, app_name: str, app_module: types.ModuleType | None) -> None:
24
- super().__init__(app_name, app_module)
25
- self.settings = get_settings()
26
- # Create a container
27
- container_factory_path = self.settings["CONTAINER_FACTORY"]
28
- if container_factory_path:
29
- try:
30
- container_factory = cast(
31
- Callable[[], anydi.Container], import_string(container_factory_path)
32
- )
33
- except ImportError as exc:
34
- raise ImproperlyConfigured(
35
- f"Cannot import container factory '{container_factory_path}'."
36
- ) from exc
37
- container = container_factory()
38
- else:
39
- container = anydi.Container()
40
-
41
- # Use test container
42
- testing = getattr(settings, "ANYDI_TESTING", False)
43
- if testing:
44
- container = TestContainer.from_container(container)
45
-
46
- self.container = container
47
-
48
- def ready(self) -> None: # noqa: C901
49
- # Register Django settings
50
- if self.settings["REGISTER_SETTINGS"]:
51
- register_settings(
52
- self.container,
53
- prefix=getattr(
54
- settings,
55
- "ANYDI_SETTINGS_PREFIX",
56
- "django.conf.settings.",
57
- ),
58
- )
59
-
60
- # Register Django components
61
- if self.settings["REGISTER_COMPONENTS"]:
62
- register_components(self.container)
63
-
64
- # Register modules
65
- for module_path in self.settings["MODULES"]:
66
- try:
67
- module_cls = import_string(module_path)
68
- except ImportError as exc:
69
- raise ImproperlyConfigured(
70
- f"Cannot import module '{module_path}'."
71
- ) from exc
72
- self.container.register_module(module_cls)
73
-
74
- # Patching the django-ninja framework if it installed
75
- if self.settings["PATCH_NINJA"]:
76
- from .ninja import patch_ninja
77
-
78
- patch_ninja()
79
-
80
- # Auto-injecting the container into views
81
- if urlconf := self.settings["INJECT_URLCONF"]:
82
- if isinstance(urlconf, str):
83
- urlconf = [urlconf]
84
- for u in urlconf:
85
- inject_urlpatterns(self.container, urlconf=u)
86
-
87
- # Scan packages
88
- for scan_package in self.settings["SCAN_PACKAGES"]:
89
- self.container.scan(scan_package)
@@ -1,29 +0,0 @@
1
- from collections.abc import Callable
2
- from typing import Any
3
-
4
- from asgiref.sync import iscoroutinefunction
5
- from django.http import HttpRequest
6
- from django.utils.decorators import sync_and_async_middleware
7
-
8
- from ._container import container
9
-
10
-
11
- @sync_and_async_middleware
12
- def request_scoped_middleware(
13
- get_response: Callable[..., Any],
14
- ) -> Callable[..., Any]:
15
- if iscoroutinefunction(get_response):
16
-
17
- async def async_middleware(request: HttpRequest) -> Any:
18
- async with container.arequest_context() as context:
19
- context.set(HttpRequest, request)
20
- return await get_response(request)
21
-
22
- return async_middleware
23
-
24
- def middleware(request: HttpRequest) -> Any:
25
- with container.request_context() as context:
26
- context.set(HttpRequest, request)
27
- return get_response(request)
28
-
29
- return middleware
@@ -1,16 +0,0 @@
1
- try:
2
- from ninja import operation
3
- except ImportError as exc: # pragma: no cover
4
- raise ImportError(
5
- "'django-ninja' is not installed. "
6
- "Please install it using 'pip install django-ninja'."
7
- ) from exc
8
-
9
- from ._operation import AsyncOperation, Operation
10
- from ._signature import ViewSignature
11
-
12
-
13
- def patch_ninja() -> None:
14
- operation.ViewSignature = ViewSignature # type: ignore
15
- operation.Operation = Operation # type: ignore
16
- operation.AsyncOperation = AsyncOperation # type: ignore
@@ -1,75 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from django.http import HttpRequest, HttpResponseBase
6
- from ninja.operation import (
7
- AsyncOperation as BaseAsyncOperation, # noqa
8
- Operation as BaseOperation,
9
- )
10
-
11
- from anydi.ext.django import container
12
-
13
- from ._signature import ViewSignature
14
-
15
-
16
- def _update_exc_args(exc: Exception) -> None:
17
- if isinstance(exc, TypeError) and "required positional argument" in str(exc):
18
- msg = "Did you fail to use functools.wraps() in a decorator?"
19
- msg = f"{exc.args[0]}: {msg}" if exc.args else msg
20
- exc.args = (msg,) + exc.args[1:]
21
-
22
-
23
- class Operation(BaseOperation):
24
- signature: ViewSignature
25
-
26
- def __init__(self, *args: Any, **kwargs: Any) -> None:
27
- super().__init__(*args, **kwargs)
28
- self.dependencies = self.signature.dependencies
29
-
30
- def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase:
31
- error = self._run_checks(request)
32
- if error:
33
- return error
34
- try:
35
- temporal_response = self.api.create_temporal_response(request)
36
- values = self._get_values(request, kw, temporal_response)
37
- values.update(self._get_dependencies())
38
- result = self.view_func(request, **values)
39
- return self._result_to_response(request, result, temporal_response)
40
- except Exception as e:
41
- _update_exc_args(e)
42
- return self.api.on_exception(request, e)
43
-
44
- def _get_dependencies(self) -> dict[str, Any]:
45
- return {
46
- name: container.resolve(interface) for name, interface in self.dependencies
47
- }
48
-
49
-
50
- class AsyncOperation(BaseAsyncOperation):
51
- signature: ViewSignature
52
-
53
- def __init__(self, *args: Any, **kwargs: Any) -> None:
54
- super().__init__(*args, **kwargs)
55
- self.dependencies = self.signature.dependencies
56
-
57
- async def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase:
58
- error = await self._run_checks(request)
59
- if error:
60
- return error
61
- try:
62
- temporal_response = self.api.create_temporal_response(request)
63
- values = self._get_values(request, kw, temporal_response)
64
- values.update(await self._get_dependencies())
65
- result = await self.view_func(request, **values)
66
- return self._result_to_response(request, result, temporal_response)
67
- except Exception as e:
68
- _update_exc_args(e)
69
- return self.api.on_exception(request, e)
70
-
71
- async def _get_dependencies(self) -> dict[str, Any]:
72
- return {
73
- name: await container.aresolve(interface)
74
- for name, interface in self.dependencies
75
- }
@@ -1,68 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- from collections.abc import Callable
5
- from typing import Any
6
-
7
- from django.http import HttpResponse
8
- from ninja.signature.details import (
9
- FuncParam, # noqa
10
- ViewSignature as BaseViewSignature,
11
- )
12
- from ninja.signature.utils import get_path_param_names, get_typed_signature
13
-
14
- from anydi._typing import is_inject_marker # noqa
15
- from anydi.ext.django import container
16
-
17
-
18
- class ViewSignature(BaseViewSignature):
19
- def __init__(self, path: str, view_func: Callable[..., Any]) -> None:
20
- self.view_func = view_func
21
- self.signature = get_typed_signature(self.view_func)
22
- self.path = path
23
- self.path_params_names = get_path_param_names(path)
24
- self.docstring = inspect.cleandoc(view_func.__doc__ or "")
25
- self.has_kwargs = False
26
- self.dependencies = []
27
-
28
- self.params = []
29
- for name, arg in self.signature.parameters.items():
30
- if name == "request":
31
- # TODO: maybe better assert that 1st param is request or check by type?
32
- # maybe even have attribute like `has_request`
33
- # so that users can ignore passing request if not needed
34
- continue
35
-
36
- if arg.kind == arg.VAR_KEYWORD:
37
- # Skipping **kwargs
38
- self.has_kwargs = True
39
- continue
40
-
41
- if arg.kind == arg.VAR_POSITIONAL:
42
- # Skipping *args
43
- continue
44
-
45
- if arg.annotation is HttpResponse:
46
- self.response_arg = name
47
- continue
48
-
49
- interface, should_inject = container.validate_injected_parameter(
50
- arg, call=self.view_func
51
- )
52
- if should_inject:
53
- self.dependencies.append((name, interface))
54
- continue
55
-
56
- func_param = self._get_param_type(name, arg)
57
- self.params.append(func_param)
58
-
59
- ninja_contribute_args = getattr(view_func, "_ninja_contribute_args", None)
60
- if ninja_contribute_args is not None:
61
- for p_name, p_type, p_source in ninja_contribute_args:
62
- self.params.append(
63
- FuncParam(p_name, p_source.alias or p_name, p_source, p_type, False)
64
- )
65
-
66
- self.models = self._create_models()
67
-
68
- self._validate_view_path_params()
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