anydi 0.33.1__tar.gz → 0.34.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.
- {anydi-0.33.1 → anydi-0.34.0}/PKG-INFO +2 -3
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_container.py +14 -3
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_context.py +8 -5
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_utils.py +49 -12
- {anydi-0.33.1 → anydi-0.34.0}/pyproject.toml +5 -6
- {anydi-0.33.1 → anydi-0.34.0}/LICENSE +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/README.md +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/__init__.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_logger.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_module.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_provider.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_scanner.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/_types.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/__init__.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/_utils.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/_container.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/_settings.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/_utils.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/apps.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/middleware.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/ninja/__init__.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/ninja/_operation.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/django/ninja/_signature.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/fastapi.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/faststream.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.33.1 → anydi-0.34.0}/anydi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anydi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.34.0
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Home-page: https://github.com/antonrh/anydi
|
|
6
6
|
License: MIT
|
|
@@ -28,9 +28,8 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
28
28
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
29
29
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
30
|
Classifier: Typing :: Typed
|
|
31
|
-
Provides-Extra: async
|
|
32
31
|
Provides-Extra: docs
|
|
33
|
-
Requires-Dist: anyio (>=3.6.2,<4.0.0)
|
|
32
|
+
Requires-Dist: anyio (>=3.6.2,<4.0.0)
|
|
34
33
|
Requires-Dist: mkdocs (>=1.4.2,<2.0.0) ; extra == "docs"
|
|
35
34
|
Requires-Dist: mkdocs-material (>=9.5.29,<10.0.0) ; extra == "docs"
|
|
36
35
|
Requires-Dist: typing-extensions (>=4.12.1,<5.0.0)
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import contextlib
|
|
6
6
|
import functools
|
|
7
7
|
import inspect
|
|
8
|
+
import threading
|
|
8
9
|
import types
|
|
9
10
|
from collections import defaultdict
|
|
10
11
|
from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
|
|
@@ -26,7 +27,7 @@ from ._module import Module, ModuleRegistry
|
|
|
26
27
|
from ._provider import Provider
|
|
27
28
|
from ._scanner import Scanner
|
|
28
29
|
from ._types import AnyInterface, DependencyWrapper, Interface, Scope, is_marker
|
|
29
|
-
from ._utils import get_full_qualname, get_typed_parameters, is_builtin_type
|
|
30
|
+
from ._utils import AsyncRLock, get_full_qualname, get_typed_parameters, is_builtin_type
|
|
30
31
|
|
|
31
32
|
T = TypeVar("T", bound=Any)
|
|
32
33
|
P = ParamSpec("P")
|
|
@@ -54,6 +55,8 @@ class Container:
|
|
|
54
55
|
self._providers: dict[type[Any], Provider] = {}
|
|
55
56
|
self._resource_cache: dict[Scope, list[type[Any]]] = defaultdict(list)
|
|
56
57
|
self._singleton_context = SingletonContext(self)
|
|
58
|
+
self._singleton_lock = threading.RLock()
|
|
59
|
+
self._singleton_async_lock = AsyncRLock()
|
|
57
60
|
self._transient_context = TransientContext(self)
|
|
58
61
|
self._request_context_var: ContextVar[RequestContext | None] = ContextVar(
|
|
59
62
|
"request_context", default=None
|
|
@@ -366,7 +369,11 @@ class Container:
|
|
|
366
369
|
|
|
367
370
|
provider = self._get_or_register_provider(interface)
|
|
368
371
|
scoped_context = self._get_scoped_context(provider.scope)
|
|
369
|
-
|
|
372
|
+
if provider.scope == "singleton":
|
|
373
|
+
with self._singleton_lock:
|
|
374
|
+
instance, created = scoped_context.get_or_create(provider)
|
|
375
|
+
else:
|
|
376
|
+
instance, created = scoped_context.get_or_create(provider)
|
|
370
377
|
if self.testing and created:
|
|
371
378
|
self._patch_test_resolver(instance)
|
|
372
379
|
return cast(T, instance)
|
|
@@ -384,7 +391,11 @@ class Container:
|
|
|
384
391
|
|
|
385
392
|
provider = self._get_or_register_provider(interface)
|
|
386
393
|
scoped_context = self._get_scoped_context(provider.scope)
|
|
387
|
-
|
|
394
|
+
if provider.scope == "singleton":
|
|
395
|
+
async with self._singleton_async_lock:
|
|
396
|
+
instance, created = await scoped_context.aget_or_create(provider)
|
|
397
|
+
else:
|
|
398
|
+
instance, created = await scoped_context.aget_or_create(provider)
|
|
388
399
|
if self.testing and created:
|
|
389
400
|
self._patch_test_resolver(instance)
|
|
390
401
|
return cast(T, instance)
|
|
@@ -10,7 +10,12 @@ from typing_extensions import Self, final
|
|
|
10
10
|
|
|
11
11
|
from ._provider import CallableKind, Provider
|
|
12
12
|
from ._types import AnyInterface, DependencyWrapper, Scope, is_event_type
|
|
13
|
-
from ._utils import
|
|
13
|
+
from ._utils import (
|
|
14
|
+
get_full_qualname,
|
|
15
|
+
is_async_context_manager,
|
|
16
|
+
is_context_manager,
|
|
17
|
+
run_async,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
if TYPE_CHECKING:
|
|
16
21
|
from ._container import Container
|
|
@@ -188,7 +193,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
188
193
|
"""Create an instance using the provider."""
|
|
189
194
|
instance = super()._create_instance(provider)
|
|
190
195
|
# Enter the context manager if the instance is closable.
|
|
191
|
-
if
|
|
196
|
+
if is_context_manager(instance):
|
|
192
197
|
self._stack.enter_context(instance)
|
|
193
198
|
return instance
|
|
194
199
|
|
|
@@ -202,7 +207,7 @@ class ResourceScopedContext(ScopedContext):
|
|
|
202
207
|
"""Create an instance asynchronously using the provider."""
|
|
203
208
|
instance = await super()._acreate_instance(provider)
|
|
204
209
|
# Enter the context manager if the instance is closable.
|
|
205
|
-
if
|
|
210
|
+
if is_async_context_manager(instance):
|
|
206
211
|
await self._async_stack.enter_async_context(instance)
|
|
207
212
|
return instance
|
|
208
213
|
|
|
@@ -233,8 +238,6 @@ class ResourceScopedContext(ScopedContext):
|
|
|
233
238
|
@abc.abstractmethod
|
|
234
239
|
def start(self) -> None:
|
|
235
240
|
"""Start the scoped context."""
|
|
236
|
-
for interface in self.container._resource_cache.get(self.scope, []): # noqa
|
|
237
|
-
self.container.resolve(interface)
|
|
238
241
|
|
|
239
242
|
def close(self) -> None:
|
|
240
243
|
"""Close the scoped context."""
|
|
@@ -8,15 +8,11 @@ import importlib
|
|
|
8
8
|
import inspect
|
|
9
9
|
import re
|
|
10
10
|
import sys
|
|
11
|
+
from types import TracebackType
|
|
11
12
|
from typing import Any, Callable, ForwardRef, TypeVar
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
try:
|
|
16
|
-
import anyio # noqa
|
|
17
|
-
except ImportError:
|
|
18
|
-
anyio = None # type: ignore[assignment]
|
|
19
|
-
|
|
14
|
+
import anyio
|
|
15
|
+
from typing_extensions import ParamSpec, Self, get_args, get_origin
|
|
20
16
|
|
|
21
17
|
T = TypeVar("T")
|
|
22
18
|
P = ParamSpec("P")
|
|
@@ -48,6 +44,16 @@ def is_builtin_type(tp: type[Any]) -> bool:
|
|
|
48
44
|
return tp.__module__ == builtins.__name__
|
|
49
45
|
|
|
50
46
|
|
|
47
|
+
def is_context_manager(obj: Any) -> bool:
|
|
48
|
+
"""Check if the given object is a context manager."""
|
|
49
|
+
return hasattr(obj, "__enter__") and hasattr(obj, "__exit__")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_async_context_manager(obj: Any) -> bool:
|
|
53
|
+
"""Check if the given object is an async context manager."""
|
|
54
|
+
return hasattr(obj, "__aenter__") and hasattr(obj, "__aexit__")
|
|
55
|
+
|
|
56
|
+
|
|
51
57
|
def get_typed_annotation(
|
|
52
58
|
annotation: Any, globalns: dict[str, Any], module: Any = None
|
|
53
59
|
) -> Any:
|
|
@@ -82,11 +88,6 @@ async def run_async(
|
|
|
82
88
|
**kwargs: P.kwargs,
|
|
83
89
|
) -> T:
|
|
84
90
|
"""Runs the given function asynchronously using the `anyio` library."""
|
|
85
|
-
if not anyio:
|
|
86
|
-
raise ImportError(
|
|
87
|
-
"`anyio` library is not currently installed. Please make sure to install "
|
|
88
|
-
"it first, or consider using `anydi[full]` instead."
|
|
89
|
-
)
|
|
90
91
|
return await anyio.to_thread.run_sync(functools.partial(func, *args, **kwargs))
|
|
91
92
|
|
|
92
93
|
|
|
@@ -103,3 +104,39 @@ def import_string(dotted_path: str) -> Any:
|
|
|
103
104
|
return importlib.import_module(attribute_name)
|
|
104
105
|
except (ImportError, AttributeError) as exc:
|
|
105
106
|
raise ImportError(f"Cannot import '{dotted_path}': {exc}") from exc
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class AsyncRLock:
|
|
110
|
+
def __init__(self) -> None:
|
|
111
|
+
self._lock = anyio.Lock()
|
|
112
|
+
self._owner: anyio.TaskInfo | None = None
|
|
113
|
+
self._count = 0
|
|
114
|
+
|
|
115
|
+
async def acquire(self) -> None:
|
|
116
|
+
current_task = anyio.get_current_task()
|
|
117
|
+
if self._owner == current_task:
|
|
118
|
+
self._count += 1
|
|
119
|
+
else:
|
|
120
|
+
await self._lock.acquire()
|
|
121
|
+
self._owner = current_task
|
|
122
|
+
self._count = 1
|
|
123
|
+
|
|
124
|
+
def release(self) -> None:
|
|
125
|
+
if self._owner != anyio.get_current_task():
|
|
126
|
+
raise RuntimeError("Lock can only be released by the owner")
|
|
127
|
+
self._count -= 1
|
|
128
|
+
if self._count == 0:
|
|
129
|
+
self._owner = None
|
|
130
|
+
self._lock.release()
|
|
131
|
+
|
|
132
|
+
async def __aenter__(self) -> Self:
|
|
133
|
+
await self.acquire()
|
|
134
|
+
return self
|
|
135
|
+
|
|
136
|
+
async def __aexit__(
|
|
137
|
+
self,
|
|
138
|
+
exc_type: type[BaseException] | None,
|
|
139
|
+
exc_val: BaseException | None,
|
|
140
|
+
exc_tb: TracebackType | None,
|
|
141
|
+
) -> Any:
|
|
142
|
+
self.release()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.34.0"
|
|
4
4
|
description = "Dependency Injection library"
|
|
5
5
|
authors = ["Anton Ruhlov <antonruhlov@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -36,19 +36,18 @@ packages = [
|
|
|
36
36
|
[tool.poetry.dependencies]
|
|
37
37
|
python = "^3.9"
|
|
38
38
|
typing-extensions = "^4.12.1"
|
|
39
|
-
anyio =
|
|
39
|
+
anyio = "^3.6.2"
|
|
40
40
|
mkdocs = { version = "^1.4.2", optional = true }
|
|
41
41
|
mkdocs-material = { version = "^9.5.29", optional = true }
|
|
42
42
|
|
|
43
43
|
[tool.poetry.extras]
|
|
44
44
|
docs = ["mkdocs", "mkdocs-material"]
|
|
45
|
-
async = ["anyio"]
|
|
46
45
|
|
|
47
46
|
[tool.poetry.group.dev.dependencies]
|
|
48
|
-
mypy = { version = "^1.14.
|
|
49
|
-
ruff = "^0.8.
|
|
47
|
+
mypy = { version = "^1.14.1", extras = ["faster-cache"] }
|
|
48
|
+
ruff = "^0.8.5"
|
|
50
49
|
pytest = "^8.3.1"
|
|
51
|
-
pytest-cov = "^
|
|
50
|
+
pytest-cov = "^6.0.0"
|
|
52
51
|
fastapi = "^0.100.0"
|
|
53
52
|
httpx = "^0.26.0"
|
|
54
53
|
django = "^4.2"
|
|
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
|