From 28c7d0287df29e9fe8e122a8a09cf59d15941a45 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 2 Dec 2022 13:05:03 +0200 Subject: [PATCH 1/4] gh-93453: Only emit deprecation warning in asyncio.get_event_loop when a new event loop is created It no longer emits a deprecation warning if the current event loop was set. --- Doc/library/asyncio-eventloop.rst | 20 ++++--- Doc/library/asyncio-llapi-index.rst | 2 +- Doc/library/asyncio-policy.rst | 5 ++ Lib/asyncio/events.py | 28 ++++++---- Lib/asyncio/futures.py | 4 +- Lib/asyncio/streams.py | 4 +- Lib/asyncio/tasks.py | 6 +- Lib/test/test_asyncio/test_base_events.py | 2 +- Lib/test/test_asyncio/test_events.py | 54 ++++++++---------- Lib/test/test_asyncio/test_futures.py | 24 +++----- Lib/test/test_asyncio/test_streams.py | 24 +++----- Lib/test/test_asyncio/test_tasks.py | 56 +++++++------------ Lib/test/test_asyncio/test_unix_events.py | 3 +- Lib/test/test_coroutines.py | 3 +- ...2-12-02-13-05-00.gh-issue-93453.EFj1NN.rst | 3 + Modules/_asynciomodule.c | 26 +-------- Modules/clinic/_asynciomodule.c.h | 41 +------------- 17 files changed, 112 insertions(+), 193 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-12-02-13-05-00.gh-issue-93453.EFj1NN.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index cf9b3b7ce079cf..b2c72d73d1fac3 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -42,10 +42,12 @@ an event loop: Get the current event loop. - If there is no current event loop set in the current OS thread, - the OS thread is main, and :func:`set_event_loop` has not yet - been called, asyncio will create a new event loop and set it as the - current one. + When called from a coroutine or a callback (e.g. scheduled with + call_soon or similar API), this function will always return the + running event loop. + + If there is no running event loop set, the function will return + the result of ``get_event_loop_policy().get_event_loop()`` call. Because this function has rather complex behavior (especially when custom event loop policies are in use), using the @@ -56,9 +58,13 @@ an event loop: lower level functions to manually create and close an event loop. .. deprecated:: 3.10 - Deprecation warning is emitted if there is no running event loop. - In future Python releases, this function will be an alias of - :func:`get_running_loop`. + Deprecation warning is emitted if there is no current event loop. + + .. note:: + In Python versions 3.10.0--3.10.8 and 3.11.0 this function + (and other functions which used it implicitly) emmitted a + :exc:`DeprecationWarning` if there was no running event loop, even if + the current loop was set. .. function:: set_event_loop(loop) diff --git a/Doc/library/asyncio-llapi-index.rst b/Doc/library/asyncio-llapi-index.rst index b7ad888a7b67ab..9ce48a24444e66 100644 --- a/Doc/library/asyncio-llapi-index.rst +++ b/Doc/library/asyncio-llapi-index.rst @@ -19,7 +19,7 @@ Obtaining the Event Loop - The **preferred** function to get the running event loop. * - :func:`asyncio.get_event_loop` - - Get an event loop instance (current or via the policy). + - Get an event loop instance (running or current via the current policy). * - :func:`asyncio.set_event_loop` - Set the event loop as current via the current policy. diff --git a/Doc/library/asyncio-policy.rst b/Doc/library/asyncio-policy.rst index bfc3e3090fdcdf..bccc7cc40893b0 100644 --- a/Doc/library/asyncio-policy.rst +++ b/Doc/library/asyncio-policy.rst @@ -112,6 +112,11 @@ asyncio ships with the following built-in policies: On Windows, :class:`ProactorEventLoop` is now used by default. + .. deprecated:: 3.11.1 + :meth:`get_event_loop` now emits a :exc:`DeprecationWarning` if there + is no current event loop set and a new policy has been created. + In Python 3.12 it will be an error. + .. class:: WindowsSelectorEventLoopPolicy diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 0d26ea545baa5d..51bd736dc4538d 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -615,7 +615,7 @@ def get_event_loop(self): Returns an event loop object implementing the BaseEventLoop interface, or raises an exception in case no event loop has been set for the - current context and the current policy does not specify to create one. + current context. It should never return None.""" raise NotImplementedError @@ -671,6 +671,21 @@ def get_event_loop(self): if (self._local._loop is None and not self._local._set_called and threading.current_thread() is threading.main_thread()): + stacklevel = 2 + try: + f = sys._getframe(1) + except AttributeError: + pass + else: + while f: + module = f.f_globals.get('__name__') + if not (module == 'asyncio' or module.startswith('asyncio.')): + break + f = f.f_back + stacklevel += 1 + import warnings + warnings.warn('There is no current event loop', + DeprecationWarning, stacklevel=stacklevel) self.set_event_loop(self.new_event_loop()) if self._local._loop is None: @@ -782,16 +797,9 @@ def get_event_loop(): the result of `get_event_loop_policy().get_event_loop()` call. """ # NOTE: this function is implemented in C (see _asynciomodule.c) - return _py__get_event_loop() - - -def _get_event_loop(stacklevel=3): current_loop = _get_running_loop() if current_loop is not None: return current_loop - import warnings - warnings.warn('There is no current event loop', - DeprecationWarning, stacklevel=stacklevel) return get_event_loop_policy().get_event_loop() @@ -821,7 +829,6 @@ def set_child_watcher(watcher): _py__set_running_loop = _set_running_loop _py_get_running_loop = get_running_loop _py_get_event_loop = get_event_loop -_py__get_event_loop = _get_event_loop try: @@ -829,7 +836,7 @@ def set_child_watcher(watcher): # functions in asyncio. Pure Python implementation is # about 4 times slower than C-accelerated. from _asyncio import (_get_running_loop, _set_running_loop, - get_running_loop, get_event_loop, _get_event_loop) + get_running_loop, get_event_loop) except ImportError: pass else: @@ -838,4 +845,3 @@ def set_child_watcher(watcher): _c__set_running_loop = _set_running_loop _c_get_running_loop = get_running_loop _c_get_event_loop = get_event_loop - _c__get_event_loop = _get_event_loop diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index 3a6b44a0910869..97fc4e3fcb60ee 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -77,7 +77,7 @@ def __init__(self, *, loop=None): the default event loop. """ if loop is None: - self._loop = events._get_event_loop() + self._loop = events.get_event_loop() else: self._loop = loop self._callbacks = [] @@ -413,7 +413,7 @@ def wrap_future(future, *, loop=None): assert isinstance(future, concurrent.futures.Future), \ f'concurrent.futures.Future is expected, got {future!r}' if loop is None: - loop = events._get_event_loop() + loop = events.get_event_loop() new_future = loop.create_future() _chain_future(future, new_future) return new_future diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index d21a31d1860d91..8ca0253732f27b 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -126,7 +126,7 @@ class FlowControlMixin(protocols.Protocol): def __init__(self, loop=None): if loop is None: - self._loop = events._get_event_loop(stacklevel=4) + self._loop = events.get_event_loop() else: self._loop = loop self._paused = False @@ -405,7 +405,7 @@ def __init__(self, limit=_DEFAULT_LIMIT, loop=None): self._limit = limit if loop is None: - self._loop = events._get_event_loop() + self._loop = events.get_event_loop() else: self._loop = loop self._buffer = bytearray() diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 3e07ce5294c8dd..b485c609c71bf6 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -579,7 +579,7 @@ def as_completed(fs, *, timeout=None): from .queues import Queue # Import here to avoid circular import problem. done = Queue() - loop = events._get_event_loop() + loop = events.get_event_loop() todo = {ensure_future(f, loop=loop) for f in set(fs)} timeout_handle = None @@ -665,7 +665,7 @@ def _ensure_future(coro_or_future, *, loop=None): 'is required') if loop is None: - loop = events._get_event_loop(stacklevel=4) + loop = events.get_event_loop() try: return loop.create_task(coro_or_future) except RuntimeError: @@ -746,7 +746,7 @@ def gather(*coros_or_futures, return_exceptions=False): gather won't cancel any other awaitables. """ if not coros_or_futures: - loop = events._get_event_loop() + loop = events.get_event_loop() outer = loop.create_future() outer.set_result([]) return outer diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 063174adacfa37..6ba602dd619fba 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -746,7 +746,7 @@ async def coro(): def test_env_var_debug(self): code = '\n'.join(( 'import asyncio', - 'loop = asyncio.get_event_loop()', + 'loop = asyncio.new_event_loop()', 'print(loop.get_debug())')) # Test with -E to not fail if the unit test was run with diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 05d9107b28e2ae..c431fea401633e 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2547,8 +2547,9 @@ def test_event_loop_policy(self): def test_get_event_loop(self): policy = asyncio.DefaultEventLoopPolicy() self.assertIsNone(policy._local._loop) - - loop = policy.get_event_loop() + with self.assertWarns(DeprecationWarning) as cm: + loop = policy.get_event_loop() + self.assertEqual(cm.filename, __file__) self.assertIsInstance(loop, asyncio.AbstractEventLoop) self.assertIs(policy._local._loop, loop) @@ -2562,7 +2563,10 @@ def test_get_event_loop_calls_set_event_loop(self): policy, "set_event_loop", wraps=policy.set_event_loop) as m_set_event_loop: - loop = policy.get_event_loop() + with self.assertWarns(DeprecationWarning) as cm: + loop = policy.get_event_loop() + self.addCleanup(loop.close) + self.assertEqual(cm.filename, __file__) # policy._local._loop must be set through .set_event_loop() # (the unix DefaultEventLoopPolicy needs this call to attach @@ -2596,7 +2600,8 @@ def test_new_event_loop(self): def test_set_event_loop(self): policy = asyncio.DefaultEventLoopPolicy() - old_loop = policy.get_event_loop() + old_loop = policy.new_event_loop() + policy.set_event_loop(old_loop) self.assertRaises(TypeError, policy.set_event_loop, object()) @@ -2709,15 +2714,11 @@ def get_event_loop(self): asyncio.set_event_loop_policy(Policy()) loop = asyncio.new_event_loop() - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(TestError): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) + with self.assertRaises(TestError): + asyncio.get_event_loop() asyncio.set_event_loop(None) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(TestError): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) + with self.assertRaises(TestError): + asyncio.get_event_loop() with self.assertRaisesRegex(RuntimeError, 'no running'): asyncio.get_running_loop() @@ -2731,16 +2732,11 @@ async def func(): loop.run_until_complete(func()) asyncio.set_event_loop(loop) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(TestError): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) - + with self.assertRaises(TestError): + asyncio.get_event_loop() asyncio.set_event_loop(None) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(TestError): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) + with self.assertRaises(TestError): + asyncio.get_event_loop() finally: asyncio.set_event_loop_policy(old_policy) @@ -2764,10 +2760,8 @@ def test_get_event_loop_returns_running_loop2(self): self.addCleanup(loop2.close) self.assertEqual(cm.filename, __file__) asyncio.set_event_loop(None) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'no current'): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current'): + asyncio.get_event_loop() with self.assertRaisesRegex(RuntimeError, 'no running'): asyncio.get_running_loop() @@ -2781,15 +2775,11 @@ async def func(): loop.run_until_complete(func()) asyncio.set_event_loop(loop) - with self.assertWarns(DeprecationWarning) as cm: - self.assertIs(asyncio.get_event_loop(), loop) - self.assertEqual(cm.filename, __file__) + self.assertIs(asyncio.get_event_loop(), loop) asyncio.set_event_loop(None) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'no current'): - asyncio.get_event_loop() - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current'): + asyncio.get_event_loop() finally: asyncio.set_event_loop_policy(old_policy) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index f8fe2e76b6226a..987772e0b1964b 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -145,10 +145,8 @@ def test_initial_state(self): self.assertTrue(f.cancelled()) def test_constructor_without_loop(self): - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - self._new_future() - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + self._new_future() def test_constructor_use_running_loop(self): async def test(): @@ -158,12 +156,10 @@ async def test(): self.assertIs(f.get_loop(), self.loop) def test_constructor_use_global_loop(self): - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) - with self.assertWarns(DeprecationWarning) as cm: - f = self._new_future() - self.assertEqual(cm.filename, __file__) + f = self._new_future() self.assertIs(f._loop, self.loop) self.assertIs(f.get_loop(), self.loop) @@ -499,10 +495,8 @@ def run(arg): return (arg, threading.get_ident()) ex = concurrent.futures.ThreadPoolExecutor(1) f1 = ex.submit(run, 'oi') - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(RuntimeError): - asyncio.wrap_future(f1) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.wrap_future(f1) ex.shutdown(wait=True) def test_wrap_future_use_running_loop(self): @@ -517,16 +511,14 @@ async def test(): ex.shutdown(wait=True) def test_wrap_future_use_global_loop(self): - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) def run(arg): return (arg, threading.get_ident()) ex = concurrent.futures.ThreadPoolExecutor(1) f1 = ex.submit(run, 'oi') - with self.assertWarns(DeprecationWarning) as cm: - f2 = asyncio.wrap_future(f1) - self.assertEqual(cm.filename, __file__) + f2 = asyncio.wrap_future(f1) self.assertIs(self.loop, f2._loop) ex.shutdown(wait=True) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 0c49099bc499a5..95fc7a159baee8 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -810,10 +810,8 @@ def test_read_all_from_pipe_reader(self): self.assertEqual(data, b'data') def test_streamreader_constructor_without_loop(self): - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - asyncio.StreamReader() - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.StreamReader() def test_streamreader_constructor_use_running_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor @@ -827,21 +825,17 @@ async def test(): def test_streamreader_constructor_use_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 self.addCleanup(asyncio.set_event_loop, None) asyncio.set_event_loop(self.loop) - with self.assertWarns(DeprecationWarning) as cm: - reader = asyncio.StreamReader() - self.assertEqual(cm.filename, __file__) + reader = asyncio.StreamReader() self.assertIs(reader._loop, self.loop) def test_streamreaderprotocol_constructor_without_loop(self): reader = mock.Mock() - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - asyncio.StreamReaderProtocol(reader) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.StreamReaderProtocol(reader) def test_streamreaderprotocol_constructor_use_running_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor @@ -855,13 +849,11 @@ async def test(): def test_streamreaderprotocol_constructor_use_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 self.addCleanup(asyncio.set_event_loop, None) asyncio.set_event_loop(self.loop) reader = mock.Mock() - with self.assertWarns(DeprecationWarning) as cm: - protocol = asyncio.StreamReaderProtocol(reader) - self.assertEqual(cm.filename, __file__) + protocol = asyncio.StreamReaderProtocol(reader) self.assertIs(protocol._loop, self.loop) def test_multiple_drain(self): diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 6b875cfb4dcb2c..9900e307a477bd 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -200,10 +200,8 @@ async def notmuch(): a = notmuch() self.addCleanup(a.close) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - asyncio.ensure_future(a) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.ensure_future(a) async def test(): return asyncio.ensure_future(notmuch()) @@ -213,12 +211,10 @@ async def test(): self.assertTrue(t.done()) self.assertEqual(t.result(), 'ok') - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) - with self.assertWarns(DeprecationWarning) as cm: - t = asyncio.ensure_future(notmuch()) - self.assertEqual(cm.filename, __file__) + t = asyncio.ensure_future(notmuch()) self.assertIs(t._loop, self.loop) self.loop.run_until_complete(t) self.assertTrue(t.done()) @@ -1536,10 +1532,8 @@ async def coro(): self.addCleanup(a.close) futs = asyncio.as_completed([a]) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - list(futs) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + list(futs) def test_as_completed_coroutine_use_running_loop(self): loop = self.new_test_loop() @@ -1969,10 +1963,8 @@ async def coro(): inner = coro() self.addCleanup(inner.close) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): - asyncio.shield(inner) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.shield(inner) def test_shield_coroutine_use_running_loop(self): async def coro(): @@ -1986,15 +1978,13 @@ async def test(): self.assertEqual(res, 42) def test_shield_coroutine_use_global_loop(self): - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 async def coro(): return 42 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) - with self.assertWarns(DeprecationWarning) as cm: - outer = asyncio.shield(coro()) - self.assertEqual(cm.filename, __file__) + outer = asyncio.shield(coro()) self.assertEqual(outer._loop, self.loop) res = self.loop.run_until_complete(outer) self.assertEqual(res, 42) @@ -2820,7 +2810,7 @@ def test_current_task_no_running_loop(self): self.assertIsNone(asyncio.current_task(loop=self.loop)) def test_current_task_no_running_loop_implicit(self): - with self.assertRaises(RuntimeError): + with self.assertRaisesRegex(RuntimeError, 'no running event loop'): asyncio.current_task() def test_current_task_with_implicit_loop(self): @@ -2984,10 +2974,8 @@ def _gather(self, *args, **kwargs): return asyncio.gather(*args, **kwargs) def test_constructor_empty_sequence_without_loop(self): - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(RuntimeError): - asyncio.gather() - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.gather() def test_constructor_empty_sequence_use_running_loop(self): async def gather(): @@ -3000,12 +2988,10 @@ async def gather(): self.assertEqual(fut.result(), []) def test_constructor_empty_sequence_use_global_loop(self): - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 asyncio.set_event_loop(self.one_loop) self.addCleanup(asyncio.set_event_loop, None) - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather() - self.assertEqual(cm.filename, __file__) + fut = asyncio.gather() self.assertIsInstance(fut, asyncio.Future) self.assertIs(fut._loop, self.one_loop) self._run_loop(self.one_loop) @@ -3093,10 +3079,8 @@ async def coro(): self.addCleanup(gen1.close) gen2 = coro() self.addCleanup(gen2.close) - with self.assertWarns(DeprecationWarning) as cm: - with self.assertRaises(RuntimeError): - asyncio.gather(gen1, gen2) - self.assertEqual(cm.filename, __file__) + with self.assertRaisesRegex(RuntimeError, 'no current event loop'): + asyncio.gather(gen1, gen2) def test_constructor_use_running_loop(self): async def coro(): @@ -3110,16 +3094,14 @@ async def gather(): self.one_loop.run_until_complete(fut) def test_constructor_use_global_loop(self): - # Deprecated in 3.10 + # Deprecated in 3.10, undeprecated in 3.11.1 async def coro(): return 'abc' asyncio.set_event_loop(self.other_loop) self.addCleanup(asyncio.set_event_loop, None) gen1 = coro() gen2 = coro() - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(gen1, gen2) - self.assertEqual(cm.filename, __file__) + fut = asyncio.gather(gen1, gen2) self.assertIs(fut._loop, self.other_loop) self.other_loop.run_until_complete(fut) diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 1d922783ce3b36..01c1214c7f7cc2 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -1740,7 +1740,8 @@ def f(): def test_child_watcher_replace_mainloop_existing(self): policy = self.create_policy() - loop = policy.get_event_loop() + loop = policy.new_event_loop() + policy.set_event_loop(loop) # Explicitly setup SafeChildWatcher, # default ThreadedChildWatcher has no _loop property diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index a15736e9f94553..10f1a9efbcbdd6 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -2411,7 +2411,8 @@ class UnawaitedWarningDuringShutdownTest(unittest.TestCase): def test_unawaited_warning_during_shutdown(self): code = ("import asyncio\n" "async def f(): pass\n" - "asyncio.gather(f())\n") + "async def t(): asyncio.gather(f())\n" + "asyncio.run(t())\n") assert_python_ok("-c", code) code = ("import sys\n" diff --git a/Misc/NEWS.d/next/Library/2022-12-02-13-05-00.gh-issue-93453.EFj1NN.rst b/Misc/NEWS.d/next/Library/2022-12-02-13-05-00.gh-issue-93453.EFj1NN.rst new file mode 100644 index 00000000000000..dd4f43351ac78d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-12-02-13-05-00.gh-issue-93453.EFj1NN.rst @@ -0,0 +1,3 @@ +:func:`asyncio.get_event_loop` now only emits a deprecation warning when a +new event loop was created implicitly. It no longer emits a deprecation +warning if the current event loop was set. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index d503018ea6c3ec..50bae039c4b1c1 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -327,7 +327,7 @@ set_running_loop(PyObject *loop) static PyObject * -get_event_loop(int stacklevel) +get_event_loop(void) { PyObject *loop; PyObject *policy; @@ -339,13 +339,6 @@ get_event_loop(int stacklevel) return loop; } - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "There is no current event loop", - stacklevel)) - { - return NULL; - } - policy = PyObject_CallNoArgs(asyncio_get_event_loop_policy); if (policy == NULL) { return NULL; @@ -505,7 +498,7 @@ future_init(FutureObj *fut, PyObject *loop) fut->fut_blocking = 0; if (loop == Py_None) { - loop = get_event_loop(1); + loop = get_event_loop(); if (loop == NULL) { return -1; } @@ -3126,19 +3119,7 @@ static PyObject * _asyncio_get_event_loop_impl(PyObject *module) /*[clinic end generated code: output=2a2d8b2f824c648b input=9364bf2916c8655d]*/ { - return get_event_loop(1); -} - -/*[clinic input] -_asyncio._get_event_loop - stacklevel: int = 3 -[clinic start generated code]*/ - -static PyObject * -_asyncio__get_event_loop_impl(PyObject *module, int stacklevel) -/*[clinic end generated code: output=9c1d6d3c802e67c9 input=d17aebbd686f711d]*/ -{ - return get_event_loop(stacklevel-1); + return get_event_loop(); } /*[clinic input] @@ -3436,7 +3417,6 @@ PyDoc_STRVAR(module_doc, "Accelerator module for asyncio"); static PyMethodDef asyncio_methods[] = { _ASYNCIO_GET_EVENT_LOOP_METHODDEF - _ASYNCIO__GET_EVENT_LOOP_METHODDEF _ASYNCIO_GET_RUNNING_LOOP_METHODDEF _ASYNCIO__GET_RUNNING_LOOP_METHODDEF _ASYNCIO__SET_RUNNING_LOOP_METHODDEF diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index add6bb2e08b55b..0a1a7d2e602255 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -688,45 +688,6 @@ _asyncio_get_event_loop(PyObject *module, PyObject *Py_UNUSED(ignored)) return _asyncio_get_event_loop_impl(module); } -PyDoc_STRVAR(_asyncio__get_event_loop__doc__, -"_get_event_loop($module, /, stacklevel=3)\n" -"--\n" -"\n"); - -#define _ASYNCIO__GET_EVENT_LOOP_METHODDEF \ - {"_get_event_loop", _PyCFunction_CAST(_asyncio__get_event_loop), METH_FASTCALL|METH_KEYWORDS, _asyncio__get_event_loop__doc__}, - -static PyObject * -_asyncio__get_event_loop_impl(PyObject *module, int stacklevel); - -static PyObject * -_asyncio__get_event_loop(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - static const char * const _keywords[] = {"stacklevel", NULL}; - static _PyArg_Parser _parser = {NULL, _keywords, "_get_event_loop", 0}; - PyObject *argsbuf[1]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; - int stacklevel = 3; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); - if (!args) { - goto exit; - } - if (!noptargs) { - goto skip_optional_pos; - } - stacklevel = _PyLong_AsInt(args[0]); - if (stacklevel == -1 && PyErr_Occurred()) { - goto exit; - } -skip_optional_pos: - return_value = _asyncio__get_event_loop_impl(module, stacklevel); - -exit: - return return_value; -} - PyDoc_STRVAR(_asyncio_get_running_loop__doc__, "get_running_loop($module, /)\n" "--\n" @@ -890,4 +851,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=b4e678c915567934 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=aef7f5264af0309a input=a9049054013a1b77]*/ From a7a2804a58f71230843a9001f01b78b1204d6210 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 5 Dec 2022 18:44:13 +0200 Subject: [PATCH 2/4] Restore asyncio._get_event_loop(). --- Lib/asyncio/events.py | 16 ++++++++++-- Modules/_asynciomodule.c | 37 ++++++++++++++++++++++++++++ Modules/clinic/_asynciomodule.c.h | 41 ++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 51bd736dc4538d..8793fd0e43d571 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -615,7 +615,7 @@ def get_event_loop(self): Returns an event loop object implementing the BaseEventLoop interface, or raises an exception in case no event loop has been set for the - current context. + current context and the current policy does not specify to create one. It should never return None.""" raise NotImplementedError @@ -803,6 +803,16 @@ def get_event_loop(): return get_event_loop_policy().get_event_loop() +def _get_event_loop(stacklevel=3): + current_loop = _get_running_loop() + if current_loop is not None: + return current_loop + import warnings + warnings.warn('There is no current event loop', + DeprecationWarning, stacklevel=stacklevel) + return get_event_loop_policy().get_event_loop() + + def set_event_loop(loop): """Equivalent to calling get_event_loop_policy().set_event_loop(loop).""" get_event_loop_policy().set_event_loop(loop) @@ -829,6 +839,7 @@ def set_child_watcher(watcher): _py__set_running_loop = _set_running_loop _py_get_running_loop = get_running_loop _py_get_event_loop = get_event_loop +_py__get_event_loop = _get_event_loop try: @@ -836,7 +847,7 @@ def set_child_watcher(watcher): # functions in asyncio. Pure Python implementation is # about 4 times slower than C-accelerated. from _asyncio import (_get_running_loop, _set_running_loop, - get_running_loop, get_event_loop) + get_running_loop, get_event_loop, _get_event_loop) except ImportError: pass else: @@ -845,3 +856,4 @@ def set_child_watcher(watcher): _c__set_running_loop = _set_running_loop _c_get_running_loop = get_running_loop _c_get_event_loop = get_event_loop + _c__get_event_loop = _get_event_loop diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 50bae039c4b1c1..6e9b9d9f146882 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3122,6 +3122,42 @@ _asyncio_get_event_loop_impl(PyObject *module) return get_event_loop(); } +/*[clinic input] +_asyncio._get_event_loop + stacklevel: int = 3 +[clinic start generated code]*/ + +static PyObject * +_asyncio__get_event_loop_impl(PyObject *module, int stacklevel) +/*[clinic end generated code: output=9c1d6d3c802e67c9 input=d17aebbd686f711d]*/ +{ + PyObject *loop; + PyObject *policy; + + if (get_running_loop(&loop)) { + return NULL; + } + if (loop != NULL) { + return loop; + } + + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "There is no current event loop", + stacklevel-1)) + { + return NULL; + } + + policy = PyObject_CallNoArgs(asyncio_get_event_loop_policy); + if (policy == NULL) { + return NULL; + } + + loop = _PyObject_CallMethodIdNoArgs(policy, &PyId_get_event_loop); + Py_DECREF(policy); + return loop; +} + /*[clinic input] _asyncio.get_running_loop @@ -3417,6 +3453,7 @@ PyDoc_STRVAR(module_doc, "Accelerator module for asyncio"); static PyMethodDef asyncio_methods[] = { _ASYNCIO_GET_EVENT_LOOP_METHODDEF + _ASYNCIO__GET_EVENT_LOOP_METHODDEF _ASYNCIO_GET_RUNNING_LOOP_METHODDEF _ASYNCIO__GET_RUNNING_LOOP_METHODDEF _ASYNCIO__SET_RUNNING_LOOP_METHODDEF diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index 0a1a7d2e602255..add6bb2e08b55b 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -688,6 +688,45 @@ _asyncio_get_event_loop(PyObject *module, PyObject *Py_UNUSED(ignored)) return _asyncio_get_event_loop_impl(module); } +PyDoc_STRVAR(_asyncio__get_event_loop__doc__, +"_get_event_loop($module, /, stacklevel=3)\n" +"--\n" +"\n"); + +#define _ASYNCIO__GET_EVENT_LOOP_METHODDEF \ + {"_get_event_loop", _PyCFunction_CAST(_asyncio__get_event_loop), METH_FASTCALL|METH_KEYWORDS, _asyncio__get_event_loop__doc__}, + +static PyObject * +_asyncio__get_event_loop_impl(PyObject *module, int stacklevel); + +static PyObject * +_asyncio__get_event_loop(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"stacklevel", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "_get_event_loop", 0}; + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int stacklevel = 3; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + stacklevel = _PyLong_AsInt(args[0]); + if (stacklevel == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = _asyncio__get_event_loop_impl(module, stacklevel); + +exit: + return return_value; +} + PyDoc_STRVAR(_asyncio_get_running_loop__doc__, "get_running_loop($module, /)\n" "--\n" @@ -851,4 +890,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=aef7f5264af0309a input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b4e678c915567934 input=a9049054013a1b77]*/ From 85d3c363f2c0ea3223c2e7464183ff3933a85e43 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 6 Dec 2022 12:38:56 +0200 Subject: [PATCH 3/4] Address review comments. --- Doc/library/asyncio-eventloop.rst | 1 + Doc/library/asyncio-policy.rst | 2 +- Lib/asyncio/events.py | 2 ++ Modules/_asynciomodule.c | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 23e9c0b0d2ad36..28b7a900583b84 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -61,6 +61,7 @@ an event loop: .. deprecated:: 3.10 Deprecation warning is emitted if there is no current event loop. + In Python 3.12 it will be an error. .. note:: In Python versions 3.10.0--3.10.8 and 3.11.0 this function diff --git a/Doc/library/asyncio-policy.rst b/Doc/library/asyncio-policy.rst index bccc7cc40893b0..a1eb03ddd44173 100644 --- a/Doc/library/asyncio-policy.rst +++ b/Doc/library/asyncio-policy.rst @@ -114,7 +114,7 @@ asyncio ships with the following built-in policies: .. deprecated:: 3.11.1 :meth:`get_event_loop` now emits a :exc:`DeprecationWarning` if there - is no current event loop set and a new policy has been created. + is no current event loop set and a new event loop has been created. In Python 3.12 it will be an error. diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 8793fd0e43d571..82a10ce19d75e6 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -803,6 +803,8 @@ def get_event_loop(): return get_event_loop_policy().get_event_loop() +# This function is no longer used, will disappear in 3.12, +# but is retained in 3.10-3.11 in case some user code was calling it. def _get_event_loop(stacklevel=3): current_loop = _get_running_loop() if current_loop is not None: diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 6e9b9d9f146882..4ff39fc2e110f3 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -3122,6 +3122,8 @@ _asyncio_get_event_loop_impl(PyObject *module) return get_event_loop(); } +// This function is no longer used, will disappear in 3.12, +// but is retained in 3.10-3.11 in case some user code was calling it. /*[clinic input] _asyncio._get_event_loop stacklevel: int = 3 From 199433cb08a48d95e92462daf56da3bae6cf6077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Tue, 6 Dec 2022 13:56:23 +0100 Subject: [PATCH 4/4] Revert removal of asyncio.events._get_event_loop and _asyncio._get_event_loop --- Doc/library/asyncio-policy.rst | 4 ++-- Lib/asyncio/events.py | 14 +++++------- Lib/asyncio/futures.py | 4 ++-- Lib/asyncio/streams.py | 4 ++-- Lib/asyncio/tasks.py | 6 +++--- Modules/_asynciomodule.c | 39 ++++++++-------------------------- 6 files changed, 23 insertions(+), 48 deletions(-) diff --git a/Doc/library/asyncio-policy.rst b/Doc/library/asyncio-policy.rst index a1eb03ddd44173..d0af45febd14e3 100644 --- a/Doc/library/asyncio-policy.rst +++ b/Doc/library/asyncio-policy.rst @@ -114,8 +114,8 @@ asyncio ships with the following built-in policies: .. deprecated:: 3.11.1 :meth:`get_event_loop` now emits a :exc:`DeprecationWarning` if there - is no current event loop set and a new event loop has been created. - In Python 3.12 it will be an error. + is no current event loop set and a new event loop has been implicitly + created. In Python 3.12 it will be an error. .. class:: WindowsSelectorEventLoopPolicy diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 82a10ce19d75e6..af3f9e970b5106 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -797,21 +797,17 @@ def get_event_loop(): the result of `get_event_loop_policy().get_event_loop()` call. """ # NOTE: this function is implemented in C (see _asynciomodule.c) - current_loop = _get_running_loop() - if current_loop is not None: - return current_loop - return get_event_loop_policy().get_event_loop() + return _py__get_event_loop() -# This function is no longer used, will disappear in 3.12, -# but is retained in 3.10-3.11 in case some user code was calling it. def _get_event_loop(stacklevel=3): + # This internal method is going away in Python 3.12, left here only for + # backwards compatibility with 3.10.0 - 3.10.8 and 3.11.0. + # Similarly, this method's C equivalent in _asyncio is going away as well. + # See GH-99949 for more details. current_loop = _get_running_loop() if current_loop is not None: return current_loop - import warnings - warnings.warn('There is no current event loop', - DeprecationWarning, stacklevel=stacklevel) return get_event_loop_policy().get_event_loop() diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index 97fc4e3fcb60ee..3a6b44a0910869 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -77,7 +77,7 @@ def __init__(self, *, loop=None): the default event loop. """ if loop is None: - self._loop = events.get_event_loop() + self._loop = events._get_event_loop() else: self._loop = loop self._callbacks = [] @@ -413,7 +413,7 @@ def wrap_future(future, *, loop=None): assert isinstance(future, concurrent.futures.Future), \ f'concurrent.futures.Future is expected, got {future!r}' if loop is None: - loop = events.get_event_loop() + loop = events._get_event_loop() new_future = loop.create_future() _chain_future(future, new_future) return new_future diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 8ca0253732f27b..d21a31d1860d91 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -126,7 +126,7 @@ class FlowControlMixin(protocols.Protocol): def __init__(self, loop=None): if loop is None: - self._loop = events.get_event_loop() + self._loop = events._get_event_loop(stacklevel=4) else: self._loop = loop self._paused = False @@ -405,7 +405,7 @@ def __init__(self, limit=_DEFAULT_LIMIT, loop=None): self._limit = limit if loop is None: - self._loop = events.get_event_loop() + self._loop = events._get_event_loop() else: self._loop = loop self._buffer = bytearray() diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index b485c609c71bf6..3e07ce5294c8dd 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -579,7 +579,7 @@ def as_completed(fs, *, timeout=None): from .queues import Queue # Import here to avoid circular import problem. done = Queue() - loop = events.get_event_loop() + loop = events._get_event_loop() todo = {ensure_future(f, loop=loop) for f in set(fs)} timeout_handle = None @@ -665,7 +665,7 @@ def _ensure_future(coro_or_future, *, loop=None): 'is required') if loop is None: - loop = events.get_event_loop() + loop = events._get_event_loop(stacklevel=4) try: return loop.create_task(coro_or_future) except RuntimeError: @@ -746,7 +746,7 @@ def gather(*coros_or_futures, return_exceptions=False): gather won't cancel any other awaitables. """ if not coros_or_futures: - loop = events.get_event_loop() + loop = events._get_event_loop() outer = loop.create_future() outer.set_result([]) return outer diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 4ff39fc2e110f3..40f1f80be4473a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -327,7 +327,7 @@ set_running_loop(PyObject *loop) static PyObject * -get_event_loop(void) +get_event_loop(int stacklevel) { PyObject *loop; PyObject *policy; @@ -498,7 +498,7 @@ future_init(FutureObj *fut, PyObject *loop) fut->fut_blocking = 0; if (loop == Py_None) { - loop = get_event_loop(); + loop = get_event_loop(1); if (loop == NULL) { return -1; } @@ -3119,11 +3119,14 @@ static PyObject * _asyncio_get_event_loop_impl(PyObject *module) /*[clinic end generated code: output=2a2d8b2f824c648b input=9364bf2916c8655d]*/ { - return get_event_loop(); + return get_event_loop(1); } -// This function is no longer used, will disappear in 3.12, -// but is retained in 3.10-3.11 in case some user code was calling it. +// This internal method is going away in Python 3.12, left here only for +// backwards compatibility with 3.10.0 - 3.10.8 and 3.11.0. +// Similarly, this method's Python equivalent in asyncio.events is going +// away as well. +// See GH-99949 for more details. /*[clinic input] _asyncio._get_event_loop stacklevel: int = 3 @@ -3133,31 +3136,7 @@ static PyObject * _asyncio__get_event_loop_impl(PyObject *module, int stacklevel) /*[clinic end generated code: output=9c1d6d3c802e67c9 input=d17aebbd686f711d]*/ { - PyObject *loop; - PyObject *policy; - - if (get_running_loop(&loop)) { - return NULL; - } - if (loop != NULL) { - return loop; - } - - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "There is no current event loop", - stacklevel-1)) - { - return NULL; - } - - policy = PyObject_CallNoArgs(asyncio_get_event_loop_policy); - if (policy == NULL) { - return NULL; - } - - loop = _PyObject_CallMethodIdNoArgs(policy, &PyId_get_event_loop); - Py_DECREF(policy); - return loop; + return get_event_loop(stacklevel-1); } /*[clinic input]