Skip to content

Support Python 3.14: stop relying on implicit event-loop creation#72

Merged
wbarnha merged 1 commit into
masterfrom
claude/py314-event-loop-compat
Jul 18, 2026
Merged

Support Python 3.14: stop relying on implicit event-loop creation#72
wbarnha merged 1 commit into
masterfrom
claude/py314-event-loop-compat

Conversation

@wbarnha

@wbarnha wbarnha commented Jul 18, 2026

Copy link
Copy Markdown
Member

Description

Python 3.14 removed the implicit creation of an event loop for the main
thread by asyncio.get_event_loop() / get_event_loop_policy().get_event_loop();
they now raise RuntimeError when no current loop is set. Mode resolves
Service.loop outside of a running loop (e.g. at import time, when services
and agents are declared at module scope), which broke imports on 3.14 —
for example faust's @app.agent() at module level.

Adds a get_event_loop() helper in mode.utils.loops that restores the
historical get-or-create semantics across Python 3.9–3.14 (running loop →
thread's current loop → create + register), and routes every
get_event_loop_policy().get_event_loop() call site through it. New unit
tests cover the running / current / absent-loop paths. No behaviour change on
3.9–3.13.

Verified: full test suite passes on Python 3.11 and 3.13 (735 passed each), ruff clean, and Service() instantiation with no current event loop set now succeeds.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd


Generated by Claude Code

Python 3.14 removed the long-deprecated behaviour where
asyncio.get_event_loop() / get_event_loop_policy().get_event_loop()
implicitly create and register an event loop for the main thread when none
is set; they now raise RuntimeError. Mode resolves Service.loop (and other
helpers) outside of a running loop -- notably at import time, when services
and agents are declared at module level -- so this broke downstream imports
on 3.14 (e.g. faust's `@app.agent()` at module scope).

Add a get_event_loop() helper in mode.utils.loops that restores the
historical get-or-create semantics in a way that works across 3.9-3.14:
return the running loop if any, else the thread's current loop if set, else
create and register a new one. Replace the
get_event_loop_policy().get_event_loop() call sites across services,
threads, locks, futures, logging and the gevent loop with it (and the lone
policy.new_event_loop() with asyncio.new_event_loop()).

Adds tests/unit/utils/test_loops.py covering the running/current/absent-loop
paths.

Assisted-by: Claude
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
@wbarnha
wbarnha merged commit a411c65 into master Jul 18, 2026
14 checks passed
@wbarnha
wbarnha deleted the claude/py314-event-loop-compat branch July 18, 2026 22:25
wbarnha pushed a commit to faust-streaming/faust that referenced this pull request Jul 19, 2026
Adds 3.14 to the test matrix, producing required 'Python 3.14/Cython:
true' and 'Python 3.14/Cython: false' jobs alongside the existing 3.13
legs. All matrix legs feed the aggregate '✅ Ensure the required checks
passing' job, so they become required wherever branch protection
requires that check.

Python 3.9 was already removed from the matrix and from python_requires
(>=3.10); the lingering 'Python 3.9/Cython: *' required status checks
exist only in branch protection and must be deleted there.

Note: the 3.14 legs need a mode-streaming release carrying the
event-loop fix (faust-streaming/mode#72) to pass, since Python 3.14
removed implicit event-loop creation that mode relied on at import time.

Assisted-by: Claude
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HgnKFtXZbCjXNoVNWa5JLd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants