Skip to content

Commit 5da4fb4

Browse files
committed
Drop anyioutils
1 parent aa88306 commit 5da4fb4

5 files changed

Lines changed: 387 additions & 149 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ sock2 = zmq_anyio.Socket(sock2)
3939

4040
async def main():
4141
async with sock1, sock2: # use an async context manager
42-
await sock1.asend(b"Hello").wait() # use `asend` instead of `send`, and await the `.wait()` method
42+
await sock1.asend(b"Hello") # use `asend` instead of `send` and await it
4343
sock1.asend(b", World!") # or don't await it, it's sent in the background
44-
assert await sock2.arecv().wait() == b"Hello" # use `arecv` instead of `recv`, and await the `.wait()` method
44+
assert await sock2.arecv() == b"Hello" # use `arecv` instead of `recv`
4545
future = sock2.arecv() # or get the future and await it later
46-
assert await future.wait() == b", World!"
46+
assert await future == b", World!"
4747

4848
anyio.run(main)
4949
```

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ classifiers = [
2929
]
3030
requires-python = ">= 3.10"
3131
dependencies = [
32-
"anyio >=4.13.0,<5.0.0",
33-
"anyioutils >=0.7.4,<0.8.0",
32+
"anyio >=4.14.0,<5.0.0",
3433
"pyzmq >=27.1.0,<28.0.0",
3534
]
3635

src/zmq_anyio/_future.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Generator
4+
from enum import Enum, auto
5+
from typing import Any, Generic, TypeVar
6+
7+
from anyio import Event
8+
9+
T = TypeVar("T")
10+
11+
12+
class Future(Generic[T]):
13+
"""
14+
An awaitable object that works similar to a :class:`asyncio.Future` but
15+
with similar characteristics to a :class:`.TaskHandle`.
16+
"""
17+
18+
class Status(Enum):
19+
"""
20+
The status of a future handle.
21+
22+
.. attribute:: PENDING
23+
24+
The future has not finished yet.
25+
.. attribute:: FINISHED
26+
27+
The future has finished with a return value.
28+
.. attribute:: CANCELLED
29+
30+
The future was cancelled and has finished since.
31+
.. attribute:: FAILED
32+
33+
The future raised an exception.
34+
"""
35+
36+
PENDING = auto()
37+
FINISHED = auto()
38+
CANCELLED = auto()
39+
FAILED = auto()
40+
41+
__slots__ = (
42+
"_cancelled",
43+
"_result_value",
44+
"_finished_event",
45+
"_exception",
46+
"_name",
47+
)
48+
_result_value: T
49+
50+
def __init__(self, *, name: str | None = None) -> None:
51+
self._finished_event = Event()
52+
self._exception: BaseException | None = None
53+
self._cancelled: bool = False
54+
self._name = name
55+
56+
def _check_pending(self) -> None:
57+
"""Shortcut for checking if a Future is pending
58+
59+
:raises FutureAlreadyFinished: if future was already given a result or exception.
60+
:raises FutureCancelled: if future has been cancelled previously.
61+
"""
62+
match self.status:
63+
case Future.Status.PENDING:
64+
return
65+
case Future.Status.FINISHED:
66+
raise FutureAlreadyFinished("future has already finished")
67+
case Future.Status.FAILED:
68+
raise FutureAlreadyFinished("future already failed")
69+
case Future.Status.CANCELLED:
70+
raise FutureCancelled("future was cancelled")
71+
72+
async def wait(self) -> None:
73+
"""
74+
Waits for the future to finish.
75+
76+
This method will attempt to wait for a result or exception
77+
"""
78+
await self._finished_event.wait()
79+
80+
def set_result(self, value: T) -> None:
81+
"""
82+
Send pending result for a `.Future` object
83+
84+
:raises FutureAlreadyFinished: if future was already given a result or exception.
85+
:raises FutureCancelled: if future has been cancelled previously.
86+
"""
87+
self._check_pending()
88+
self._result_value = value
89+
self._finished_event.set()
90+
91+
def set_exception(self, exception: BaseException) -> None:
92+
"""Send exception for a `.Future` object
93+
94+
:raises FutureAlreadyFinished: if future was already given a result or exception.
95+
:raises FutureCancelled: if future has been cancelled previously.
96+
"""
97+
self._check_pending()
98+
self._exception = exception
99+
self._finished_event.set()
100+
101+
def cancel(self) -> None:
102+
"""Cancels a pending `.Future` object
103+
104+
Does nothing if the Future was already finished.
105+
"""
106+
if self.status is Future.Status.PENDING:
107+
self._cancelled = True
108+
self._finished_event.set()
109+
110+
@property
111+
def exception(self) -> BaseException | None:
112+
"""
113+
The exception value of a `.Future`
114+
115+
:raises TaskNotFinished: if future is still pending
116+
:raises FutureCancelled: if future was cancelled
117+
:returns: None if future succeeds with a result sent instead
118+
otherwise this will be an exception
119+
"""
120+
121+
match self.status:
122+
case Future.Status.PENDING:
123+
raise TaskNotFinished("the future has not finished yet")
124+
case Future.Status.FINISHED:
125+
return None
126+
case Future.Status.CANCELLED:
127+
raise FutureCancelled("the future was cancelled")
128+
case Future.Status.FAILED:
129+
return self._exception
130+
131+
@property
132+
def return_value(self) -> T:
133+
"""
134+
The return value of the future.
135+
136+
:raises TaskNotFinished: if the future has not finished yet
137+
:raises FutureCancelled: if the future was cancelled
138+
:raises TaskFailed: if the future raised an exception
139+
140+
"""
141+
match self.status:
142+
case Future.Status.PENDING:
143+
raise TaskNotFinished("the future has not finished yet")
144+
case Future.Status.FINISHED:
145+
return self._result_value
146+
case Future.Status.CANCELLED:
147+
raise FutureCancelled("the future was cancelled")
148+
case Future.Status.FAILED:
149+
raise TaskFailed("the future raised an exception") from self._exception
150+
151+
@property
152+
def status(self) -> Future.Status:
153+
"""
154+
The current status of a future.
155+
156+
Every future starts in the :attr:`~Future.Status.PENDING` state.
157+
If a future is cancelled while in this state, it will transition to the
158+
:attr:`Future.Status.CANCELLING` state. When the task finishes, it will
159+
transition to one of the three final states (
160+
:attr:`Future.Status.FINISHED`, :attr:`Future.Status.FAILED`, or
161+
:attr:`Future.Status.CANCELLING`) depending on the exception the task
162+
raised, if any. No other status transitions will happen.
163+
"""
164+
if not self._finished_event.is_set():
165+
return Future.Status.PENDING
166+
elif self._cancelled:
167+
return Future.Status.CANCELLED
168+
elif self._exception is not None:
169+
return Future.Status.FAILED
170+
else:
171+
return Future.Status.FINISHED
172+
173+
def __await__(self) -> Generator[Any, Any, T]:
174+
yield from self.wait().__await__()
175+
return self.return_value
176+
177+
def __repr__(self) -> str:
178+
return (
179+
f"<{self.__class__.__name__} {self.status.name.lower()} "
180+
f"name={self._name!r}>"
181+
)
182+
183+
184+
class FutureAlreadyFinished(Exception):
185+
"""
186+
Raised when attempting set a result of or await a
187+
:class:`.Future` that has already completed.
188+
"""
189+
190+
191+
class FutureCancelled(Exception):
192+
"""
193+
Raised when attempting to access the return value or exception of a
194+
:class:`.Future` that was cancelled.
195+
"""
196+
197+
198+
class TaskFailed(Exception):
199+
"""
200+
Raised when awaiting on, or attempting to access the return value of, a
201+
:class:`.TaskHandle` that raised an exception.
202+
"""
203+
204+
205+
class TaskNotFinished(Exception):
206+
"""
207+
Raised when attempting to access the return value or exception of a
208+
:class:`.TaskHandle` that is still pending completion.
209+
"""

0 commit comments

Comments
 (0)