Skip to content

[3.11] gh-93453: Only emit deprecation warning in asyncio.get_event_loop when a new event loop is created #99949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,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
Expand All @@ -58,10 +60,14 @@ an event loop:
event loop.

.. deprecated:: 3.10
Emits a deprecation warning if there is no running event loop.
In future Python releases, this function may become an alias of
:func:`get_running_loop` and will accordingly raise a
:exc:`RuntimeError` if there is no running event loop.
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
(and other functions which used it implicitly) emitted a
:exc:`DeprecationWarning` if there was no running event loop, even if
the current loop was set.

.. function:: set_event_loop(loop)

Expand Down
2 changes: 1 addition & 1 deletion Doc/library/asyncio-llapi-index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions Doc/library/asyncio-policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 event loop has been implicitly
created. In Python 3.12 it will be an error.


.. class:: WindowsSelectorEventLoopPolicy

Expand Down
22 changes: 19 additions & 3 deletions Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -786,12 +801,13 @@ def get_event_loop():


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()


Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 22 additions & 32 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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())

Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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)
Expand Down
24 changes: 8 additions & 16 deletions Lib/test/test_asyncio/test_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand All @@ -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)

Expand Down
24 changes: 8 additions & 16 deletions Lib/test/test_asyncio/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down
Loading