Skip to content

Enforcing matplotlib.use("Agg") #2

Closed

Description

@StatisticianOne

Currently when importing any of the entropy calculator, _core is imported, which runs matplotlib.use("Agg") automatically, suppressing plots to show as per intended by this package.

However, in experimental environments (like working with notebooks during testing for example), users may want other upstream scripts to output plots automatically: e.g. running df['feature'].plot() to visually inspect the trend. With matplotlib.use("Agg"), pandas' Series.plot() doesn't work naively.

Perhaps should restrict the matplotlib.use("Agg") option to within the utils.plot module itself if users want a clean CI pipeline/Docker integration.

Activity

  1. self-assigned this
    on Jun 3, 2026
  2. Par-python commented on Jun 3, 2026

    @Par-python
    Owner

    Thanks for this, matplotlib.use("Agg") runs at import time in _core, and since every measure imports _core, just doing from entroscope import shannon quietly switches your whole backend and breaks things like df.plot() in a notebook. A library shouldn't be mutating global matplotlib state on import like that. Plan is to drop the forced backend entirely and let matplotlib resolve it normally (it already falls back to a non-interactive backend when there's no display), and move the headless guarantee into the test/CI environment instead. Working on it now.

  3. Par-python commented on Jun 3, 2026

    @Par-python
    Owner

    reproduced the bug:

    import matplotlib
    matplotlib.use("module://matplotlib_inline.backend_inline")  # what a notebook uses
    import matplotlib; print("before:", matplotlib.get_backend())
    
    from entroscope import shannon   # importing ANY measure pulls in _core
    
    print("after: ", matplotlib.get_backend())
    # before: module://matplotlib_inline.backend_inline
    # after:  agg      <-- _core forced it, df.plot() now broken
    

    Root cause was exactly what you said: _core.py ran matplotlib.use("Agg") at import time, and every measure imports _core, so just importing shannon flipped the whole backend.

    The fix: I pulled the forced backend out of _core entirely and let matplotlib resolve its own (it already falls back to a non-interactive backend when there's no display, so headless still works on its own).

    # entroscope/_core.py  (before)
    import matplotlib
    matplotlib.use("Agg")          # <-- removed
    import matplotlib.pyplot as plt
    
    # entroscope/_core.py  (after)
    import matplotlib.pyplot as plt   # importing pyplot does NOT force a backend
    

    I went slightly further than restricting it to utils.plot like you suggested, since even there it would still flip the backend globally whenever you called a plot function. Instead, nothing in the library touches the global backend now. For folks who want a guaranteed non-interactive backend in Docker/CI, the standard env var does it:

    export MPLBACKEND=Agg # or in a Dockerfile: ENV MPLBACKEND=Agg

    Added a regression test that imports entroscope in a subprocess under a non-Agg backend and asserts it stays put, so this can't silently come back. Fix is on a branch now, will ship in the next release. Thanks again for the clean report.

  4. added a commit that references this issue on Jun 3, 2026
    982abb0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions