Skip to content

fix: replace deprecated asyncio event loop patterns for Python 3.14 compatibility - #876

Closed
mangelajo wants to merge 10 commits into
jumpstarter-dev:mainfrom
mangelajo:upgrade-python-3.14
Closed

fix: replace deprecated asyncio event loop patterns for Python 3.14 compatibility#876
mangelajo wants to merge 10 commits into
jumpstarter-dev:mainfrom
mangelajo:upgrade-python-3.14

Conversation

@mangelajo

Copy link
Copy Markdown
Member

Summary

Built on top of #775 (Python 3.14 upgrade). After the SNMP driver was fixed in 769be00, we audited the entire codebase for the same deprecated asyncio event loop patterns. Found three more locations that need fixing.

Audit findings

We searched all Python packages for these deprecated patterns:

  • asyncio.get_event_loop() — deprecated since 3.10, emits DeprecationWarning in 3.12/3.13, RuntimeError in 3.14 (when no running loop)
  • asyncio.new_event_loop() + set_event_loop() + run_until_complete()set_event_loop() is deprecated since 3.10 and becomes a no-op in 3.14
  • asyncio.Event() created outside async context — requires get_running_loop() in 3.14, RuntimeError otherwise
  • asyncio.ensure_future() — deprecated since 3.10

Compatibility matrix

Pattern 3.12 3.13 3.14
get_event_loop() (no running loop) DeprecationWarning DeprecationWarning RuntimeError
set_event_loop() DeprecationWarning DeprecationWarning No-op / broken
asyncio.Event() (no running loop) DeprecationWarning DeprecationWarning RuntimeError
ensure_future() DeprecationWarning DeprecationWarning DeprecationWarning
get_running_loop() OK OK OK
asyncio.run() OK OK OK
asyncio.create_task() OK OK OK

Changes

1. TFTP driver — HIGH RISK (will crash on 3.14)

jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py

The _start_server() method used the exact same anti-pattern that was fixed in the SNMP driver:

# Before (broken on 3.14):
def _start_server(self):
    self._loop = asyncio.new_event_loop()
    asyncio.set_event_loop(self._loop)          # deprecated, no-op in 3.14
    self.server = TftpServer(...)               # creates asyncio.Event() — RuntimeError in 3.14!
    self._loop.run_until_complete(...)

Three problems:

  1. set_event_loop() is deprecated and becomes ineffective in 3.14
  2. TftpServer.__init__ creates asyncio.Event() objects (server.py lines 52, 61) before the loop is running — RuntimeError in 3.14
  3. Manual run_until_complete() + shutdown_asyncgens() + close() lifecycle

Fix: Replace with asyncio.run() and move TftpServer construction into an async method so asyncio.Event() objects are created with a running loop:

# After:
def _start_server(self):
    asyncio.run(self._run_server_lifecycle())

async def _run_server_lifecycle(self):
    self._loop = asyncio.get_running_loop()
    self.server = TftpServer(...)               # Event() now has a running loop
    self._loop_ready.set()
    await self._run_server()

2. Shell driver — MEDIUM RISK (deprecation warnings on 3.12/3.13)

jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py lines 201, 208

# Before:
start_time = asyncio.get_event_loop().time()
# After:
start_time = asyncio.get_running_loop().time()

Called from async def _run_inline_shell_script() so a loop IS running and it works today, but get_event_loop() emits DeprecationWarning in 3.12+. Trivial swap to the correct API.

3. mitmproxy example — LOW RISK (deprecated, still works)

jumpstarter-driver-mitmproxy/examples/addons/data_stream_websocket.py lines 134, 172

Replaced asyncio.ensure_future() (deprecated since 3.10) with asyncio.create_task().

What was already clean

The rest of the codebase (including core jumpstarter, network driver, qemu driver, pyserial driver, etc.) already uses the correct modern APIs (get_running_loop(), asyncio.run(), create_task()). The SNMP driver was fixed in 769be00.

jtligon and others added 10 commits July 8, 2026 11:13
Upgrade project Python version from 3.11/3.12 to 3.14.

Changes:
- Update .py-version to 3.14
- Update all requires-python fields to >=3.14 across all packages
- Update ruff target-version to py314
- Update CI to test with Python 3.14
- Update uv.lock with Python 3.14 compatible dependencies

This required upgrading pydantic-core to 2.46.4 which has Python 3.14
support via prebuilt wheels.

Note: Building with Python 3.14 requires:
- PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1 for Rust extensions
- OpenBLAS (macOS: brew install openblas)
- gfortran (macOS: brew install gcc)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add missing Apache-2.0 license to jumpstarter-driver-iscsi
- Remove duplicate pytest-asyncio dependency in jumpstarter-driver-http
- Fix jumpstarter-driver-opendal entry point group from adapters to drivers

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Revert jumpstarter-driver-opendal entry point back to jumpstarter.adapters
- Update driver template to use Python 3.12+ (was 3.11)
- Remove PR_DESCRIPTION.md file

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The .py-version file was updated to 3.14, so e2e tests also need
build dependencies for Pillow and scipy since they don't have
pre-built wheels for Python 3.14 yet.

Added the same dependency installation steps to all e2e jobs that
use .py-version:
- e2e-tests
- e2e-compat-old-controller
- e2e-compat-old-client

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This empty commit triggers CI to test the Python 3.14 build dependencies.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Pillow, scipy, and cffi need system libraries when building from source for Python 3.14.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add build dependencies to type-check-python workflow
- Fix .py-version to use full version 3.14.6 (e2e scripts require X.Y.Z format)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Python 3.14 requires explicit event loop management and doesn't allow
creating asyncio.Event() or calling get_event_loop() in sync contexts.

Changes:
- Made on() and off() methods async
- Simplified _snmp_set() to be async and use get_running_loop()
- Removed manual event loop creation/management code
- Updated all tests to use pytest.mark.asyncio
- Removed redundant event loop mocking in tests

This is a breaking API change - callers must now await on()/off().
However, many other Jumpstarter drivers already use async methods
(http, mitmproxy, ble, etc.), so this pattern is established.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove unused Tuple import
- Remove unused loop variable assignment

Fixes ruff linting errors.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ompatibility

- TFTP driver: replace new_event_loop()/set_event_loop()/run_until_complete()
  with asyncio.run(), and move TftpServer construction into async context
  so asyncio.Event() objects are created with a running loop (required by 3.14)
- Shell driver: replace get_event_loop() with get_running_loop() (called
  from async context, but get_event_loop() is deprecated since 3.10)
- mitmproxy example: replace ensure_future() with create_task()
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@mangelajo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee1db0cb-8d1a-4f13-98fe-c532f5a2de93

📥 Commits

Reviewing files that changed from the base of the PR and between d46e91a and 798cdd6.

⛔ Files ignored due to path filters (1)
  • python/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (74)
  • .github/workflows/documentation.yaml
  • .github/workflows/e2e.yaml
  • .github/workflows/lint.yaml
  • .github/workflows/python-tests.yaml
  • .py-version
  • python/__templates__/driver/pyproject.toml.tmpl
  • python/examples/android-emulator/pyproject.toml
  • python/examples/automotive/pyproject.toml
  • python/examples/soc-pytest/pyproject.toml
  • python/examples/xcp-ecu/pyproject.toml
  • python/packages/hatch-pin-jumpstarter/pyproject.toml
  • python/packages/jumpstarter-all/pyproject.toml
  • python/packages/jumpstarter-cli-admin/pyproject.toml
  • python/packages/jumpstarter-cli-common/pyproject.toml
  • python/packages/jumpstarter-cli-driver/pyproject.toml
  • python/packages/jumpstarter-cli/pyproject.toml
  • python/packages/jumpstarter-driver-adb/pyproject.toml
  • python/packages/jumpstarter-driver-androidemulator/pyproject.toml
  • python/packages/jumpstarter-driver-ble/pyproject.toml
  • python/packages/jumpstarter-driver-can/pyproject.toml
  • python/packages/jumpstarter-driver-composite/pyproject.toml
  • python/packages/jumpstarter-driver-corellium/pyproject.toml
  • python/packages/jumpstarter-driver-doip/pyproject.toml
  • python/packages/jumpstarter-driver-dut-network/pyproject.toml
  • python/packages/jumpstarter-driver-dutlink/pyproject.toml
  • python/packages/jumpstarter-driver-energenie/pyproject.toml
  • python/packages/jumpstarter-driver-esp32/pyproject.toml
  • python/packages/jumpstarter-driver-flashers/pyproject.toml
  • python/packages/jumpstarter-driver-gpiod/pyproject.toml
  • python/packages/jumpstarter-driver-http-power/pyproject.toml
  • python/packages/jumpstarter-driver-http/pyproject.toml
  • python/packages/jumpstarter-driver-iscsi/pyproject.toml
  • python/packages/jumpstarter-driver-mitmproxy/examples/addons/data_stream_websocket.py
  • python/packages/jumpstarter-driver-mitmproxy/pyproject.toml
  • python/packages/jumpstarter-driver-network/pyproject.toml
  • python/packages/jumpstarter-driver-noyito-relay/pyproject.toml
  • python/packages/jumpstarter-driver-opendal/pyproject.toml
  • python/packages/jumpstarter-driver-pi-pico/pyproject.toml
  • python/packages/jumpstarter-driver-power/pyproject.toml
  • python/packages/jumpstarter-driver-probe-rs/pyproject.toml
  • python/packages/jumpstarter-driver-pyserial/pyproject.toml
  • python/packages/jumpstarter-driver-qemu/pyproject.toml
  • python/packages/jumpstarter-driver-renode/pyproject.toml
  • python/packages/jumpstarter-driver-ridesx/pyproject.toml
  • python/packages/jumpstarter-driver-sdwire/pyproject.toml
  • python/packages/jumpstarter-driver-shell/jumpstarter_driver_shell/driver.py
  • python/packages/jumpstarter-driver-shell/pyproject.toml
  • python/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver.py
  • python/packages/jumpstarter-driver-snmp/jumpstarter_driver_snmp/driver_test.py
  • python/packages/jumpstarter-driver-snmp/pyproject.toml
  • python/packages/jumpstarter-driver-someip/pyproject.toml
  • python/packages/jumpstarter-driver-ssh-mitm/pyproject.toml
  • python/packages/jumpstarter-driver-ssh-mount/pyproject.toml
  • python/packages/jumpstarter-driver-ssh/pyproject.toml
  • python/packages/jumpstarter-driver-stlink-msd/pyproject.toml
  • python/packages/jumpstarter-driver-tasmota/pyproject.toml
  • python/packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py
  • python/packages/jumpstarter-driver-tftp/pyproject.toml
  • python/packages/jumpstarter-driver-tmt/pyproject.toml
  • python/packages/jumpstarter-driver-uboot/pyproject.toml
  • python/packages/jumpstarter-driver-uds-can/pyproject.toml
  • python/packages/jumpstarter-driver-uds-doip/pyproject.toml
  • python/packages/jumpstarter-driver-uds/pyproject.toml
  • python/packages/jumpstarter-driver-ustreamer/pyproject.toml
  • python/packages/jumpstarter-driver-vnc/pyproject.toml
  • python/packages/jumpstarter-driver-xcp/pyproject.toml
  • python/packages/jumpstarter-driver-yepkit/pyproject.toml
  • python/packages/jumpstarter-imagehash/pyproject.toml
  • python/packages/jumpstarter-kubernetes/pyproject.toml
  • python/packages/jumpstarter-mcp/pyproject.toml
  • python/packages/jumpstarter-protocol/pyproject.toml
  • python/packages/jumpstarter-testing/pyproject.toml
  • python/packages/jumpstarter/pyproject.toml
  • python/pyproject.toml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mangelajo
mangelajo marked this pull request as draft July 10, 2026 09:49
@mangelajo mangelajo closed this Jul 10, 2026
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