From 7cb77cc35eed822f3a2eff9682ef4acaf2a5249d Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 12 Mar 2026 15:08:44 -0600 Subject: [PATCH] Blacklist figure.hooks from matplotlibrc and style files --- galleries/examples/user_interfaces/mplcvd.py | 6 ++++-- lib/matplotlib/__init__.py | 16 ++++++++++++++-- lib/matplotlib/mpl-data/matplotlibrc | 3 ++- lib/matplotlib/rcsetup.py | 4 ++++ lib/matplotlib/style/__init__.py | 3 ++- lib/matplotlib/tests/test_rcparams.py | 14 ++++++++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/galleries/examples/user_interfaces/mplcvd.py b/galleries/examples/user_interfaces/mplcvd.py index 967cb7a38779..89db7f800dd8 100644 --- a/galleries/examples/user_interfaces/mplcvd.py +++ b/galleries/examples/user_interfaces/mplcvd.py @@ -3,8 +3,10 @@ =================================== To use this hook, ensure that this module is in your ``PYTHONPATH``, and set -``rcParams["figure.hooks"] = ["mplcvd:setup"]``. This hook depends on -the ``colorspacious`` third-party module. +``rcParams["figure.hooks"] = ["mplcvd:setup"]`` in your Python code. Note +that ``figure.hooks`` must be set programmatically and cannot be set from +``matplotlibrc`` or style files. This hook depends on the ``colorspacious`` +third-party module. """ import functools diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index b4f3cc7d21df..427a4a0d6ec1 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -876,7 +876,8 @@ def _open_file_or_url(fname): yield f -def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): +def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False, + use_file_blacklist=True): """ Construct a `RcParams` instance from file *fname*. @@ -892,6 +893,9 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): before further parsing. fail_on_error : bool, default: False Whether invalid entries should result in an exception or a warning. + use_file_blacklist : bool, default: True + If True, filter out rcParams in `_RCPARAMS_FROM_FILE_BLACKLIST` that + should only be set programmatically (e.g. ``figure.hooks``). """ import matplotlib as mpl rc_temp = {} @@ -924,6 +928,13 @@ def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False): config = RcParams() for key, (val, line, line_no) in rc_temp.items(): + if use_file_blacklist and key in rcsetup._RCPARAMS_FROM_FILE_BLACKLIST: + _log.warning('%r is not supported in config files and will be ' + 'ignored; set it programmatically with ' + '`matplotlib.rcParams[%r] = ...` instead. ' + '(file %r, line %d)', + key, key, fname, line_no) + continue if key in rcsetup._validators: if fail_on_error: config[key] = val # try to convert to proper type or raise @@ -988,7 +999,8 @@ def rc_params_from_file(fname, fail_on_error=False, use_default_template=True): cbook._get_data_path("matplotlibrc"), # Strip leading comment. transform=lambda line: line[1:] if line.startswith("#") else line, - fail_on_error=True) + fail_on_error=True, + use_file_blacklist=False) rcParamsDefault._update_raw(rcsetup._hardcoded_defaults) rcParamsDefault._ensure_has_backend() diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index e934109ee492..2bfea1f78116 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -103,7 +103,8 @@ #backend_fallback: True #interactive: False -#figure.hooks: # list of dotted.module.name:dotted.callable.name +#figure.hooks: # list of dotted.module.name:dotted.callable.name (must be set + # programmatically; ignored if set in this file or a style file) #toolbar: toolbar2 # {None, toolbar2, toolmanager} #timezone: UTC # a pytz timezone string, e.g., US/Central or Europe/Paris diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index e56da5200386..40f91d5cf795 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1445,6 +1445,10 @@ def _convert_validator_spec(key, conv): _validators = {k: _convert_validator_spec(k, conv) for k, conv in _validators.items()} +# rcParams that can execute arbitrary code and should only be set +# programmatically, never loaded from config files. +_RCPARAMS_FROM_FILE_BLACKLIST = {'figure.hooks'} + @dataclass class _Param: diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py index 80c6de00a18d..1a919ee1eeaa 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -37,7 +37,8 @@ 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', 'toolbar', 'timezone', 'figure.max_open_warning', 'figure.raise_window', 'savefig.directory', 'tk.window_focus', - 'docstring.hardcopy', 'date.epoch'} + 'docstring.hardcopy', 'date.epoch', + 'figure.hooks'} @_docstring.Substitution( diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 525a9ff60d1a..103b86305840 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -657,6 +657,20 @@ def test_rcparams_path_sketch_from_file(tmp_path, value): assert mpl.rcParams["path.sketch"] == (1, 2, 3) +def test_file_blacklist(tmp_path, caplog): + """Blacklisted rcParams should be ignored with a warning when loaded.""" + rc_path = tmp_path / "matplotlibrc" + rc_path.write_text("figure.hooks: my_module:my_func\nlines.linewidth: 42") + + with caplog.at_level("WARNING", logger="matplotlib"): + with mpl.rc_context(fname=rc_path): + # Blacklisted param should be unchanged (default is []). + assert mpl.rcParams["figure.hooks"] == [] + # Non-blacklisted param should load normally. + assert mpl.rcParams["lines.linewidth"] == 42 + assert "not supported in config files" in caplog.text + + @pytest.mark.parametrize('group, option, alias, value', [ ('lines', 'linewidth', 'lw', 3), ('lines', 'linestyle', 'ls', 'dashed'),