anydi 0.39.0__py3-none-any.whl → 0.39.2__py3-none-any.whl
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/_container.py +23 -46
- anydi/_context.py +13 -2
- anydi/_types.py +4 -5
- anydi/_utils.py +16 -0
- {anydi-0.39.0.dist-info → anydi-0.39.2.dist-info}/METADATA +2 -2
- {anydi-0.39.0.dist-info → anydi-0.39.2.dist-info}/RECORD +9 -9
- {anydi-0.39.0.dist-info → anydi-0.39.2.dist-info}/WHEEL +0 -0
- {anydi-0.39.0.dist-info → anydi-0.39.2.dist-info}/entry_points.txt +0 -0
- {anydi-0.39.0.dist-info → anydi-0.39.2.dist-info}/licenses/LICENSE +0 -0
anydi/_container.py
CHANGED
|
@@ -8,7 +8,6 @@ import importlib
|
|
|
8
8
|
import inspect
|
|
9
9
|
import logging
|
|
10
10
|
import pkgutil
|
|
11
|
-
import threading
|
|
12
11
|
import types
|
|
13
12
|
import uuid
|
|
14
13
|
from collections import defaultdict
|
|
@@ -36,7 +35,6 @@ from ._types import (
|
|
|
36
35
|
is_marker,
|
|
37
36
|
)
|
|
38
37
|
from ._utils import (
|
|
39
|
-
AsyncRLock,
|
|
40
38
|
get_full_qualname,
|
|
41
39
|
get_typed_annotation,
|
|
42
40
|
get_typed_parameters,
|
|
@@ -44,15 +42,11 @@ from ._utils import (
|
|
|
44
42
|
is_async_context_manager,
|
|
45
43
|
is_builtin_type,
|
|
46
44
|
is_context_manager,
|
|
45
|
+
is_iterator_type,
|
|
46
|
+
is_none_type,
|
|
47
47
|
run_async,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
try:
|
|
51
|
-
from types import NoneType
|
|
52
|
-
except ImportError:
|
|
53
|
-
NoneType = type(None) # type: ignore[misc]
|
|
54
|
-
|
|
55
|
-
|
|
56
50
|
T = TypeVar("T", bound=Any)
|
|
57
51
|
M = TypeVar("M", bound="Module")
|
|
58
52
|
P = ParamSpec("P")
|
|
@@ -107,8 +101,6 @@ class Container:
|
|
|
107
101
|
self._logger = logger or logging.getLogger(__name__)
|
|
108
102
|
self._resources: dict[str, list[type[Any]]] = defaultdict(list)
|
|
109
103
|
self._singleton_context = InstanceContext()
|
|
110
|
-
self._singleton_lock = threading.RLock()
|
|
111
|
-
self._singleton_async_lock = AsyncRLock()
|
|
112
104
|
self._request_context_var: ContextVar[InstanceContext | None] = ContextVar(
|
|
113
105
|
"request_context", default=None
|
|
114
106
|
)
|
|
@@ -254,7 +246,7 @@ class Container:
|
|
|
254
246
|
)
|
|
255
247
|
return request_context
|
|
256
248
|
|
|
257
|
-
def
|
|
249
|
+
def _get_instance_context(self, scope: Scope) -> InstanceContext:
|
|
258
250
|
"""Get the instance context for the specified scope."""
|
|
259
251
|
if scope == "singleton":
|
|
260
252
|
return self._singleton_context
|
|
@@ -292,7 +284,7 @@ class Container:
|
|
|
292
284
|
# Cleanup instance context
|
|
293
285
|
if provider.scope != "transient":
|
|
294
286
|
try:
|
|
295
|
-
context = self.
|
|
287
|
+
context = self._get_instance_context(provider.scope)
|
|
296
288
|
except LookupError:
|
|
297
289
|
pass
|
|
298
290
|
else:
|
|
@@ -343,16 +335,16 @@ class Container:
|
|
|
343
335
|
interface = signature.return_annotation
|
|
344
336
|
if interface is inspect.Signature.empty:
|
|
345
337
|
interface = None
|
|
346
|
-
|
|
347
|
-
|
|
338
|
+
|
|
339
|
+
if isinstance(interface, str):
|
|
340
|
+
interface = get_typed_annotation(interface, globalns, module)
|
|
348
341
|
|
|
349
342
|
# If the callable is an iterator, return the actual type
|
|
350
|
-
|
|
351
|
-
if interface in iterator_types or get_origin(interface) in iterator_types:
|
|
343
|
+
if is_iterator_type(interface) or is_iterator_type(get_origin(interface)):
|
|
352
344
|
if args := get_args(interface):
|
|
353
345
|
interface = args[0]
|
|
354
346
|
# If the callable is a generator, return the resource type
|
|
355
|
-
if interface
|
|
347
|
+
if is_none_type(interface):
|
|
356
348
|
interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
|
|
357
349
|
else:
|
|
358
350
|
raise TypeError(
|
|
@@ -361,7 +353,7 @@ class Container:
|
|
|
361
353
|
)
|
|
362
354
|
|
|
363
355
|
# None interface is not allowed
|
|
364
|
-
if interface
|
|
356
|
+
if is_none_type(interface):
|
|
365
357
|
raise TypeError(f"Missing `{name}` provider return annotation.")
|
|
366
358
|
|
|
367
359
|
# Check for existing provider
|
|
@@ -424,7 +416,7 @@ class Container:
|
|
|
424
416
|
|
|
425
417
|
# Check for unresolved parameters
|
|
426
418
|
if unresolved_parameter:
|
|
427
|
-
if detected_scope not in
|
|
419
|
+
if detected_scope not in ("singleton", "transient"):
|
|
428
420
|
self._unresolved_interfaces.add(interface)
|
|
429
421
|
else:
|
|
430
422
|
raise LookupError(
|
|
@@ -466,10 +458,7 @@ class Container:
|
|
|
466
458
|
f"scopes are supported: {', '.join(allowed_scopes)}. "
|
|
467
459
|
"Please use one of the supported scopes when registering a provider."
|
|
468
460
|
)
|
|
469
|
-
if (
|
|
470
|
-
kind in {ProviderKind.GENERATOR, ProviderKind.ASYNC_GENERATOR}
|
|
471
|
-
and scope == "transient"
|
|
472
|
-
):
|
|
461
|
+
if scope == "transient" and ProviderKind.is_resource(kind):
|
|
473
462
|
raise TypeError(
|
|
474
463
|
f"The resource provider `{name}` is attempting to register "
|
|
475
464
|
"with a transient scope, which is not allowed."
|
|
@@ -565,7 +554,7 @@ class Container:
|
|
|
565
554
|
return False
|
|
566
555
|
if provider.scope == "transient":
|
|
567
556
|
return False
|
|
568
|
-
context = self.
|
|
557
|
+
context = self._get_instance_context(provider.scope)
|
|
569
558
|
return interface in context
|
|
570
559
|
|
|
571
560
|
def release(self, interface: AnyInterface) -> None:
|
|
@@ -573,7 +562,7 @@ class Container:
|
|
|
573
562
|
provider = self._get_provider(interface)
|
|
574
563
|
if provider.scope == "transient":
|
|
575
564
|
return None
|
|
576
|
-
context = self.
|
|
565
|
+
context = self._get_instance_context(provider.scope)
|
|
577
566
|
del context[interface]
|
|
578
567
|
|
|
579
568
|
def reset(self) -> None:
|
|
@@ -582,7 +571,7 @@ class Container:
|
|
|
582
571
|
if provider.scope == "transient":
|
|
583
572
|
continue
|
|
584
573
|
try:
|
|
585
|
-
context = self.
|
|
574
|
+
context = self._get_instance_context(provider.scope)
|
|
586
575
|
except LookupError:
|
|
587
576
|
continue
|
|
588
577
|
del context[interface]
|
|
@@ -595,15 +584,8 @@ class Container:
|
|
|
595
584
|
if provider.scope == "transient":
|
|
596
585
|
instance = self._create_instance(provider, None, **defaults)
|
|
597
586
|
else:
|
|
598
|
-
context = self.
|
|
599
|
-
|
|
600
|
-
with self._singleton_lock:
|
|
601
|
-
instance = (
|
|
602
|
-
self._get_or_create_instance(provider, context)
|
|
603
|
-
if not create
|
|
604
|
-
else self._create_instance(provider, context, **defaults)
|
|
605
|
-
)
|
|
606
|
-
else:
|
|
587
|
+
context = self._get_instance_context(provider.scope)
|
|
588
|
+
with context.lock():
|
|
607
589
|
instance = (
|
|
608
590
|
self._get_or_create_instance(provider, context)
|
|
609
591
|
if not create
|
|
@@ -623,15 +605,8 @@ class Container:
|
|
|
623
605
|
if provider.scope == "transient":
|
|
624
606
|
instance = await self._acreate_instance(provider, None, **defaults)
|
|
625
607
|
else:
|
|
626
|
-
context = self.
|
|
627
|
-
|
|
628
|
-
async with self._singleton_async_lock:
|
|
629
|
-
instance = (
|
|
630
|
-
await self._aget_or_create_instance(provider, context)
|
|
631
|
-
if not create
|
|
632
|
-
else await self._acreate_instance(provider, context, **defaults)
|
|
633
|
-
)
|
|
634
|
-
else:
|
|
608
|
+
context = self._get_instance_context(provider.scope)
|
|
609
|
+
async with context.alock():
|
|
635
610
|
instance = (
|
|
636
611
|
await self._aget_or_create_instance(provider, context)
|
|
637
612
|
if not create
|
|
@@ -853,8 +828,10 @@ class Container:
|
|
|
853
828
|
"not registered."
|
|
854
829
|
)
|
|
855
830
|
self._override_instances[interface] = instance
|
|
856
|
-
|
|
857
|
-
|
|
831
|
+
try:
|
|
832
|
+
yield
|
|
833
|
+
finally:
|
|
834
|
+
self._override_instances.pop(interface, None)
|
|
858
835
|
|
|
859
836
|
############################
|
|
860
837
|
# Testing Methods
|
anydi/_context.py
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import threading
|
|
4
5
|
from types import TracebackType
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from typing_extensions import Self
|
|
8
9
|
|
|
9
|
-
from ._utils import run_async
|
|
10
|
+
from ._utils import AsyncRLock, run_async
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class InstanceContext:
|
|
13
14
|
"""A context to store instances."""
|
|
14
15
|
|
|
15
|
-
__slots__ = ("_instances", "_stack", "_async_stack")
|
|
16
|
+
__slots__ = ("_instances", "_stack", "_async_stack", "_lock", "_async_lock")
|
|
16
17
|
|
|
17
18
|
def __init__(self) -> None:
|
|
18
19
|
self._instances: dict[type[Any], Any] = {}
|
|
19
20
|
self._stack = contextlib.ExitStack()
|
|
20
21
|
self._async_stack = contextlib.AsyncExitStack()
|
|
22
|
+
self._lock = threading.RLock()
|
|
23
|
+
self._async_lock = AsyncRLock()
|
|
21
24
|
|
|
22
25
|
def get(self, interface: type[Any]) -> Any | None:
|
|
23
26
|
"""Get an instance from the context."""
|
|
@@ -82,3 +85,11 @@ class InstanceContext:
|
|
|
82
85
|
async def aclose(self) -> None:
|
|
83
86
|
"""Close the scoped context asynchronously."""
|
|
84
87
|
await self.__aexit__(None, None, None)
|
|
88
|
+
|
|
89
|
+
def lock(self) -> threading.RLock:
|
|
90
|
+
"""Acquire the context lock."""
|
|
91
|
+
return self._lock
|
|
92
|
+
|
|
93
|
+
def alock(self) -> AsyncRLock:
|
|
94
|
+
"""Acquire the context lock asynchronously."""
|
|
95
|
+
return self._async_lock
|
anydi/_types.py
CHANGED
|
@@ -82,10 +82,9 @@ class ProviderKind(enum.IntEnum):
|
|
|
82
82
|
"object. Only callable providers are allowed."
|
|
83
83
|
)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
pass
|
|
85
|
+
@classmethod
|
|
86
|
+
def is_resource(cls, kind: ProviderKind) -> bool:
|
|
87
|
+
return kind in (cls.GENERATOR, cls.ASYNC_GENERATOR)
|
|
89
88
|
|
|
90
89
|
|
|
91
90
|
@dataclass(kw_only=True, frozen=True)
|
|
@@ -122,7 +121,7 @@ class Provider:
|
|
|
122
121
|
|
|
123
122
|
@cached_property
|
|
124
123
|
def is_resource(self) -> bool:
|
|
125
|
-
return
|
|
124
|
+
return ProviderKind.is_resource(self.kind)
|
|
126
125
|
|
|
127
126
|
|
|
128
127
|
class ProviderArgs(NamedTuple):
|
anydi/_utils.py
CHANGED
|
@@ -8,12 +8,18 @@ import importlib
|
|
|
8
8
|
import inspect
|
|
9
9
|
import re
|
|
10
10
|
import sys
|
|
11
|
+
from collections.abc import AsyncIterator, Iterator
|
|
11
12
|
from types import TracebackType
|
|
12
13
|
from typing import Any, Callable, ForwardRef, TypeVar
|
|
13
14
|
|
|
14
15
|
import anyio
|
|
15
16
|
from typing_extensions import ParamSpec, Self, get_args, get_origin
|
|
16
17
|
|
|
18
|
+
try:
|
|
19
|
+
from types import NoneType
|
|
20
|
+
except ImportError:
|
|
21
|
+
NoneType = type(None) # type: ignore[misc]
|
|
22
|
+
|
|
17
23
|
T = TypeVar("T")
|
|
18
24
|
P = ParamSpec("P")
|
|
19
25
|
|
|
@@ -57,6 +63,16 @@ def is_async_context_manager(obj: Any) -> bool:
|
|
|
57
63
|
return hasattr(obj, "__aenter__") and hasattr(obj, "__aexit__")
|
|
58
64
|
|
|
59
65
|
|
|
66
|
+
def is_none_type(tp: Any) -> bool:
|
|
67
|
+
"""Check if the given object is a None type."""
|
|
68
|
+
return tp in (None, NoneType)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_iterator_type(tp: Any) -> bool:
|
|
72
|
+
"""Check if the given object is an iterator type."""
|
|
73
|
+
return tp in (Iterator, AsyncIterator)
|
|
74
|
+
|
|
75
|
+
|
|
60
76
|
def get_typed_annotation(
|
|
61
77
|
annotation: Any, globalns: dict[str, Any], module: Any = None
|
|
62
78
|
) -> Any:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anydi
|
|
3
|
-
Version: 0.39.
|
|
3
|
+
Version: 0.39.2
|
|
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>
|
|
@@ -91,7 +91,7 @@ container = Container()
|
|
|
91
91
|
|
|
92
92
|
@container.provider(scope="singleton")
|
|
93
93
|
def message() -> str:
|
|
94
|
-
return "Hello,
|
|
94
|
+
return "Hello, World!"
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
@container.inject
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
anydi/__init__.py,sha256=aAq10a1V_zQ3_Me3p_pll5d1O77PIgqotkOm3pshORI,495
|
|
2
|
-
anydi/_container.py,sha256=
|
|
3
|
-
anydi/_context.py,sha256=
|
|
4
|
-
anydi/_types.py,sha256=
|
|
5
|
-
anydi/_utils.py,sha256=
|
|
2
|
+
anydi/_container.py,sha256=TmiH3ZXY1edF_i1-peCwrxNpJxjJ1eyIbbO5WMSVDNE,41499
|
|
3
|
+
anydi/_context.py,sha256=dlRvOFHbbIcg-q7g4TqJfytiSOERtbwTlCsdzXwo4GE,3012
|
|
4
|
+
anydi/_types.py,sha256=ColGaOgF7Zqdh34N2JSWxr-8rt5bTVJomW0JqZASKHM,3631
|
|
5
|
+
anydi/_utils.py,sha256=X_sSQFpRogHLh6Bxu0MdUthpc24jvnkHqdwKndQminA,4848
|
|
6
6
|
anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
anydi/ext/_utils.py,sha256=U6sRqWzccWUu7eMhbXX1NrwcaxitQF9cO1KxnKF37gw,2566
|
|
@@ -21,8 +21,8 @@ anydi/ext/django/ninja/_operation.py,sha256=wSWa7D73XTVlOibmOciv2l6JHPe1ERZcXrqI
|
|
|
21
21
|
anydi/ext/django/ninja/_signature.py,sha256=2cSzKxBIxXLqtwNuH6GSlmjVJFftoGmleWfyk_NVEWw,2207
|
|
22
22
|
anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
anydi/ext/starlette/middleware.py,sha256=9CQtGg5ZzUz2gFSzJr8U4BWzwNjK8XMctm3n52M77Z0,792
|
|
24
|
-
anydi-0.39.
|
|
25
|
-
anydi-0.39.
|
|
26
|
-
anydi-0.39.
|
|
27
|
-
anydi-0.39.
|
|
28
|
-
anydi-0.39.
|
|
24
|
+
anydi-0.39.2.dist-info/METADATA,sha256=lqw8W8T5TTlZs--a9J7F9h-tf1f9zrngbntTCE-VNNY,4957
|
|
25
|
+
anydi-0.39.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
anydi-0.39.2.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
|
|
27
|
+
anydi-0.39.2.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
|
|
28
|
+
anydi-0.39.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|