anydi 0.38.2rc0__py3-none-any.whl → 0.38.2rc2__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
@@ -15,9 +15,9 @@ from collections import defaultdict
15
15
  from collections.abc import AsyncIterator, Iterable, Iterator, Sequence
16
16
  from contextvars import ContextVar
17
17
  from types import ModuleType
18
- from typing import Any, Callable, TypeVar, Union, cast, get_origin, overload
18
+ from typing import Annotated, Any, Callable, TypeVar, Union, cast, overload
19
19
 
20
- from typing_extensions import Concatenate, ParamSpec, Self, final, get_args
20
+ from typing_extensions import Concatenate, ParamSpec, Self, final, get_args, get_origin
21
21
 
22
22
  from ._context import InstanceContext
23
23
  from ._types import (
@@ -119,12 +119,11 @@ class Container:
119
119
  # Register providers
120
120
  providers = providers or []
121
121
  for provider in providers:
122
- _provider = self._create_provider(
123
- call=provider.call,
124
- scope=provider.scope,
125
- interface=provider.interface,
122
+ self._register_provider(
123
+ provider.call,
124
+ provider.scope,
125
+ provider.interface,
126
126
  )
127
- self._register_provider(_provider, False)
128
127
 
129
128
  # Register modules
130
129
  modules = modules or []
@@ -274,8 +273,7 @@ class Container:
274
273
  override: bool = False,
275
274
  ) -> Provider:
276
275
  """Register a provider for the specified interface."""
277
- provider = self._create_provider(call=call, scope=scope, interface=interface)
278
- return self._register_provider(provider, override)
276
+ return self._register_provider(call, scope, interface, override)
279
277
 
280
278
  def is_registered(self, interface: AnyInterface) -> bool:
281
279
  """Check if a provider is registered for the specified interface."""
@@ -309,73 +307,74 @@ class Container:
309
307
  """Decorator to register a provider function with the specified scope."""
310
308
 
311
309
  def decorator(call: Callable[P, T]) -> Callable[P, T]:
312
- provider = self._create_provider(call=call, scope=scope)
313
- self._register_provider(provider, override)
310
+ self._register_provider(call, scope, NOT_SET, override)
314
311
  return call
315
312
 
316
313
  return decorator
317
314
 
318
- def _create_provider( # noqa: C901
319
- self, call: Callable[..., Any], *, scope: Scope, interface: Any = NOT_SET
315
+ def _register_provider( # noqa: C901
316
+ self,
317
+ call: Callable[..., Any],
318
+ scope: Scope | None,
319
+ interface: Any = NOT_SET,
320
+ override: bool = False,
321
+ /,
322
+ **defaults: Any,
320
323
  ) -> Provider:
324
+ """Register a provider with the specified scope."""
321
325
  name = get_full_qualname(call)
322
-
323
- # Detect the kind of callable provider
324
326
  kind = ProviderKind.from_call(call)
327
+ detected_scope = scope
325
328
 
326
- # Validate the scope of the provider
327
- if scope not in get_args(Scope):
328
- raise ValueError(
329
- f"The provider `{name}` scope is invalid. Only the following "
330
- f"scopes are supported: {', '.join(get_args(Scope))}. "
331
- "Please use one of the supported scopes when registering a provider."
332
- )
333
-
334
- # Validate the scope of the provider
335
- if (
336
- kind in {ProviderKind.GENERATOR, ProviderKind.ASYNC_GENERATOR}
337
- and scope == "transient"
338
- ):
339
- raise TypeError(
340
- f"The resource provider `{name}` is attempting to register "
341
- "with a transient scope, which is not allowed."
342
- )
329
+ # Validate scope if it provided
330
+ if scope:
331
+ self._validate_provider_scope(scope, name, kind)
343
332
 
344
333
  # Get the signature
345
334
  globalns = getattr(call, "__globals__", {})
335
+ module = getattr(call, "__module__", None)
346
336
  signature = inspect.signature(call, globals=globalns)
347
337
 
348
338
  # Detect the interface
349
- if kind == ProviderKind.CLASS:
350
- interface = call
351
- else:
352
- if interface is NOT_SET:
339
+ if interface is NOT_SET:
340
+ if kind == ProviderKind.CLASS:
341
+ interface = call
342
+ else:
353
343
  interface = signature.return_annotation
354
344
  if interface is inspect.Signature.empty:
355
345
  interface = None
356
346
  else:
357
- interface = get_typed_annotation(interface, globalns)
358
-
359
- # If the callable is an iterator, return the actual type
360
- iterator_types = {Iterator, AsyncIterator}
361
- if interface in iterator_types or get_origin(interface) in iterator_types:
362
- if args := get_args(interface):
363
- interface = args[0]
364
- # If the callable is a generator, return the resource type
365
- if interface in {None, NoneType}:
366
- interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
367
- else:
368
- raise TypeError(
369
- f"Cannot use `{name}` resource type annotation "
370
- "without actual type argument."
371
- )
347
+ interface = get_typed_annotation(interface, globalns, module)
348
+
349
+ # If the callable is an iterator, return the actual type
350
+ iterator_types = {Iterator, AsyncIterator}
351
+ if interface in iterator_types or get_origin(interface) in iterator_types:
352
+ if args := get_args(interface):
353
+ interface = args[0]
354
+ # If the callable is a generator, return the resource type
355
+ if interface in {None, NoneType}:
356
+ interface = type(f"Event_{uuid.uuid4().hex}", (Event,), {})
357
+ else:
358
+ raise TypeError(
359
+ f"Cannot use `{name}` resource type annotation "
360
+ "without actual type argument."
361
+ )
362
+
363
+ # None interface is not allowed
364
+ if interface in {None, NoneType}:
365
+ raise TypeError(f"Missing `{name}` provider return annotation.")
372
366
 
373
- # None interface is not allowed
374
- if interface in {None, NoneType}:
375
- raise TypeError(f"Missing `{name}` provider return annotation.")
367
+ # Check for existing provider
368
+ if interface in self._providers and not override:
369
+ raise LookupError(
370
+ f"The provider interface `{get_full_qualname(interface)}` "
371
+ "already registered."
372
+ )
376
373
 
377
- # Detect the parameters
374
+ unresolved_parameter = None
378
375
  parameters = []
376
+ scopes = {}
377
+
379
378
  for parameter in signature.parameters.values():
380
379
  if parameter.annotation is inspect.Parameter.empty:
381
380
  raise TypeError(
@@ -387,36 +386,95 @@ class Container:
387
386
  "Positional-only parameters "
388
387
  f"are not allowed in the provider `{name}`."
389
388
  )
390
- annotation = get_typed_annotation(parameter.annotation, globalns)
391
- parameters.append(parameter.replace(annotation=annotation))
392
389
 
393
- return Provider(
390
+ parameter = parameter.replace(
391
+ annotation=get_typed_annotation(parameter.annotation, globalns, module)
392
+ )
393
+
394
+ try:
395
+ sub_provider = self._get_or_register_provider(
396
+ parameter.annotation, scope
397
+ )
398
+ except LookupError:
399
+ if self._parameter_has_default(parameter, **defaults):
400
+ continue
401
+ unresolved_parameter = parameter
402
+ continue
403
+
404
+ # Store first provider for each scope
405
+ if sub_provider.scope not in scopes:
406
+ scopes[sub_provider.scope] = sub_provider
407
+
408
+ parameters.append(parameter)
409
+
410
+ # Set detected scope
411
+ if detected_scope is None:
412
+ if "transient" in scopes:
413
+ detected_scope = "transient"
414
+ elif "request" in scopes:
415
+ detected_scope = "request"
416
+ elif "singleton" in scopes:
417
+ detected_scope = "singleton"
418
+ else:
419
+ detected_scope = self.default_scope
420
+
421
+ # Validate the provider scope after detection
422
+ if scope is None:
423
+ self._validate_provider_scope(detected_scope, name, kind)
424
+
425
+ # Check for unresolved parameters
426
+ if unresolved_parameter:
427
+ if detected_scope not in {"singleton", "transient"}:
428
+ self._unresolved_interfaces.add(interface)
429
+ else:
430
+ raise LookupError(
431
+ f"The provider `{name}` depends on `{unresolved_parameter.name}` "
432
+ f"of type `{get_full_qualname(unresolved_parameter.annotation)}`, "
433
+ "which has not been registered or set. To resolve this, ensure "
434
+ f"that `{unresolved_parameter.name}` is registered before "
435
+ f"attempting to use it."
436
+ ) from None
437
+
438
+ # Check scope compatibility
439
+ for sub_provider in scopes.values():
440
+ if sub_provider.scope not in ALLOWED_SCOPES.get(detected_scope, []):
441
+ raise ValueError(
442
+ f"The provider `{name}` with a `{detected_scope}` scope cannot "
443
+ f"depend on `{sub_provider}` with a `{sub_provider.scope}` scope. "
444
+ "Please ensure all providers are registered with matching scopes."
445
+ )
446
+
447
+ provider = Provider(
394
448
  call=call,
395
- scope=scope,
449
+ scope=detected_scope,
396
450
  interface=interface,
397
451
  name=name,
398
452
  kind=kind,
399
453
  parameters=parameters,
400
454
  )
401
455
 
402
- def _register_provider(
403
- self, provider: Provider, override: bool, /, **defaults: Any
404
- ) -> Provider:
405
- """Register a provider."""
406
- if provider.interface in self._providers:
407
- if override:
408
- self._set_provider(provider)
409
- return provider
410
-
411
- raise LookupError(
412
- f"The provider interface `{get_full_qualname(provider.interface)}` "
413
- "already registered."
414
- )
415
-
416
- self._validate_sub_providers(provider, **defaults)
417
456
  self._set_provider(provider)
418
457
  return provider
419
458
 
459
+ def _validate_provider_scope(
460
+ self, scope: Scope, name: str, kind: ProviderKind
461
+ ) -> None:
462
+ """Validate the provider scope."""
463
+ if scope not in (allowed_scopes := get_args(Scope)):
464
+ raise ValueError(
465
+ f"The provider `{name}` scope is invalid. Only the following "
466
+ f"scopes are supported: {', '.join(allowed_scopes)}. "
467
+ "Please use one of the supported scopes when registering a provider."
468
+ )
469
+ if (
470
+ kind in {ProviderKind.GENERATOR, ProviderKind.ASYNC_GENERATOR}
471
+ and scope == "transient"
472
+ ):
473
+ raise TypeError(
474
+ f"The resource provider `{name}` is attempting to register "
475
+ "with a transient scope, which is not allowed."
476
+ )
477
+
420
478
  def _get_provider(self, interface: AnyInterface) -> Provider:
421
479
  """Get provider by interface."""
422
480
  try:
@@ -435,22 +493,16 @@ class Container:
435
493
  try:
436
494
  return self._get_provider(interface)
437
495
  except LookupError:
438
- if (
439
- not self.strict
440
- and inspect.isclass(interface)
441
- and not is_builtin_type(interface)
442
- and interface is not inspect.Parameter.empty
443
- ):
496
+ if self.strict or interface is inspect.Parameter.empty:
497
+ raise
498
+ if get_origin(interface) is Annotated and (args := get_args(interface)):
499
+ call = args[0]
500
+ else:
501
+ call = interface
502
+ if inspect.isclass(call) and not is_builtin_type(call):
444
503
  # Try to get defined scope
445
504
  scope = getattr(interface, "__scope__", parent_scope)
446
- # Try to detect scope
447
- if scope is None:
448
- scope = self._detect_provider_scope(interface, **defaults)
449
- scope = scope or self.default_scope
450
- provider = self._create_provider(
451
- call=interface, scope=scope, interface=interface
452
- )
453
- return self._register_provider(provider, False, **defaults)
505
+ return self._register_provider(call, scope, interface, **defaults)
454
506
  raise
455
507
 
456
508
  def _set_provider(self, provider: Provider) -> None:
@@ -466,69 +518,6 @@ class Container:
466
518
  if provider.is_resource:
467
519
  self._resources[provider.scope].remove(provider.interface)
468
520
 
469
- def _validate_sub_providers(self, provider: Provider, /, **defaults: Any) -> None:
470
- """Validate the sub-providers of a provider."""
471
-
472
- for parameter in provider.parameters:
473
- try:
474
- sub_provider = self._get_or_register_provider(
475
- parameter.annotation, provider.scope
476
- )
477
- except LookupError:
478
- if self._parameter_has_default(parameter, **defaults):
479
- continue
480
-
481
- if provider.scope not in {"singleton", "transient"}:
482
- self._unresolved_interfaces.add(provider.interface)
483
- continue
484
- raise ValueError(
485
- f"The provider `{provider}` depends on `{parameter.name}` of type "
486
- f"`{get_full_qualname(parameter.annotation)}`, which "
487
- "has not been registered or set. To resolve this, ensure that "
488
- f"`{parameter.name}` is registered before attempting to use it."
489
- ) from None
490
-
491
- # Check scope compatibility
492
- if sub_provider.scope not in ALLOWED_SCOPES.get(provider.scope, []):
493
- raise ValueError(
494
- f"The provider `{provider}` with a `{provider.scope}` scope cannot "
495
- f"depend on `{sub_provider}` with a `{sub_provider.scope}` scope. "
496
- "Please ensure all providers are registered with matching scopes."
497
- )
498
-
499
- def _detect_provider_scope(
500
- self, call: Callable[..., Any], /, **defaults: Any
501
- ) -> Scope | None:
502
- """Detect the scope for a callable."""
503
- scopes = set()
504
-
505
- for parameter in get_typed_parameters(call):
506
- try:
507
- sub_provider = self._get_or_register_provider(
508
- parameter.annotation, None
509
- )
510
- except LookupError:
511
- if self._parameter_has_default(parameter, **defaults):
512
- continue
513
- raise
514
- scope = sub_provider.scope
515
-
516
- if scope == "transient":
517
- return "transient"
518
- scopes.add(scope)
519
-
520
- # If all scopes are found, we can return based on priority order
521
- if {"transient", "request", "singleton"}.issubset(scopes):
522
- break # pragma: no cover
523
-
524
- # Determine scope based on priority
525
- if "request" in scopes:
526
- return "request"
527
- if "singleton" in scopes:
528
- return "singleton"
529
-
530
- return None
531
-
532
521
  def _parameter_has_default(
533
522
  self, parameter: inspect.Parameter, /, **defaults: Any
534
523
  ) -> bool:
@@ -558,7 +547,7 @@ class Container:
558
547
 
559
548
  async def aresolve(self, interface: type[T]) -> T:
560
549
  """Resolve an instance by interface asynchronously."""
561
- return await self._aresolve_or_acreate(interface, False)
550
+ return await self._aresolve_or_create(interface, False)
562
551
 
563
552
  def create(self, interface: type[T], /, **defaults: Any) -> T:
564
553
  """Create an instance by interface."""
@@ -566,7 +555,7 @@ class Container:
566
555
 
567
556
  async def acreate(self, interface: type[T], /, **defaults: Any) -> T:
568
557
  """Create an instance by interface asynchronously."""
569
- return await self._aresolve_or_acreate(interface, True, **defaults)
558
+ return await self._aresolve_or_create(interface, True, **defaults)
570
559
 
571
560
  def is_resolved(self, interface: AnyInterface) -> bool:
572
561
  """Check if an instance by interface exists."""
@@ -626,7 +615,7 @@ class Container:
626
615
 
627
616
  return cast(T, instance)
628
617
 
629
- async def _aresolve_or_acreate(
618
+ async def _aresolve_or_create(
630
619
  self, interface: type[T], create: bool, /, **defaults: Any
631
620
  ) -> T:
632
621
  """Internal method to handle instance resolution and creation asynchronously."""
anydi/_types.py CHANGED
@@ -128,7 +128,7 @@ class Provider:
128
128
  class ProviderArgs(NamedTuple):
129
129
  call: Callable[..., Any]
130
130
  scope: Scope
131
- interface: Any | None = None
131
+ interface: Any = NOT_SET
132
132
 
133
133
 
134
134
  class ProviderDecoratorArgs(NamedTuple):
anydi/_utils.py CHANGED
@@ -20,6 +20,9 @@ P = ParamSpec("P")
20
20
 
21
21
  def get_full_qualname(obj: Any) -> str:
22
22
  """Get the fully qualified name of an object."""
23
+ if isinstance(obj, str):
24
+ return obj
25
+
23
26
  # Get module and qualname with defaults to handle non-types directly
24
27
  module = getattr(obj, "__module__", type(obj).__module__)
25
28
  qualname = getattr(obj, "__qualname__", type(obj).__qualname__)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.38.2rc0
3
+ Version: 0.38.2rc2
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>
@@ -108,6 +108,8 @@ if __name__ == "__main__":
108
108
  *app.py*
109
109
 
110
110
  ```python
111
+ from typing import Annotated
112
+
111
113
  from fastapi import FastAPI
112
114
 
113
115
  import anydi.ext.fastapi
@@ -126,7 +128,7 @@ app = FastAPI()
126
128
 
127
129
 
128
130
  @app.get("/hello")
129
- def say_hello(message: str = Inject()) -> dict[str, str]:
131
+ def say_hello(message: Annotated[str, Inject()]) -> dict[str, str]:
130
132
  return {"message": message}
131
133
 
132
134
 
@@ -1,8 +1,8 @@
1
1
  anydi/__init__.py,sha256=aAq10a1V_zQ3_Me3p_pll5d1O77PIgqotkOm3pshORI,495
2
- anydi/_container.py,sha256=BqpvUPeYt6OW7TLIDm-OvMGCbcxnvXA6KyOF9XBmi7M,43072
2
+ anydi/_container.py,sha256=wZ-_p0hehK7MBeHos7mi4TrZoPd1daeG52bcFmX4ZRA,42421
3
3
  anydi/_context.py,sha256=7LV_SL4QWkJeiG7_4D9PZ5lmU-MPzhofxC95zCgY9Gc,2651
4
- anydi/_types.py,sha256=oFyx6jxkEsz5FZk6tdRjUmBBQ2tX7eA_bLaa2elq7Mg,3586
5
- anydi/_utils.py,sha256=INI0jNIXrJ6LS4zqJymMO2yUEobpxmBGASf4G_vR6AU,4378
4
+ anydi/_types.py,sha256=S7scIdAzc2SyDUfEA4Knc89W6q-sdoGa3UBq-dqtq2c,3582
5
+ anydi/_utils.py,sha256=OxePzLBD_mn3Ex2I93udoodlIARyKT3TLjXTFbZdmqw,4427
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.38.2rc0.dist-info/METADATA,sha256=t8FL1ILCUK37XUFQ_gqgAlwX77UytT5FeP4QueIDX50,4920
25
- anydi-0.38.2rc0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- anydi-0.38.2rc0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
27
- anydi-0.38.2rc0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
- anydi-0.38.2rc0.dist-info/RECORD,,
24
+ anydi-0.38.2rc2.dist-info/METADATA,sha256=UD8qMBq6_YJvtH8NPApBZnBO0XXJzF1CmR3LXhvNHd0,4960
25
+ anydi-0.38.2rc2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ anydi-0.38.2rc2.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
27
+ anydi-0.38.2rc2.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
28
+ anydi-0.38.2rc2.dist-info/RECORD,,