Subject: bug#70687: python-matplotlib not respecting env var MPLBACKEND=TkAgg

From: Jake <[email-masked]>
Date: Fri, 4 Jul 2025 17:39:13 +0930

Fixed, I think.  We just need to patch a dlopen call.

Now TkAgg works out of the box, without requiring changes to user Python
code.  I hope that also means we can change the default backend from Agg to
TkAgg.

Submitted this PR (and got lucky number 1000!)
https://codeberg.org/guix/guix/pulls/1000
with the fix and change of backend.

Cheers
Jake

On Thu, 3 Jul 2025 at 11:18 pm, Jake <[email-masked]> wrote:

> Continuing:
> In `lib/matplotlib/cbook.py`, the function
> `_get_running_interactive_framework` has:
>
> ```text
>     if not _c_internal_utils.display_is_valid():
>         return "headless"
> ```
>
> In `src/_c_internal_utils.c` we have this docstring for `display_is_valid`:
>
> ```text
>      "Check whether the current X11 or Wayland display is valid.\n\n"
>      "On Linux, returns True if either $DISPLAY is set and
> XOpenDisplay(NULL)\n"
>      "succeeds, or $WAYLAND_DISPLAY is set and wl_display_connect(NULL)\n"
>      "succeeds.\n\n"
>      "On other platforms, always returns True."},
> ```
>
> $DISPLAY is set, so it must be XOpenDisplay(NULL) failing...
>
> I found a post [1] from a Nix user:
> > For anyone else facing similar issues - I resolved this by adding the
> X11 library to the LD_LIBRARY_PATH of my development shell.
>
> But I haven't made any more progress.
>
> Cheers
> Jake
>
> [1]
> https://discourse.nixos.org/t/python-matplotlib-tkinter-matplotlib-reverts-to-agg-backend-because-it-cant-find-a-valid-display/45064
>
> On Thu, Jul 3, 2025 at 1:19 PM Jake <[email-masked]> wrote:
> >
> > Hi Lily
> >
> > It looks like this bug isn't going away anytime soon.
> >
> > Here is the offending code in `lib/matplotlib/pyplot.py`, with an
> > added print statement:
> >
> > ```text
> > # If rcParams['backend_fallback'] is true, and an interactive backend is
> > # requested, ignore rcParams['backend'] and force selection of a backend
> that
> > # is compatible with the current running interactive framework.
> > if (rcParams["backend_fallback"]
> >         and rcParams._get_backend_or_none() in (  # type: ignore
> >             set(rcsetup.interactive_bk) - {'WebAgg', 'nbAgg'})
> >         and cbook._get_running_interactive_framework()):  # type: ignore
> >     print(f"running interactive framework is:
> > {cbook._get_running_interactive_framework()}")
> >     rcParams._set("backend", rcsetup._auto_backend_sentinel)  # type:
> ignore
> > ```
> >
> > Running this
> >
> > ```text
> > MPLBACKEND=tkagg guix shell
> > --with-source=python-matplotlib=$HOME/matplotlib
> > --without-tests=python-matplotlib python python-matplotlib -- python3
> > -c "import matplotlib; print(matplotlib.get_backend()); import
> > matplotlib.pyplot as plt; print(matplotlib.get_backend())"
> > ```
> >
> > gives
> >
> > ```text
> > TkAgg
> > running interactive framework is: headless
> > agg
> > ```
> >
> > So I guess the next question is: why does it think I'm headless?  To
> > be continued...
> >
> > Thanks
> > Jake
> >
> >
> > On Sat, Apr 5, 2025 at 11:00 PM Liliana Marie Prikler
> > <[email-masked]> wrote:
> > >
> > > Hi Jake,
> > >
> > > Am Donnerstag, dem 03.04.2025 um 11:36 +0000 schrieb Jake:
> > > > It appears that importing matplotlib.pyplot resets the Matplotlib
> > > > backend to Agg.
> > > >
> > > >   $ guix shell python python-matplotlib -- bash -c 'MPLBACKEND=tkagg
> > > > python3 -c "import matplotlib; print(matplotlib.get_backend());
> > > > import matplotlib.pyplot; print(matplotlib.get_backend())"'
> > > >
> > > >   TkAgg
> > > >   agg
> > > I recently encountered the same issue.  The issue is that matplotlib
> > > internally sources a configuration file that sets the backend *after*
> > > reading the environment variable.  To circumvent this, you use
> > > something along the lines of the following code until the issue is
> > > fixed:
> > >
> > >   from matplotlib import set_backend
> > >   from os import environ
> > >
> > >   […]
> > >
> > >   if __name__ == '__main__':
> > >     if 'MPLBACKEND' in environ: set_backend(environ['MPLBACKEND'])
> > >     […]
> > >
> > > Cheers
>