anydi 0.66.0__py3-none-any.whl → 0.67.1__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 CHANGED
@@ -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
- is_resource = kind in (ProviderKind.GENERATOR, ProviderKind.ASYNC_GENERATOR)
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 if it provided
398
+ # Validate scope
396
399
  self._validate_provider_scope(scope, name, is_resource)
397
400
 
398
- # Get the signature
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
- interface = call
405
- else:
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
- # If the callable is an iterator, return the actual type
411
- if is_iterator_type(interface) or is_iterator_type(get_origin(interface)):
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
- # None interface is not allowed
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
- unresolved_parameter = None
434
- unresolved_exc: LookupError | None = None
431
+ # Process parameters
435
432
  parameters: list[ProviderParameter] = []
436
433
  scope_provider: dict[Scope, Provider] = {}
437
-
438
- # Precompute constant checks
434
+ unresolved_parameter = None
435
+ unresolved_exc: LookupError | None = None
439
436
  is_scoped = scope not in ("singleton", "transient")
440
- has_defaults = defaults is not None
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
- default = (
455
- parameter.default
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 (has_defaults and parameter.name in defaults) or has_default:
457
+ if (defaults and parameter.name in defaults) or has_default:
470
458
  continue
471
- # For request/custom scopes, allow unregistered dependencies
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, # Will check context at runtime
484
- shared_scope=True, # Same scope, check context
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
- # Store first provider for each scope
493
- if sub_provider.scope not in scope_provider:
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
- # If provider was auto-registered and has same scope, mark as unresolved
497
- if was_auto_registered and sub_provider.scope == scope:
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
- # Check scope compatibility
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 scope not in ("singleton", "transient"):
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
- is_coroutine = kind == ProviderKind.COROUTINE
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=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
 
anydi/_resolver.py CHANGED
@@ -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
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.66.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
  [![CI](https://github.com/antonrh/anydi/actions/workflows/ci.yml/badge.svg)](https://github.com/antonrh/anydi/actions/workflows/ci.yml)
43
43
  [![Coverage](https://codecov.io/gh/antonrh/anydi/branch/main/graph/badge.svg)](https://codecov.io/gh/antonrh/anydi)
44
- [![Documentation](https://readthedocs.org/projects/anydi/badge/?version=latest)](https://anydi.readthedocs.io/en/latest/)
44
+ [![Documentation](https://readthedocs.org/projects/anydi/badge/?version=latest)](https://anydi.readthedocs.io/en/stable/)
45
45
  [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](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/latest/concepts/) - Learn about containers, providers, scopes, and dependency injection
275
- - [Providers](https://anydi.readthedocs.io/en/latest/usage/providers/) - How to register providers and manage resources
276
- - [Scopes](https://anydi.readthedocs.io/en/latest/usage/scopes/) - How to use built-in and custom scopes
277
- - [Dependency Injection](https://anydi.readthedocs.io/en/latest/usage/injection/) - Different ways to inject dependencies
278
- - [Testing](https://anydi.readthedocs.io/en/latest/usage/testing/) - How to test your code with provider overrides
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/latest/extensions/fastapi/) - How to use with FastAPI
282
- - [Django](https://anydi.readthedocs.io/en/latest/extensions/django/) - How to use with Django and Django Ninja
283
- - [FastStream](https://anydi.readthedocs.io/en/latest/extensions/faststream/) - How to use with message brokers
284
- - [Typer](https://anydi.readthedocs.io/en/latest/extensions/typer/) - How to use in CLI applications
285
- - [Pydantic Settings](https://anydi.readthedocs.io/en/latest/extensions/pydantic_settings/) - How to manage configuration
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
@@ -1,13 +1,13 @@
1
1
  anydi/__init__.py,sha256=KFX8OthKXwBuYDPCV61t-044DpJ88tAOzIxeUWRC5OA,633
2
2
  anydi/_async_lock.py,sha256=3dwZr0KthXFYha0XKMyXf8jMmGb1lYoNC0O5w29V9ic,1104
3
- anydi/_container.py,sha256=NFxrzJuWwc4DyT2Ffl0RUU3ecCCF4cl-tLp9LhgKnfQ,29675
3
+ anydi/_container.py,sha256=4bv3_fiDw6aPZ-bKjU5jmqeT66CvRnteHbosqMk5R0U,29408
4
4
  anydi/_context.py,sha256=-9QqeMWo9OpZVXZxZCQgIsswggl3Ch7lgx1KiFX_ezc,3752
5
5
  anydi/_decorators.py,sha256=J3W261ZAG7q4XKm4tbAv1wsWr9ysx9_5MUbUvSJB_MQ,2809
6
6
  anydi/_injector.py,sha256=1Ux71DhGxu3dLwPJP8gU73olI0pcZ3_tVaVzwKH7100,4411
7
7
  anydi/_marker.py,sha256=xVydjGdkxd_DqqwttnJZRkQbhpCTE9OnrhFmFJMlgvI,3415
8
8
  anydi/_module.py,sha256=2kN5uEXLd2Dsc58gz5IWK43wJewr_QgIVGSO3iWp798,2609
9
9
  anydi/_provider.py,sha256=OV1WFHTYv7W2U0XDk_Kql1r551Vhq8o-pUV5ep1HQcU,1574
10
- anydi/_resolver.py,sha256=nKPuEI69ZBLdPubQlnlQTTkhMK15vG0YcW9jgitzIto,31393
10
+ anydi/_resolver.py,sha256=6pJS-9F0epCj7goVbc7GeGdpqe0woI3Blu-3dA3jeNs,31464
11
11
  anydi/_scanner.py,sha256=rbRkHzyd2zMu7AFLffN6_tZJcMaW9gy7E-lVdHLHYrs,4294
12
12
  anydi/_types.py,sha256=lsShY_-_CM2EFajeknAYXvLl-rHfopBT8udnK5_BtS4,1161
13
13
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -21,7 +21,7 @@ anydi/ext/starlette/middleware.py,sha256=n_JJ7BcG2Mg2M5HwM_SBboxZ-mnnD6WWJn4khq7
21
21
  anydi/ext/typer.py,sha256=z-sDd3jZMPTE2CyEuJ0f9uIJB43FjoLWbjpnkOvqSKA,6236
22
22
  anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  anydi/testing.py,sha256=cHg3mMScZbEep9smRqSNQ81BZMQOkyugHe8TvKdPnEg,1347
24
- anydi-0.66.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
25
- anydi-0.66.0.dist-info/entry_points.txt,sha256=AgOcQYM5KyS4D37QcYb00tiid0QA-pD1VrjHHq4QAps,44
26
- anydi-0.66.0.dist-info/METADATA,sha256=UJVE26FM3Nsn4PgAJDvy5FiwoAQ2oU1U7TIs311y_Zs,8061
27
- anydi-0.66.0.dist-info/RECORD,,
24
+ anydi-0.67.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
25
+ anydi-0.67.1.dist-info/entry_points.txt,sha256=AgOcQYM5KyS4D37QcYb00tiid0QA-pD1VrjHHq4QAps,44
26
+ anydi-0.67.1.dist-info/METADATA,sha256=lqtZFxSRc7ohAAkAAdjcgxQDy7q5FTNP2MtBPZo3c4o,8061
27
+ anydi-0.67.1.dist-info/RECORD,,
File without changes