asyncstdlib 3.12.1__tar.gz → 3.12.2__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.
Files changed (33) hide show
  1. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/PKG-INFO +2 -1
  2. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/__init__.py +9 -2
  3. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/_lrucache.py +29 -16
  4. asyncstdlib-3.12.2/asyncstdlib/_lrucache.pyi +90 -0
  5. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/_typing.py +16 -3
  6. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/asynctools.py +6 -5
  7. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/builtins.py +1 -1
  8. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/contextlib.py +64 -41
  9. asyncstdlib-3.12.2/asyncstdlib/contextlib.pyi +115 -0
  10. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/functools.py +12 -15
  11. asyncstdlib-3.12.2/asyncstdlib/functools.pyi +26 -0
  12. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/heapq.py +9 -31
  13. asyncstdlib-3.12.2/asyncstdlib/heapq.pyi +40 -0
  14. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/itertools.py +13 -135
  15. asyncstdlib-3.12.2/asyncstdlib/itertools.pyi +238 -0
  16. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/pyproject.toml +1 -0
  17. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_contextlib.py +19 -0
  18. asyncstdlib-3.12.1/asyncstdlib/_lrucache.pyi +0 -61
  19. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/LICENSE +0 -0
  20. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/README.rst +0 -0
  21. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/_core.py +0 -0
  22. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/_utility.py +0 -0
  23. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/builtins.pyi +0 -0
  24. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/asyncstdlib/py.typed +0 -0
  25. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/__init__.py +0 -0
  26. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_asynctools.py +0 -0
  27. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_builtins.py +0 -0
  28. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_functools.py +0 -0
  29. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_functools_lru.py +0 -0
  30. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_heapq.py +0 -0
  31. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_helpers.py +0 -0
  32. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/test_itertools.py +0 -0
  33. {asyncstdlib-3.12.1 → asyncstdlib-3.12.2}/unittests/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: asyncstdlib
3
- Version: 3.12.1
3
+ Version: 3.12.2
4
4
  Summary: The missing async toolbox
5
5
  Keywords: async,enumerate,itertools,builtins,functools,contextlib
6
6
  Author-email: Max Fischer <maxfischer2781@gmail.com>
@@ -26,6 +26,7 @@ Requires-Dist: coverage ; extra == "test"
26
26
  Requires-Dist: pytest-cov ; extra == "test"
27
27
  Requires-Dist: flake8-2020 ; extra == "test"
28
28
  Requires-Dist: mypy ; extra == "test" and ( implementation_name=='cpython')
29
+ Requires-Dist: typing-extensions ; extra == "test"
29
30
  Project-URL: Documentation, https://asyncstdlib.readthedocs.io/en/latest/
30
31
  Project-URL: Source, https://github.com/maxfischer2781/asyncstdlib
31
32
  Provides-Extra: doc
@@ -19,7 +19,13 @@ from .builtins import (
19
19
  sorted,
20
20
  )
21
21
  from .functools import reduce, lru_cache, cache, cached_property
22
- from .contextlib import closing, contextmanager, nullcontext, ExitStack
22
+ from .contextlib import (
23
+ closing,
24
+ ContextDecorator,
25
+ contextmanager,
26
+ nullcontext,
27
+ ExitStack,
28
+ )
23
29
  from .itertools import (
24
30
  accumulate,
25
31
  batched,
@@ -39,7 +45,7 @@ from .itertools import (
39
45
  from .asynctools import borrow, scoped_iter, await_each, any_iter, apply, sync
40
46
  from .heapq import merge, nlargest, nsmallest
41
47
 
42
- __version__ = "3.12.1"
48
+ __version__ = "3.12.2"
43
49
 
44
50
  __all__ = [
45
51
  "anext",
@@ -65,6 +71,7 @@ __all__ = [
65
71
  "cached_property",
66
72
  # contextlib
67
73
  "closing",
74
+ "ContextDecorator",
68
75
  "contextmanager",
69
76
  "nullcontext",
70
77
  "ExitStack",
@@ -6,7 +6,10 @@ several performance hacks are skipped in favour of maintainability,
6
6
  especially when they might not apply to PyPy.
7
7
  """
8
8
 
9
+ from __future__ import annotations
9
10
  from typing import (
11
+ Generic,
12
+ TypeVar,
10
13
  NamedTuple,
11
14
  Callable,
12
15
  Any,
@@ -72,6 +75,14 @@ class LRUAsyncCallable(Protocol[AC]):
72
75
  """The callable wrapped by this cache"""
73
76
  raise NotImplementedError
74
77
 
78
+ def __get__(
79
+ self: LRUAsyncCallable[Any], instance: object, owner: Optional[type] = None
80
+ ) -> Any:
81
+ """Descriptor ``__get__`` for caches to bind them on lookup"""
82
+ if instance is None:
83
+ return self
84
+ return LRUAsyncBoundCallable(self, instance)
85
+
75
86
  #: Get the result of ``await __wrapped__(...)`` from the cache or evaluation
76
87
  __call__: AC
77
88
 
@@ -106,23 +117,37 @@ class LRUAsyncCallable(Protocol[AC]):
106
117
  ...
107
118
 
108
119
 
109
- class LRUAsyncBoundCallable(LRUAsyncCallable[AC]):
120
+ # these are fake and only exist for placeholders
121
+ S = TypeVar("S")
122
+ S2 = TypeVar("S2")
123
+ P = TypeVar("P")
124
+ R = TypeVar("R")
125
+
126
+
127
+ class LRUAsyncBoundCallable(Generic[S, P, R]): # type: ignore[reportInvalidTypeVarUse]
110
128
  """A :py:class:`~.LRUAsyncCallable` that is bound like a method"""
111
129
 
112
130
  __slots__ = ("__weakref__", "_lru", "__self__")
113
131
 
114
- def __init__(self, lru: LRUAsyncCallable[AC], __self__: object):
132
+ def __init__(self, lru: LRUAsyncCallable[Any], __self__: object):
115
133
  self._lru = lru
116
134
  self.__self__ = __self__
117
135
 
118
136
  @property
119
- def __wrapped__(self) -> AC:
137
+ def __wrapped__(self) -> Any:
120
138
  return self._lru.__wrapped__
121
139
 
122
140
  @property
123
- def __func__(self) -> LRUAsyncCallable[AC]:
141
+ def __func__(self) -> LRUAsyncCallable[Any]:
124
142
  return self._lru
125
143
 
144
+ def __get__(
145
+ self: LRUAsyncBoundCallable[S, P, R],
146
+ instance: S2,
147
+ owner: Optional[type] = None,
148
+ ) -> LRUAsyncBoundCallable[S2, P, R]:
149
+ return LRUAsyncBoundCallable(self._lru, instance)
150
+
126
151
  def __call__(self, *args, **kwargs): # type: ignore
127
152
  return self._lru(self.__self__, *args, **kwargs)
128
153
 
@@ -289,22 +314,12 @@ class CallKey:
289
314
  return cls(key)
290
315
 
291
316
 
292
- def cache__get(
293
- self: LRUAsyncCallable[AC], instance: object, owner: Optional[type] = None
294
- ) -> LRUAsyncCallable[AC]:
295
- """Descriptor ``__get__`` for caches to bind them on lookup"""
296
- if instance is None:
297
- return self
298
- return LRUAsyncBoundCallable(self, instance)
299
-
300
-
301
317
  class UncachedLRUAsyncCallable(LRUAsyncCallable[AC]):
302
318
  """Wrap the async ``call`` to track accesses as for caching/memoization"""
303
319
 
304
320
  __slots__ = ("__weakref__", "__dict__", "__wrapped__", "__misses", "__typed")
305
321
 
306
322
  __wrapped__: AC
307
- __get__ = cache__get
308
323
 
309
324
  def __init__(self, call: AC, typed: bool):
310
325
  self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
@@ -342,7 +357,6 @@ class MemoizedLRUAsyncCallable(LRUAsyncCallable[AC]):
342
357
  )
343
358
 
344
359
  __wrapped__: AC
345
- __get__ = cache__get
346
360
 
347
361
  def __init__(self, call: AC, typed: bool):
348
362
  self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
@@ -397,7 +411,6 @@ class CachedLRUAsyncCallable(LRUAsyncCallable[AC]):
397
411
  )
398
412
 
399
413
  __wrapped__: AC
400
- __get__ = cache__get
401
414
 
402
415
  def __init__(self, call: AC, typed: bool, maxsize: int):
403
416
  self.__wrapped__ = call # type: ignore[reportIncompatibleMethodOverride]
@@ -0,0 +1,90 @@
1
+ from typing import (
2
+ TypeVar,
3
+ Any,
4
+ Awaitable,
5
+ Callable,
6
+ Coroutine,
7
+ Generic,
8
+ NamedTuple,
9
+ overload,
10
+ Protocol,
11
+ )
12
+ from typing_extensions import ParamSpec, Concatenate
13
+
14
+ from ._typing import AC, TypedDict
15
+
16
+ class CacheInfo(NamedTuple):
17
+ hits: int
18
+ misses: int
19
+ maxsize: int | None
20
+ currsize: int
21
+
22
+ class CacheParameters(TypedDict):
23
+ maxsize: int | None
24
+ typed: bool
25
+
26
+ R = TypeVar("R")
27
+ P = ParamSpec("P")
28
+ S = TypeVar("S")
29
+ S2 = TypeVar("S2")
30
+
31
+ class LRUAsyncCallable(Protocol[AC]):
32
+ __slots__: tuple[str, ...]
33
+ __call__: AC
34
+ @overload
35
+ def __get__(
36
+ self: LRUAsyncCallable[AC], instance: None, owner: type | None = ...
37
+ ) -> LRUAsyncCallable[AC]: ...
38
+ @overload
39
+ def __get__(
40
+ self: LRUAsyncCallable[Callable[Concatenate[S, P], Coroutine[Any, Any, R]]],
41
+ instance: S,
42
+ owner: type | None = ...,
43
+ ) -> LRUAsyncBoundCallable[S, P, R]: ...
44
+ @overload
45
+ def __get__(
46
+ self: LRUAsyncCallable[Callable[Concatenate[S, P], Awaitable[R]]],
47
+ instance: S,
48
+ owner: type | None = ...,
49
+ ) -> LRUAsyncBoundCallable[S, P, R]: ...
50
+ @property
51
+ def __wrapped__(self) -> AC: ...
52
+ def cache_parameters(self) -> CacheParameters: ...
53
+ def cache_info(self) -> CacheInfo: ...
54
+ def cache_clear(self) -> None: ...
55
+ def cache_discard(self, *args: Any, **kwargs: Any) -> None: ...
56
+
57
+ class LRUAsyncBoundCallable(Generic[S, P, R]):
58
+ __slots__: tuple[str, ...]
59
+ __self__: S
60
+ __call__: Callable[P, Awaitable[R]]
61
+ @overload
62
+ def __get__(
63
+ self, instance: None, owner: type | None = ...
64
+ ) -> LRUAsyncBoundCallable[S, P, R]: ...
65
+ @overload
66
+ def __get__(
67
+ self, instance: S2, owner: type | None = ...
68
+ ) -> LRUAsyncBoundCallable[S2, P, R]: ...
69
+ def __init__(
70
+ self,
71
+ lru: LRUAsyncCallable[Callable[Concatenate[S, P], Awaitable[R]]],
72
+ __self__: S,
73
+ ) -> None: ...
74
+ @property
75
+ def __wrapped__(self) -> Callable[Concatenate[S, P], Awaitable[R]]: ...
76
+ @property
77
+ def __func__(
78
+ self,
79
+ ) -> LRUAsyncCallable[Callable[Concatenate[S, P], Awaitable[R]]]: ...
80
+ def cache_parameters(self) -> CacheParameters: ...
81
+ def cache_info(self) -> CacheInfo: ...
82
+ def cache_clear(self) -> None: ...
83
+ def cache_discard(self, *args: Any, **kwargs: Any) -> None: ...
84
+
85
+ @overload
86
+ def lru_cache(maxsize: AC, typed: bool = ...) -> LRUAsyncCallable[AC]: ...
87
+ @overload
88
+ def lru_cache(
89
+ maxsize: int | None = ..., typed: bool = ...
90
+ ) -> Callable[[AC], LRUAsyncCallable[AC]]: ...
@@ -14,13 +14,14 @@ from typing import (
14
14
  Callable,
15
15
  Any,
16
16
  Awaitable,
17
+ runtime_checkable,
18
+ Protocol,
19
+ ContextManager,
20
+ TypedDict,
17
21
  )
18
22
 
19
- from typing import Protocol, AsyncContextManager, ContextManager, TypedDict
20
-
21
23
  __all__ = [
22
24
  "Protocol",
23
- "AsyncContextManager",
24
25
  "ContextManager",
25
26
  "TypedDict",
26
27
  "T",
@@ -35,6 +36,8 @@ __all__ = [
35
36
  "HK",
36
37
  "LT",
37
38
  "ADD",
39
+ "AClose",
40
+ "ACloseable",
38
41
  "AnyIterable",
39
42
  ]
40
43
 
@@ -70,5 +73,15 @@ class SupportsAdd(Protocol):
70
73
  raise NotImplementedError
71
74
 
72
75
 
76
+ # await AClose.aclose()
77
+ AClose = TypeVar("AClose", bound="ACloseable")
78
+
79
+
80
+ @runtime_checkable
81
+ class ACloseable(Protocol):
82
+ async def aclose(self) -> None:
83
+ """Asynchronously close this object"""
84
+
85
+
73
86
  #: (async) iter T
74
87
  AnyIterable = Union[Iterable[T], AsyncIterable[T]]
@@ -2,6 +2,7 @@ from asyncio import iscoroutinefunction
2
2
  from functools import wraps
3
3
  from typing import (
4
4
  Union,
5
+ AsyncContextManager,
5
6
  AsyncIterator,
6
7
  TypeVar,
7
8
  AsyncGenerator,
@@ -14,7 +15,7 @@ from typing import (
14
15
  Optional,
15
16
  )
16
17
 
17
- from ._typing import AsyncContextManager, T, T1, T2, T3, T4, T5, AnyIterable
18
+ from ._typing import T, T1, T2, T3, T4, T5, AnyIterable
18
19
  from ._core import aiter
19
20
  from .contextlib import nullcontext
20
21
 
@@ -50,11 +51,11 @@ class _BorrowedAsyncIterator(AsyncGenerator[T, S]):
50
51
  self.__anext__ = self._wrapper.__anext__ # type: ignore
51
52
  if hasattr(iterator, "asend"):
52
53
  self.asend = (
53
- iterator.asend # pyright: ignore[reportUnknownMemberType,reportGeneralTypeIssues]
54
+ iterator.asend # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue]
54
55
  )
55
56
  if hasattr(iterator, "athrow"):
56
57
  self.athrow = (
57
- iterator.athrow # pyright: ignore[reportUnknownMemberType,reportGeneralTypeIssues]
58
+ iterator.athrow # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue]
58
59
  )
59
60
 
60
61
  def __aiter__(self) -> AsyncGenerator[T, S]:
@@ -409,9 +410,9 @@ async def any_iter(
409
410
  async for item in iterable:
410
411
  yield (
411
412
  item if not isinstance(item, Awaitable) else await item
412
- ) # pyright: ignore[reportGeneralTypeIssues]
413
+ ) # pyright: ignore[reportReturnType]
413
414
  else:
414
415
  for item in iterable:
415
416
  yield (
416
417
  item if not isinstance(item, Awaitable) else await item
417
- ) # pyright: ignore[reportGeneralTypeIssues]
418
+ ) # pyright: ignore[reportReturnType]
@@ -444,7 +444,7 @@ async def sorted(
444
444
  try:
445
445
  return _sync_builtins.sorted(iterable, reverse=reverse) # type: ignore
446
446
  except TypeError:
447
- items: "_sync_builtins.list[Any]" = [item async for item in aiter(iterable)]
447
+ items: _sync_builtins.list[Any] = [item async for item in aiter(iterable)]
448
448
  items.sort(reverse=reverse)
449
449
  return items
450
450
  else:
@@ -8,18 +8,17 @@ from typing import (
8
8
  Any,
9
9
  Awaitable,
10
10
  Deque,
11
- overload,
11
+ AsyncContextManager,
12
12
  )
13
13
  from functools import wraps
14
14
  from collections import deque
15
15
  from functools import partial
16
16
  import sys
17
17
 
18
- from ._typing import Protocol, AsyncContextManager, ContextManager, T, C
18
+ from ._typing import AClose, ContextManager, AC, T, C
19
19
  from ._core import awaitify
20
20
  from ._utility import public_module
21
21
 
22
-
23
22
  AnyContextManager = Union[AsyncContextManager[T], ContextManager[T]]
24
23
 
25
24
 
@@ -28,18 +27,10 @@ AnyContextManager = Union[AsyncContextManager[T], ContextManager[T]]
28
27
  AbstractContextManager = AsyncContextManager
29
28
 
30
29
 
31
- class ACloseable(Protocol):
32
- async def aclose(self) -> None:
33
- """Asynchronously close this object"""
34
-
35
-
36
- AC = TypeVar("AC", bound=ACloseable)
37
-
38
-
39
30
  def contextmanager(
40
31
  func: Callable[..., AsyncGenerator[T, None]]
41
32
  ) -> Callable[..., AsyncContextManager[T]]:
42
- """
33
+ r"""
43
34
  Create an asynchronous context manager out of an asynchronous generator function
44
35
 
45
36
  This is intended as a decorator for an asynchronous generator function.
@@ -50,7 +41,7 @@ def contextmanager(
50
41
  .. code-block:: python3
51
42
 
52
43
  @contextmanager
53
- async def Context(*args, **kwargs):
44
+ async def context(*args, **kwargs):
54
45
  # __aenter__
55
46
  yield # context value
56
47
  # __aexit__
@@ -58,6 +49,9 @@ def contextmanager(
58
49
  Note that if an exception ends the context block, it gets re-raised at the ``yield``
59
50
  inside the asynchronous generator (via :py:meth:`~agen.athrow`). In order to handle
60
51
  this exception, the ``yield`` should be wrapped in a ``try`` statement.
52
+
53
+ The created context manager is a :py:class:`~.ContextDecorator` and can also be used
54
+ as a decorator. It is automatically entered when a decorated function is ``await``\ ed.
61
55
  """
62
56
 
63
57
  @wraps(func)
@@ -67,13 +61,62 @@ def contextmanager(
67
61
  return helper
68
62
 
69
63
 
70
- class _AsyncGeneratorContextManager(Generic[T]):
64
+ class ContextDecorator(AsyncContextManager[T]):
65
+ """
66
+ Base class to turn an async context manager into a decorator as well
67
+
68
+ Inheriting from this class adds the scaffolding to automatically enter
69
+ an async context manager on awaiting any callable decorated with it:
70
+
71
+ .. code:: python3
72
+
73
+ class DecoratorAndContext(ContextDecorator):
74
+ async def __aenter__(self) -> Any:
75
+ print("entering", self)
76
+
77
+ async def __aexit__(self, *exc):
78
+ print("exiting", self)
79
+
80
+ @DecoratorAndContext()
81
+ async def func():
82
+ print("running some function...")
83
+
84
+ The context manager can still be used regularly in `async with` statements.
85
+
86
+ Since functions are decorated with an existing context manager instance,
87
+ the same instance is entered and exited on every call. If the context is
88
+ not safe to be entered multiple times or even concurrently it should implement
89
+ the method ``_recreate_cm(:Self) -> Self`` to create a copy of itself.
90
+ """
91
+
92
+ __slots__ = ()
93
+
94
+ def _recreate_cm(self):
95
+ """Return another instance of the context manager that is ready for entering again"""
96
+ # Default to assuming the CM is reentrant, concurrency safe, ... and just return itself.
97
+ # Since for the `async` case many CMs have to be this anyway, this should apply most.
98
+ return self
99
+
100
+ def __call__(self, func: AC, /) -> AC:
101
+ @wraps(func)
102
+ async def inner(*args: Any, **kwds: Any) -> Any:
103
+ async with self._recreate_cm():
104
+ return await func(*args, **kwds)
105
+
106
+ return inner # type: ignore
107
+
108
+
109
+ class _AsyncGeneratorContextManager(ContextDecorator[T]):
71
110
  def __init__(
72
111
  self, func: Callable[..., AsyncGenerator[T, None]], args: Any, kwds: Any
73
112
  ):
74
113
  self.gen = func(*args, **kwds)
114
+ self.__recreate_args = func, args, kwds
75
115
  self.__doc__ = getattr(func, "__doc__", type(self).__doc__)
76
116
 
117
+ def _recreate_cm(self):
118
+ return type(self)(*self.__recreate_args)
119
+
77
120
  async def __aenter__(self) -> T:
78
121
  try:
79
122
  return await self.gen.__anext__()
@@ -126,7 +169,7 @@ class _AsyncGeneratorContextManager(Generic[T]):
126
169
 
127
170
 
128
171
  @public_module(__name__, "closing")
129
- class Closing(Generic[AC]):
172
+ class Closing(Generic[AClose]):
130
173
  """
131
174
  Create an :term:`asynchronous context manager` to ``aclose`` some ``thing`` on exit
132
175
 
@@ -150,10 +193,10 @@ class Closing(Generic[AC]):
150
193
  is eventually closed and only :term:`borrowed <borrowing>` until then.
151
194
  """
152
195
 
153
- def __init__(self, thing: AC):
196
+ def __init__(self, thing: AClose):
154
197
  self.thing = thing
155
198
 
156
- async def __aenter__(self) -> AC:
199
+ async def __aenter__(self) -> AClose:
157
200
  return self.thing
158
201
 
159
202
  async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
@@ -165,7 +208,7 @@ closing = Closing
165
208
 
166
209
 
167
210
  @public_module(__name__, "nullcontext")
168
- class NullContext(Generic[T]):
211
+ class NullContext(AsyncContextManager[T]):
169
212
  """
170
213
  Create an :term:`asynchronous context manager` that only returns ``enter_result``
171
214
 
@@ -190,22 +233,10 @@ class NullContext(Generic[T]):
190
233
 
191
234
  __slots__ = ("enter_result",)
192
235
 
193
- @overload
194
- def __init__(self: "NullContext[None]", enter_result: None = ...) -> None: ...
195
-
196
- @overload
197
- def __init__(self: "NullContext[T]", enter_result: T) -> None: ...
198
-
199
- def __init__(self, enter_result: Optional[T] = None):
236
+ def __init__(self, enter_result: T = None):
200
237
  self.enter_result = enter_result
201
238
 
202
- @overload
203
- async def __aenter__(self: "NullContext[None]") -> None: ...
204
-
205
- @overload
206
- async def __aenter__(self: "NullContext[T]") -> T: ...
207
-
208
- async def __aenter__(self) -> Optional[T]:
239
+ async def __aenter__(self) -> T:
209
240
  return self.enter_result
210
241
 
211
242
  async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
@@ -215,15 +246,7 @@ class NullContext(Generic[T]):
215
246
  nullcontext = NullContext
216
247
 
217
248
 
218
- SE = TypeVar(
219
- "SE",
220
- bound=Union[
221
- AsyncContextManager[Any],
222
- ContextManager[Any],
223
- Callable[[Any, BaseException, Any], Optional[bool]],
224
- Callable[[Any, BaseException, Any], Awaitable[Optional[bool]]],
225
- ],
226
- )
249
+ SE = TypeVar("SE")
227
250
 
228
251
 
229
252
  class ExitStack:
@@ -0,0 +1,115 @@
1
+ from typing import (
2
+ TypeVar,
3
+ Generic,
4
+ AsyncGenerator,
5
+ Callable,
6
+ Optional,
7
+ Any,
8
+ Awaitable,
9
+ overload,
10
+ AsyncContextManager,
11
+ )
12
+ from typing_extensions import ParamSpec, Self
13
+ from types import TracebackType
14
+ from abc import ABCMeta
15
+
16
+ from ._typing import AClose, ContextManager, AC, T, R
17
+
18
+ AnyContextManager = AsyncContextManager[T] | ContextManager[T]
19
+
20
+ class ContextDecorator(AsyncContextManager[T], metaclass=ABCMeta):
21
+ """
22
+ Base class for an async context manager useable as a decorator as well
23
+
24
+ Inheriting from this class adds the scaffolding to automatically enter
25
+ an async context manager on awaiting any callable decorated with it:
26
+
27
+ .. code:: python3
28
+
29
+ class DecoratorAndContext(AsyncContextDecorator):
30
+ async def __aenter__(self) -> Any:
31
+ print("entering", self)
32
+
33
+ async def __aexit__(self, *exc):
34
+ print("exiting", self)
35
+
36
+ @DecoratorAndContext()
37
+ async def func():
38
+ # DecoratorAndContext has been entered already
39
+ print("running some function...")
40
+ # DecoratorAndContext will be exited immediately
41
+
42
+ The context manager can still be used regularly in `async with` statements.
43
+
44
+ Since functions are decorated with an existing context manager instance,
45
+ the same instance is entered and exited on every call. If the context is
46
+ not safe to be entered multiple times or even concurrently the subclass
47
+ should implement the method `_recreate_cm(:Self) -> Self` to create a copy.
48
+ """
49
+
50
+ __slots__ = ()
51
+
52
+ def _recreate_cm(self: Self) -> Self: ...
53
+ def __call__(self, func: AC, /) -> AC: ...
54
+
55
+ P = ParamSpec("P")
56
+
57
+ def contextmanager(
58
+ func: Callable[P, AsyncGenerator[T, None]]
59
+ ) -> Callable[P, ContextDecorator[T]]: ...
60
+
61
+ class closing(Generic[AClose]):
62
+ def __init__(self, thing: AClose) -> None: ...
63
+ async def __aenter__(self: Self) -> Self: ...
64
+ async def __aexit__(
65
+ self,
66
+ exc_type: type[BaseException] | None,
67
+ exc_val: BaseException | None,
68
+ exc_tb: TracebackType | None,
69
+ ) -> bool: ...
70
+
71
+ class nullcontext(AsyncContextManager[T]):
72
+ enter_result: T
73
+
74
+ @overload
75
+ def __init__(self: nullcontext[None], enter_result: None = ...) -> None: ...
76
+ @overload
77
+ def __init__(self: nullcontext[T], enter_result: T) -> None: ...
78
+ async def __aenter__(self: nullcontext[T]) -> T: ...
79
+ async def __aexit__(
80
+ self,
81
+ exc_type: type[BaseException] | None,
82
+ exc_val: BaseException | None,
83
+ exc_tb: TracebackType | None,
84
+ ) -> bool: ...
85
+
86
+ SE = TypeVar(
87
+ "SE",
88
+ bound=AsyncContextManager[Any]
89
+ | ContextManager[Any]
90
+ | Callable[
91
+ [type[BaseException] | None, BaseException | None, TracebackType | None],
92
+ Optional[bool],
93
+ ]
94
+ | Callable[
95
+ [type[BaseException] | None, BaseException | None, TracebackType | None],
96
+ Awaitable[Optional[bool]],
97
+ ],
98
+ )
99
+
100
+ class ExitStack:
101
+ def __init__(self) -> None: ...
102
+ def pop_all(self: Self) -> Self: ...
103
+ def push(self, exit: SE) -> SE: ...
104
+ def callback(
105
+ self, callback: Callable[P, R], *args: P.args, **kwargs: P.kwargs
106
+ ) -> Callable[P, R]: ...
107
+ async def enter_context(self, cm: AnyContextManager[T]) -> T: ...
108
+ async def aclose(self) -> None: ...
109
+ async def __aenter__(self: Self) -> Self: ...
110
+ async def __aexit__(
111
+ self,
112
+ exc_type: type[BaseException] | None,
113
+ exc_val: BaseException | None,
114
+ tb: TracebackType | None,
115
+ ) -> bool: ...