anydi 0.60.1__tar.gz → 0.61.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anydi
3
- Version: 0.60.1
3
+ Version: 0.61.0
4
4
  Summary: Dependency Injection library
5
5
  Keywords: dependency injection,dependencies,di,async,asyncio,application
6
6
  Author: Anton Ruhlov
@@ -58,6 +58,13 @@ class Container:
58
58
  # Register default scopes
59
59
  self.register_scope("request")
60
60
 
61
+ # Register self as provider
62
+ self._register_provider(
63
+ lambda: self,
64
+ "singleton",
65
+ Container,
66
+ )
67
+
61
68
  # Register providers
62
69
  providers = providers or []
63
70
  for provider in providers:
@@ -257,31 +264,52 @@ class Container:
257
264
  # Register the scope
258
265
  self._scopes[scope] = tuple({scope, "singleton"} | set(parents))
259
266
 
260
- def get_ordered_scopes(self, scopes: set[Scope]) -> list[str]:
261
- """Get ordered list of scopes to enter."""
262
- # Expand scopes to include all parent scopes
263
- expanded_scopes: set[str] = set()
264
- for scope in scopes:
267
+ def get_context_scopes(self, scopes: set[Scope] | None = None) -> list[str]: # noqa: C901
268
+ """Return scopes that require context management in dependency order."""
269
+ # Build execution order: singleton -> request -> custom (by depth)
270
+ ordered = ["singleton"]
271
+ custom_scopes: list[tuple[int, str]] = []
272
+ has_request = False
273
+
274
+ for scope, parents in self._scopes.items():
275
+ if scope == "singleton":
276
+ continue
277
+ if scope == "request":
278
+ has_request = True
279
+ continue
265
280
  if scope == "transient":
266
281
  continue
267
- elif scope == "singleton":
268
- expanded_scopes.add("singleton")
269
- else:
270
- # Add the scope and all its parents from container._scopes
271
- expanded_scopes.update(self._scopes[scope])
282
+ custom_scopes.append((len(parents), scope))
272
283
 
273
- # Separate singleton from other scopes
274
- has_singleton = "singleton" in expanded_scopes
275
- other_scopes = expanded_scopes - {"singleton"}
284
+ if has_request:
285
+ ordered.append("request")
276
286
 
277
- # Sort other scopes by dependency depth (parents before children)
278
- # Scopes with fewer parents come first
279
- ordered_scopes = sorted(
280
- other_scopes, key=lambda scope: len(self._scopes[scope])
281
- )
287
+ custom_scopes.sort(key=lambda item: item[0])
288
+ ordered.extend(scope for _, scope in custom_scopes)
289
+
290
+ # If no filter, return all scopes with contexts (transient excluded)
291
+ if scopes is None:
292
+ return ordered
293
+
294
+ # Helper to add scope with its parents to needed set
295
+ def add_scope_tree(needed: set[str], scope: str) -> None:
296
+ if scope == "singleton":
297
+ needed.add("singleton")
298
+ elif scope != "transient":
299
+ needed.update(self._scopes[scope])
300
+
301
+ needed_scopes: set[str] = set()
302
+
303
+ # Add injected scopes and their parents
304
+ for scope in scopes:
305
+ add_scope_tree(needed_scopes, scope)
306
+
307
+ # Add scopes with resource providers and their parents
308
+ for scope in ordered:
309
+ if self._resources.get(scope):
310
+ add_scope_tree(needed_scopes, scope)
282
311
 
283
- # Return with singleton first if needed
284
- return ["singleton", *ordered_scopes] if has_singleton else ordered_scopes
312
+ return [scope for scope in ordered if scope in needed_scopes]
285
313
 
286
314
  # == Provider Registry ==
287
315
 
@@ -12,11 +12,10 @@ import anyio
12
12
  from typer import Typer
13
13
 
14
14
  from anydi import Container, Scope
15
+ from anydi._decorators import is_provided
15
16
 
16
17
  __all__ = ["install"]
17
18
 
18
- from anydi._decorators import is_provided
19
-
20
19
 
21
20
  def _wrap_async_callback_no_injection(callback: Callable[..., Any]) -> Any:
22
21
  """Wrap async callback without injection in anyio.run()."""
@@ -40,11 +39,12 @@ def _wrap_async_callback_with_injection(
40
39
  @functools.wraps(callback)
41
40
  def async_wrapper(*args: Any, **kwargs: Any) -> Any:
42
41
  async def _run() -> Any:
43
- ordered_scopes = container.get_ordered_scopes(scopes)
42
+ # Get scopes for execution (injected OR have resources)
43
+ needed_scopes = container.get_context_scopes(scopes)
44
44
 
45
45
  async with contextlib.AsyncExitStack() as stack:
46
46
  # Start scoped contexts in dependency order
47
- for scope in ordered_scopes:
47
+ for scope in needed_scopes:
48
48
  if scope == "singleton":
49
49
  await stack.enter_async_context(container)
50
50
  else:
@@ -100,11 +100,12 @@ def _process_callback(callback: Callable[..., Any], container: Container) -> Any
100
100
 
101
101
  @functools.wraps(callback)
102
102
  def wrapper(*args: Any, **kwargs: Any) -> Any:
103
- ordered_scopes = container.get_ordered_scopes(scopes)
103
+ # Get scopes for execution (injected OR have resources)
104
+ needed_scopes = container.get_context_scopes(scopes)
104
105
 
105
106
  with contextlib.ExitStack() as stack:
106
107
  # Start scoped contexts in dependency order
107
- for scope in ordered_scopes:
108
+ for scope in needed_scopes:
108
109
  if scope == "singleton":
109
110
  stack.enter_context(container)
110
111
  else:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "anydi"
3
- version = "0.60.1"
3
+ version = "0.61.0"
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"
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