anydi 0.47.0rc0__py3-none-any.whl → 0.48.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
@@ -28,7 +28,6 @@ from ._typing import (
28
28
  get_typed_annotation,
29
29
  get_typed_parameters,
30
30
  is_async_context_manager,
31
- is_builtin_type,
32
31
  is_context_manager,
33
32
  is_event_type,
34
33
  is_inject_marker,
@@ -49,11 +48,9 @@ class Container:
49
48
  *,
50
49
  providers: Iterable[ProviderDef] | None = None,
51
50
  modules: Iterable[ModuleDef] | None = None,
52
- default_scope: Scope = "transient",
53
51
  logger: logging.Logger | None = None,
54
52
  ) -> None:
55
53
  self._providers: dict[Any, Provider] = {}
56
- self._default_scope: Scope = default_scope
57
54
  self._logger = logger or logging.getLogger(__name__)
58
55
  self._resources: dict[str, list[Any]] = defaultdict(list)
59
56
  self._singleton_context = InstanceContext()
@@ -85,11 +82,6 @@ class Container:
85
82
  # Properties
86
83
  ############################
87
84
 
88
- @property
89
- def default_scope(self) -> Scope:
90
- """Get the default scope."""
91
- return self._default_scope
92
-
93
85
  @property
94
86
  def providers(self) -> dict[type[Any], Provider]:
95
87
  """Get the registered providers."""
@@ -259,7 +251,7 @@ class Container:
259
251
  def _register_provider( # noqa: C901
260
252
  self,
261
253
  call: Callable[..., Any],
262
- scope: Scope | None,
254
+ scope: Scope,
263
255
  interface: Any = NOT_SET,
264
256
  override: bool = False,
265
257
  /,
@@ -268,11 +260,9 @@ class Container:
268
260
  """Register a provider with the specified scope."""
269
261
  name = type_repr(call)
270
262
  kind = ProviderKind.from_call(call)
271
- detected_scope = scope
272
263
 
273
264
  # Validate scope if it provided
274
- if scope:
275
- self._validate_provider_scope(scope, name, kind)
265
+ self._validate_provider_scope(scope, name, kind)
276
266
 
277
267
  # Get the signature
278
268
  globalns = getattr(call, "__globals__", {})
@@ -315,6 +305,7 @@ class Container:
315
305
  )
316
306
 
317
307
  unresolved_parameter = None
308
+ unresolved_exc: LookupError | None = None
318
309
  parameters: list[inspect.Parameter] = []
319
310
  scopes: dict[Scope, Provider] = {}
320
311
 
@@ -335,13 +326,12 @@ class Container:
335
326
  )
336
327
 
337
328
  try:
338
- sub_provider = self._get_or_register_provider(
339
- parameter.annotation, scope
340
- )
341
- except LookupError:
329
+ sub_provider = self._get_or_register_provider(parameter.annotation)
330
+ except LookupError as exc:
342
331
  if self._parameter_has_default(parameter, **defaults):
343
332
  continue
344
333
  unresolved_parameter = parameter
334
+ unresolved_exc = exc
345
335
  continue
346
336
 
347
337
  # Store first provider for each scope
@@ -350,24 +340,9 @@ class Container:
350
340
 
351
341
  parameters.append(parameter)
352
342
 
353
- # Set detected scope
354
- if detected_scope is None:
355
- if "transient" in scopes:
356
- detected_scope = "transient"
357
- elif "request" in scopes:
358
- detected_scope = "request"
359
- elif "singleton" in scopes:
360
- detected_scope = "singleton"
361
- else:
362
- detected_scope = self.default_scope
363
-
364
- # Validate the provider scope after detection
365
- if scope is None:
366
- self._validate_provider_scope(detected_scope, name, kind)
367
-
368
343
  # Check for unresolved parameters
369
344
  if unresolved_parameter:
370
- if detected_scope not in ("singleton", "transient"):
345
+ if scope not in ("singleton", "transient"):
371
346
  self._unresolved_interfaces.add(interface)
372
347
  else:
373
348
  raise LookupError(
@@ -376,20 +351,20 @@ class Container:
376
351
  "which has not been registered or set. To resolve this, ensure "
377
352
  f"that `{unresolved_parameter.name}` is registered before "
378
353
  f"attempting to use it."
379
- ) from None
354
+ ) from unresolved_exc
380
355
 
381
356
  # Check scope compatibility
382
357
  for sub_provider in scopes.values():
383
- if sub_provider.scope not in ALLOWED_SCOPES.get(detected_scope, []):
358
+ if sub_provider.scope not in ALLOWED_SCOPES.get(scope, []):
384
359
  raise ValueError(
385
- f"The provider `{name}` with a `{detected_scope}` scope cannot "
360
+ f"The provider `{name}` with a `{scope}` scope cannot "
386
361
  f"depend on `{sub_provider}` with a `{sub_provider.scope}` scope. "
387
362
  "Please ensure all providers are registered with matching scopes."
388
363
  )
389
364
 
390
365
  provider = Provider(
391
366
  call=call,
392
- scope=detected_scope,
367
+ scope=scope,
393
368
  interface=interface,
394
369
  name=name,
395
370
  kind=kind,
@@ -425,20 +400,18 @@ class Container:
425
400
  "properly registered before attempting to use it."
426
401
  ) from exc
427
402
 
428
- def _get_or_register_provider(
429
- self, interface: Any, parent_scope: Scope | None, /, **defaults: Any
430
- ) -> Provider:
403
+ def _get_or_register_provider(self, interface: Any, /, **defaults: Any) -> Provider:
431
404
  """Get or register a provider by interface."""
432
405
  try:
433
406
  return self._providers[interface]
434
407
  except KeyError:
435
- if inspect.isclass(interface) and not is_builtin_type(interface):
436
- # Try to get defined scope
437
- if is_provided(interface):
438
- scope = interface.__provided__["scope"]
439
- else:
440
- scope = parent_scope
441
- return self._register_provider(interface, scope, interface, **defaults)
408
+ if inspect.isclass(interface) and is_provided(interface):
409
+ return self._register_provider(
410
+ interface,
411
+ interface.__provided__["scope"],
412
+ NOT_SET,
413
+ **defaults,
414
+ )
442
415
  raise LookupError(
443
416
  f"The provider interface `{type_repr(interface)}` is either not "
444
417
  "registered, not provided, or not set in the scoped context. "
@@ -533,7 +506,7 @@ class Container:
533
506
  self, interface: Any, create: bool, /, **defaults: Any
534
507
  ) -> Any:
535
508
  """Internal method to handle instance resolution and creation."""
536
- provider = self._get_or_register_provider(interface, None, **defaults)
509
+ provider = self._get_or_register_provider(interface, **defaults)
537
510
  if provider.scope == "transient":
538
511
  return self._create_instance(provider, None, **defaults)
539
512
  context = self._get_instance_context(provider.scope)
@@ -548,7 +521,7 @@ class Container:
548
521
  self, interface: Any, create: bool, /, **defaults: Any
549
522
  ) -> Any:
550
523
  """Internal method to handle instance resolution and creation asynchronously."""
551
- provider = self._get_or_register_provider(interface, None, **defaults)
524
+ provider = self._get_or_register_provider(interface, **defaults)
552
525
  if provider.scope == "transient":
553
526
  return await self._acreate_instance(provider, None, **defaults)
554
527
  context = self._get_instance_context(provider.scope)
@@ -884,3 +857,16 @@ class Container:
884
857
  self, /, packages: PackageOrIterable, *, tags: Iterable[str] | None = None
885
858
  ) -> None:
886
859
  self._scanner.scan(packages=packages, tags=tags)
860
+
861
+ ############################
862
+ # Testing
863
+ ############################
864
+
865
+ @contextlib.contextmanager
866
+ def override(self, interface: Any, instance: Any) -> Iterator[None]:
867
+ raise RuntimeError(
868
+ "Dependency overriding is not supported in this container.\n"
869
+ "Wrap your container with `anydi.testing.Container` instead.\n"
870
+ "Example:\n\n"
871
+ " container = TestContainer.from_container(container)"
872
+ )
anydi/_typing.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import builtins
6
5
  import inspect
7
6
  import re
8
7
  import sys
@@ -44,11 +43,6 @@ def type_repr(obj: Any) -> str:
44
43
  )
45
44
 
46
45
 
47
- def is_builtin_type(tp: type[Any]) -> bool:
48
- """Check if the given type is a built-in type."""
49
- return tp.__module__ == builtins.__name__
50
-
51
-
52
46
  def is_context_manager(obj: Any) -> bool:
53
47
  """Check if the given object is a context manager."""
54
48
  return hasattr(obj, "__enter__") and hasattr(obj, "__exit__")
anydi/ext/django/apps.py CHANGED
@@ -9,6 +9,7 @@ from django.core.exceptions import ImproperlyConfigured
9
9
  from django.utils.module_loading import import_string
10
10
 
11
11
  import anydi
12
+ from anydi.testing import TestContainer
12
13
 
13
14
  from ._settings import get_settings
14
15
  from ._utils import inject_urlpatterns, register_components, register_settings
@@ -32,9 +33,16 @@ class ContainerConfig(AppConfig):
32
33
  raise ImproperlyConfigured(
33
34
  f"Cannot import container factory '{container_factory_path}'."
34
35
  ) from exc
35
- self.container = container_factory()
36
+ container = container_factory()
36
37
  else:
37
- self.container = anydi.Container()
38
+ container = anydi.Container()
39
+
40
+ # Use test container
41
+ testing = getattr(settings, "ANYDI_TESTING", False)
42
+ if testing:
43
+ container = TestContainer.from_container(container)
44
+
45
+ self.container = container
38
46
 
39
47
  def ready(self) -> None: # noqa: C901
40
48
  # Register Django settings
anydi/testing.py CHANGED
@@ -28,12 +28,7 @@ class TestContainer(Container):
28
28
  default_scope: Scope = "transient",
29
29
  logger: logging.Logger | None = None,
30
30
  ) -> None:
31
- super().__init__(
32
- providers=providers,
33
- modules=modules,
34
- default_scope=default_scope,
35
- logger=logger,
36
- )
31
+ super().__init__(providers=providers, modules=modules, logger=logger)
37
32
  self._override_instances: dict[Any, Any] = {}
38
33
 
39
34
  @classmethod
@@ -47,7 +42,6 @@ class TestContainer(Container):
47
42
  )
48
43
  for provider in container.providers.values()
49
44
  ],
50
- default_scope=container.default_scope,
51
45
  logger=container.logger,
52
46
  )
53
47
 
@@ -1,36 +1,36 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.47.0rc0
3
+ Version: 0.48.1
4
4
  Summary: Dependency Injection library
5
- Project-URL: Repository, https://github.com/antonrh/anydi
5
+ Keywords: dependency injection,dependencies,di,async,asyncio,application
6
+ Author: Anton Ruhlov
6
7
  Author-email: Anton Ruhlov <antonruhlov@gmail.com>
7
8
  License-Expression: MIT
8
- License-File: LICENSE
9
- Keywords: application,async,asyncio,dependencies,dependency injection,di
9
+ Classifier: Intended Audience :: Information Technology
10
+ Classifier: Intended Audience :: System Administrators
11
+ Classifier: Operating System :: OS Independent
10
12
  Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Topic :: Internet
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Typing :: Typed
11
19
  Classifier: Environment :: Web Environment
12
20
  Classifier: Intended Audience :: Developers
13
- Classifier: Intended Audience :: Information Technology
14
- Classifier: Intended Audience :: System Administrators
15
21
  Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: OS Independent
17
22
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3 :: Only
19
23
  Classifier: Programming Language :: Python :: 3.9
20
24
  Classifier: Programming Language :: Python :: 3.10
21
25
  Classifier: Programming Language :: Python :: 3.11
22
26
  Classifier: Programming Language :: Python :: 3.12
23
27
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Topic :: Internet
25
- Classifier: Topic :: Software Development
26
- Classifier: Topic :: Software Development :: Libraries
27
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
28
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
- Classifier: Typing :: Typed
30
- Requires-Python: ~=3.9
28
+ Classifier: Programming Language :: Python :: 3 :: Only
29
+ Requires-Dist: typing-extensions>=4.15.0,<5
31
30
  Requires-Dist: anyio>=3.7.1
32
- Requires-Dist: typing-extensions<5,>=4.14.0
33
- Requires-Dist: wrapt<2,>=1.17.0
31
+ Requires-Dist: wrapt>=1.17.0,<2
32
+ Requires-Python: ~=3.9
33
+ Project-URL: Repository, https://github.com/antonrh/anydi
34
34
  Description-Content-Type: text/markdown
35
35
 
36
36
  # AnyDI
@@ -1,33 +1,32 @@
1
1
  anydi/__init__.py,sha256=KjjYm-1yAFxiPYaMs1WRJMtxE0q_vdX7ZRLQR1fFGs8,567
2
2
  anydi/_async.py,sha256=KhRd3RmZFcwNDzrMm8ctA1gwrg-6m_7laECTYsZdF5k,1445
3
- anydi/_container.py,sha256=Ug0TVOuNQ1cpwUQiHSNtAiZB-qJHv8ECd89WcK6ZwWg,32350
3
+ anydi/_container.py,sha256=fELFvxMrsrO2MQhAtnMmLKFxkvsaySx5ZwFGHy5KcOw,31860
4
4
  anydi/_context.py,sha256=_Xy8cTpRskb4cxTd-Fe-5NnIZyBe1DnovkofhdeUfmw,3020
5
5
  anydi/_decorators.py,sha256=F3yBeGQSz1EsulZaEvYn3cd6FEjJRMoyA6u1QCbEwcs,2813
6
6
  anydi/_module.py,sha256=QPvP27JndZkwl-FYUZWscJm6yfkNzjwoFGURyDhb6Pc,2582
7
7
  anydi/_provider.py,sha256=ig2ecn-STmFGcpkLE5A5OM35XHtU2NsxFVrGp2CvuvM,2123
8
8
  anydi/_scan.py,sha256=nOpspmceVucdwf8nUv1QVFsz2sRVWCVUb0QTH9EbWr4,3653
9
9
  anydi/_scope.py,sha256=PFHjPb2-n0vhRo9mvD_craTFfoJBzR3y-N3_0apL5Q0,258
10
- anydi/_typing.py,sha256=7Nhg4ezPgvFmdGOd2SI_9y5iQv8j-QkNOohXRGNAaIE,4057
11
- anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- anydi/testing.py,sha256=ex9grqKpQmmJWwhIVnzq6aHaUAGLu2-7fwLyYTUuKHE,5678
10
+ anydi/_typing.py,sha256=cYsEnv_QpI8B1DIZ8zPZwywEmZjtx0otJjj6vTLLk_E,3895
13
11
  anydi/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- anydi/ext/fastapi.py,sha256=L9VGPHGy23se1sflmJqTE7LNfQuElVdYEogoT1f5-4A,2324
15
- anydi/ext/faststream.py,sha256=Dy81Vf34CP6pEIbZ-41vh_-Dn6Qc-rcf14U5poebjxI,1905
16
- anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
17
- anydi/ext/pytest_plugin.py,sha256=IoP6XKuGLGLd2Xlpfttc3mI4pxCm2WQLE7x_a7asbv4,4732
18
12
  anydi/ext/django/__init__.py,sha256=QI1IABCVgSDTUoh7M9WMECKXwB3xvh04HfQ9TOWw1Mk,223
19
13
  anydi/ext/django/_container.py,sha256=cxVoYQG16WP0S_Yv4TnLwuaaT7NVEOhLWO-YdALJUb4,418
20
14
  anydi/ext/django/_settings.py,sha256=NsYbFBeslqw_tcXCn2b2gKnSp7eb64DM4dRc4tn_eiw,808
21
15
  anydi/ext/django/_utils.py,sha256=sYfUgBV9azddWYB4vNaZadDXcv_MiU-wSfVKV-TZDrE,2695
22
- anydi/ext/django/apps.py,sha256=YLL1uU6dSQ3uf3GB0VHzPW_eLuB1j9h6pv8EdAutMqo,2725
16
+ anydi/ext/django/apps.py,sha256=kGqTUZK55OPtaG5lktavSr88_quDm9GNFUE94eQh6zQ,2965
23
17
  anydi/ext/django/middleware.py,sha256=5OUdp0OWRozyW338Sq04BDhacaFlyUTTOduS_7EwCTA,854
24
18
  anydi/ext/django/ninja/__init__.py,sha256=4J0zoHPK9itbTVrjjvLX6Ftrsb2ND8bITqNDIJzEhks,520
25
19
  anydi/ext/django/ninja/_operation.py,sha256=wk5EOjLY3KVIHk9jMCGsFsja9-dQmMOpLpHXciqxQdk,2680
26
20
  anydi/ext/django/ninja/_signature.py,sha256=p7JtyMdFhX4fWQOvAhvZNss6iURNERcdsTsQADTHkMY,2358
21
+ anydi/ext/fastapi.py,sha256=L9VGPHGy23se1sflmJqTE7LNfQuElVdYEogoT1f5-4A,2324
22
+ anydi/ext/faststream.py,sha256=Dy81Vf34CP6pEIbZ-41vh_-Dn6Qc-rcf14U5poebjxI,1905
23
+ anydi/ext/pydantic_settings.py,sha256=8IXXLuG_OvKbvKlBkBRQUHcXgbTpgQUxeWyoMcRIUQM,1488
24
+ anydi/ext/pytest_plugin.py,sha256=IoP6XKuGLGLd2Xlpfttc3mI4pxCm2WQLE7x_a7asbv4,4732
27
25
  anydi/ext/starlette/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
26
  anydi/ext/starlette/middleware.py,sha256=MxnzshAs-CMvjJp0r457k52MzBL8O4KAuClnF6exBdU,803
29
- anydi-0.47.0rc0.dist-info/METADATA,sha256=MB9wf9GrUWqVQ-SlB4Y3jdz8Fx4WYS6XNA9L298u0lc,4960
30
- anydi-0.47.0rc0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- anydi-0.47.0rc0.dist-info/entry_points.txt,sha256=Nklo9f3Oe4AkNsEgC4g43nCJ-23QDngZSVDNRMdaILI,43
32
- anydi-0.47.0rc0.dist-info/licenses/LICENSE,sha256=V6rU8a8fv6o2jQ-7ODHs0XfDFimot8Q6Km6CylRIDTo,1069
33
- anydi-0.47.0rc0.dist-info/RECORD,,
27
+ anydi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ anydi/testing.py,sha256=waz0nO-MnfPGQLnPuDT0bdEdfqfmgkbsuFuxWfct6kw,5539
29
+ anydi-0.48.1.dist-info/WHEEL,sha256=Pi5uDq5Fdo_Rr-HD5h9BiPn9Et29Y9Sh8NhcJNnFU1c,79
30
+ anydi-0.48.1.dist-info/entry_points.txt,sha256=AgOcQYM5KyS4D37QcYb00tiid0QA-pD1VrjHHq4QAps,44
31
+ anydi-0.48.1.dist-info/METADATA,sha256=X9swcb7pHvqntskN44YfhEJlCnR6wCGJWGwARHAPWfM,4956
32
+ anydi-0.48.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.17
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -1,2 +1,3 @@
1
1
  [pytest11]
2
2
  anydi = anydi.ext.pytest_plugin
3
+
@@ -1,4 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
3
- Root-Is-Purelib: true
4
- Tag: py3-none-any
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 Anton Ruhlov
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.