Skip to content

Commit 148482c

Browse files
committed
PYTHON-5071 Bind to pytest's event loop so that it gets cleaned up at exit
1 parent 87cbd36 commit 148482c

File tree

4 files changed

+54
-44
lines changed

4 files changed

+54
-44
lines changed

test/__init__.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -872,25 +872,6 @@ class PyMongoTestCase(unittest.TestCase):
872872
if not _IS_SYNC:
873873
# An async TestCase that uses a single event loop for all tests.
874874
# Inspired by TestCase.
875-
def __init__(self, methodName="runTest"):
876-
super().__init__(methodName)
877-
self._loop = None
878-
879-
@property
880-
def loop(self):
881-
if self._loop:
882-
return self._loop
883-
try:
884-
with warnings.catch_warnings():
885-
# Ignore DeprecationWarning: There is no current event loop
886-
warnings.simplefilter("ignore", DeprecationWarning)
887-
loop = asyncio.get_event_loop()
888-
except RuntimeError:
889-
loop = asyncio.new_event_loop()
890-
asyncio.set_event_loop(loop)
891-
self._loop = loop
892-
return loop
893-
894875
def setUp(self):
895876
pass
896877

@@ -917,11 +898,11 @@ def _callCleanup(self, function, *args, **kwargs):
917898

918899
def _callAsync(self, func, /, *args, **kwargs):
919900
assert inspect.iscoroutinefunction(func), f"{func!r} is not an async function"
920-
return self.loop.run_until_complete(func(*args, **kwargs))
901+
return get_loop().run_until_complete(func(*args, **kwargs))
921902

922903
def _callMaybeAsync(self, func, /, *args, **kwargs):
923904
if inspect.iscoroutinefunction(func):
924-
return self.loop.run_until_complete(func(*args, **kwargs))
905+
return get_loop().run_until_complete(func(*args, **kwargs))
925906
else:
926907
return func(*args, **kwargs)
927908

@@ -1233,7 +1214,31 @@ def tearDown(self) -> None:
12331214
super().tearDown()
12341215

12351216

1217+
LOOP = None
1218+
1219+
1220+
def get_loop() -> asyncio.AbstractEventLoop:
1221+
global LOOP
1222+
if LOOP is None:
1223+
try:
1224+
LOOP = asyncio.get_running_loop()
1225+
except RuntimeError:
1226+
# no running event loop, fallback to get_event_loop.
1227+
try:
1228+
# Ignore DeprecationWarning: There is no current event loop
1229+
with warnings.catch_warnings():
1230+
warnings.simplefilter("ignore", DeprecationWarning)
1231+
LOOP = asyncio.get_event_loop()
1232+
except RuntimeError:
1233+
LOOP = asyncio.new_event_loop()
1234+
asyncio.set_event_loop(LOOP)
1235+
return LOOP
1236+
1237+
12361238
def setup():
1239+
if not _IS_SYNC:
1240+
global LOOP
1241+
LOOP = asyncio.get_running_loop()
12371242
client_context.init()
12381243
warnings.resetwarnings()
12391244
warnings.simplefilter("always")

test/asynchronous/__init__.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -874,25 +874,6 @@ class AsyncPyMongoTestCase(unittest.TestCase):
874874
if not _IS_SYNC:
875875
# An async TestCase that uses a single event loop for all tests.
876876
# Inspired by IsolatedAsyncioTestCase.
877-
def __init__(self, methodName="runTest"):
878-
super().__init__(methodName)
879-
self._loop = None
880-
881-
@property
882-
def loop(self):
883-
if self._loop:
884-
return self._loop
885-
try:
886-
with warnings.catch_warnings():
887-
# Ignore DeprecationWarning: There is no current event loop
888-
warnings.simplefilter("ignore", DeprecationWarning)
889-
loop = asyncio.get_event_loop()
890-
except RuntimeError:
891-
loop = asyncio.new_event_loop()
892-
asyncio.set_event_loop(loop)
893-
self._loop = loop
894-
return loop
895-
896877
async def asyncSetUp(self):
897878
pass
898879

@@ -919,11 +900,11 @@ def _callCleanup(self, function, *args, **kwargs):
919900

920901
def _callAsync(self, func, /, *args, **kwargs):
921902
assert inspect.iscoroutinefunction(func), f"{func!r} is not an async function"
922-
return self.loop.run_until_complete(func(*args, **kwargs))
903+
return get_loop().run_until_complete(func(*args, **kwargs))
923904

924905
def _callMaybeAsync(self, func, /, *args, **kwargs):
925906
if inspect.iscoroutinefunction(func):
926-
return self.loop.run_until_complete(func(*args, **kwargs))
907+
return get_loop().run_until_complete(func(*args, **kwargs))
927908
else:
928909
return func(*args, **kwargs)
929910

@@ -1251,7 +1232,31 @@ async def asyncTearDown(self) -> None:
12511232
await super().asyncTearDown()
12521233

12531234

1235+
LOOP = None
1236+
1237+
1238+
def get_loop() -> asyncio.AbstractEventLoop:
1239+
global LOOP
1240+
if LOOP is None:
1241+
try:
1242+
LOOP = asyncio.get_running_loop()
1243+
except RuntimeError:
1244+
# no running event loop, fallback to get_event_loop.
1245+
try:
1246+
# Ignore DeprecationWarning: There is no current event loop
1247+
with warnings.catch_warnings():
1248+
warnings.simplefilter("ignore", DeprecationWarning)
1249+
LOOP = asyncio.get_event_loop()
1250+
except RuntimeError:
1251+
LOOP = asyncio.new_event_loop()
1252+
asyncio.set_event_loop(LOOP)
1253+
return LOOP
1254+
1255+
12541256
async def async_setup():
1257+
if not _IS_SYNC:
1258+
global LOOP
1259+
LOOP = asyncio.get_running_loop()
12551260
await async_client_context.init()
12561261
warnings.resetwarnings()
12571262
warnings.simplefilter("always")

test/asynchronous/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def event_loop_policy():
2222
return asyncio.get_event_loop_policy()
2323

2424

25-
@pytest_asyncio.fixture(scope="package", autouse=True)
25+
@pytest_asyncio.fixture(scope="session", autouse=True)
2626
async def test_setup_and_teardown():
2727
await async_setup()
2828
yield

test/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def event_loop_policy():
2020
return asyncio.get_event_loop_policy()
2121

2222

23-
@pytest.fixture(scope="package", autouse=True)
23+
@pytest.fixture(scope="session", autouse=True)
2424
def test_setup_and_teardown():
2525
setup()
2626
yield

0 commit comments

Comments
 (0)