diff --git a/poetry.lock b/poetry.lock index c68b20e..304062d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "asyncgui" -version = "0.6.3" +version = "0.7.0" description = "A thin layer that helps to wrap a callback-style API in an async/await-style API" optional = false -python-versions = "<4.0.0,>=3.8.1" +python-versions = "<4.0,>=3.9" files = [ - {file = "asyncgui-0.6.3-py3-none-any.whl", hash = "sha256:46723e65d8bf023e28d141f6a9d38634ef5b97a5236fafdaf80b47e13275a5eb"}, - {file = "asyncgui-0.6.3.tar.gz", hash = "sha256:05de49c2221128d3530f010df98491f3553183ec8909d205a0c0250bee5d6e0c"}, + {file = "asyncgui-0.7.0-py3-none-any.whl", hash = "sha256:c1e53b5c0c4df8817b28aa58bda3bc13752ea65519b14c2d7fa1c1721c9bb362"}, + {file = "asyncgui-0.7.0.tar.gz", hash = "sha256:6a5cfdfee34f6478e857a55ff88981995479930c3ddc577c5ff9d85a79f12a4a"}, ] [package.dependencies] @@ -677,4 +677,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "7413bee58275c87c12ec7887c5a5130c9ccb8db0a4f895a2e0b2580414495779" +content-hash = "f8a212b5e103cbf976fbe4373e0dfd7320d43111cf9f6c469f4162681833b33c" diff --git a/pyproject.toml b/pyproject.toml index 57f9a58..ccf4bb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ packages = [ [tool.poetry.dependencies] python = "^3.10" -asyncgui = "^0.6" +asyncgui = "~0.7" [tool.poetry.group.dev.dependencies] pytest = "^8.0" diff --git a/sphinx/box.rst b/sphinx/box.rst deleted file mode 100644 index eb2f623..0000000 --- a/sphinx/box.rst +++ /dev/null @@ -1,8 +0,0 @@ -=== -Box -=== - -.. automodule:: asyncgui_ext.synctools.box - :members: - :undoc-members: - :exclude-members: diff --git a/sphinx/event.rst b/sphinx/event.rst deleted file mode 100644 index 9d1f9b1..0000000 --- a/sphinx/event.rst +++ /dev/null @@ -1,8 +0,0 @@ -===== -Event -===== - -.. automodule:: asyncgui_ext.synctools.event - :members: - :undoc-members: - :exclude-members: diff --git a/sphinx/index.rst b/sphinx/index.rst index 35d1329..c296887 100644 --- a/sphinx/index.rst +++ b/sphinx/index.rst @@ -7,6 +7,4 @@ Inter-task sychronization and communication. .. toctree:: :hidden: - box - event queue diff --git a/src/asyncgui_ext/synctools/all.py b/src/asyncgui_ext/synctools/all.py index 3659064..935aa85 100644 --- a/src/asyncgui_ext/synctools/all.py +++ b/src/asyncgui_ext/synctools/all.py @@ -1,6 +1,4 @@ __all__ = ( - 'Event', 'Box', 'Queue', + 'Queue', ) -from .event import Event -from .box import Box from .queue import Queue diff --git a/src/asyncgui_ext/synctools/box.py b/src/asyncgui_ext/synctools/box.py deleted file mode 100644 index 4841b1a..0000000 --- a/src/asyncgui_ext/synctools/box.py +++ /dev/null @@ -1,74 +0,0 @@ -''' -.. code-block:: - - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - - async def async_fn1(box): - for __ in range(10): - args, kwargs = await box.get() - assert args == (1, ) - assert kwargs == {'crow': 'raven', } - - async def async_fn2(box): - for __ in range(10): - args, kwargs = await box.get() - assert args == (2, ) - assert kwargs == {'frog': 'toad', } - - box = Box() - box.put(1, crow='raven') - ag.start(async_fn1(box)) - box.update(2, frog='toad') - ag.start(async_fn2(box)) -''' - - -__all__ = ('Box', ) -import types - - -class Box: - __slots__ = ('_item', '_waiting_tasks', ) - - def __init__(self): - self._item = None - self._waiting_tasks = [] - - @property - def is_empty(self) -> bool: - return self._item is None - - def put(self, *args, **kwargs): - '''Put an item into the box if it's empty.''' - if self._item is None: - self.put_or_update(*args, **kwargs) - - def update(self, *args, **kwargs): - '''Replace the item in the box if there is one already.''' - if self._item is not None: - self.put_or_update(*args, **kwargs) - - def put_or_update(self, *args, **kwargs): - self._item = (args, kwargs, ) - tasks = self._waiting_tasks - self._waiting_tasks = [] - for t in tasks: - if t is not None: - t._step(*args, **kwargs) - - def clear(self): - '''Remove the item from the box if there is one.''' - self._item = None - - @types.coroutine - def get(self): - '''Get the item from the box if there is one. Otherwise, wait until it's put.''' - if self._item is not None: - return self._item - tasks = self._waiting_tasks - idx = len(tasks) - try: - return (yield tasks.append) - finally: - tasks[idx] = None diff --git a/src/asyncgui_ext/synctools/event.py b/src/asyncgui_ext/synctools/event.py deleted file mode 100644 index 37ace1d..0000000 --- a/src/asyncgui_ext/synctools/event.py +++ /dev/null @@ -1,52 +0,0 @@ -''' -.. code-block:: - - import asyncgui as ag - from asyncgui_ext.synctools.event import Event - - async def async_fn1(e): - args, kwargs = await e.wait() - assert args == (1, ) - assert kwargs == {'crow': 'raven', } - - args, kwargs = await e.wait() - assert args == (2, ) - assert kwargs == {'toad': 'frog', } - - async def async_fn2(e): - args, kwargs = await e.wait() - assert args == (2, ) - assert kwargs == {'toad': 'frog', } - - e = Event() - ag.start(async_fn1(e)) - e.fire(1, crow='raven') - ag.start(async_fn2(e)) - e.fire(2, toad='frog') -''' - -__all__ = ('Event', ) -import types - - -class Event: - __slots__ = ('_waiting_tasks', ) - - def __init__(self): - self._waiting_tasks = [] - - def fire(self, *args, **kwargs): - tasks = self._waiting_tasks - self._waiting_tasks = [] - for t in tasks: - if t is not None: - t._step(*args, **kwargs) - - @types.coroutine - def wait(self): - tasks = self._waiting_tasks - idx = len(tasks) - try: - return (yield tasks.append) - finally: - tasks[idx] = None diff --git a/src/asyncgui_ext/synctools/queue.py b/src/asyncgui_ext/synctools/queue.py index afab13a..d1ccecc 100644 --- a/src/asyncgui_ext/synctools/queue.py +++ b/src/asyncgui_ext/synctools/queue.py @@ -74,7 +74,7 @@ async def async_fn2(q, consumed): from functools import partial from collections import deque -from asyncgui import AsyncEvent +from asyncgui import ExclusiveEvent class QueueState(enum.Enum): @@ -138,8 +138,8 @@ def __init__(self, *, capacity: int | None=None, order: Order='fifo'): raise ValueError(f"'capacity' must be either a positive integer or None. (was {capacity!r})") self._init_container(capacity, order) self._state = QueueState.OPENED - self._putters = deque[tuple[AsyncEvent, Item]]() - self._getters = deque[AsyncEvent]() + self._putters = deque[tuple[ExclusiveEvent, Item]]() + self._getters = deque[ExclusiveEvent]() self._capacity = capacity self._order = order self._is_transferring = False @@ -200,7 +200,7 @@ async def get(self) -> T.Awaitable[Item]: raise Closed if self._is_transferring or self.is_empty: - event = AsyncEvent() + event = ExclusiveEvent() self._getters.append(event) exc, item = (await event.wait())[0] if exc is not None: @@ -240,7 +240,7 @@ async def put(self, item) -> T.Awaitable: raise Closed if self._is_transferring or self.is_full: - event = AsyncEvent() + event = ExclusiveEvent() self._putters.append((event, item, )) exc = (await event.wait())[0][0] if exc is not None: diff --git a/tests/test_box.py b/tests/test_box.py deleted file mode 100644 index 71d46ed..0000000 --- a/tests/test_box.py +++ /dev/null @@ -1,102 +0,0 @@ -import pytest - - -def test_get_then_put(): - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - TS = ag.TaskState - b = Box() - t1 = ag.start(b.get()) - t2 = ag.start(b.get()) - assert t1.state is TS.STARTED - assert t2.state is TS.STARTED - b.put(7, crow='raven') - assert t1.result == ((7, ), {'crow': 'raven', }) - assert t2.result == ((7, ), {'crow': 'raven', }) - - -def test_put_then_get(): - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - TS = ag.TaskState - b = Box() - b.put(7, crow='raven') - t1 = ag.start(b.get()) - t2 = ag.start(b.get()) - assert t1.state is TS.FINISHED - assert t2.state is TS.FINISHED - assert t1.result == ((7, ), {'crow': 'raven', }) - assert t2.result == ((7, ), {'crow': 'raven', }) - - -def test_clear(): - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - b1 = Box() - b2 = Box() - - async def async_fn(): - assert (await b1.get()) == ((7, ), {'crow': 'raven', }) - assert (await b2.get()) == ((6, ), {'crocodile': 'alligator', }) - assert (await b1.get()) == ((5, ), {'toad': 'frog', }) - - task = ag.start(async_fn()) - b1.put(7, crow='raven') - b1.clear() - b2.put(6, crocodile='alligator') - b1.put(5, toad='frog') - assert task.finished - - -def test_cancel(): - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - TS = ag.TaskState - - async def async_fn(ctx, b): - async with ag.open_cancel_scope() as scope: - ctx['scope'] = scope - await b.get() - pytest.fail() - await ag.sleep_forever() - - ctx = {} - b = Box() - task = ag.start(async_fn(ctx, b)) - assert task.state is TS.STARTED - ctx['scope'].cancel() - assert task.state is TS.STARTED - b.put() - assert task.state is TS.STARTED - task._step() - assert task.state is TS.FINISHED - - -def test_complicated_cancel(): - import asyncgui as ag - from asyncgui_ext.synctools.box import Box - TS = ag.TaskState - - async def async_fn_1(ctx, b): - await b.get() - ctx['scope'].cancel() - - async def async_fn_2(ctx, b): - async with ag.open_cancel_scope() as scope: - ctx['scope'] = scope - await b.get() - pytest.fail() - await ag.sleep_forever() - - ctx = {} - b = Box() - t1 = ag.start(async_fn_1(ctx, b)) - t2 = ag.start(async_fn_2(ctx, b)) - assert b._waiting_tasks == [t1, t2, ] - assert t2.state is TS.STARTED - b.put() - assert t1.state is TS.FINISHED - assert t2.state is TS.STARTED - assert b._waiting_tasks == [] - t2._step() - assert t2.state is TS.FINISHED diff --git a/tests/test_event.py b/tests/test_event.py deleted file mode 100644 index fa3e7f7..0000000 --- a/tests/test_event.py +++ /dev/null @@ -1,84 +0,0 @@ -import pytest - - -def test_wait_then_fire(): - import asyncgui as ag - from asyncgui_ext.synctools.event import Event - TS = ag.TaskState - e = Event() - t1 = ag.start(e.wait()) - t2 = ag.start(e.wait()) - assert t1.state is TS.STARTED - assert t2.state is TS.STARTED - e.fire(7, crow='raven') - assert t1.result == ((7, ), {'crow': 'raven', }) - assert t2.result == ((7, ), {'crow': 'raven', }) - - -def test_fire_then_wait_then_fire(): - import asyncgui as ag - from asyncgui_ext.synctools.event import Event - TS = ag.TaskState - e = Event() - e.fire(8, crocodile='alligator') - t1 = ag.start(e.wait()) - t2 = ag.start(e.wait()) - assert t1.state is TS.STARTED - assert t2.state is TS.STARTED - e.fire(7, crow='raven') - assert t1.result == ((7, ), {'crow': 'raven', }) - assert t2.result == ((7, ), {'crow': 'raven', }) - - -def test_cancel(): - import asyncgui as ag - from asyncgui_ext.synctools.event import Event - TS = ag.TaskState - - async def async_fn(ctx, e): - async with ag.open_cancel_scope() as scope: - ctx['scope'] = scope - await e.wait() - pytest.fail() - await ag.sleep_forever() - - ctx = {} - e = Event() - task = ag.start(async_fn(ctx, e)) - assert task.state is TS.STARTED - ctx['scope'].cancel() - assert task.state is TS.STARTED - e.fire() - assert task.state is TS.STARTED - task._step() - assert task.state is TS.FINISHED - - -def test_complicated_cancel(): - import asyncgui as ag - from asyncgui_ext.synctools.event import Event - TS = ag.TaskState - - async def async_fn_1(ctx, e): - assert (await e.wait()) == ((7, ), {'crow': 'raven', }) - ctx['scope'].cancel() - - async def async_fn_2(ctx, e): - async with ag.open_cancel_scope() as scope: - ctx['scope'] = scope - await e.wait() - pytest.fail() - await ag.sleep_forever() - - ctx = {} - e = Event() - t1 = ag.start(async_fn_1(ctx, e)) - t2 = ag.start(async_fn_2(ctx, e)) - assert e._waiting_tasks == [t1, t2, ] - assert t2.state is TS.STARTED - e.fire(7, crow='raven') - assert t1.state is TS.FINISHED - assert t2.state is TS.STARTED - assert e._waiting_tasks == [] - t2._step() - assert t2.result is None