From c213746bee69855433c19640349060e91ed3dc81 Mon Sep 17 00:00:00 2001 From: Andrew Herrington Date: Tue, 7 May 2024 18:43:35 -0500 Subject: [PATCH 1/4] feat: NestedAsyncIO contextmanager This comes from https://github.com/erdewit/nest_asyncio/pull/88 implemented by @CharlieJiangXXX before NestedAsyncIO was archived. It should help with some issues where the patched eventloop causes problems with some other libraries, like Discord.py, that do some "odd" things with eventloops --- pydantic_aioredis/model.py | 7 +- pydantic_aioredis/utils.py | 307 +++++++++++++++++++++++++++++++++++++ test/test_nested_aio.py | 104 +++++++++++++ 3 files changed, 414 insertions(+), 4 deletions(-) create mode 100644 test/test_nested_aio.py diff --git a/pydantic_aioredis/model.py b/pydantic_aioredis/model.py index 1ff7d753..333be45e 100644 --- a/pydantic_aioredis/model.py +++ b/pydantic_aioredis/model.py @@ -10,9 +10,8 @@ from typing import Tuple from typing import Union -import nest_asyncio from pydantic_aioredis.abstract import _AbstractModel -from pydantic_aioredis.utils import bytes_to_string +from pydantic_aioredis.utils import bytes_to_string, NestedAsyncIO class Model(_AbstractModel): @@ -69,8 +68,8 @@ def __save_from_sync(self): # Use nest_asyncio so we can call the async save except RuntimeError: io_loop = asyncio.new_event_loop() - nest_asyncio.apply() - io_loop.run_until_complete(self.save()) + with NestedAsyncIO(): + io_loop.run_until_complete(self.save()) @asynccontextmanager async def update(self): diff --git a/pydantic_aioredis/utils.py b/pydantic_aioredis/utils.py index a9fc119e..8a5a740b 100644 --- a/pydantic_aioredis/utils.py +++ b/pydantic_aioredis/utils.py @@ -1,6 +1,313 @@ """Module containing common utilities""" +import asyncio +import asyncio.events as events +import os +import sys +import threading +from contextlib import contextmanager, suppress +from heapq import heappop + def bytes_to_string(data: bytes): """Converts data to string""" return str(data, "utf-8") if isinstance(data, bytes) else data + + +class NestedAsyncIO: + """Patch asyncio to allow nested event loops.""" + + __slots__ = [ + "_loop", + "orig_run", + "orig_tasks", + "orig_futures", + "orig_loop_attrs", + "policy_get_loop", + "orig_get_loops", + "orig_tc", + "patched", + ] + _instance = None + _initialized = False + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, loop=None): + if not self._initialized: + self._loop = loop + self.orig_run = None + self.orig_tasks = [] + self.orig_futures = [] + self.orig_loop_attrs = {} + self.policy_get_loop = None + self.orig_get_loops = {} + self.orig_tc = None + self.patched = False + self.__class__._initialized = True + + def __enter__(self): + self.apply(self._loop) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.revert() + + def apply(self, loop=None): + """Patch asyncio to make its event loop reentrant.""" + if not self.patched: + self.patch_asyncio() + self.patch_policy() + self.patch_tornado() + + loop = loop or asyncio.get_event_loop() + self.patch_loop(loop) + self.patched = True + + def revert(self): + if self.patched: + for loop in self.orig_loop_attrs: + self.unpatch_loop(loop) + self.unpatch_tornado() + self.unpatch_policy() + self.unpatch_asyncio() + self.patched = False + + def patch_asyncio(self): + """Patch asyncio module to use pure Python tasks and futures.""" + + def run(main, *, debug=False): + loop = asyncio.get_event_loop() + loop.set_debug(debug) + task = asyncio.ensure_future(main) + try: + return loop.run_until_complete(task) + finally: + if not task.done(): + task.cancel() + with suppress(asyncio.CancelledError): + loop.run_until_complete(task) + + def _get_event_loop(stacklevel=3): + return events._get_running_loop() or events.get_event_loop_policy().get_event_loop() + + # Use module level _current_tasks, all_tasks and patch run method. + if getattr(asyncio, "_nest_patched", False): + return + self.orig_tasks = [asyncio.Task, asyncio.tasks._CTask, asyncio.tasks.Task] + asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = asyncio.tasks._PyTask + self.orig_futures = [asyncio.Future, asyncio.futures._CFuture, asyncio.futures.Future] + asyncio.Future = asyncio.futures._CFuture = asyncio.futures.Future = asyncio.futures._PyFuture + if sys.version_info < (3, 7, 0): + asyncio.tasks._current_tasks = asyncio.tasks.Task._current_tasks + asyncio.all_tasks = asyncio.tasks.Task.all_tasks + elif sys.version_info >= (3, 9, 0): + self.orig_get_loops = { + "events__get_event_loop": events._get_event_loop, + "events_get_event_loop": events.get_event_loop, + "asyncio_get_event_loop": asyncio.get_event_loop, + } + events._get_event_loop = events.get_event_loop = asyncio.get_event_loop = _get_event_loop + self.orig_run = asyncio.run + asyncio.run = run + asyncio._nest_patched = True + + def unpatch_asyncio(self): + if self.orig_run: + asyncio.run = self.orig_run + asyncio._nest_patched = False + (asyncio.Task, asyncio.tasks._CTask, asyncio.tasks.Task) = self.orig_tasks + (asyncio.Future, asyncio.futures._CFuture, asyncio.futures.Future) = self.orig_futures + if sys.version_info >= (3, 9, 0): + for key, value in self.orig_get_loops.items(): + setattr(asyncio if key.startswith("asyncio") else events, key.split("_")[-1], value) + + def patch_policy(self): + """Patch the policy to always return a patched loop.""" + + def get_event_loop(this): + if this._local._loop is None: + loop = this.new_event_loop() + self.patch_loop(loop) + this.set_event_loop(loop) + return this._local._loop + + cls = events.get_event_loop_policy().__class__ + self.policy_get_loop = cls.get_event_loop + cls.get_event_loop = get_event_loop + + def unpatch_policy(self): + cls = events.get_event_loop_policy().__class__ + orig = self.policy_get_loop + if orig: + cls.get_event_loop = orig + + def patch_loop(self, loop): + """Patch loop to make it reentrant.""" + + def run_forever(this): + with manage_run(this), manage_asyncgens(this): + while True: + this._run_once() + if this._stopping: + break + this._stopping = False + + def run_until_complete(this, future): + with manage_run(this): + f = asyncio.ensure_future(future, loop=this) + if f is not future: + f._log_destroy_pending = False + while not f.done(): + this._run_once() + if this._stopping: + break + if not f.done(): + raise RuntimeError("Event loop stopped before Future completed.") + return f.result() + + def _run_once(this): + """ + Simplified re-implementation of asyncio's _run_once that + runs handles as they become ready. + """ + ready = this._ready + scheduled = this._scheduled + while scheduled and scheduled[0]._cancelled: + heappop(scheduled) + + timeout = ( + 0 + if ready or this._stopping + else min(max(scheduled[0]._when - this.time(), 0), 86400) + if scheduled + else None + ) + event_list = this._selector.select(timeout) + this._process_events(event_list) + + end_time = this.time() + this._clock_resolution + while scheduled and scheduled[0]._when < end_time: + handle = heappop(scheduled) + ready.append(handle) + + for _ in range(len(ready)): + if not ready: + break + handle = ready.popleft() + if not handle._cancelled: + # preempt the current task so that that checks in + # Task.__step do not raise + curr_task = curr_tasks.pop(this, None) + + try: + handle._run() + finally: + # restore the current task + if curr_task is not None: + curr_tasks[this] = curr_task + + handle = None + + @contextmanager + def manage_run(this): + """Set up the loop for running.""" + this._check_closed() + old_thread_id = this._thread_id + old_running_loop = events._get_running_loop() + try: + this._thread_id = threading.get_ident() + events._set_running_loop(this) + this._num_runs_pending += 1 + if this._is_proactorloop: + if this._self_reading_future is None: + this.call_soon(this._loop_self_reading) + yield + finally: + this._thread_id = old_thread_id + events._set_running_loop(old_running_loop) + this._num_runs_pending -= 1 + if this._is_proactorloop: + if this._num_runs_pending == 0 and this._self_reading_future is not None: + ov = this._self_reading_future._ov + this._self_reading_future.cancel() + if ov is not None: + this._proactor._unregister(ov) + this._self_reading_future = None + + @contextmanager + def manage_asyncgens(this): + if not hasattr(sys, "get_asyncgen_hooks"): + # Python version is too old. + return + old_agen_hooks = sys.get_asyncgen_hooks() + try: + this._set_coroutine_origin_tracking(this._debug) + if this._asyncgens is not None: + sys.set_asyncgen_hooks( + firstiter=this._asyncgen_firstiter_hook, finalizer=this._asyncgen_finalizer_hook + ) + yield + finally: + this._set_coroutine_origin_tracking(False) + if this._asyncgens is not None: + sys.set_asyncgen_hooks(*old_agen_hooks) + + def _check_running(this): + """Do not throw exception if loop is already running.""" + pass + + if getattr(loop, "_nest_patched", False): + return + if not isinstance(loop, asyncio.BaseEventLoop): + raise ValueError("Can't patch loop of type %s" % type(loop)) + cls = loop.__class__ + self.orig_loop_attrs[cls] = {} + self.orig_loop_attrs[cls]["run_forever"] = cls.run_forever + cls.run_forever = run_forever + self.orig_loop_attrs[cls]["run_until_complete"] = cls.run_until_complete + cls.run_until_complete = run_until_complete + self.orig_loop_attrs[cls]["_run_once"] = cls._run_once + cls._run_once = _run_once + self.orig_loop_attrs[cls]["_check_running"] = cls._check_running + cls._check_running = _check_running + self.orig_loop_attrs[cls]["_check_runnung"] = cls._check_running + cls._check_runnung = _check_running # typo in Python 3.7 source + cls._num_runs_pending = 1 if loop.is_running() else 0 + cls._is_proactorloop = os.name == "nt" and issubclass(cls, asyncio.ProactorEventLoop) + if sys.version_info < (3, 7, 0): + cls._set_coroutine_origin_tracking = cls._set_coroutine_wrapper + curr_tasks = asyncio.tasks._current_tasks if sys.version_info >= (3, 7, 0) else asyncio.Task._current_tasks + cls._nest_patched = True + + def unpatch_loop(self, loop): + loop._nest_patched = False + if self.orig_loop_attrs[loop]: + for key, value in self.orig_loop_attrs[loop].items(): + setattr(loop, key, value) + + for attr in ["_num_runs_pending", "_is_proactorloop"]: + if hasattr(loop, attr): + delattr(loop, attr) + + def patch_tornado(self): + """ + If tornado is imported before nest_asyncio, make tornado aware of + the pure-Python asyncio Future. + """ + if "tornado" in sys.modules: + import tornado.concurrent as tc # type: ignore + + self.orig_tc = tc.Future + tc.Future = asyncio.Future + if asyncio.Future not in tc.FUTURES: + tc.FUTURES += (asyncio.Future,) + + def unpatch_tornado(self): + if self.orig_tc: + import tornado.concurrent as tc # noqa + + tc.Future = self.orig_tc diff --git a/test/test_nested_aio.py b/test/test_nested_aio.py new file mode 100644 index 00000000..56052ab2 --- /dev/null +++ b/test/test_nested_aio.py @@ -0,0 +1,104 @@ +import asyncio +import sys +import unittest + +from pydantic_aioredis.utils import NestedAsyncIO + + +def exception_handler(loop, context): + print("Exception:", context) + + +class NestTest(unittest.TestCase): + def setUp(self): + self.loop = asyncio.new_event_loop() + NestedAsyncIO().apply(self.loop) + asyncio.set_event_loop(self.loop) + self.loop.set_debug(True) + self.loop.set_exception_handler(exception_handler) + + def tearDown(self): + NestedAsyncIO().revert() + self.assertIsNone(asyncio._get_running_loop()) + self.loop.close() + del self.loop + + async def coro(self): + await asyncio.sleep(0.01) + return 42 + + def test_nesting(self): + async def f1(): + result = self.loop.run_until_complete(self.coro()) + self.assertEqual(result, await self.coro()) + return result + + async def f2(): + result = self.loop.run_until_complete(f1()) + self.assertEqual(result, await f1()) + return result + + result = self.loop.run_until_complete(f2()) + self.assertEqual(result, 42) + + def test_ensure_future_with_run_until_complete(self): + async def f(): + task = asyncio.ensure_future(self.coro()) + return self.loop.run_until_complete(task) + + result = self.loop.run_until_complete(f()) + self.assertEqual(result, 42) + + def test_ensure_future_with_run_until_complete_with_wait(self): + async def f(): + task = asyncio.ensure_future(self.coro()) + done, pending = self.loop.run_until_complete(asyncio.wait([task], return_when=asyncio.ALL_COMPLETED)) + task = done.pop() + return task.result() + + result = self.loop.run_until_complete(f()) + self.assertEqual(result, 42) + + def test_timeout(self): + async def f1(): + await asyncio.sleep(0.1) + + async def f2(): + asyncio.run(asyncio.wait_for(f1(), 0.01)) + + with self.assertRaises(asyncio.TimeoutError): + self.loop.run_until_complete(f2()) + + def test_two_run_until_completes_in_one_outer_loop(self): + async def f1(): + self.loop.run_until_complete(asyncio.sleep(0.02)) + return 4 + + async def f2(): + self.loop.run_until_complete(asyncio.sleep(0.01)) + return 2 + + result = self.loop.run_until_complete(asyncio.gather(f1(), f2())) + self.assertEqual(result, [4, 2]) + + @unittest.skipIf(sys.version_info < (3, 7, 0), "No contextvars module") + def test_contextvars(self): + from contextvars import ContextVar + + var = ContextVar("var") + var.set(0) + + async def set_val(): + var.set(42) + + async def coro(): + await set_val() + await asyncio.sleep(0.01) + return var.get() + + result = self.loop.run_until_complete(coro()) + self.assertEqual(result, 42) + + +if __name__ == "__main__": + unittest.main() From b1bb5d366464647ee4766937db7448d9db06ff46 Mon Sep 17 00:00:00 2001 From: Andrew Herrington Date: Tue, 7 May 2024 18:46:04 -0500 Subject: [PATCH 2/4] fix: remove nest-asyncio from requirements --- poetry.lock | 40 +++++++--------------------------------- pyproject.toml | 1 - 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7c4da628..3e80a88e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1158,17 +1158,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "nest-asyncio" -version = "1.6.0" -description = "Patch asyncio to allow nested event loops" -optional = false -python-versions = ">=3.5" -files = [ - {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, - {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, -] - [[package]] name = "nodeenv" version = "1.8.0" @@ -1679,7 +1668,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -1813,51 +1801,37 @@ python-versions = ">=3.6" files = [ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, - {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, - {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"}, {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, - {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, - {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, - {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, - {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, @@ -2693,4 +2667,4 @@ fastapi-crudrouter = ["fastapi-crudrouter"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "b70ee9953532c1570a67cf7b4a073d09d8d36eebee32a20b6d4709e42462e58b" +content-hash = "8bbb9e6578bbc076c76cd1beb2487f55105728a340e3c5b12c18013f4580b0ad" diff --git a/pyproject.toml b/pyproject.toml index 48eb0577..d1fe94ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,6 @@ python = "^3.8" pydantic = "^1.10.2" redis = ">=4.4.4,<6.0.0" anyio = ">=3.6.2,<5.0.0" -nest-asyncio = "^1.5.6" fastapi = {version = ">=0.110", optional = true} fastapi-crudrouter = {version = "^0.8.6", optional = true} From 1cfe1e4e1a65eb3e25c261a6a3e82f1677204203 Mon Sep 17 00:00:00 2001 From: Andrew Herrington Date: Tue, 7 May 2024 18:51:52 -0500 Subject: [PATCH 3/4] fix: adjust ci coverage and 3.12 support --- pydantic_aioredis/utils.py | 7 +++---- pyproject.toml | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pydantic_aioredis/utils.py b/pydantic_aioredis/utils.py index 8a5a740b..4fefc414 100644 --- a/pydantic_aioredis/utils.py +++ b/pydantic_aioredis/utils.py @@ -15,8 +15,6 @@ def bytes_to_string(data: bytes): class NestedAsyncIO: - """Patch asyncio to allow nested event loops.""" - __slots__ = [ "_loop", "orig_run", @@ -106,10 +104,11 @@ def _get_event_loop(stacklevel=3): asyncio.all_tasks = asyncio.tasks.Task.all_tasks elif sys.version_info >= (3, 9, 0): self.orig_get_loops = { - "events__get_event_loop": events._get_event_loop, "events_get_event_loop": events.get_event_loop, "asyncio_get_event_loop": asyncio.get_event_loop, } + if sys.version_info <= (3, 12, 0): + self.orig_get_loops["events__get_event_loop"] = (events._get_event_loop,) events._get_event_loop = events.get_event_loop = asyncio.get_event_loop = _get_event_loop self.orig_run = asyncio.run asyncio.run = run @@ -308,6 +307,6 @@ def patch_tornado(self): def unpatch_tornado(self): if self.orig_tc: - import tornado.concurrent as tc # noqa + import tornado.concurrent as tc tc.Future = self.orig_tc diff --git a/pyproject.toml b/pyproject.toml index d1fe94ed..19933298 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,8 +85,8 @@ build-backend = "poetry.core.masonry.api" exclude_dirs = ["test", "noxfile.py", ".github/scripts", "dist", "examples/*"] [tool.pytest.ini_options] -addopts = "-n 4 --ignore examples --cov=pydantic_aioredis --cov-report xml:.coverage.xml --cov-report=term-missing --cov-fail-under 91" +addopts = "-n 4 --ignore examples --cov=pydantic_aioredis --cov-report xml:.coverage.xml --cov-report=term-missing --cov-fail-under 85" [tool.ruff] line-length = 120 -target-version = "py37" +target-version = "py38" From 9ec811bb2db64335a24b0f496d3d57bcf93824ed Mon Sep 17 00:00:00 2001 From: Andrew Herrington Date: Tue, 7 May 2024 19:19:47 -0500 Subject: [PATCH 4/4] fix: more loop fixes for 3.12 --- pydantic_aioredis/utils.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pydantic_aioredis/utils.py b/pydantic_aioredis/utils.py index 4fefc414..c5a2caf0 100644 --- a/pydantic_aioredis/utils.py +++ b/pydantic_aioredis/utils.py @@ -99,17 +99,13 @@ def _get_event_loop(stacklevel=3): asyncio.Task = asyncio.tasks._CTask = asyncio.tasks.Task = asyncio.tasks._PyTask self.orig_futures = [asyncio.Future, asyncio.futures._CFuture, asyncio.futures.Future] asyncio.Future = asyncio.futures._CFuture = asyncio.futures.Future = asyncio.futures._PyFuture - if sys.version_info < (3, 7, 0): - asyncio.tasks._current_tasks = asyncio.tasks.Task._current_tasks - asyncio.all_tasks = asyncio.tasks.Task.all_tasks - elif sys.version_info >= (3, 9, 0): - self.orig_get_loops = { - "events_get_event_loop": events.get_event_loop, - "asyncio_get_event_loop": asyncio.get_event_loop, - } - if sys.version_info <= (3, 12, 0): - self.orig_get_loops["events__get_event_loop"] = (events._get_event_loop,) - events._get_event_loop = events.get_event_loop = asyncio.get_event_loop = _get_event_loop + self.orig_get_loops = { + "events_get_event_loop": events.get_event_loop, + "asyncio_get_event_loop": asyncio.get_event_loop, + } + if sys.version_info >= (3, 10, 0) and sys.version_info < (3, 12, 0): + self.orig_get_loops["events__get_event_loop"] = events._get_event_loop + events._get_event_loop = events.get_event_loop = asyncio.get_event_loop = _get_event_loop self.orig_run = asyncio.run asyncio.run = run asyncio._nest_patched = True