-
Notifications
You must be signed in to change notification settings - Fork 164
Open
Description
glib event loop has its own event loop policy, and implicitly sets the event loop for the main thread. See https://pygobject.gnome.org/guide/asynchronous.html
Note this needs gobject >= 3.50.0, to have the native asyncio support.
This doesn't work with pytest-asyncio and results in exception as in title when pytest_fixture_setup
calls policy.set_event_loop(loop)
.
Minimal reproducer:
import asyncio
import pytest
from gi.events import GLibEventLoopPolicy
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
@pytest.mark.asyncio
async def test_foo():
pass
The output:
======================================= ERRORS =======================================
_____________________________ ERROR at setup of test_foo _____________________________
policy = <gi.events.GLibEventLoopPolicy object at 0x7f81069ecc20>
@contextlib.contextmanager
def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]:
old_loop_policy = _get_event_loop_policy()
try:
old_loop = _get_event_loop_no_warn()
except RuntimeError:
old_loop = None
_set_event_loop_policy(policy)
try:
> yield
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:543:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:757: in _scoped_runner
runner = Runner().__enter__()
usr/lib64/python3.14/asyncio/runners.py:59: in __enter__
self._lazy_init()
usr/lib64/python3.14/asyncio/runners.py:150: in _lazy_init
events.set_event_loop(self._loop)
usr/lib64/python3.14/asyncio/events.py:839: in set_event_loop
_get_event_loop_policy().set_event_loop(loop)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <gi.events.GLibEventLoopPolicy object at 0x7f81069ecc20>
loop = <GLibEventLoop running=False closed=False debug=False ctx=0x5FB5B1886F60 loop=0x5FB5B1B968A0>
def set_event_loop(self, loop):
"""Set the event loop for the current context (python thread) to loop.
This is only permitted if the thread has no thread default main context
with the main thread using the default main context.
"""
# Only accept glib event loops, otherwise things will just mess up
assert loop is None or isinstance(loop, GLibEventLoop)
ctx = ctx_td = GLib.MainContext.get_thread_default()
if ctx is None and threading.current_thread() is threading.main_thread():
ctx = GLib.MainContext.default()
if loop is None:
# We do permit unsetting the current loop/context
old = self._loops.pop(hash(ctx), None)
if old:
if hash(old._context) != hash(ctx):
warnings.warn(
"GMainContext was changed unknowingly by asyncio integration!",
RuntimeWarning,
)
if ctx_td:
GLib.MainContext.pop_thread_default(ctx_td)
else:
# Only allow attaching if the thread has no main context yet
if ctx:
> raise RuntimeError(
f"Thread {threading.current_thread().name!r} already has a main context, "
"get_event_loop() will create a new loop if needed"
)
E RuntimeError: Thread 'MainThread' already has a main context, get_event_loop() will create a new loop if needed
usr/lib64/python3.14/site-packages/gi/events.py:813: RuntimeError
During handling of the above exception, another exception occurred:
fixturedef = <FixtureDef argname='_function_scoped_runner' scope='function' baseid=''>
request = <SubRequest '_function_scoped_runner' for <Coroutine test_foo>>
@pytest.hookimpl(wrapper=True)
def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None:
asyncio_mode = _get_asyncio_mode(request.config)
if not _is_asyncio_fixture_function(fixturedef.func):
if asyncio_mode == Mode.STRICT:
# Ignore async fixtures without explicit asyncio mark in strict mode
# This applies to pytest_trio fixtures, for example
> return (yield)
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:681:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:756: in _scoped_runner
with _temporary_event_loop_policy(new_loop_policy):
usr/lib64/python3.14/contextlib.py:162: in __exit__
self.gen.throw(value)
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:546: in _temporary_event_loop_policy
_set_event_loop(old_loop)
usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:575: in _set_event_loop
asyncio.set_event_loop(loop)
usr/lib64/python3.14/asyncio/events.py:839: in set_event_loop
_get_event_loop_policy().set_event_loop(loop)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <gi.events.GLibEventLoopPolicy object at 0x7f81069ecc20>
loop = <GLibEventLoop running=False closed=False debug=False ctx=0x5FB5B1762B90 loop=0x5FB5B19B7110>
def set_event_loop(self, loop):
"""Set the event loop for the current context (python thread) to loop.
This is only permitted if the thread has no thread default main context
with the main thread using the default main context.
"""
# Only accept glib event loops, otherwise things will just mess up
assert loop is None or isinstance(loop, GLibEventLoop)
ctx = ctx_td = GLib.MainContext.get_thread_default()
if ctx is None and threading.current_thread() is threading.main_thread():
ctx = GLib.MainContext.default()
if loop is None:
# We do permit unsetting the current loop/context
old = self._loops.pop(hash(ctx), None)
if old:
if hash(old._context) != hash(ctx):
warnings.warn(
"GMainContext was changed unknowingly by asyncio integration!",
RuntimeWarning,
)
if ctx_td:
GLib.MainContext.pop_thread_default(ctx_td)
else:
# Only allow attaching if the thread has no main context yet
if ctx:
> raise RuntimeError(
f"Thread {threading.current_thread().name!r} already has a main context, "
"get_event_loop() will create a new loop if needed"
)
E RuntimeError: Thread 'MainThread' already has a main context, get_event_loop() will create a new loop if needed
usr/lib64/python3.14/site-packages/gi/events.py:813: RuntimeError
================================== warnings summary ==================================
usr/lib64/python3.14/site-packages/gi/events.py:718
/usr/lib64/python3.14/site-packages/gi/events.py:718: DeprecationWarning: 'asyncio.AbstractEventLoopPolicy' is deprecated and slated for removal in Python 3.16
class GLibEventLoopPolicy(asyncio.AbstractEventLoopPolicy):
testcase.py:5
/testcase.py:5: DeprecationWarning: 'asyncio.set_event_loop_policy' is deprecated and slated for removal in Python 3.16
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
============================== short test summary info ===============================
ERROR testcase.py::test_foo - RuntimeError: Thread 'MainThread' already has a main context, get_event_loop() wi...
Non-minimal failure: https://gitlab.com/QubesOS/qubes-desktop-linux-menu/-/jobs/11350892441
Metadata
Metadata
Assignees
Labels
No labels