|
|
@@ -35,6 +35,8 @@ import unittest |
|
|
|
# XXX - this matches on both FreeBSD and Darwin |
|
|
|
KQ_EV_RECEIPT = 0x40 |
|
|
|
|
|
|
|
__all__ = [ 'watch_file', 'run_on_modify', ] |
|
|
|
|
|
|
|
class AsyncKqueue(object): |
|
|
|
def __init__(self): |
|
|
|
self._kq = select.kqueue() |
|
|
@@ -82,15 +84,43 @@ class AsyncKqueue(object): |
|
|
|
|
|
|
|
@contextlib.asynccontextmanager |
|
|
|
async def watch_file(self, fp): |
|
|
|
''' |
|
|
|
A context manager, for watching for modification of a file. |
|
|
|
|
|
|
|
Example usage: |
|
|
|
fp = open(fname) |
|
|
|
async with aiokq.watch_file(fp) as wf: |
|
|
|
while True: |
|
|
|
# do something with fp |
|
|
|
|
|
|
|
await wf() |
|
|
|
''' |
|
|
|
try: |
|
|
|
id, waitfun = self.addevent(fp, select.KQ_FILTER_VNODE, select.KQ_NOTE_EXTEND|select.KQ_NOTE_WRITE) |
|
|
|
yield waitfun |
|
|
|
finally: |
|
|
|
self.removeevent(id) |
|
|
|
|
|
|
|
async def run_on_modify(self, fp, fun, *args, **kwargs): |
|
|
|
''' |
|
|
|
The coroutine fun is run once at first, and then, when ever the |
|
|
|
fp is modified, rerun the fun. The first argument to fun will |
|
|
|
be the file like object. |
|
|
|
|
|
|
|
Currently, it is just fp, but in the future if fp is a file |
|
|
|
name, it is necessary to provide fun a different fp. |
|
|
|
''' |
|
|
|
|
|
|
|
async with self.watch_file(fp) as wf: |
|
|
|
while True: |
|
|
|
await fun(fp, *args, **kwargs) |
|
|
|
|
|
|
|
await wf() |
|
|
|
|
|
|
|
_globalkq = AsyncKqueue() |
|
|
|
|
|
|
|
watch_file = _globalkq.watch_file |
|
|
|
run_on_modify = _globalkq.run_on_modify |
|
|
|
|
|
|
|
# https://stackoverflow.com/questions/23033939/how-to-test-python-3-4-asyncio-code |
|
|
|
# Slightly modified to timeout and to print trace back when canceled. |
|
|
@@ -128,6 +158,40 @@ class Tests(unittest.TestCase): |
|
|
|
shutil.rmtree(self.basetempdir) |
|
|
|
self.tempdir = None |
|
|
|
|
|
|
|
@async_test |
|
|
|
async def test_runonmodify(self): |
|
|
|
loop = asyncio.get_event_loop() |
|
|
|
|
|
|
|
res = [] |
|
|
|
|
|
|
|
sem = asyncio.Semaphore(0) |
|
|
|
|
|
|
|
async def myfun(rfp, *args, **kwargs): |
|
|
|
rfp.seek(0) |
|
|
|
res.append((rfp, rfp.read(), args, kwargs)) |
|
|
|
sem.release() |
|
|
|
|
|
|
|
with open('samplefile.txt', 'w+') as fp, open('samplefile.txt', 'r') as rfp: |
|
|
|
loop.create_task(run_on_modify(rfp, myfun, 1, foo='bar')) |
|
|
|
|
|
|
|
def fpwrflush(data): |
|
|
|
fp.write(data) |
|
|
|
fp.flush() |
|
|
|
|
|
|
|
await sem.acquire() |
|
|
|
self.assertEqual(res.pop(), (rfp, '', (1,), { 'foo': 'bar' })) |
|
|
|
|
|
|
|
fpwrflush('foo') |
|
|
|
|
|
|
|
await sem.acquire() |
|
|
|
self.assertEqual(res.pop(), (rfp, 'foo', (1,), { 'foo': 'bar' })) |
|
|
|
|
|
|
|
fpwrflush('foobar') |
|
|
|
|
|
|
|
await sem.acquire() |
|
|
|
self.assertEqual(res.pop(), (rfp, 'foofoobar', (1,), { 'foo': 'bar' })) |
|
|
|
|
|
|
|
|
|
|
|
@async_test |
|
|
|
async def test_filemod(self): |
|
|
|
loop = asyncio.get_event_loop() |
|
|
|