fix: Use asyncio.run for full Python 3.14 compatibility - #124
Merged
Conversation
Python 3.14 turns the long-deprecated implicit-loop creation in asyncio.get_event_loop() into a hard RuntimeError. PR #123 guarded only Configurator.collect_diffs(), leaving Configurator.push_configs() and ConfigDiffBase.get_actual_configs() broken, and relied on asyncio.set_event_loop(), which 3.14 itself deprecates for removal in 3.16. Replace all three call sites with asyncio.run(), which is correct on 3.10 through 3.16+, needs no try/except, and closes the loop and shuts down async generators instead of leaking them. asyncio.gather() in get_actual_configs() is wrapped in a new _get_actual_configs() coroutine, since gather() also requires a running loop. Drop the blanket "-p no:warnings" from pytest addopts: it hid this DeprecationWarning for the two releases before it became an error. Add 3.13/3.14 to the classifiers and the lint matrix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #123, which fixed only one of three broken call sites and used an API that 3.14 itself deprecates.
Background
Python 3.14 turns the long-deprecated implicit-loop creation in
asyncio.get_event_loop()into a hardRuntimeError. Verified on CPython 3.14.0a6:What #123 left broken
Configurator.push_configs()— a separate job entry point (jobs.py:35, enqueued fromviews/configuration.pyandapi/views.py) that never callscollect_diffs()first. Under the RQ work-horse it is the first asyncio call in a fresh process, so applying a ConfigurationRequest still died withRuntimeErrorbefore touching a device.ConfigDiffBase.get_actual_configs()— the connect-to-device branch taken wheneverdata_sourceandcustom_fieldare both empty, i.e. the plugin's headline compliance script.asyncio.set_event_loop()— deprecated in 3.14, removed in 3.16, so the guard buys one release and breaks again.trywrapped onlyget_event_loop(). A thread that already has a closed loop set (asgiref'sAsyncToSync, or anyset_event_loop(l)+l.close()) gets that loop back without raising, andrun_until_complete()then fails withRuntimeError: Event loop is closed. The newly created loop was also never closed, leaking an epoll fd plus self-pipe per thread.This PR
All three call sites use
asyncio.run(), which is correct on 3.10 through 3.16+, needs notry/except, and closes the loop and shuts down async generators.asyncio.gather()inget_actual_configs()is wrapped in a new_get_actual_configs()coroutine, sincegather()also requires a running loop.Dropped the blanket
-p no:warningsfrom pytestaddopts— it hid this exactDeprecationWarningfor the two releases (3.12, 3.13) before it became an error, and would have hidden the newset_event_loopone the same way. Added 3.13/3.14 to the classifiers and the lint matrix.The
compliance/secrets.pyline is unrelated: a pre-existing over-indentedpassthatruff formatnormalizes. CI runsruff format ., which rewrites files rather than failing, so it was never caught. Happy to split it out.ruff formatandruff checkpass; noget_event_loop/new_event_loop/set_event_loop/run_until_completeremains undernetbox_config_diff/.🤖 Generated with Claude Code