Skip to content

plot: fail fast with guidance when interactive backend fails to load - #449

Open
yarikoptic-gitmate wants to merge 8 commits into
mainfrom
claude/confident-johnson-8vj13f
Open

yarikoptic-gitmate wants to merge 8 commits into
mainfrom
claude/confident-johnson-8vj13f

Conversation

@yarikoptic-gitmate

Copy link
Copy Markdown
Collaborator

Previously, con-duct only checked that MPLBACKEND named a known
interactive backend, not that it actually loaded. A backend that
matched by name but failed to import (e.g. TkAgg with a broken/missing
tkinter) only surfaced as a raw traceback deep inside plt.subplots(),
with no guidance (#447).

Move the interactivity check earlier, before any data loading/plot
building, and actually try to load the backend module there so a
failure is caught with an actionable message pointing at --output or
a different MPLBACKEND value, instead of an unguided traceback.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf

Previously, con-duct only checked that MPLBACKEND named a known
interactive backend, not that it actually loaded. A backend that
matched by name but failed to import (e.g. TkAgg with a broken/missing
tkinter) only surfaced as a raw traceback deep inside plt.subplots(),
with no guidance (#447).

Move the interactivity check earlier, before any data loading/plot
building, and actually try to load the backend module there so a
failure is caught with an actionable message pointing at --output or
a different MPLBACKEND value, instead of an unguided traceback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf
When the current backend isn't recognized as interactive, name the
built-in interactive backends matplotlib knows about (one of which
needs a GUI toolkit installed) instead of just pointing at MPLBACKEND
in the abstract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf
If MPLBACKEND isn't set and the resolved matplotlib backend can't
display interactively, try loading each builtin interactive backend
ourselves (native GUI toolkits first, then browser/cairo variants
matplotlib's own auto-fallback doesn't consider, e.g. webagg) and use
the first one that actually works, instead of just reporting the
non-interactive backend matplotlib gave up on.

When MPLBACKEND is set explicitly, that choice is respected as before:
report the failure rather than silently overriding it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf
backend_registry.load_backend_module() doesn't consistently raise
ImportError for a missing dependency -- e.g. webagg raises RuntimeError
when tornado is missing. The pinned-backend branch only caught
ImportError, so a pinned backend failing this way (e.g.
MPLBACKEND=webagg without tornado installed) leaked a raw traceback
instead of the intended friendly message. Widen it to Exception, same
as the auto-probe helper already does for the same reason.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf
@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.02%. Comparing base (fa85e5c) to head (d47fe23).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #449   +/-   ##
=======================================
  Coverage   92.02%   92.02%           
=======================================
  Files          15       15           
  Lines        1316     1342   +26     
  Branches      179      182    +3     
=======================================
+ Hits         1211     1235   +24     
- Misses         70       71    +1     
- Partials       35       36    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@yarikoptic yarikoptic added the semver-minor Increment the minor version when merged label Sep 21, 2026
claude and others added 2 commits September 21, 2026 14:17
The oldestdeps CI job (Python 3.10 + matplotlib 3.5, our documented
floor per PR #322/issue #320 for Debian Bookworm compat) was failing:
tests that decorate a test function with
@patch("matplotlib.backends.backend_registry....") have that patch
target resolved before the test body runs, so an inline
`if not hasattr(...): pytest.skip(...)` inside the function never gets
a chance to run on matplotlib < 3.9, which has no backend_registry
module at all -- the decorator itself raises first.

Add a shared `requires_backend_registry` skipif marker, evaluated at
collection time before pytest calls the (patch-wrapped) test function
at all, and apply it to every test that patches backend_registry
directly. Verified against a real matplotlib 3.5.3 install: previously
5 errors, now those tests skip cleanly and the rest of the suite
passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf

Copy link
Copy Markdown
Collaborator Author

test (macos-15-intel, 3.10, py) and test (macos-15-intel, 3.11, py) are failing on test_spawn_children (an unrelated e2e process-spawning test, already @pytest.mark.flaky(reruns=3)) — this matches the known pre-existing runner flakiness tracked in #439, not this PR's diff (which only touches plot.py/test_plot.py). Re-running those two jobs once to confirm.

The oldestdeps (Python 3.10 + matplotlib 3.5) job that was previously failing here is now green after 3bd3207.


Generated by Claude Code

_INTERACTIVE_BACKEND_PROBE_ORDER's own entries are always builtin
interactive backend names, so "candidate not in interactive_backends"
can't fire for them in practice, and the loop's broad except Exception
already handles a candidate that fails to resolve to a real backend
module (load_backend_module on a bad name just raises ModuleNotFoundError,
which that clause catches). Dead code identified by codecov's patch
coverage flagging it as the one uncovered line/branch in the PR.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hv454Dwv5QSLfUArTtdYyf

Copy link
Copy Markdown
Collaborator Author

codecov/project: patch coverage is now 100% ("All modified and coverable lines are covered by tests") after d47fe23 removed the one genuinely dead defensive line codecov had flagged. The remaining -0.15% project-level dip is just the aggregate diluting slightly from adding new (fully-covered) code to the denominator — there's no codecov.yml here enforcing a stricter blocking threshold, and chasing it further would mean adding tests for pre-existing, unrelated uncovered lines elsewhere in the file rather than anything this PR's diff introduced. Considering this resolved on this PR's side.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-minor Increment the minor version when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants