Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions ultraplot/internals/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,14 @@ def copy(self):
if not hasattr(RcParams, "validate"): # not mission critical so skip
warnings._warn_ultraplot("Failed to update matplotlib rcParams validators.")
else:

def _validator_accepts(validator, value):
try:
validator(value)
return True
except Exception:
return False

_validate = RcParams.validate
_validate["image.cmap"] = _validate_cmap("continuous")
_validate["legend.loc"] = _validate_belongs(*LEGEND_LOCS)
Expand All @@ -752,6 +760,20 @@ def copy(self):
_validate[_key] = functools.partial(_validate_color, alternative="auto")
if _validator is getattr(msetup, "validate_color_or_inherit", None):
_validate[_key] = functools.partial(_validate_color, alternative="inherit")
# Matplotlib may wrap fontsize validators in callable objects instead of
# exposing validate_fontsize directly. Detect these by behavior so custom
# shorthands like "med-large" remain valid regardless of import order.
if (
_key.endswith("size")
and _key not in FONT_KEYS
and _validator_accepts(_validator, "large")
and not _validator_accepts(_validator, "med-large")
):
FONT_KEYS.add(_key)
if _validator_accepts(_validator, None):
_validate[_key] = _validate_or_none(_validate_fontsize)
else:
_validate[_key] = _validate_fontsize
for _keys, _validator_replace in ((EM_KEYS, _validate_em), (PT_KEYS, _validate_pt)):
for _key in _keys:
_validator = _validate.get(_key, None)
Expand Down
46 changes: 46 additions & 0 deletions ultraplot/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import importlib
import os
import pathlib
import subprocess
import sys
import threading
from queue import Queue

Expand Down Expand Up @@ -212,3 +216,45 @@ def _reader():
observed = [results.get() for _ in range(results.qsize())]
assert observed, "No rcParams observations were recorded."
assert all(value in allowed for value in observed)


def _run_in_subprocess(code):
code = (
"import pathlib\n"
"import sys\n"
"sys.path.insert(0, str(pathlib.Path.cwd()))\n" + code
)
env = os.environ.copy()
env["MPLBACKEND"] = "Agg"
return subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
cwd=str(pathlib.Path(__file__).resolve().parents[2]),
env=env,
)


def test_matplotlib_import_before_ultraplot_allows_rc_mutation():
"""
Import order regression test for issue #568.
"""
result = _run_in_subprocess(
"import matplotlib.pyplot as plt\n"
"import ultraplot as uplt\n"
"uplt.rc['figure.facecolor'] = 'white'\n"
)
assert result.returncode == 0, result.stderr


def test_matplotlib_import_before_ultraplot_allows_custom_fontsize_tokens():
"""
Ensure patched fontsize validators are active regardless of import order.
"""
result = _run_in_subprocess(
"import matplotlib.pyplot as plt\n"
"import ultraplot as uplt\n"
"for key in ('axes.titlesize', 'figure.titlesize', 'legend.fontsize', 'xtick.labelsize'):\n"
" uplt.rc[key] = 'med-large'\n"
)
assert result.returncode == 0, result.stderr
Loading