Skip to content

Conversation

@evakhoni
Copy link
Contributor

@evakhoni evakhoni commented Jan 26, 2026

Imported from jumpstarter-dev/jumpstarter-python#805

closes: #79

this PR fixes an issue where a client that is unable to load the full set of drivers exported (for example with exporter version newer than the client) is prevented form connecting to said exporter at all. the issue is remedied with the following changes:

  • during client side loading, a missing import is now non-fatal
  • stub driver is introduced to handle an attempt to access a missing driver
  • for j command it is skipped so the missing driver will not even be listed
  • a warning is displayed at jmp shell connection time, further duplicate warnings from j command are suppressed
  • a missing class in existing driver (in case the same driver on exporter side added functionality) is handled similarly
  • a missing driver and a missing class in a driver in exporterconfig is handled by the exporter

Summary by CodeRabbit

Release Notes

  • New Features

    • Added improved error handling for missing drivers with clear installation guidance in error messages, allowing the system to gracefully defer failures until a missing driver is actually invoked.
  • Bug Fixes

    • System no longer crashes immediately on startup when drivers are unavailable; now provides helpful error messages with installation instructions when you attempt to use them.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

Warning

Rate limit exceeded

@evakhoni has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

A StubDriverClient fallback is added for unavailable drivers; import failures now surface as MissingDriverError during class import and are optionally suppressed via an env var. Client initialization falls back to the stub and invoking stub methods raises a standardized ImportError with installation/version guidance.

Changes

Cohort / File(s) Summary
Core Stub & Exception
python/packages/jumpstarter/jumpstarter/client/base.py, python/packages/jumpstarter/jumpstarter/common/exceptions.py
Adds StubDriverClient (inherits DriverClient) that raises a formatted ImportError when used; introduces MissingDriverError(message, class_path) to represent missing driver imports.
Import Handling
python/packages/jumpstarter/jumpstarter/common/importlib.py, python/packages/jumpstarter/jumpstarter/common/importlib_test.py
Consolidates ModuleNotFoundError/AttributeError handling into MissingDriverError, adds _format_missing_driver_message() to build context-aware messages; tests updated to expect MissingDriverError.
Client Initialization
python/packages/jumpstarter/jumpstarter/client/client.py
Wraps dynamic driver import to catch MissingDriverError, logs a warning (suppressible via env var) and falls back to StubDriverClient instead of failing.
Tests for Stub Behavior
python/packages/jumpstarter/jumpstarter/client/base_test.py
New tests covering warning logging, stub creation, and that stub methods (call, streamingcall, stream, log_stream) raise ImportError with correct messages (version mismatch / pip install guidance).
Config & Env
python/packages/jumpstarter/jumpstarter/config/exporter.py, python/packages/jumpstarter/jumpstarter/common/utils.py
Exporter now translates MissingDriverError into a ConfigurationError with descriptive text; adds _JMP_SUPPRESS_DRIVER_WARNINGS to launch environment to optionally silence driver-missing warnings.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant Importer
participant Logger
participant Stub as StubDriverClient
Client->>Importer: import_class(driver_class_path)
alt Import succeeds
Importer-->>Client: DriverClass
Client->>Client: instantiate real DriverClient
else Import fails
Importer-->>Client: raise MissingDriverError(class_path)
Client->>Logger: warn about missing driver (unless suppressed)
Client->>Stub: instantiate StubDriverClient(class_path)
Client->>Stub: call/stream/...()
Stub-->>Client: raise ImportError (formatted message with install/version guidance)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • jumpstarter-dev/jumpstarter#297 — touches client log_stream and related log-stream implementations; likely related to stub log_stream behavior and tests.

Suggested reviewers

  • mangelajo

Poem

🐰 In codefields where drivers hide away,
A stub hops in to save the day,
It warns, it guides with version light,
And keeps the system calm and bright,
Hooray for stubs — and carrots tonight! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Gracefully handle missing driver client imports' directly and clearly describes the main objective of the changeset: implementing graceful handling of missing driver imports across the client initialization and stub driver system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@python/packages/jumpstarter/jumpstarter/client/base.py`:
- Around line 118-126: Change _get_missing_class_path to defensively access the
"jumpstarter.dev/client" label (e.g., use
self.labels.get("jumpstarter.dev/client")) and return a sensible fallback or
None; then update _raise_missing_error to handle a missing class_path by
constructing and raising the ImportError with a clear message (via
_format_missing_driver_message or a new message) instead of allowing a KeyError.
Ensure references are in _get_missing_class_path and _raise_missing_error so
malformed reports produce the intended ImportError with installation
instructions.

In `@python/packages/jumpstarter/jumpstarter/common/importlib.py`:
- Around line 66-76: The current ModuleNotFoundError handler masks transitive
dependency failures; change it to only convert to MissingDriverError when the
missing name matches the target module (or a submodule). Extract the module
portion from class_path (e.g., module_name = class_path.rsplit('.', 1)[0]) and
in the except ModuleNotFoundError as e block, if e.name == module_name or
e.name.startswith(module_name + ".") then raise
MissingDriverError(message=_format_missing_driver_message(class_path),
class_path=class_path) from e; otherwise re-raise the original
ModuleNotFoundError to preserve transitive dependency errors; keep the existing
AttributeError -> MissingDriverError behavior intact.
🧹 Nitpick comments (2)
python/packages/jumpstarter/jumpstarter/client/base.py (1)

15-15: Consider making the helper function public or moving it to a shared location.

_format_missing_driver_message is a private function (underscore prefix) being imported from another module. While this works, it couples base.py to the internal implementation of importlib.py. Consider either removing the underscore to make it a public API or moving it to a shared utilities module.

python/packages/jumpstarter/jumpstarter/config/exporter.py (1)

47-52: Consider preserving the exception chain for debugging.

Using from None suppresses the original exception chain, which can make debugging harder when diagnosing why a driver import failed. Consider preserving the cause.

Suggested change
             except MissingDriverError:
                 raise ConfigurationError(
                     f"Driver '{self.root.type}' is not installed. Please check exporter configuration."
-                ) from None
+                )

Or if you want a cleaner traceback while still showing the root cause:

except MissingDriverError as e:
    raise ConfigurationError(
        f"Driver '{self.root.type}' is not installed. Please check exporter configuration."
    ) from e

@evakhoni evakhoni force-pushed the import/python-pr-805 branch 2 times, most recently from 0e3663b to 95faca4 Compare January 26, 2026 14:57
@mangelajo
Copy link
Member

I have tried with a local jumpstarter install with not SNMP driver:


➜  python git:(import/python-pr-805) ✗ uv run jmp shell -l board-type=qc8650
[01/26/2026 15:51:13] INFO     INFO:jumpstarter.client.lease:Acquiring lease 019bfac9-9619-79b2-8229-504596502982 for selector board-type=qc8650 for duration 0:30:00                                                      lease.py:78
                      INFO     INFO:jumpstarter.client.lease:Waiting for ready connection at /var/folders/j_/9v47_5750_s85_5j4tybsx200000gn/T/jumpstarter-7zqlwbho/socket                                                 lease.py:252
[01/26/2026 15:51:15] WARNING  WARNING:jumpstarter.client.client:Driver client 'jumpstarter_driver_snmp.client.SNMPServerClient' is not available.                                                                        client.py:65
python ⚡qti-snapdragon-ride4-qam8650p-03 ➤ j
Usage: j [OPTIONS] COMMAND [ARGS]...

  Qualcomm Ride SX 4 SA8775P Exporter

Options:
  --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]
                                  Set the log level
  --help                          Show this message and exit.

Commands:
  acm             EPM serial interface
  fw-maintenance  Firmware maintenance commands
  power           Integrated Power control
  sail            SAIL serial output
  serial          Serial port client
  ssh             Run SSH command with arguments
  storage         RideSX storage operations for flashing images use flash...
  tcp             TCP connection to board
  tmt             Run TMT command with arguments
  vnc             Open a VNC session and block until the user closes it.
python ⚡qti-snapdragon-ride4-qam8650p-03 ➤ python
Python 3.13.7 (main, Sep 18 2025, 22:52:34) [Clang 20.1.4 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from jumpstarter.common.utils import env
...
... with env() as client:
...     print(client.children.keys())
...
dict_keys(['storage', 'power', 'power-external', 'serial', 'acm', 'sail', 'tcp', 'fw-maintenance', 'ssh', 'tmt', 'vnc'])
>>> from jumpstarter.common.utils import env
...
... with env() as client:
...     client.children['power-external'].on()
...
Traceback (most recent call last):
  File "<python-input-2>", line 4, in <module>
    client.children['power-external'].on()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'StubDriverClient' object has no attribute 'on'

I suspect we may want to just grab any call on the client for catching other non call/stream methods clients may have.

But I think that is better, I would merge it now, and iterate on that a continuation.

@evakhoni evakhoni force-pushed the import/python-pr-805 branch from 95faca4 to e68ae6a Compare January 26, 2026 15:00
Copy link
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment on the main conversation, that will need iteration, let's wait for the python tests to pass before trying to merge.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@python/packages/jumpstarter/jumpstarter/client/client.py`:
- Around line 59-66: The StubDriverClient should surface a helpful ImportError
whenever driver attributes or methods are accessed; modify StubDriverClient in
jumpstarter/client/base.py to accept and store the missing driver identifier
(e.g., class_path or driver name) when constructed, and implement __getattr__
(and if needed __getitem__) so any unknown attribute access immediately raises
ImportError with a clear message pointing to the missing driver identifier and
guidance, or alternatively return a proxy callable that raises that ImportError
when invoked; ensure the constructor signature matches how StubDriverClient is
instantiated from client.py so the missing driver info is available for the
error message.

@evakhoni
Copy link
Contributor Author

evakhoni commented Jan 26, 2026

See my comment on the main conversation, that will need iteration, let's wait for the python tests to pass before trying to merge.

@mangelajo
i have added a quick fix 085af1f haven't tested it on live system myself but seems simple enough to "just work" :)
the previous stuff btw is more or less properly tested

@mangelajo mangelajo self-requested a review January 26, 2026 15:20
@mangelajo mangelajo merged commit f669784 into jumpstarter-dev:main Jan 26, 2026
19 checks passed
@evakhoni evakhoni deleted the import/python-pr-805 branch January 26, 2026 17: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.

Allow clients not to have all exporter drivers installed

2 participants