anydi 0.66.0__tar.gz → 0.67.1__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.66.0 → anydi-0.67.1}/PKG-INFO +12 -12
- {anydi-0.66.0 → anydi-0.67.1}/README.md +11 -11
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_container.py +58 -64
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_resolver.py +3 -1
- {anydi-0.66.0 → anydi-0.67.1}/pyproject.toml +2 -2
- {anydi-0.66.0 → anydi-0.67.1}/anydi/__init__.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_async_lock.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_context.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_decorators.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_injector.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_marker.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_module.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_provider.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_scanner.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/_types.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/__init__.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/django/__init__.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/fastapi.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/faststream.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/pydantic_settings.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/pytest_plugin.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/starlette/__init__.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/starlette/middleware.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/ext/typer.py +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/py.typed +0 -0
- {anydi-0.66.0 → anydi-0.67.1}/anydi/testing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anydi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.67.1
|
|
4
4
|
Summary: Dependency Injection library
|
|
5
5
|
Keywords: dependency injection,dependencies,di,async,asyncio,application
|
|
6
6
|
Author: Anton Ruhlov
|
|
@@ -41,7 +41,7 @@ Simple Dependency Injection library that uses Python type annotations.
|
|
|
41
41
|
|
|
42
42
|
[](https://github.com/antonrh/anydi/actions/workflows/ci.yml)
|
|
43
43
|
[](https://codecov.io/gh/antonrh/anydi)
|
|
44
|
-
[](https://anydi.readthedocs.io/en/
|
|
44
|
+
[](https://anydi.readthedocs.io/en/stable/)
|
|
45
45
|
[](https://codspeed.io/antonrh/anydi?utm_source=badge)
|
|
46
46
|
|
|
47
47
|
</div>
|
|
@@ -271,18 +271,18 @@ urlpatterns = [
|
|
|
271
271
|
Want to know more? Here are helpful resources:
|
|
272
272
|
|
|
273
273
|
**Core Documentation:**
|
|
274
|
-
- [Core Concepts](https://anydi.readthedocs.io/en/
|
|
275
|
-
- [Providers](https://anydi.readthedocs.io/en/
|
|
276
|
-
- [Scopes](https://anydi.readthedocs.io/en/
|
|
277
|
-
- [Dependency Injection](https://anydi.readthedocs.io/en/
|
|
278
|
-
- [Testing](https://anydi.readthedocs.io/en/
|
|
274
|
+
- [Core Concepts](https://anydi.readthedocs.io/en/stable/concepts/) - Learn about containers, providers, scopes, and dependency injection
|
|
275
|
+
- [Providers](https://anydi.readthedocs.io/en/stable/usage/providers/) - How to register providers and manage resources
|
|
276
|
+
- [Scopes](https://anydi.readthedocs.io/en/stable/usage/scopes/) - How to use built-in and custom scopes
|
|
277
|
+
- [Dependency Injection](https://anydi.readthedocs.io/en/stable/usage/injection/) - Different ways to inject dependencies
|
|
278
|
+
- [Testing](https://anydi.readthedocs.io/en/stable/usage/testing/) - How to test your code with provider overrides
|
|
279
279
|
|
|
280
280
|
**Framework Integrations:**
|
|
281
|
-
- [FastAPI](https://anydi.readthedocs.io/en/
|
|
282
|
-
- [Django](https://anydi.readthedocs.io/en/
|
|
283
|
-
- [FastStream](https://anydi.readthedocs.io/en/
|
|
284
|
-
- [Typer](https://anydi.readthedocs.io/en/
|
|
285
|
-
- [Pydantic Settings](https://anydi.readthedocs.io/en/
|
|
281
|
+
- [FastAPI](https://anydi.readthedocs.io/en/stable/extensions/fastapi/) - How to use with FastAPI
|
|
282
|
+
- [Django](https://anydi.readthedocs.io/en/stable/extensions/django/) - How to use with Django and Django Ninja
|
|
283
|
+
- [FastStream](https://anydi.readthedocs.io/en/stable/extensions/faststream/) - How to use with message brokers
|
|
284
|
+
- [Typer](https://anydi.readthedocs.io/en/stable/extensions/typer/) - How to use in CLI applications
|
|
285
|
+
- [Pydantic Settings](https://anydi.readthedocs.io/en/stable/extensions/pydantic_settings/) - How to manage configuration
|
|
286
286
|
|
|
287
287
|
**Full Documentation:**
|
|
288
288
|
- [Read the Docs](https://anydi.readthedocs.io/) - All documentation with examples and guides
|
|
@@ -6,7 +6,7 @@ Simple Dependency Injection library that uses Python type annotations.
|
|
|
6
6
|
|
|
7
7
|
[](https://github.com/antonrh/anydi/actions/workflows/ci.yml)
|
|
8
8
|
[](https://codecov.io/gh/antonrh/anydi)
|
|
9
|
-
[](https://anydi.readthedocs.io/en/
|
|
9
|
+
[](https://anydi.readthedocs.io/en/stable/)
|
|
10
10
|
[](https://codspeed.io/antonrh/anydi?utm_source=badge)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
@@ -236,18 +236,18 @@ urlpatterns = [
|
|
|
236
236
|
Want to know more? Here are helpful resources:
|
|
237
237
|
|
|
238
238
|
**Core Documentation:**
|
|
239
|
-
- [Core Concepts](https://anydi.readthedocs.io/en/
|
|
240
|
-
- [Providers](https://anydi.readthedocs.io/en/
|
|
241
|
-
- [Scopes](https://anydi.readthedocs.io/en/
|
|
242
|
-
- [Dependency Injection](https://anydi.readthedocs.io/en/
|
|
243
|
-
- [Testing](https://anydi.readthedocs.io/en/
|
|
239
|
+
- [Core Concepts](https://anydi.readthedocs.io/en/stable/concepts/) - Learn about containers, providers, scopes, and dependency injection
|
|
240
|
+
- [Providers](https://anydi.readthedocs.io/en/stable/usage/providers/) - How to register providers and manage resources
|
|
241
|
+
- [Scopes](https://anydi.readthedocs.io/en/stable/usage/scopes/) - How to use built-in and custom scopes
|
|
242
|
+
- [Dependency Injection](https://anydi.readthedocs.io/en/stable/usage/injection/) - Different ways to inject dependencies
|
|
243
|
+
- [Testing](https://anydi.readthedocs.io/en/stable/usage/testing/) - How to test your code with provider overrides
|
|
244
244
|
|
|
245
245
|
**Framework Integrations:**
|
|
246
|
-
- [FastAPI](https://anydi.readthedocs.io/en/
|
|
247
|
-
- [Django](https://anydi.readthedocs.io/en/
|
|
248
|
-
- [FastStream](https://anydi.readthedocs.io/en/
|
|
249
|
-
- [Typer](https://anydi.readthedocs.io/en/
|
|
250
|
-
- [Pydantic Settings](https://anydi.readthedocs.io/en/
|
|
246
|
+
- [FastAPI](https://anydi.readthedocs.io/en/stable/extensions/fastapi/) - How to use with FastAPI
|
|
247
|
+
- [Django](https://anydi.readthedocs.io/en/stable/extensions/django/) - How to use with Django and Django Ninja
|
|
248
|
+
- [FastStream](https://anydi.readthedocs.io/en/stable/extensions/faststream/) - How to use with message brokers
|
|
249
|
+
- [Typer](https://anydi.readthedocs.io/en/stable/extensions/typer/) - How to use in CLI applications
|
|
250
|
+
- [Pydantic Settings](https://anydi.readthedocs.io/en/stable/extensions/pydantic_settings/) - How to manage configuration
|
|
251
251
|
|
|
252
252
|
**Full Documentation:**
|
|
253
253
|
- [Read the Docs](https://anydi.readthedocs.io/) - All documentation with examples and guides
|
|
@@ -390,28 +390,27 @@ class Container:
|
|
|
390
390
|
name = type_repr(call)
|
|
391
391
|
kind = ProviderKind.from_call(call)
|
|
392
392
|
is_class = kind == ProviderKind.CLASS
|
|
393
|
-
|
|
393
|
+
is_coroutine = kind == ProviderKind.COROUTINE
|
|
394
|
+
is_generator = kind == ProviderKind.GENERATOR
|
|
395
|
+
is_async_generator = kind == ProviderKind.ASYNC_GENERATOR
|
|
396
|
+
is_resource = is_generator or is_async_generator
|
|
394
397
|
|
|
395
|
-
# Validate scope
|
|
398
|
+
# Validate scope
|
|
396
399
|
self._validate_provider_scope(scope, name, is_resource)
|
|
397
400
|
|
|
398
|
-
# Get
|
|
401
|
+
# Get signature and detect interface
|
|
399
402
|
signature = inspect.signature(call, eval_str=True)
|
|
400
403
|
|
|
401
|
-
# Detect the interface
|
|
402
404
|
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
|
|
405
|
+
interface = call if is_class else signature.return_annotation
|
|
406
|
+
if interface is inspect.Signature.empty:
|
|
407
|
+
interface = None
|
|
409
408
|
|
|
410
|
-
#
|
|
411
|
-
|
|
409
|
+
# Handle iterator types for resources
|
|
410
|
+
interface_origin = get_origin(interface)
|
|
411
|
+
if is_iterator_type(interface) or is_iterator_type(interface_origin):
|
|
412
412
|
if args := get_args(interface):
|
|
413
413
|
interface = args[0]
|
|
414
|
-
# If the callable is a generator, return the resource type
|
|
415
414
|
if is_none_type(interface):
|
|
416
415
|
interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
|
|
417
416
|
else:
|
|
@@ -420,24 +419,22 @@ class Container:
|
|
|
420
419
|
"without actual type argument."
|
|
421
420
|
)
|
|
422
421
|
|
|
423
|
-
#
|
|
422
|
+
# Validate interface
|
|
424
423
|
if is_none_type(interface):
|
|
425
424
|
raise TypeError(f"Missing `{name}` provider return annotation.")
|
|
426
425
|
|
|
427
|
-
# Check for existing provider
|
|
428
426
|
if interface in self._providers and not override:
|
|
429
427
|
raise LookupError(
|
|
430
428
|
f"The provider interface `{type_repr(interface)}` already registered."
|
|
431
429
|
)
|
|
432
430
|
|
|
433
|
-
|
|
434
|
-
unresolved_exc: LookupError | None = None
|
|
431
|
+
# Process parameters
|
|
435
432
|
parameters: list[ProviderParameter] = []
|
|
436
433
|
scope_provider: dict[Scope, Provider] = {}
|
|
437
|
-
|
|
438
|
-
|
|
434
|
+
unresolved_parameter = None
|
|
435
|
+
unresolved_exc: LookupError | None = None
|
|
439
436
|
is_scoped = scope not in ("singleton", "transient")
|
|
440
|
-
|
|
437
|
+
scope_hierarchy = self._scopes.get(scope, ()) if scope != "transient" else ()
|
|
441
438
|
|
|
442
439
|
for parameter in signature.parameters.values():
|
|
443
440
|
if parameter.annotation is inspect.Parameter.empty:
|
|
@@ -451,28 +448,16 @@ class Container:
|
|
|
451
448
|
f"are not allowed in the provider `{name}`."
|
|
452
449
|
)
|
|
453
450
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if parameter.default is not inspect.Parameter.empty
|
|
457
|
-
else NOT_SET
|
|
458
|
-
)
|
|
459
|
-
has_default = default is not NOT_SET
|
|
460
|
-
|
|
461
|
-
# Check if provider exists before attempting to register (for scoped only)
|
|
462
|
-
was_auto_registered = (
|
|
463
|
-
is_scoped and parameter.annotation not in self._providers
|
|
464
|
-
)
|
|
451
|
+
has_default = parameter.default is not inspect.Parameter.empty
|
|
452
|
+
default = parameter.default if has_default else NOT_SET
|
|
465
453
|
|
|
466
454
|
try:
|
|
467
455
|
sub_provider = self._get_or_register_provider(parameter.annotation)
|
|
468
456
|
except LookupError as exc:
|
|
469
|
-
if (
|
|
457
|
+
if (defaults and parameter.name in defaults) or has_default:
|
|
470
458
|
continue
|
|
471
|
-
# For
|
|
472
|
-
# They might be provided via context.set()
|
|
459
|
+
# For scoped dependencies, allow unresolved parameters via context.set()
|
|
473
460
|
if is_scoped:
|
|
474
|
-
# Add to unresolved list to provide better error messages
|
|
475
|
-
# and prevent infinite recursion
|
|
476
461
|
self._resolver.add_unresolved(parameter.annotation)
|
|
477
462
|
parameters.append(
|
|
478
463
|
ProviderParameter(
|
|
@@ -480,8 +465,8 @@ class Container:
|
|
|
480
465
|
annotation=parameter.annotation,
|
|
481
466
|
default=default,
|
|
482
467
|
has_default=has_default,
|
|
483
|
-
provider=None,
|
|
484
|
-
shared_scope=True,
|
|
468
|
+
provider=None,
|
|
469
|
+
shared_scope=True,
|
|
485
470
|
)
|
|
486
471
|
)
|
|
487
472
|
continue
|
|
@@ -489,13 +474,37 @@ class Container:
|
|
|
489
474
|
unresolved_exc = exc
|
|
490
475
|
continue
|
|
491
476
|
|
|
492
|
-
#
|
|
493
|
-
|
|
494
|
-
scope_provider[sub_provider.scope] = sub_provider
|
|
477
|
+
# Track scope providers for validation
|
|
478
|
+
scope_provider.setdefault(sub_provider.scope, sub_provider)
|
|
495
479
|
|
|
496
|
-
#
|
|
497
|
-
|
|
480
|
+
# For scoped dependencies with same scope having unresolved params,
|
|
481
|
+
# defer to context.set() instead
|
|
482
|
+
if (
|
|
483
|
+
is_scoped
|
|
484
|
+
and sub_provider.scope == scope
|
|
485
|
+
and any(p.provider is None for p in sub_provider.parameters)
|
|
486
|
+
):
|
|
498
487
|
self._resolver.add_unresolved(parameter.annotation)
|
|
488
|
+
parameters.append(
|
|
489
|
+
ProviderParameter(
|
|
490
|
+
name=parameter.name,
|
|
491
|
+
annotation=parameter.annotation,
|
|
492
|
+
default=default,
|
|
493
|
+
has_default=has_default,
|
|
494
|
+
provider=None,
|
|
495
|
+
shared_scope=True,
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
continue
|
|
499
|
+
|
|
500
|
+
# Validate scope compatibility inline
|
|
501
|
+
if scope_hierarchy and sub_provider.scope not in scope_hierarchy:
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"The provider `{name}` with a `{scope}` scope "
|
|
504
|
+
f"cannot depend on `{sub_provider}` with a "
|
|
505
|
+
f"`{sub_provider.scope}` scope. Please ensure all "
|
|
506
|
+
"providers are registered with matching scopes."
|
|
507
|
+
)
|
|
499
508
|
|
|
500
509
|
parameters.append(
|
|
501
510
|
ProviderParameter(
|
|
@@ -508,21 +517,11 @@ class Container:
|
|
|
508
517
|
)
|
|
509
518
|
)
|
|
510
519
|
|
|
511
|
-
#
|
|
512
|
-
# Transient scope can use any scoped dependencies
|
|
513
|
-
if scope != "transient":
|
|
514
|
-
for sub_provider in scope_provider.values():
|
|
515
|
-
if sub_provider.scope not in self._scopes.get(scope, []):
|
|
516
|
-
raise ValueError(
|
|
517
|
-
f"The provider `{name}` with a `{scope}` scope "
|
|
518
|
-
f"cannot depend on `{sub_provider}` with a "
|
|
519
|
-
f"`{sub_provider.scope}` scope. Please ensure all "
|
|
520
|
-
"providers are registered with matching scopes."
|
|
521
|
-
)
|
|
522
|
-
|
|
523
|
-
# Check for unresolved parameters
|
|
520
|
+
# Handle unresolved parameters
|
|
524
521
|
if unresolved_parameter:
|
|
525
|
-
if
|
|
522
|
+
if is_scoped: # pragma: no cover
|
|
523
|
+
# Note: This branch is currently unreachable because
|
|
524
|
+
# unresolved_parameter is only set when is_scoped=False
|
|
526
525
|
self._resolver.add_unresolved(interface)
|
|
527
526
|
else:
|
|
528
527
|
raise LookupError(
|
|
@@ -533,11 +532,7 @@ class Container:
|
|
|
533
532
|
f"attempting to use it."
|
|
534
533
|
) from unresolved_exc
|
|
535
534
|
|
|
536
|
-
|
|
537
|
-
is_generator = kind == ProviderKind.GENERATOR
|
|
538
|
-
is_async_generator = kind == ProviderKind.ASYNC_GENERATOR
|
|
539
|
-
is_async = is_coroutine or is_async_generator
|
|
540
|
-
|
|
535
|
+
# Create and register provider
|
|
541
536
|
provider = Provider(
|
|
542
537
|
call=call,
|
|
543
538
|
scope=scope,
|
|
@@ -548,13 +543,12 @@ class Container:
|
|
|
548
543
|
is_coroutine=is_coroutine,
|
|
549
544
|
is_generator=is_generator,
|
|
550
545
|
is_async_generator=is_async_generator,
|
|
551
|
-
is_async=
|
|
546
|
+
is_async=is_coroutine or is_async_generator,
|
|
552
547
|
is_resource=is_resource,
|
|
553
548
|
)
|
|
554
549
|
|
|
555
550
|
self._set_provider(provider)
|
|
556
551
|
|
|
557
|
-
# Clear cached resolvers when overriding
|
|
558
552
|
if override:
|
|
559
553
|
self._resolver.clear_caches()
|
|
560
554
|
|
|
@@ -299,7 +299,7 @@ class Resolver:
|
|
|
299
299
|
create_lines.append(" if compiled is None:")
|
|
300
300
|
create_lines.append(
|
|
301
301
|
" provider = "
|
|
302
|
-
"container._get_or_register_provider(_param_annotations[{idx}])"
|
|
302
|
+
f"container._get_or_register_provider(_param_annotations[{idx}])"
|
|
303
303
|
)
|
|
304
304
|
create_lines.append(
|
|
305
305
|
" compiled = "
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "anydi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.67.1"
|
|
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
|