Copy async_lru for Python 3.7 compatibility
This commit is contained in:
parent
2b7188b1b8
commit
18c6ba62bb
15 changed files with 1321 additions and 0 deletions
0
tests/external/__init__.py
vendored
Normal file
0
tests/external/__init__.py
vendored
Normal file
47
tests/external/conftest.py
vendored
Normal file
47
tests/external/conftest.py
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import asyncio
|
||||
import gc
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import _CacheInfo
|
||||
|
||||
asyncio.set_event_loop(None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def event_loop(request):
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.set_debug(bool(os.environ.get("PYTHONASYNCIODEBUG")))
|
||||
|
||||
yield loop
|
||||
|
||||
loop.call_soon(loop.stop)
|
||||
loop.run_forever()
|
||||
loop.close()
|
||||
|
||||
gc.collect()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loop(event_loop, request):
|
||||
asyncio.set_event_loop(None)
|
||||
request.addfinalizer(lambda: asyncio.set_event_loop(None))
|
||||
|
||||
return event_loop
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def check_lru(request):
|
||||
def _check_lru(wrapped, *, hits, misses, cache, tasks, maxsize=128):
|
||||
assert wrapped.hits == hits
|
||||
assert wrapped.misses == misses
|
||||
assert len(wrapped._cache) == cache
|
||||
assert len(wrapped.tasks) == tasks
|
||||
assert wrapped.cache_info() == _CacheInfo(
|
||||
hits=hits,
|
||||
misses=misses,
|
||||
maxsize=maxsize,
|
||||
currsize=cache,
|
||||
)
|
||||
|
||||
return _check_lru
|
||||
187
tests/external/test_basic.py
vendored
Normal file
187
tests/external/test_basic.py
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import asyncio
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
alru_cache_attrs = [
|
||||
"hits",
|
||||
"misses",
|
||||
"tasks",
|
||||
"closed",
|
||||
"cache_info",
|
||||
"cache_clear",
|
||||
"invalidate",
|
||||
"close",
|
||||
"open",
|
||||
]
|
||||
|
||||
alru_cache_calable_attrs = alru_cache_attrs.copy()
|
||||
for attr in ["hits", "misses", "tasks", "closed"]:
|
||||
alru_cache_calable_attrs.remove(attr)
|
||||
|
||||
|
||||
def test_alru_cache_not_callable(loop):
|
||||
with pytest.raises(NotImplementedError):
|
||||
alru_cache("foo")
|
||||
|
||||
|
||||
def test_alru_cache_not_coroutine(loop):
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
@alru_cache
|
||||
def not_coro(val):
|
||||
return val
|
||||
|
||||
|
||||
def test_alru_cache_deco(loop, check_lru):
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
@alru_cache
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
assert asyncio.iscoroutinefunction(coro)
|
||||
|
||||
for attr in alru_cache_attrs:
|
||||
assert hasattr(coro, attr)
|
||||
for attr in alru_cache_calable_attrs:
|
||||
assert callable(getattr(coro, attr))
|
||||
|
||||
assert isinstance(coro._cache, dict)
|
||||
assert isinstance(coro.tasks, set)
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
awaitable = coro()
|
||||
assert asyncio.iscoroutine(awaitable)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
|
||||
def test_alru_cache_deco_called(check_lru, loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
@alru_cache()
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
assert asyncio.iscoroutinefunction(coro)
|
||||
|
||||
for attr in alru_cache_attrs:
|
||||
assert hasattr(coro, attr)
|
||||
for attr in alru_cache_calable_attrs:
|
||||
assert callable(getattr(coro, attr))
|
||||
|
||||
assert isinstance(coro._cache, dict)
|
||||
assert isinstance(coro.tasks, set)
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
awaitable = coro()
|
||||
assert asyncio.iscoroutine(awaitable)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
|
||||
def test_alru_cache_fn_called(check_lru, loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
coro_wrapped = alru_cache(coro)
|
||||
|
||||
assert asyncio.iscoroutinefunction(coro_wrapped)
|
||||
|
||||
for attr in alru_cache_attrs:
|
||||
assert hasattr(coro_wrapped, attr)
|
||||
for attr in alru_cache_calable_attrs:
|
||||
assert callable(getattr(coro_wrapped, attr))
|
||||
|
||||
assert isinstance(coro_wrapped._cache, dict)
|
||||
assert isinstance(coro_wrapped.tasks, set)
|
||||
check_lru(coro_wrapped, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
awaitable = coro_wrapped()
|
||||
assert asyncio.iscoroutine(awaitable)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
|
||||
def test_alru_cache_origin(loop):
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
async def coro():
|
||||
pass
|
||||
|
||||
coro_wrapped = alru_cache(coro)
|
||||
|
||||
assert coro_wrapped._origin is coro
|
||||
|
||||
coro_wrapped = alru_cache(partial(coro))
|
||||
|
||||
assert coro_wrapped._origin is coro
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alru_cache_await_same_result_async(check_lru, loop):
|
||||
calls = 0
|
||||
val = object()
|
||||
|
||||
@alru_cache()
|
||||
async def coro():
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
|
||||
return val
|
||||
|
||||
coros = [coro() for _ in range(100)]
|
||||
ret = await asyncio.gather(*coros)
|
||||
expected = [val] * 100
|
||||
assert ret == expected
|
||||
check_lru(coro, hits=99, misses=1, cache=1, tasks=0)
|
||||
|
||||
assert calls == 1
|
||||
assert await coro() is val
|
||||
check_lru(coro, hits=100, misses=1, cache=1, tasks=0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alru_cache_await_same_result_coroutine(check_lru, loop):
|
||||
calls = 0
|
||||
val = object()
|
||||
|
||||
@alru_cache()
|
||||
async def coro():
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
|
||||
return val
|
||||
|
||||
coros = [coro() for _ in range(100)]
|
||||
ret = await asyncio.gather(*coros)
|
||||
expected = [val] * 100
|
||||
assert ret == expected
|
||||
check_lru(coro, hits=99, misses=1, cache=1, tasks=0)
|
||||
|
||||
assert calls == 1
|
||||
assert await coro() is val
|
||||
check_lru(coro, hits=100, misses=1, cache=1, tasks=0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_alru_cache_dict_not_shared(check_lru, loop):
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
coro1 = alru_cache()(coro)
|
||||
coro2 = alru_cache()(coro)
|
||||
|
||||
ret1 = await coro1(1)
|
||||
check_lru(coro1, hits=0, misses=1, cache=1, tasks=0)
|
||||
|
||||
ret2 = await coro2(1)
|
||||
check_lru(coro2, hits=0, misses=1, cache=1, tasks=0)
|
||||
|
||||
assert ret1 == ret2
|
||||
|
||||
assert coro1._cache[1].result() == coro2._cache[1].result()
|
||||
assert coro1._cache != coro2._cache
|
||||
assert coro1._cache.keys() == coro2._cache.keys()
|
||||
assert coro1._cache is not coro2._cache
|
||||
22
tests/external/test_cache_clear.py
vendored
Normal file
22
tests/external/test_cache_clear.py
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_cache_clear(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3]
|
||||
coros = [coro(v) for v in inputs]
|
||||
ret = await asyncio.gather(*coros)
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=0, misses=3, cache=3, tasks=0)
|
||||
|
||||
coro.cache_clear()
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
38
tests/external/test_cache_info.py
vendored
Normal file
38
tests/external/test_cache_info.py
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_cache_info(check_lru, loop):
|
||||
@alru_cache(maxsize=4)
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3]
|
||||
coros = [coro(v) for v in inputs]
|
||||
ret = await asyncio.gather(*coros)
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=0, misses=3, cache=3, tasks=0, maxsize=4)
|
||||
|
||||
coro.cache_clear()
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0, maxsize=4)
|
||||
|
||||
inputs = [1, 1, 1]
|
||||
coros = [coro(v) for v in inputs]
|
||||
ret = await asyncio.gather(*coros)
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=2, misses=1, cache=1, tasks=0, maxsize=4)
|
||||
|
||||
coro.cache_clear()
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0, maxsize=4)
|
||||
|
||||
inputs = [1, 2, 3, 4] * 2
|
||||
coros = [coro(v) for v in inputs]
|
||||
ret = await asyncio.gather(*coros)
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=4, misses=4, cache=4, tasks=0, maxsize=4)
|
||||
85
tests/external/test_cache_invalidate.py
vendored
Normal file
85
tests/external/test_cache_invalidate.py
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_cache_invalidate(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3]
|
||||
|
||||
coro.invalidate(1)
|
||||
coro.invalidate(2)
|
||||
coro.invalidate(3)
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=0, misses=3, cache=3, tasks=0)
|
||||
|
||||
coro.invalidate(1)
|
||||
check_lru(coro, hits=0, misses=3, cache=2, tasks=0)
|
||||
coro.invalidate(2)
|
||||
check_lru(coro, hits=0, misses=3, cache=1, tasks=0)
|
||||
coro.invalidate(3)
|
||||
check_lru(coro, hits=0, misses=3, cache=0, tasks=0)
|
||||
|
||||
inputs = [1, 2, 3]
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
assert ret == inputs
|
||||
check_lru(coro, hits=0, misses=6, cache=3, tasks=0)
|
||||
|
||||
|
||||
async def test_cache_invalidate_multiple_args(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(*args):
|
||||
return len(args)
|
||||
|
||||
for i, size in enumerate(range(10)):
|
||||
args = tuple(range(size))
|
||||
ret = await coro(*args)
|
||||
assert ret == size
|
||||
check_lru(coro, hits=0, misses=i + 1, cache=1, tasks=0)
|
||||
coro.invalidate(*args)
|
||||
check_lru(coro, hits=0, misses=i + 1, cache=0, tasks=0)
|
||||
|
||||
for size in range(10):
|
||||
args = tuple(range(size))
|
||||
ret = await coro(*args)
|
||||
assert ret == size
|
||||
check_lru(coro, hits=0, misses=20, cache=10, tasks=0)
|
||||
|
||||
|
||||
async def test_cache_invalidate_multiple_args_different_order(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(*args):
|
||||
return len(args)
|
||||
|
||||
for i, size in enumerate(range(2, 10)):
|
||||
args = tuple(range(size))
|
||||
rev_args = tuple(reversed(args))
|
||||
ret = await coro(*args)
|
||||
assert ret == size
|
||||
check_lru(coro, hits=0, misses=2 * i + 1, cache=i + 1, tasks=0)
|
||||
ret = await coro(*rev_args)
|
||||
# The reversed args should be a miss
|
||||
check_lru(coro, hits=0, misses=2 * i + 2, cache=i + 2, tasks=0)
|
||||
coro.invalidate(*rev_args)
|
||||
# The reversed args should be invalidated
|
||||
check_lru(coro, hits=0, misses=2 * i + 2, cache=i + 1, tasks=0)
|
||||
|
||||
for i, size in enumerate(range(2, 10)):
|
||||
args = tuple(range(size))
|
||||
ret = await coro(*args)
|
||||
assert ret == size
|
||||
check_lru(coro, hits=i + 1, misses=16, cache=8, tasks=0)
|
||||
170
tests/external/test_close.py
vendored
Normal file
170
tests/external/test_close.py
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_cache_close(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
return val
|
||||
|
||||
assert not coro.closed
|
||||
|
||||
inputs = [1, 2, 3, 4, 5]
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
gather = asyncio.gather(*coros)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
close = coro.close()
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
await coro(1)
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
ret_close = await close
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
ret_gather = await gather
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
assert set(ret_close) == set(ret_gather) == set(inputs)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
coro.close()
|
||||
|
||||
|
||||
async def test_cache_close_cancel_return_exceptions(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3, 4, 5]
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
gather = asyncio.gather(*coros)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
close = coro.close(cancel=True)
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
ret_close = await close
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
for err in ret_close:
|
||||
assert isinstance(err, asyncio.CancelledError)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gather
|
||||
|
||||
|
||||
async def test_cache_close_cancel_not_return_exceptions(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3, 4, 5]
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
gather = asyncio.gather(*coros)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
close = coro.close(cancel=True, return_exceptions=False)
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await close
|
||||
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await gather
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
|
||||
async def test_cache_close_return_exceptions(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
raise ZeroDivisionError
|
||||
|
||||
inputs = [1, 2, 3, 4, 5]
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
gather = asyncio.gather(*coros)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
close = coro.close()
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await gather
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=0)
|
||||
|
||||
ret_close = await close
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
for err in ret_close:
|
||||
assert isinstance(err, ZeroDivisionError)
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
|
||||
async def test_cache_close_not_return_exceptions(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
raise ZeroDivisionError
|
||||
|
||||
inputs = [1, 2, 3, 4, 5]
|
||||
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
gather = asyncio.gather(*coros, return_exceptions=True)
|
||||
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
close = coro.close(return_exceptions=False)
|
||||
|
||||
check_lru(coro, hits=0, misses=5, cache=5, tasks=5)
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await close
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
ret_gather = await gather
|
||||
|
||||
for err in ret_gather:
|
||||
assert isinstance(err, ZeroDivisionError)
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
48
tests/external/test_exception.py
vendored
Normal file
48
tests/external/test_exception.py
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_alru_cache_exception(check_lru, loop):
|
||||
@alru_cache(cache_exceptions=True)
|
||||
async def coro(val):
|
||||
1 / 0
|
||||
|
||||
inputs = [1, 1, 1]
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros, return_exceptions=True)
|
||||
|
||||
check_lru(coro, hits=2, misses=1, cache=1, tasks=0)
|
||||
|
||||
for item in ret:
|
||||
assert isinstance(item, ZeroDivisionError)
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await coro(1)
|
||||
|
||||
check_lru(coro, hits=3, misses=1, cache=1, tasks=0)
|
||||
|
||||
|
||||
async def test_alru_not_cache_exception(check_lru, loop):
|
||||
@alru_cache(cache_exceptions=False)
|
||||
async def coro(val):
|
||||
1 / 0
|
||||
|
||||
inputs = [1, 1, 1]
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros, return_exceptions=True)
|
||||
|
||||
check_lru(coro, hits=2, misses=1, cache=1, tasks=0)
|
||||
|
||||
for item in ret:
|
||||
assert isinstance(item, ZeroDivisionError)
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await coro(1)
|
||||
|
||||
check_lru(coro, hits=2, misses=2, cache=1, tasks=0)
|
||||
366
tests/external/test_internals.py
vendored
Normal file
366
tests/external/test_internals.py
vendored
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
import asyncio
|
||||
from collections import OrderedDict
|
||||
from functools import _make_key, partial
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import (
|
||||
__cache_touch,
|
||||
_cache_clear,
|
||||
_cache_hit,
|
||||
_cache_info,
|
||||
_cache_invalidate,
|
||||
_cache_miss,
|
||||
_close,
|
||||
_close_waited,
|
||||
_done_callback,
|
||||
_open,
|
||||
_wait_closed,
|
||||
)
|
||||
|
||||
|
||||
class Wrapped:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_done_callback_cancelled(loop):
|
||||
task = loop.create_future()
|
||||
fut = loop.create_future()
|
||||
|
||||
task.add_done_callback(partial(_done_callback, fut))
|
||||
|
||||
task.cancel()
|
||||
|
||||
await asyncio.sleep(0)
|
||||
|
||||
assert fut.cancelled()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_done_callback_exception(loop):
|
||||
task = loop.create_future()
|
||||
fut = loop.create_future()
|
||||
|
||||
task.add_done_callback(partial(_done_callback, fut))
|
||||
|
||||
exc = ZeroDivisionError()
|
||||
|
||||
task.set_exception(exc)
|
||||
|
||||
await asyncio.sleep(0)
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await fut
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
fut.result()
|
||||
|
||||
assert fut.exception() is exc
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_done_callback(loop):
|
||||
task = loop.create_future()
|
||||
fut = loop.create_future()
|
||||
|
||||
task.add_done_callback(partial(_done_callback, fut))
|
||||
|
||||
task.set_result(1)
|
||||
|
||||
await asyncio.sleep(0)
|
||||
|
||||
assert fut.result() == 1
|
||||
|
||||
|
||||
def test_cache_invalidate_typed():
|
||||
wrapped = Wrapped()
|
||||
wrapped._cache = {}
|
||||
|
||||
args = (1,)
|
||||
kwargs = {"1": 1}
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, True, *args, **kwargs)
|
||||
|
||||
assert not from_cache
|
||||
|
||||
key = _make_key(args, kwargs, True)
|
||||
|
||||
wrapped._cache[key] = 0
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, True, *args, **kwargs)
|
||||
|
||||
assert from_cache
|
||||
|
||||
assert len(wrapped._cache) == 0
|
||||
|
||||
wrapped._cache[key] = 0
|
||||
|
||||
args = (1.0,)
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, True, *args, **kwargs)
|
||||
|
||||
assert not from_cache
|
||||
|
||||
wrapped._cache[key] = 1
|
||||
|
||||
|
||||
def test_cache_invalidate_not_typed():
|
||||
wrapped = Wrapped()
|
||||
wrapped._cache = {}
|
||||
|
||||
args = (1,)
|
||||
kwargs = {"1": 1}
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, False, *args, **kwargs)
|
||||
|
||||
assert not from_cache
|
||||
|
||||
key = _make_key(args, kwargs, False)
|
||||
|
||||
wrapped._cache[key] = 0
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, False, *args, **kwargs)
|
||||
|
||||
assert from_cache
|
||||
|
||||
assert len(wrapped._cache) == 0
|
||||
|
||||
wrapped._cache[key] = 0
|
||||
|
||||
args = (1.0,)
|
||||
|
||||
from_cache = _cache_invalidate(wrapped, False, *args, **kwargs)
|
||||
|
||||
assert from_cache
|
||||
|
||||
assert len(wrapped._cache) == 0
|
||||
|
||||
|
||||
def test_cache_clear():
|
||||
wrapped = Wrapped()
|
||||
|
||||
attrs = ["hits", "_cache", "tasks"]
|
||||
for attr in attrs:
|
||||
assert not hasattr(wrapped, attr)
|
||||
|
||||
_cache_clear(wrapped)
|
||||
|
||||
for attr in attrs:
|
||||
assert hasattr(wrapped, attr)
|
||||
|
||||
assert wrapped.hits == wrapped.misses == 0
|
||||
assert isinstance(wrapped._cache, dict)
|
||||
assert len(wrapped._cache) == 0
|
||||
assert isinstance(wrapped.tasks, set)
|
||||
assert len(wrapped.tasks) == 0
|
||||
|
||||
_cache = wrapped._cache
|
||||
tasks = wrapped.tasks
|
||||
|
||||
_cache_clear(wrapped)
|
||||
|
||||
assert wrapped._cache is not _cache
|
||||
assert wrapped.tasks is not tasks
|
||||
|
||||
|
||||
def test_open():
|
||||
wrapped = Wrapped()
|
||||
wrapped.hits = wrapped.misses = 1
|
||||
wrapped._cache = {}
|
||||
wrapped.tasks = set()
|
||||
wrapped.closed = True
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
_open(wrapped)
|
||||
|
||||
wrapped.hits = wrapped.misses = 0
|
||||
|
||||
_open(wrapped)
|
||||
|
||||
assert not wrapped.closed
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
_open(wrapped)
|
||||
|
||||
|
||||
def test_close(loop):
|
||||
wrapped = Wrapped()
|
||||
wrapped.closed = False
|
||||
wrapped.tasks = set()
|
||||
|
||||
awaitable = _close(wrapped, cancel=False, return_exceptions=True)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
assert wrapped.closed
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
_close(wrapped, cancel=False, return_exceptions=True)
|
||||
|
||||
fut = loop.create_future()
|
||||
wrapped.closed = False
|
||||
wrapped.tasks = {fut}
|
||||
|
||||
awaitable = _close(wrapped, cancel=True, return_exceptions=True)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
assert fut.cancelled()
|
||||
|
||||
fut = loop.create_future()
|
||||
fut.set_result(None)
|
||||
wrapped.closed = False
|
||||
wrapped.tasks = {fut}
|
||||
|
||||
awaitable = _close(wrapped, cancel=True, return_exceptions=True)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
assert not fut.cancelled()
|
||||
|
||||
fut = loop.create_future()
|
||||
fut.set_exception(ZeroDivisionError)
|
||||
wrapped.closed = False
|
||||
wrapped.tasks = {fut}
|
||||
|
||||
awaitable = _close(wrapped, cancel=True, return_exceptions=True)
|
||||
loop.run_until_complete(awaitable)
|
||||
|
||||
assert not fut.cancelled()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wait_closed(loop):
|
||||
wrapped = Wrapped()
|
||||
wrapped.tasks = set()
|
||||
|
||||
with mock.patch("great_ai.external.async_lru._close_waited") as mocked:
|
||||
ret = await _wait_closed(
|
||||
wrapped,
|
||||
return_exceptions=True,
|
||||
)
|
||||
assert ret == []
|
||||
assert mocked.called_once()
|
||||
|
||||
asyncio.set_event_loop(loop)
|
||||
with mock.patch("great_ai.external.async_lru._close_waited") as mocked:
|
||||
ret = await _wait_closed(
|
||||
wrapped,
|
||||
return_exceptions=True,
|
||||
)
|
||||
assert ret == []
|
||||
assert mocked.called_once()
|
||||
asyncio.set_event_loop(None)
|
||||
|
||||
fut = loop.create_future()
|
||||
fut.set_result(None)
|
||||
wrapped.tasks = {fut}
|
||||
with mock.patch("great_ai.external.async_lru._close_waited") as mocked:
|
||||
ret = await _wait_closed(
|
||||
wrapped,
|
||||
return_exceptions=True,
|
||||
)
|
||||
assert ret == [None]
|
||||
assert mocked.called_once()
|
||||
|
||||
exc = ZeroDivisionError()
|
||||
fut = loop.create_future()
|
||||
fut.set_exception(exc)
|
||||
wrapped.tasks = {fut}
|
||||
with mock.patch("great_ai.external.async_lru._close_waited") as mocked:
|
||||
ret = await _wait_closed(
|
||||
wrapped,
|
||||
return_exceptions=True,
|
||||
)
|
||||
assert ret == [exc]
|
||||
assert mocked.called_once()
|
||||
|
||||
fut = loop.create_future()
|
||||
fut.set_exception(ZeroDivisionError)
|
||||
wrapped.tasks = {fut}
|
||||
with mock.patch("great_ai.external.async_lru._close_waited") as mocked:
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
await _wait_closed(
|
||||
wrapped,
|
||||
return_exceptions=False,
|
||||
)
|
||||
assert mocked.called_once()
|
||||
|
||||
|
||||
def test_close_waited():
|
||||
wrapped = Wrapped()
|
||||
wrapped.cache_clear = partial(_cache_clear, wrapped)
|
||||
|
||||
with mock.patch("great_ai.external.async_lru._cache_clear") as mocked:
|
||||
_close_waited(wrapped, None)
|
||||
|
||||
assert mocked.called_once()
|
||||
|
||||
|
||||
def test_cache_info():
|
||||
wrapped = Wrapped()
|
||||
wrapped._cache = {}
|
||||
wrapped.hits = wrapped.misses = 0
|
||||
|
||||
assert (0, 0, 3, 0) == _cache_info(wrapped, 3)
|
||||
|
||||
wrapped._cache[1] = 1
|
||||
|
||||
assert (0, 0, 1, 1) == _cache_info(wrapped, 1)
|
||||
|
||||
wrapped.hits = 2
|
||||
wrapped.misses = 3
|
||||
wrapped._cache[2] = 2
|
||||
|
||||
assert (2, 3, 5, 2) == _cache_info(wrapped, 5)
|
||||
|
||||
|
||||
def test__cache_touch():
|
||||
wrapped = Wrapped()
|
||||
|
||||
wrapped._cache = OrderedDict()
|
||||
wrapped._cache[1] = 1
|
||||
wrapped._cache[2] = 2
|
||||
|
||||
__cache_touch(wrapped, 1)
|
||||
assert list(wrapped._cache) == [2, 1]
|
||||
|
||||
__cache_touch(wrapped, 2)
|
||||
assert list(wrapped._cache) == [1, 2]
|
||||
|
||||
# test KeyError
|
||||
__cache_touch(wrapped, 100)
|
||||
|
||||
|
||||
def test_cache_hit():
|
||||
wrapped = Wrapped()
|
||||
wrapped.hits = 1
|
||||
wrapped._cache = OrderedDict()
|
||||
wrapped._cache[1] = 1
|
||||
|
||||
with mock.patch("great_ai.external.async_lru.__cache_touch") as mocked:
|
||||
_cache_hit(wrapped, 1)
|
||||
|
||||
assert mocked.called_once()
|
||||
|
||||
assert wrapped.hits == 2
|
||||
|
||||
_cache_hit(wrapped, 1)
|
||||
|
||||
assert wrapped.hits == 3
|
||||
|
||||
|
||||
def test_cache_miss():
|
||||
wrapped = Wrapped()
|
||||
wrapped.misses = 1
|
||||
wrapped._cache = OrderedDict()
|
||||
wrapped._cache[1] = 1
|
||||
|
||||
with mock.patch("great_ai.external.async_lru.__cache_touch") as mocked:
|
||||
_cache_miss(wrapped, 1)
|
||||
|
||||
assert mocked.called_once()
|
||||
|
||||
assert wrapped.misses == 2
|
||||
|
||||
_cache_miss(wrapped, 1)
|
||||
|
||||
assert wrapped.misses == 3
|
||||
39
tests/external/test_open.py
vendored
Normal file
39
tests/external/test_open.py
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_alru_cache_open(check_lru, loop):
|
||||
@alru_cache()
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
await coro(1)
|
||||
|
||||
check_lru(coro, hits=0, misses=1, cache=1, tasks=0)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
coro.open()
|
||||
|
||||
close = coro.close()
|
||||
|
||||
assert coro.closed
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
await coro()
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
coro.open()
|
||||
|
||||
await close
|
||||
|
||||
check_lru(coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
coro.open()
|
||||
|
||||
ret = await coro(1)
|
||||
|
||||
assert ret == 1
|
||||
|
||||
check_lru(coro, hits=0, misses=1, cache=1, tasks=0)
|
||||
50
tests/external/test_partialmethod.py
vendored
Normal file
50
tests/external/test_partialmethod.py
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import asyncio
|
||||
from functools import partial, partialmethod
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_partialmethod_basic(check_lru, loop):
|
||||
class Obj:
|
||||
async def _coro(self, val):
|
||||
return val
|
||||
|
||||
coro = alru_cache(partialmethod(_coro, 2))
|
||||
|
||||
obj = Obj()
|
||||
|
||||
coros = [obj.coro() for _ in range(5)]
|
||||
|
||||
check_lru(obj.coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
check_lru(obj.coro, hits=4, misses=1, cache=1, tasks=0)
|
||||
|
||||
assert ret == [2, 2, 2, 2, 2]
|
||||
|
||||
|
||||
async def test_partialmethod_partial(check_lru, loop):
|
||||
class Obj:
|
||||
def __init__(self):
|
||||
self.coro = alru_cache(partial(self._coro, 2))
|
||||
|
||||
async def __coro(self, val1, val2):
|
||||
return val1 + val2
|
||||
|
||||
_coro = partialmethod(__coro, 1)
|
||||
|
||||
obj = Obj()
|
||||
|
||||
coros = [obj.coro() for _ in range(5)]
|
||||
|
||||
check_lru(obj.coro, hits=0, misses=0, cache=0, tasks=0)
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
check_lru(obj.coro, hits=4, misses=1, cache=1, tasks=0)
|
||||
|
||||
assert ret == [3, 3, 3, 3, 3]
|
||||
58
tests/external/test_size.py
vendored
Normal file
58
tests/external/test_size.py
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import asyncio
|
||||
|
||||
import pytest
|
||||
from great_ai.external.async_lru import _make_key, alru_cache
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_alru_cache_removing_lru_keys(check_lru, loop):
|
||||
@alru_cache(maxsize=3)
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
key5 = _make_key((5,), {}, False)
|
||||
key4 = _make_key((4,), {}, False)
|
||||
key3 = _make_key((3,), {}, False)
|
||||
key2 = _make_key((2,), {}, False)
|
||||
key1 = _make_key((1,), {}, False)
|
||||
|
||||
for i, v in enumerate([3, 4, 5]):
|
||||
await coro(v)
|
||||
check_lru(coro, hits=0, misses=i + 1, cache=i + 1, tasks=0, maxsize=3)
|
||||
|
||||
check_lru(coro, hits=0, misses=3, cache=3, tasks=0, maxsize=3)
|
||||
assert list(coro._cache) == [key3, key4, key5]
|
||||
|
||||
for v in [3, 2, 1]:
|
||||
await coro(v)
|
||||
check_lru(coro, hits=1, misses=5, cache=3, tasks=0, maxsize=3)
|
||||
assert list(coro._cache) == [key3, key2, key1]
|
||||
|
||||
|
||||
async def test_alru_cache_none_max_size(check_lru, loop):
|
||||
@alru_cache(maxsize=None)
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3, 4] * 2
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
check_lru(coro, hits=4, misses=4, cache=4, tasks=0, maxsize=None)
|
||||
assert ret == inputs
|
||||
|
||||
|
||||
async def test_alru_cache_zero_max_size(check_lru, loop):
|
||||
@alru_cache(maxsize=0)
|
||||
async def coro(val):
|
||||
return val
|
||||
|
||||
inputs = [1, 2, 3, 4] * 2
|
||||
coros = [coro(v) for v in inputs]
|
||||
|
||||
ret = await asyncio.gather(*coros)
|
||||
|
||||
check_lru(coro, hits=0, misses=8, cache=0, tasks=0, maxsize=0)
|
||||
assert ret == inputs
|
||||
19
tests/external/test_utils.py
vendored
Normal file
19
tests/external/test_utils.py
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from functools import partial
|
||||
|
||||
from great_ai.external.async_lru import unpartial
|
||||
|
||||
|
||||
def test_unpartial():
|
||||
def foo():
|
||||
pass
|
||||
|
||||
assert unpartial(foo) is foo
|
||||
|
||||
bar = partial(foo)
|
||||
|
||||
assert unpartial(bar) is foo
|
||||
|
||||
for _ in range(10):
|
||||
bar = partial(bar)
|
||||
|
||||
assert unpartial(bar) is foo
|
||||
Loading…
Add table
Add a link
Reference in a new issue