Skip to content

fix: Use asyncio.run for full Python 3.14 compatibility - #124

Merged
miaow2 merged 1 commit into
mainfrom
fix/python-3.14-asyncio-run
Aug 22, 2026
Merged

fix: Use asyncio.run for full Python 3.14 compatibility#124
miaow2 merged 1 commit into
mainfrom
fix/python-3.14-asyncio-run

Conversation

@miaow2

@miaow2 miaow2 commented Aug 22, 2026

Copy link
Copy Markdown
Owner

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 hard RuntimeError. Verified on CPython 3.14.0a6:

asyncio.get_event_loop()      -> RuntimeError: There is no current event loop in thread 'MainThread'.
asyncio.set_event_loop(loop)  -> DeprecationWarning: 'asyncio.set_event_loop' is deprecated
                                 and slated for removal in Python 3.16

What #123 left broken

  • Configurator.push_configs() — a separate job entry point (jobs.py:35, enqueued from views/configuration.py and api/views.py) that never calls collect_diffs() first. Under the RQ work-horse it is the first asyncio call in a fresh process, so applying a ConfigurationRequest still died with RuntimeError before touching a device.
  • ConfigDiffBase.get_actual_configs() — the connect-to-device branch taken whenever data_source and custom_field are 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.
  • The guard covered the wrong statement. try wrapped only get_event_loop(). A thread that already has a closed loop set (asgiref's AsyncToSync, or any set_event_loop(l) + l.close()) gets that loop back without raising, and run_until_complete() then fails with RuntimeError: 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 no try/except, and closes the loop and shuts down async generators. asyncio.gather() in get_actual_configs() is wrapped in a new _get_actual_configs() coroutine, since gather() also requires a running loop.

Dropped the blanket -p no:warnings from pytest addopts — it hid this exact DeprecationWarning for the two releases (3.12, 3.13) before it became an error, and would have hidden the new set_event_loop one the same way. Added 3.13/3.14 to the classifiers and the lint matrix.

The compliance/secrets.py line is unrelated: a pre-existing over-indented pass that ruff format normalizes. CI runs ruff format ., which rewrites files rather than failing, so it was never caught. Happy to split it out.

ruff format and ruff check pass; no get_event_loop/new_event_loop/set_event_loop/run_until_complete remains under netbox_config_diff/.

🤖 Generated with Claude Code

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>
@miaow2
miaow2 merged commit b2a4460 into main Aug 22, 2026
10 checks passed
@miaow2
miaow2 deleted the fix/python-3.14-asyncio-run branch August 22, 2026 14:57
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.

1 participant