Skip to content

Commit

Permalink
Replace use of the deprecated asyncio.get_event_loop function. (#492)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdickinson committed Jan 6, 2022
1 parent b4a837b commit 4530c9d
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 20 deletions.
10 changes: 10 additions & 0 deletions docs/source/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Features

* Support Qt6-based toolkits PyQt6 and PySide6. (This is dependent on
corresponding support in Pyface.) (#488)
* Allow an ``asyncio`` event loop to be specified when creating an
instance of ``AsyncioEventLoop``. (#492)

Changes
~~~~~~~
Expand All @@ -28,6 +30,14 @@ Changes
entry point group have been removed. Their behaviour was ill-defined, and
dependent on ``asyncio.get_event_loop``, which is deprecated in Python.
(#490)
* ``AsyncioEventLoop`` now creates a new ``asyncio`` event loop using
``asyncio.new_event_loop`` if no event loop is explicitly passed in, instead
of trying to get the currently-set event loop using the (now deprecated)
``asyncio.get_event_loop`` function. A new ``AsyncioEventLoop.close`` method
is available to close the event loop owned by ``AsyncioEventLoop``.
Moreover, in a future version of Traits Futures it will be required to
pass an explicit event loop. Instantiating an ``AsyncioEventLoop`` without
an ``asyncio`` event loop is deprecated. (#492)

Documentation
~~~~~~~~~~~~~
Expand Down
19 changes: 13 additions & 6 deletions docs/source/guide/examples/headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import asyncio
import random
import sys

from traits_futures.api import (
AsyncioEventLoop,
Expand Down Expand Up @@ -47,9 +48,13 @@ def set_result(event):
traits_future = event.object
asyncio_future.set_result(traits_future.result)

# Once we can assume a minimum Python version of 3.7, this should
# be changed to use get_running_event_loop instead of get_event_loop.
asyncio_future = asyncio.get_event_loop().create_future()
if sys.version_info < (3, 7):
# We want to use get_running_loop, but it's new in Python 3.7.
# This branch can be dropped once we can assume a minimum Python
# version of 3.7.
asyncio_future = asyncio.get_event_loop().create_future()
else:
asyncio_future = asyncio.get_running_loop().create_future()

traits_future.observe(set_result, "done")

Expand All @@ -64,9 +69,11 @@ def print_progress(event):


if __name__ == "__main__":
traits_executor = TraitsExecutor(event_loop=AsyncioEventLoop())
asyncio_event_loop = asyncio.new_event_loop()
traits_executor = TraitsExecutor(
event_loop=AsyncioEventLoop(event_loop=asyncio_event_loop)
)
traits_future = submit_iteration(traits_executor, approximate_pi)
traits_future.observe(print_progress, "result_event")

# For Python 3.7 and later, just use asyncio.run.
asyncio.get_event_loop().run_until_complete(future_wrapper(traits_future))
asyncio_event_loop.run_until_complete(future_wrapper(traits_future))
34 changes: 30 additions & 4 deletions traits_futures/asyncio/event_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
# Thanks for using Enthought open source!

"""
IEventLoop implementation for the main-thread asyncio event loop.
IEventLoop implementation wrapping an asyncio event loop.
"""
import asyncio
import warnings

from traits_futures.asyncio.event_loop_helper import EventLoopHelper
from traits_futures.asyncio.pingee import Pingee
Expand All @@ -21,11 +22,36 @@
@IEventLoop.register
class AsyncioEventLoop:
"""
IEventLoop implementation for the main-thread asyncio event loop.
IEventLoop implementation wrapping an asyncio event loop.
Parameters
----------
event_loop : asyncio.AbstractEventLoop, optional
The asyncio event loop to wrap. If not provided, a new
event loop will be created and used.
"""

def __init__(self):
self._event_loop = asyncio.get_event_loop()
def __init__(self, *, event_loop=None):
own_event_loop = event_loop is None
if own_event_loop:
warnings.warn(
(
"The event_loop parameter to AsyncioEventLoop will "
"become required in a future version of Traits Futures"
),
DeprecationWarning,
)
event_loop = asyncio.new_event_loop()

self._own_event_loop = own_event_loop
self._event_loop = event_loop

def close(self):
"""
Free any resources allocated by this object.
"""
if self._own_event_loop:
self._event_loop.close()

def pingee(self, on_ping):
"""
Expand Down
35 changes: 32 additions & 3 deletions traits_futures/asyncio/tests/test_asyncio_event_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,43 @@
Tests for the asyncio event loop.
"""


import asyncio
import unittest

from traits_futures.asyncio.event_loop import AsyncioEventLoop
from traits_futures.tests.i_event_loop_tests import IEventLoopTests


class TestAsyncioEventLoop(IEventLoopTests, unittest.TestCase):
def event_loop_factory(self):
"""
Factory for the event loop.
Returns
-------
event_loop: IEventLoop
"""
asyncio_event_loop = asyncio.new_event_loop()
self.addCleanup(asyncio_event_loop.close)
return AsyncioEventLoop(event_loop=asyncio_event_loop)

def test_asyncio_event_loop_closed(self):
with self.assertWarns(DeprecationWarning):
event_loop = AsyncioEventLoop()
# Dig out the underlying asyncio event loop.
asyncio_event_loop = event_loop._event_loop
self.assertFalse(asyncio_event_loop.is_closed())
event_loop.close()
self.assertTrue(asyncio_event_loop.is_closed())

#: Factory for instances of the event loop.
event_loop_factory = AsyncioEventLoop
def test_creation_from_asyncio_event_loop(self):
asyncio_event_loop = asyncio.new_event_loop()
event_loop = AsyncioEventLoop(event_loop=asyncio_event_loop)
self.assertEqual(event_loop._event_loop, asyncio_event_loop)
try:
self.assertFalse(asyncio_event_loop.is_closed())
# Closing our wrapper shouldn't close the asyncio event loop.
event_loop.close()
self.assertFalse(asyncio_event_loop.is_closed())
finally:
asyncio_event_loop.close()
13 changes: 11 additions & 2 deletions traits_futures/asyncio/tests/test_event_loop_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Tests for the asyncio implementation of IEventLoopHelper.
"""

import asyncio
import unittest

from traits_futures.asyncio.event_loop import AsyncioEventLoop
Expand All @@ -21,6 +22,14 @@


class TestEventLoopHelper(IEventLoopHelperTests, unittest.TestCase):
def event_loop_factory(self):
"""
Factory for the event loop.
#: Zero-parameter callable that creates a suitable IEventLoop instance.
event_loop_factory = AsyncioEventLoop
Returns
-------
event_loop: IEventLoop
"""
asyncio_event_loop = asyncio.new_event_loop()
self.addCleanup(asyncio_event_loop.close)
return AsyncioEventLoop(event_loop=asyncio_event_loop)
12 changes: 11 additions & 1 deletion traits_futures/asyncio/tests/test_pingee.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Tests for the asyncio implementations of IPingee and IPinger.
"""

import asyncio
import unittest

from traits_futures.asyncio.event_loop import AsyncioEventLoop
Expand All @@ -20,5 +21,14 @@


class TestPingee(TestAssistant, IPingeeTests, unittest.TestCase):
def event_loop_factory(self):
"""
Factory for the event loop.
event_loop_factory = AsyncioEventLoop
Returns
-------
event_loop: IEventLoop
"""
asyncio_event_loop = asyncio.new_event_loop()
self.addCleanup(asyncio_event_loop.close)
return AsyncioEventLoop(event_loop=asyncio_event_loop)
17 changes: 13 additions & 4 deletions traits_futures/testing/test_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"""


import asyncio

from traits.api import Bool, HasStrictTraits

from traits_futures.asyncio.event_loop import AsyncioEventLoop
Expand Down Expand Up @@ -44,10 +46,17 @@ class TestAssistant:
Most of the logic is devolved to a toolkit-specific EventLoopHelper class.
"""

#: Factory for the event loop. This should be a zero-argument callable
#: that provides an IEventLoop instance. Override in subclasses to
#: run tests with a particular toolkit.
event_loop_factory = AsyncioEventLoop
def event_loop_factory(self):
"""
Factory for the event loop.
Returns
-------
event_loop: IEventLoop
"""
asyncio_event_loop = asyncio.new_event_loop()
self.addCleanup(asyncio_event_loop.close)
return AsyncioEventLoop(event_loop=asyncio_event_loop)

def setUp(self):
self._event_loop = self.event_loop_factory()
Expand Down

0 comments on commit 4530c9d

Please sign in to comment.