- commit
- 62f05c12a608b69bda7bb5eff30a0be859df96a4
- parent
- 8ed7defd595a8e895c90830093d01959b19a03cc
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2026-02-26 18:42
add support for reading/writing files So far we can wait for timeouts. In a real system, we also want to be able to wait for files. So we replace the `Timeout` class by a more generic `Condition` class together with the `sleep()`, `read()` and `write()` helpers. We use the `selectors` module to wait for files to become available.
Diffstat
| M | tests.py | 8 | ++++---- |
| M | xiio.py | 50 | +++++++++++++++++++++++++++++++++++++++++--------- |
2 files changed, 45 insertions, 13 deletions
diff --git a/tests.py b/tests.py
@@ -20,7 +20,7 @@ class XiioTestCase(unittest.TestCase): 20 20 class TestRun(XiioTestCase): 21 21 def test_sleep(self): 22 22 async def foo():23 -1 await xiio.Timeout(0.1)-1 23 await xiio.sleep(0.1) 24 24 return 'Hello World' 25 25 26 26 with self.assert_duration(0.1): @@ -32,11 +32,11 @@ class TestRun(XiioTestCase): 32 32 33 33 async def foo(): 34 34 try:35 -1 await xiio.Timeout(0.1)-1 35 await xiio.sleep(0.1) 36 36 finally: 37 37 stack.append(1) 38 3839 -1 with mock.patch('time.sleep', side_effect=KeyboardInterrupt):-1 39 with mock.patch('xiio.Condition.select', side_effect=KeyboardInterrupt): 40 40 with self.assertRaises(KeyboardInterrupt): 41 41 xiio.run(foo()) 42 42 self.assertEqual(stack, [1]) @@ -46,7 +46,7 @@ class TestRun(XiioTestCase): 46 46 try: 47 47 raise ValueError 48 48 finally:49 -1 await xiio.Timeout(0.1)-1 49 await xiio.sleep(0.1) 50 50 51 51 with self.assertRaises(ValueError): 52 52 with self.assert_duration(0.1):
diff --git a/xiio.py b/xiio.py
@@ -1,27 +1,59 @@ -1 1 import math -1 2 import os -1 3 import selectors 1 4 import time -1 5 from selectors import EVENT_READ as READ -1 6 from selectors import EVENT_WRITE as WRITE 2 7 3 84 -1 class Timeout:5 -1 def __init__(self, seconds):6 -1 self.seconds = seconds-1 9 class Condition: -1 10 def __init__(self, *, files={}, time=math.inf): -1 11 self.files = files or {} -1 12 self.time = time 7 13 8 14 def __await__(self): 9 15 yield self 10 1611 -1 def wait(self):12 -1 time.sleep(self.seconds)-1 17 def select(self): -1 18 timeout = self.time - time.monotonic() -1 19 with selectors.DefaultSelector() as sel: -1 20 for fileno, events in self.files.items(): -1 21 sel.register(fileno, events) -1 22 sel.select(None if timeout == math.inf else timeout) -1 23 -1 24 -1 25 async def sleep(seconds): -1 26 await Condition(time=time.monotonic() + seconds) -1 27 -1 28 -1 29 async def read(file, size): -1 30 fileno = file if isinstance(file, int) else file.fileno() -1 31 await Condition(files={fileno: READ}) -1 32 return os.read(fileno, size) -1 33 -1 34 -1 35 async def write(file, data): -1 36 fileno = file if isinstance(file, int) else file.fileno() -1 37 await Condition(files={fileno: WRITE}) -1 38 return os.write(fileno, data) -1 39 -1 40 -1 41 async def writeall(file, data): -1 42 while data: -1 43 size = await write(file, data) -1 44 data = data[size:] 13 45 14 46 15 47 def run(coro): 16 48 gen = coro.__await__() 17 49 try:18 -1 timeout = next(gen)-1 50 condition = next(gen) 19 51 while True: 20 52 try:21 -1 timeout.wait()-1 53 condition.select() 22 54 except BaseException as e:23 -1 timeout = gen.throw(e)-1 55 condition = gen.throw(e) 24 56 else:25 -1 timeout = next(gen)-1 57 condition = next(gen) 26 58 except StopIteration as e: 27 59 return e.value