Skip to content

Commit 94e7b03

Browse files
committed
Load style files from third-party packages.
1 parent ca2db5e commit 94e7b03

File tree

4 files changed

+64
-13
lines changed

4 files changed

+64
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Style files can be imported from third-party packages
2+
-----------------------------------------------------
3+
4+
Third-party packages can now distribute style files that are globally available
5+
as follows. Assume that a package is importable as ``import my.package``, with
6+
a ``my/package/__init__.py`` module. Then a
7+
``my/package/presentation.mplstyle`` style sheet can be used as
8+
``plt.style.use("my.package.presentation")``. (Note that the implementation
9+
does not actually import ``my.package``, making this process safe against
10+
possible import-time side effects.)

lib/matplotlib/style/core.py

+35-13
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"""
1313

1414
import contextlib
15+
import importlib.resources
1516
import logging
1617
import os
1718
from pathlib import Path
@@ -63,20 +64,27 @@ def use(style):
6364
Parameters
6465
----------
6566
style : str, dict, Path or list
66-
A style specification. Valid options are:
6767
68-
+------+-------------------------------------------------------------+
69-
| str | The name of a style or a path/URL to a style file. For a |
70-
| | list of available style names, see `.style.available`. |
71-
+------+-------------------------------------------------------------+
72-
| dict | Dictionary with valid key/value pairs for |
73-
| | `matplotlib.rcParams`. |
74-
+------+-------------------------------------------------------------+
75-
| Path | A path-like object which is a path to a style file. |
76-
+------+-------------------------------------------------------------+
77-
| list | A list of style specifiers (str, Path or dict) applied from |
78-
| | first to last in the list. |
79-
+------+-------------------------------------------------------------+
68+
A style specification.
69+
70+
- If a str, this can be one of the style names in `.style.available`
71+
(a builtin style or a style installed in the user library path).
72+
73+
This can also be a dotted name of the form "package.name.style_name";
74+
in that case, "package.name" should be an importable Python package
75+
name, e.g. at ``/path/to/package/name/__init__.py``; the loaded style
76+
file is ``/path/to/package/name/style_name.mplstyle``.
77+
78+
This can also be the path or URL to a style file, which gets loaded
79+
by `.rc_params_from_file`.
80+
81+
- If a dict, this is a mapping of key/value pairs for `.rcParams`.
82+
83+
- If a Path, this is the path to a style file, which gets loaded by
84+
`.rc_params_from_file`.
85+
86+
- If a list, this is a a list of style specifiers (str, Path or dict),
87+
which get applied from first to last in the list.
8088
8189
Notes
8290
-----
@@ -130,6 +138,20 @@ def use(style):
130138
if k not in STYLE_BLACKLIST}
131139
elif style in library:
132140
style = library[style]
141+
elif "." in style:
142+
pkg, name = style.rsplit(".")
143+
try:
144+
with importlib.resources.path(
145+
pkg, f"{name}.{STYLE_EXTENSION}") as path:
146+
style = _rc_params_in_file(path)
147+
except (ModuleNotFoundError, IOError):
148+
# There is an ambiguity whether a dotted name refers to a
149+
# package.style_name or to a dotted file path. Currently,
150+
# we silently try the first form and then the second one;
151+
# in the future, we may consider forcing file paths to
152+
# either use Path objects or be prepended with "./" and use
153+
# the slash as marker for file paths.
154+
pass
133155
if isinstance(style, (str, Path)):
134156
try:
135157
style = _rc_params_in_file(style)

lib/matplotlib/tests/test_style.py

+10
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,13 @@ def test_deprecated_seaborn_styles():
188188

189189
def test_up_to_date_blacklist():
190190
assert mpl.style.core.STYLE_BLACKLIST <= {*mpl.rcsetup._validators}
191+
192+
193+
def test_style_from_module(tmp_path, monkeypatch):
194+
monkeypatch.syspath_prepend(tmp_path)
195+
pkg_path = tmp_path / "mpl_test_style_pkg"
196+
pkg_path.mkdir()
197+
(pkg_path / "test_style.mplstyle").write_text(
198+
f"{PARAM}: {VALUE}", encoding="utf-8")
199+
mpl.style.use("mpl_test_style_pkg.test_style")
200+
assert mpl.rcParams[PARAM] == VALUE

tutorials/introductory/customizing.py

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ def plotting_function():
137137
# >>> import matplotlib.pyplot as plt
138138
# >>> plt.style.use('./images/presentation.mplstyle')
139139
#
140+
# You can include style sheets into standard importable Python packages (which
141+
# can be e.g. distributed on PyPI). If your package is importable as
142+
# ``import my.package``, with a ``my/package/__init__.py`` module, and you add
143+
# a ``my/package/presentation.mplstyle`` style sheet, then in can be used as
144+
# ``plt.style.use("my.package.presentation")``.
145+
#
140146
# Alternatively, you can make your style known to Matplotlib by placing
141147
# your ``<style-name>.mplstyle`` file into ``mpl_configdir/stylelib``. You
142148
# can then load your custom style sheet with a call to
@@ -156,6 +162,9 @@ def plotting_function():
156162
# >>> import matplotlib.pyplot as plt
157163
# >>> plt.style.use(<style-name>)
158164
#
165+
# The approach of using ``mpl_configdir`` should be considered legacy, as it
166+
# does not allow distribution on PyPI.
167+
#
159168
#
160169
# Composing styles
161170
# ----------------

0 commit comments

Comments
 (0)