anyio-executor 0.1.1__tar.gz → 0.2.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.
- {anyio_executor-0.1.1/src/anyio_executor.egg-info → anyio_executor-0.2.0}/PKG-INFO +8 -5
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/README.md +3 -1
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/pyproject.toml +4 -3
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor/__init__.py +1 -1
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor/core.py +18 -16
- {anyio_executor-0.1.1 → anyio_executor-0.2.0/src/anyio_executor.egg-info}/PKG-INFO +8 -5
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor.egg-info/requires.txt +5 -2
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/tests/test_executor.py +40 -8
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/LICENSE +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/setup.cfg +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor/azip.py +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor/py.typed +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor.egg-info/SOURCES.txt +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor.egg-info/dependency_links.txt +0 -0
- {anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anyio-executor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: anyio implementation of an asynchronous Executor.
|
|
5
5
|
Author-email: Vizonex <VizonexBusiness@gmail.com>
|
|
6
6
|
Project-URL: repository, https://github.com/Vizonex/anyio-executor.git
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: aiologic>=0.
|
|
11
|
-
Requires-Dist: anyio>=4.
|
|
10
|
+
Requires-Dist: aiologic>=0.17.1
|
|
11
|
+
Requires-Dist: anyio>=4.16.0
|
|
12
|
+
Requires-Dist: typing_extensions; python_version < "3.15"
|
|
12
13
|
Provides-Extra: test
|
|
13
14
|
Requires-Dist: pytest; extra == "test"
|
|
14
15
|
Provides-Extra: test-advanced
|
|
@@ -21,7 +22,7 @@ Dynamic: license-file
|
|
|
21
22
|
# Anyio-Executor
|
|
22
23
|
Inspired by the newer [aiolibs-executor](https://github.com/aio-libs/aiolibs-executor) library
|
|
23
24
|
this library provides a asynchronous executor for anyio and has compatability
|
|
24
|
-
with 3.
|
|
25
|
+
with 3.11 and newer versions of python.
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
## Installation
|
|
@@ -47,6 +48,7 @@ async def fn(i: int):
|
|
|
47
48
|
print(f"{i} done!")
|
|
48
49
|
return i
|
|
49
50
|
|
|
51
|
+
|
|
50
52
|
async def main():
|
|
51
53
|
async with Executor(10) as e:
|
|
52
54
|
items = await e.map(fn, [1, 2, 1, 2, 3])
|
|
@@ -57,6 +59,7 @@ async def main():
|
|
|
57
59
|
async for i in e.map(fn, [1, 2, 1, 2, 3]):
|
|
58
60
|
print(i)
|
|
59
61
|
|
|
62
|
+
|
|
60
63
|
if __name__ == "__main__":
|
|
61
64
|
anyio.run(main)
|
|
62
65
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Anyio-Executor
|
|
2
2
|
Inspired by the newer [aiolibs-executor](https://github.com/aio-libs/aiolibs-executor) library
|
|
3
3
|
this library provides a asynchronous executor for anyio and has compatability
|
|
4
|
-
with 3.
|
|
4
|
+
with 3.11 and newer versions of python.
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
@@ -27,6 +27,7 @@ async def fn(i: int):
|
|
|
27
27
|
print(f"{i} done!")
|
|
28
28
|
return i
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
async def main():
|
|
31
32
|
async with Executor(10) as e:
|
|
32
33
|
items = await e.map(fn, [1, 2, 1, 2, 3])
|
|
@@ -37,6 +38,7 @@ async def main():
|
|
|
37
38
|
async for i in e.map(fn, [1, 2, 1, 2, 3]):
|
|
38
39
|
print(i)
|
|
39
40
|
|
|
41
|
+
|
|
40
42
|
if __name__ == "__main__":
|
|
41
43
|
anyio.run(main)
|
|
42
44
|
```
|
|
@@ -6,10 +6,11 @@ readme = "README.md"
|
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "Vizonex", email = "VizonexBusiness@gmail.com" }
|
|
8
8
|
]
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"aiologic>=0.
|
|
12
|
-
"anyio>=4.
|
|
11
|
+
"aiologic>=0.17.1",
|
|
12
|
+
"anyio>=4.16.0",
|
|
13
|
+
'typing_extensions; python_version < "3.15"',
|
|
13
14
|
]
|
|
14
15
|
[build-system]
|
|
15
16
|
requires = ["setuptools"]
|
|
@@ -7,14 +7,13 @@ from collections.abc import (
|
|
|
7
7
|
Coroutine,
|
|
8
8
|
Generator,
|
|
9
9
|
Iterable,
|
|
10
|
-
Iterator,
|
|
11
10
|
Sequence,
|
|
12
11
|
)
|
|
13
12
|
from contextlib import contextmanager
|
|
14
13
|
from dataclasses import dataclass
|
|
15
14
|
from functools import partial
|
|
16
15
|
from types import TracebackType
|
|
17
|
-
from typing import Any, Generic, TypeVar, final, overload
|
|
16
|
+
from typing import Any, Generic, Self, TypeVar, final, overload
|
|
18
17
|
|
|
19
18
|
import anyio
|
|
20
19
|
from aiologic import CountdownEvent, SimpleQueue
|
|
@@ -22,11 +21,8 @@ from aiologic import CountdownEvent, SimpleQueue
|
|
|
22
21
|
from .azip import azip
|
|
23
22
|
|
|
24
23
|
|
|
25
|
-
if sys.version_info < (3,
|
|
26
|
-
from typing_extensions import
|
|
27
|
-
else:
|
|
28
|
-
from typing import Self
|
|
29
|
-
|
|
24
|
+
if sys.version_info < (3, 15):
|
|
25
|
+
from typing_extensions import Sentinel as sentinel
|
|
30
26
|
|
|
31
27
|
R = TypeVar("R")
|
|
32
28
|
T = TypeVar("T")
|
|
@@ -36,12 +32,15 @@ T3 = TypeVar("T3")
|
|
|
36
32
|
T4 = TypeVar("T4")
|
|
37
33
|
T5 = TypeVar("T5")
|
|
38
34
|
|
|
35
|
+
MISSING = sentinel("MISSING")
|
|
36
|
+
"""Internal variable not meant to be used publically,
|
|
37
|
+
meant to mark results ran with an exeception handler."""
|
|
39
38
|
|
|
40
39
|
@dataclass(frozen=True, slots=True)
|
|
41
40
|
class _WorkItem(Generic[T]):
|
|
42
41
|
func: Callable[[], Awaitable[T]]
|
|
43
42
|
exc_handler: Callable[[BaseException], None] | None
|
|
44
|
-
queue: SimpleQueue[T]
|
|
43
|
+
queue: SimpleQueue[T | MISSING]
|
|
45
44
|
|
|
46
45
|
async def execute(self) -> None:
|
|
47
46
|
try:
|
|
@@ -49,6 +48,8 @@ class _WorkItem(Generic[T]):
|
|
|
49
48
|
except Exception as e:
|
|
50
49
|
if self.exc_handler:
|
|
51
50
|
self.exc_handler(e)
|
|
51
|
+
# mark as work done even though there is no objects.
|
|
52
|
+
await self.queue.async_put(MISSING)
|
|
52
53
|
else:
|
|
53
54
|
raise e
|
|
54
55
|
|
|
@@ -56,19 +57,21 @@ class _WorkItem(Generic[T]):
|
|
|
56
57
|
@dataclass(frozen=True, slots=True)
|
|
57
58
|
class ExecutorResult(Awaitable[list[T]], AsyncIterable[T]):
|
|
58
59
|
"""used for waiting upon results to return in different asynchronous
|
|
59
|
-
fashions.
|
|
60
|
+
fashions. It is designed as a hybrid ot be awaited or used in
|
|
61
|
+
async generator fashion."""
|
|
60
62
|
|
|
61
|
-
queue: SimpleQueue[T]
|
|
63
|
+
queue: SimpleQueue[T | MISSING]
|
|
62
64
|
count: CountdownEvent
|
|
63
65
|
executor: "Executor"
|
|
64
66
|
|
|
65
67
|
async def __aiter__(self) -> AsyncIterator[T]:
|
|
66
68
|
while not self.executor.is_closed:
|
|
67
69
|
item = await self.queue.async_get()
|
|
68
|
-
|
|
70
|
+
if item is not MISSING:
|
|
71
|
+
yield item
|
|
69
72
|
|
|
70
73
|
self.count.down()
|
|
71
|
-
if
|
|
74
|
+
if self.count:
|
|
72
75
|
break
|
|
73
76
|
|
|
74
77
|
async def __wait__(self) -> list[T]:
|
|
@@ -133,7 +136,7 @@ class Executor:
|
|
|
133
136
|
return self
|
|
134
137
|
|
|
135
138
|
@contextmanager
|
|
136
|
-
def _worker_scope(self) ->
|
|
139
|
+
def _worker_scope(self) -> Generator[None, None, None]:
|
|
137
140
|
"""Assistance with Lazy event up/down event functionality."""
|
|
138
141
|
self._workers_done.up()
|
|
139
142
|
try:
|
|
@@ -278,7 +281,7 @@ class Executor:
|
|
|
278
281
|
raise RuntimeError("Executor already closed.")
|
|
279
282
|
cd = CountdownEvent()
|
|
280
283
|
cd.up()
|
|
281
|
-
queue: SimpleQueue[T] = SimpleQueue()
|
|
284
|
+
queue: SimpleQueue[T | MISSING] = SimpleQueue()
|
|
282
285
|
self._tg.start_soon(
|
|
283
286
|
self._load_tasks_sync_iter,
|
|
284
287
|
queue,
|
|
@@ -376,10 +379,9 @@ class Executor:
|
|
|
376
379
|
if self.is_closed:
|
|
377
380
|
raise RuntimeError("Executor already closed.")
|
|
378
381
|
|
|
379
|
-
queue: SimpleQueue[T] = SimpleQueue()
|
|
380
382
|
cd = CountdownEvent()
|
|
381
383
|
cd.up()
|
|
382
|
-
queue: SimpleQueue[T] = SimpleQueue()
|
|
384
|
+
queue: SimpleQueue[T | MISSING] = SimpleQueue()
|
|
383
385
|
self._tg.start_soon(
|
|
384
386
|
self._load_tasks_async_iter,
|
|
385
387
|
queue,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anyio-executor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: anyio implementation of an asynchronous Executor.
|
|
5
5
|
Author-email: Vizonex <VizonexBusiness@gmail.com>
|
|
6
6
|
Project-URL: repository, https://github.com/Vizonex/anyio-executor.git
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: aiologic>=0.
|
|
11
|
-
Requires-Dist: anyio>=4.
|
|
10
|
+
Requires-Dist: aiologic>=0.17.1
|
|
11
|
+
Requires-Dist: anyio>=4.16.0
|
|
12
|
+
Requires-Dist: typing_extensions; python_version < "3.15"
|
|
12
13
|
Provides-Extra: test
|
|
13
14
|
Requires-Dist: pytest; extra == "test"
|
|
14
15
|
Provides-Extra: test-advanced
|
|
@@ -21,7 +22,7 @@ Dynamic: license-file
|
|
|
21
22
|
# Anyio-Executor
|
|
22
23
|
Inspired by the newer [aiolibs-executor](https://github.com/aio-libs/aiolibs-executor) library
|
|
23
24
|
this library provides a asynchronous executor for anyio and has compatability
|
|
24
|
-
with 3.
|
|
25
|
+
with 3.11 and newer versions of python.
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
## Installation
|
|
@@ -47,6 +48,7 @@ async def fn(i: int):
|
|
|
47
48
|
print(f"{i} done!")
|
|
48
49
|
return i
|
|
49
50
|
|
|
51
|
+
|
|
50
52
|
async def main():
|
|
51
53
|
async with Executor(10) as e:
|
|
52
54
|
items = await e.map(fn, [1, 2, 1, 2, 3])
|
|
@@ -57,6 +59,7 @@ async def main():
|
|
|
57
59
|
async for i in e.map(fn, [1, 2, 1, 2, 3]):
|
|
58
60
|
print(i)
|
|
59
61
|
|
|
62
|
+
|
|
60
63
|
if __name__ == "__main__":
|
|
61
64
|
anyio.run(main)
|
|
62
65
|
```
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
1
4
|
import anyio
|
|
2
5
|
import pytest
|
|
3
6
|
|
|
@@ -16,17 +19,22 @@ class BaseExecutorTest:
|
|
|
16
19
|
def executor(self) -> Executor:
|
|
17
20
|
return Executor()
|
|
18
21
|
|
|
22
|
+
def executor_with_handler(
|
|
23
|
+
self, handler:Callable[[BaseException], None]
|
|
24
|
+
) -> Executor:
|
|
25
|
+
return Executor(exc_handler=handler)
|
|
26
|
+
|
|
19
27
|
|
|
20
28
|
class TestInvalidStates(BaseExecutorTest):
|
|
21
29
|
@pytest.mark.anyio
|
|
22
30
|
async def test_invalid_worker_amount(self):
|
|
23
|
-
with pytest.raises(ValueError, match="num_wokers must be a positive integer"):
|
|
31
|
+
with pytest.raises(ValueError, match=r"num_wokers must be a positive integer"):
|
|
24
32
|
_ = Executor(-69)
|
|
25
33
|
|
|
26
34
|
@pytest.mark.anyio
|
|
27
35
|
async def test_bad_map_before_entrance(self):
|
|
28
36
|
e = self.executor()
|
|
29
|
-
with pytest.raises(RuntimeError, match="Executor already closed."):
|
|
37
|
+
with pytest.raises(RuntimeError, match=r"Executor already closed."):
|
|
30
38
|
await e.map(noop, [1, 2, 3])
|
|
31
39
|
|
|
32
40
|
@pytest.mark.anyio
|
|
@@ -37,14 +45,14 @@ class TestInvalidStates(BaseExecutorTest):
|
|
|
37
45
|
for i in range(3):
|
|
38
46
|
yield i
|
|
39
47
|
|
|
40
|
-
with pytest.raises(RuntimeError, match="Executor already closed."):
|
|
48
|
+
with pytest.raises(RuntimeError, match=r"Executor already closed."):
|
|
41
49
|
await e.amap(noop, fake_aiter())
|
|
42
50
|
|
|
43
51
|
@pytest.mark.anyio
|
|
44
52
|
async def test_bad_map_after_shutdown(self):
|
|
45
53
|
async with self.executor() as e:
|
|
46
54
|
await e.shutdown()
|
|
47
|
-
with pytest.raises(RuntimeError, match="Executor already closed."):
|
|
55
|
+
with pytest.raises(RuntimeError, match=r"Executor already closed."):
|
|
48
56
|
await e.map(noop, [1, 2, 3])
|
|
49
57
|
|
|
50
58
|
@pytest.mark.anyio
|
|
@@ -56,9 +64,10 @@ class TestInvalidStates(BaseExecutorTest):
|
|
|
56
64
|
for i in range(3):
|
|
57
65
|
yield i
|
|
58
66
|
|
|
59
|
-
with pytest.raises(RuntimeError, match="Executor already closed."):
|
|
67
|
+
with pytest.raises(RuntimeError, match=r"Executor already closed."):
|
|
60
68
|
await e.amap(noop, fake_aiter())
|
|
61
69
|
|
|
70
|
+
@pytest.mark.anyio
|
|
62
71
|
|
|
63
72
|
class TestExecutor(BaseExecutorTest):
|
|
64
73
|
async def wait(self, item: int) -> int:
|
|
@@ -70,7 +79,7 @@ class TestExecutor(BaseExecutorTest):
|
|
|
70
79
|
async with self.executor() as e:
|
|
71
80
|
items = [i for i in range(1, 5)]
|
|
72
81
|
i = await e.map(self.wait, items)
|
|
73
|
-
# it doesn't matter what order they come back
|
|
82
|
+
# it doesn't matter what order they come back
|
|
74
83
|
# in (as long as they all come back of course)
|
|
75
84
|
assert set(i) == {1, 2, 3, 4}
|
|
76
85
|
|
|
@@ -83,8 +92,8 @@ class TestExecutor(BaseExecutorTest):
|
|
|
83
92
|
|
|
84
93
|
# NOTE: Sometimes trio will attempt to execute things
|
|
85
94
|
# differently example: [4, 1, 2, 3]
|
|
86
|
-
# as long as they all come return back to us then it did
|
|
87
|
-
# it's job.
|
|
95
|
+
# as long as they all come return back to us then it did
|
|
96
|
+
# it's job.
|
|
88
97
|
@pytest.mark.anyio
|
|
89
98
|
async def test_amap_await(self):
|
|
90
99
|
async def ait():
|
|
@@ -104,3 +113,26 @@ class TestExecutor(BaseExecutorTest):
|
|
|
104
113
|
async with self.executor() as e:
|
|
105
114
|
async for i in e.amap(fast_ret, ait()):
|
|
106
115
|
assert i in [1, 2, 3, 4]
|
|
116
|
+
|
|
117
|
+
@pytest.mark.anyio
|
|
118
|
+
async def test_exception_handler(self):
|
|
119
|
+
callback = mock.Mock()
|
|
120
|
+
|
|
121
|
+
class TornadoError(Exception):
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
def on_exception(exc: Exception):
|
|
125
|
+
assert isinstance(exc, TornadoError)
|
|
126
|
+
callback(exc)
|
|
127
|
+
|
|
128
|
+
async def func(i: int):
|
|
129
|
+
raise TornadoError("Tornado approching...")
|
|
130
|
+
|
|
131
|
+
async with self.executor_with_handler(on_exception) as e:
|
|
132
|
+
ret = await e.map(func, (1,))
|
|
133
|
+
assert not ret
|
|
134
|
+
assert callback.called
|
|
135
|
+
assert callback.call_args.args
|
|
136
|
+
assert isinstance(callback.call_args.args[0], TornadoError)
|
|
137
|
+
|
|
138
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anyio_executor-0.1.1 → anyio_executor-0.2.0}/src/anyio_executor.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|