anydi 0.67.0__tar.gz → 0.67.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.
- {anydi-0.67.0 → anydi-0.67.2}/PKG-INFO +1 -1
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_container.py +54 -74
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_resolver.py +2 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/typer.py +3 -3
- {anydi-0.67.0 → anydi-0.67.2}/pyproject.toml +2 -2
- {anydi-0.67.0 → anydi-0.67.2}/README.md +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/__init__.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_async_lock.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_context.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_decorators.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_injector.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_marker.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_module.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_provider.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_scanner.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/_types.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/__init__.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/fastapi.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/faststream.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/py.typed +0 -0
- {anydi-0.67.0 → anydi-0.67.2}/anydi/testing.py +0 -0
|
@@ -23,14 +23,7 @@ from ._module import ModuleDef, ModuleRegistrar
|
|
|
23
23
|
from ._provider import Provider, ProviderDef, ProviderKind, ProviderParameter
|
|
24
24
|
from ._resolver import Resolver
|
|
25
25
|
from ._scanner import PackageOrIterable, Scanner
|
|
26
|
-
from ._types import
|
|
27
|
-
NOT_SET,
|
|
28
|
-
Event,
|
|
29
|
-
Scope,
|
|
30
|
-
is_event_type,
|
|
31
|
-
is_iterator_type,
|
|
32
|
-
is_none_type,
|
|
33
|
-
)
|
|
26
|
+
from ._types import NOT_SET, Event, Scope, is_event_type, is_iterator_type, is_none_type
|
|
34
27
|
|
|
35
28
|
T = TypeVar("T", bound=Any)
|
|
36
29
|
P = ParamSpec("P")
|
|
@@ -390,28 +383,27 @@ class Container:
|
|
|
390
383
|
name = type_repr(call)
|
|
391
384
|
kind = ProviderKind.from_call(call)
|
|
392
385
|
is_class = kind == ProviderKind.CLASS
|
|
393
|
-
|
|
386
|
+
is_coroutine = kind == ProviderKind.COROUTINE
|
|
387
|
+
is_generator = kind == ProviderKind.GENERATOR
|
|
388
|
+
is_async_generator = kind == ProviderKind.ASYNC_GENERATOR
|
|
389
|
+
is_resource = is_generator or is_async_generator
|
|
394
390
|
|
|
395
|
-
# Validate scope
|
|
391
|
+
# Validate scope
|
|
396
392
|
self._validate_provider_scope(scope, name, is_resource)
|
|
397
393
|
|
|
398
|
-
# Get
|
|
394
|
+
# Get signature and detect interface
|
|
399
395
|
signature = inspect.signature(call, eval_str=True)
|
|
400
396
|
|
|
401
|
-
# Detect the interface
|
|
402
397
|
if interface is NOT_SET:
|
|
403
|
-
if is_class
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
interface = signature.return_annotation
|
|
407
|
-
if interface is inspect.Signature.empty:
|
|
408
|
-
interface = None
|
|
398
|
+
interface = call if is_class else signature.return_annotation
|
|
399
|
+
if interface is inspect.Signature.empty:
|
|
400
|
+
interface = None
|
|
409
401
|
|
|
410
|
-
#
|
|
411
|
-
|
|
402
|
+
# Handle iterator types for resources
|
|
403
|
+
interface_origin = get_origin(interface)
|
|
404
|
+
if is_iterator_type(interface) or is_iterator_type(interface_origin):
|
|
412
405
|
if args := get_args(interface):
|
|
413
406
|
interface = args[0]
|
|
414
|
-
# If the callable is a generator, return the resource type
|
|
415
407
|
if is_none_type(interface):
|
|
416
408
|
interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
|
|
417
409
|
else:
|
|
@@ -420,27 +412,27 @@ class Container:
|
|
|
420
412
|
"without actual type argument."
|
|
421
413
|
)
|
|
422
414
|
|
|
423
|
-
#
|
|
415
|
+
# Validate interface
|
|
424
416
|
if is_none_type(interface):
|
|
425
417
|
raise TypeError(f"Missing `{name}` provider return annotation.")
|
|
426
418
|
|
|
427
|
-
# Check for existing provider
|
|
428
419
|
if interface in self._providers and not override:
|
|
429
420
|
raise LookupError(
|
|
430
421
|
f"The provider interface `{type_repr(interface)}` already registered."
|
|
431
422
|
)
|
|
432
423
|
|
|
433
|
-
|
|
434
|
-
unresolved_exc: LookupError | None = None
|
|
424
|
+
# Process parameters
|
|
435
425
|
parameters: list[ProviderParameter] = []
|
|
436
426
|
scope_provider: dict[Scope, Provider] = {}
|
|
437
|
-
|
|
438
|
-
|
|
427
|
+
unresolved_parameter = None
|
|
428
|
+
unresolved_exc: LookupError | None = None
|
|
439
429
|
is_scoped = scope not in ("singleton", "transient")
|
|
440
|
-
|
|
430
|
+
scope_hierarchy = self._scopes.get(scope, ()) if scope != "transient" else ()
|
|
441
431
|
|
|
442
432
|
for parameter in signature.parameters.values():
|
|
443
|
-
|
|
433
|
+
annotation = parameter.annotation
|
|
434
|
+
|
|
435
|
+
if annotation is inspect.Parameter.empty:
|
|
444
436
|
raise TypeError(
|
|
445
437
|
f"Missing provider `{name}` "
|
|
446
438
|
f"dependency `{parameter.name}` annotation."
|
|
@@ -451,26 +443,21 @@ class Container:
|
|
|
451
443
|
f"are not allowed in the provider `{name}`."
|
|
452
444
|
)
|
|
453
445
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if parameter.default is not inspect.Parameter.empty
|
|
457
|
-
else NOT_SET
|
|
458
|
-
)
|
|
459
|
-
has_default = default is not NOT_SET
|
|
446
|
+
has_default = parameter.default is not inspect.Parameter.empty
|
|
447
|
+
default = parameter.default if has_default else NOT_SET
|
|
460
448
|
|
|
461
449
|
try:
|
|
462
|
-
sub_provider = self._get_or_register_provider(
|
|
450
|
+
sub_provider = self._get_or_register_provider(annotation)
|
|
463
451
|
except LookupError as exc:
|
|
464
|
-
if (
|
|
452
|
+
if (defaults and parameter.name in defaults) or has_default:
|
|
465
453
|
continue
|
|
466
|
-
# For scoped
|
|
467
|
-
# that will be provided via context.set()
|
|
454
|
+
# For scoped dependencies, allow unresolved parameters via context.set()
|
|
468
455
|
if is_scoped:
|
|
469
|
-
self._resolver.add_unresolved(
|
|
456
|
+
self._resolver.add_unresolved(annotation)
|
|
470
457
|
parameters.append(
|
|
471
458
|
ProviderParameter(
|
|
472
459
|
name=parameter.name,
|
|
473
|
-
annotation=
|
|
460
|
+
annotation=annotation,
|
|
474
461
|
default=default,
|
|
475
462
|
has_default=has_default,
|
|
476
463
|
provider=None,
|
|
@@ -482,22 +469,21 @@ class Container:
|
|
|
482
469
|
unresolved_exc = exc
|
|
483
470
|
continue
|
|
484
471
|
|
|
485
|
-
#
|
|
486
|
-
|
|
487
|
-
scope_provider[sub_provider.scope] = sub_provider
|
|
472
|
+
# Track scope providers for validation
|
|
473
|
+
scope_provider.setdefault(sub_provider.scope, sub_provider)
|
|
488
474
|
|
|
489
|
-
# For scoped dependencies with same scope
|
|
490
|
-
#
|
|
475
|
+
# For scoped dependencies with same scope having unresolved params,
|
|
476
|
+
# defer to context.set() instead
|
|
491
477
|
if (
|
|
492
478
|
is_scoped
|
|
493
479
|
and sub_provider.scope == scope
|
|
494
480
|
and any(p.provider is None for p in sub_provider.parameters)
|
|
495
481
|
):
|
|
496
|
-
self._resolver.add_unresolved(
|
|
482
|
+
self._resolver.add_unresolved(annotation)
|
|
497
483
|
parameters.append(
|
|
498
484
|
ProviderParameter(
|
|
499
485
|
name=parameter.name,
|
|
500
|
-
annotation=
|
|
486
|
+
annotation=annotation,
|
|
501
487
|
default=default,
|
|
502
488
|
has_default=has_default,
|
|
503
489
|
provider=None,
|
|
@@ -506,10 +492,19 @@ class Container:
|
|
|
506
492
|
)
|
|
507
493
|
continue
|
|
508
494
|
|
|
495
|
+
# Validate scope compatibility inline
|
|
496
|
+
if scope_hierarchy and sub_provider.scope not in scope_hierarchy:
|
|
497
|
+
raise ValueError(
|
|
498
|
+
f"The provider `{name}` with a `{scope}` scope "
|
|
499
|
+
f"cannot depend on `{sub_provider}` with a "
|
|
500
|
+
f"`{sub_provider.scope}` scope. Please ensure all "
|
|
501
|
+
"providers are registered with matching scopes."
|
|
502
|
+
)
|
|
503
|
+
|
|
509
504
|
parameters.append(
|
|
510
505
|
ProviderParameter(
|
|
511
506
|
name=parameter.name,
|
|
512
|
-
annotation=
|
|
507
|
+
annotation=annotation,
|
|
513
508
|
default=default,
|
|
514
509
|
has_default=has_default,
|
|
515
510
|
provider=sub_provider,
|
|
@@ -517,21 +512,11 @@ class Container:
|
|
|
517
512
|
)
|
|
518
513
|
)
|
|
519
514
|
|
|
520
|
-
#
|
|
521
|
-
# Transient scope can use any scoped dependencies
|
|
522
|
-
if scope != "transient":
|
|
523
|
-
for sub_provider in scope_provider.values():
|
|
524
|
-
if sub_provider.scope not in self._scopes.get(scope, []):
|
|
525
|
-
raise ValueError(
|
|
526
|
-
f"The provider `{name}` with a `{scope}` scope "
|
|
527
|
-
f"cannot depend on `{sub_provider}` with a "
|
|
528
|
-
f"`{sub_provider.scope}` scope. Please ensure all "
|
|
529
|
-
"providers are registered with matching scopes."
|
|
530
|
-
)
|
|
531
|
-
|
|
532
|
-
# Check for unresolved parameters
|
|
515
|
+
# Handle unresolved parameters
|
|
533
516
|
if unresolved_parameter:
|
|
534
|
-
if
|
|
517
|
+
if is_scoped: # pragma: no cover
|
|
518
|
+
# Note: This branch is currently unreachable because
|
|
519
|
+
# unresolved_parameter is only set when is_scoped=False
|
|
535
520
|
self._resolver.add_unresolved(interface)
|
|
536
521
|
else:
|
|
537
522
|
raise LookupError(
|
|
@@ -542,11 +527,7 @@ class Container:
|
|
|
542
527
|
f"attempting to use it."
|
|
543
528
|
) from unresolved_exc
|
|
544
529
|
|
|
545
|
-
|
|
546
|
-
is_generator = kind == ProviderKind.GENERATOR
|
|
547
|
-
is_async_generator = kind == ProviderKind.ASYNC_GENERATOR
|
|
548
|
-
is_async = is_coroutine or is_async_generator
|
|
549
|
-
|
|
530
|
+
# Create and register provider
|
|
550
531
|
provider = Provider(
|
|
551
532
|
call=call,
|
|
552
533
|
scope=scope,
|
|
@@ -557,13 +538,12 @@ class Container:
|
|
|
557
538
|
is_coroutine=is_coroutine,
|
|
558
539
|
is_generator=is_generator,
|
|
559
540
|
is_async_generator=is_async_generator,
|
|
560
|
-
is_async=
|
|
541
|
+
is_async=is_coroutine or is_async_generator,
|
|
561
542
|
is_resource=is_resource,
|
|
562
543
|
)
|
|
563
544
|
|
|
564
545
|
self._set_provider(provider)
|
|
565
546
|
|
|
566
|
-
# Clear cached resolvers when overriding
|
|
567
547
|
if override:
|
|
568
548
|
self._resolver.clear_caches()
|
|
569
549
|
|
|
@@ -589,20 +569,20 @@ class Container:
|
|
|
589
569
|
"""Get provider by interface."""
|
|
590
570
|
try:
|
|
591
571
|
return self._providers[interface]
|
|
592
|
-
except KeyError
|
|
572
|
+
except KeyError:
|
|
593
573
|
raise LookupError(
|
|
594
574
|
f"The provider interface for `{type_repr(interface)}` has "
|
|
595
575
|
"not been registered. Please ensure that the provider interface is "
|
|
596
576
|
"properly registered before attempting to use it."
|
|
597
|
-
) from
|
|
577
|
+
) from None
|
|
598
578
|
|
|
599
579
|
def _get_or_register_provider(
|
|
600
580
|
self, interface: Any, defaults: dict[str, Any] | None = None
|
|
601
581
|
) -> Provider:
|
|
602
582
|
"""Get or register a provider by interface."""
|
|
603
583
|
try:
|
|
604
|
-
return self.
|
|
605
|
-
except
|
|
584
|
+
return self._get_provider(interface)
|
|
585
|
+
except LookupError:
|
|
606
586
|
if inspect.isclass(interface) and is_provided(interface):
|
|
607
587
|
return self._register_provider(
|
|
608
588
|
interface,
|
|
@@ -672,6 +672,8 @@ class Resolver:
|
|
|
672
672
|
|
|
673
673
|
def _wrap_for_override(self, annotation: Any, value: Any) -> Any:
|
|
674
674
|
"""Hook for wrapping dependencies to enable override patching."""
|
|
675
|
+
if isinstance(value, InstanceProxy):
|
|
676
|
+
return value
|
|
675
677
|
return InstanceProxy(value, interface=annotation)
|
|
676
678
|
|
|
677
679
|
def _post_resolve_override(self, interface: Any, instance: Any) -> Any: # noqa: C901
|
|
@@ -42,7 +42,7 @@ def _wrap_async_callback_with_injection(
|
|
|
42
42
|
callback: Callable[..., Awaitable[Any]],
|
|
43
43
|
container: Container,
|
|
44
44
|
sig: inspect.Signature,
|
|
45
|
-
non_injected_params:
|
|
45
|
+
non_injected_params: list[inspect.Parameter],
|
|
46
46
|
scopes: set[Scope],
|
|
47
47
|
) -> Any:
|
|
48
48
|
"""Wrap async callback with injection in anyio.run()."""
|
|
@@ -86,7 +86,7 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
|
|
|
86
86
|
"""Validate and wrap a callback for dependency injection."""
|
|
87
87
|
sig = inspect.signature(callback, eval_str=True)
|
|
88
88
|
injected_param_names: set[str] = set()
|
|
89
|
-
non_injected_params:
|
|
89
|
+
non_injected_params: list[inspect.Parameter] = []
|
|
90
90
|
scopes: set[Scope] = set()
|
|
91
91
|
|
|
92
92
|
# Validate parameters and collect which ones need injection
|
|
@@ -103,7 +103,7 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
|
|
|
103
103
|
if inspect.isclass(interface) and is_provided(interface):
|
|
104
104
|
scopes.add(interface.__provided__["scope"])
|
|
105
105
|
else:
|
|
106
|
-
non_injected_params.
|
|
106
|
+
non_injected_params.append(processed_parameter)
|
|
107
107
|
|
|
108
108
|
# If no parameters need injection and callback is not async, return original
|
|
109
109
|
if not injected_param_names and not inspect.iscoroutinefunction(callback):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.67.
|
|
3
|
+
version = "0.67.2"
|
|
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,7 +51,7 @@ anydi = "anydi.ext.pytest_plugin"
|
|
|
51
51
|
[dependency-groups]
|
|
52
52
|
dev = [
|
|
53
53
|
"ruff>=0.14.0",
|
|
54
|
-
"pyright>=1.1.
|
|
54
|
+
"pyright>=1.1.408",
|
|
55
55
|
"pytest>=8.4.0,<9",
|
|
56
56
|
"pytest-cov>=7.0.0",
|
|
57
57
|
"pytest-mock>=3.14.1",
|
|
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
|