Skip to content

Load style files from third-party packages. #24257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 4, 2022
Merged

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Oct 23, 2022

PR Summary

The first commit inlines the logic of the undocumented _remove_blacklisted_style_params and _apply_style helpers into mpl.style.use, which makes it easier to follow (the exact semantics of _apply_style are really not that clear without going to check its implementation anyways). It's mostly just preparation for the second commit.

The second commit implements loading of style files from third-party packages (as mpl.style.use("package.name.style_name"), which loads package/name/style_name.mplstyle). This fixes the long-standing issue of how third-parties can distribute style sheets (for those who really don't want to actually import the third-party package).

The exact syntax and semantics could be discussed, but I think the basic design is there.

Fixes #17978

PR Checklist

Tests and Styling

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • New features are documented, with examples if plot related.
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).

@anntzer anntzer force-pushed the style branch 4 times, most recently from 8a7bb67 to 4a815ae Compare October 23, 2022 19:19
Comment on lines 165 to 166
# The approach of using ``mpl_configdir`` should be considered legacy, as it
# does not allow distribution on PyPI.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to go this far? I think I prefer the method you are advocating here, but the old method is surely not going away at any point is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unlikely to go away for years, but I think it's still good to be opinionated if we think one approach is clearly better (I think mplconfigdir is clearly worse...).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it depends. For installing someone else's style file, I agree pip install cool-style-from-internet is preferable. But if I want to write my own style file and stick it somewhere global, mpl_configdir is easier than writing my own package and pip-installing it. I could figure out how to do it, and I'd probably go through the bother because I change machines enough, and its more reproducible because I can make it an environment dependency. But, a lot of our end users aren't going to know how to make their own pip-installable package that contains a style sheet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, let's just remove that for now.

Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. The only thing I wonder is if we can extend to other things, colormaps in particular.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 24, 2022

Unlike style files, which exist next to modules, colormaps currently have no representation other than as python objects living in a module, so I don't think this is easily feasible.

@tacaswell
Copy link
Member

colormaps currently have no representation other than as python objects living in a module,

There was some early talk of developing a spec for specifying color maps (basically enough metadata to give a name + classification information and then a CSV of RGB values), but that did not come to fruition.

@tacaswell
Copy link
Member

I am 👍🏻 on this, just have a minor nit code wise.

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should talk about this on the dev call as it is a pretty big conceptual change but given how relatively simple it is I think we should take.

Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. It's simple, it solves the distribution problem and it has namespacing (and thus avoids name conflicts).

I had the idea whether we would want to support a mypackage/default.mplstyle so that one can do mpl.style.use('mypackage'). This could be nicer than forcing packages to name their style. One could have mpl.style.use('seaborn') instead of mpl.style.use('seaborn.seaborn') or other similar constructs.

Comment on lines 5 to 9
as follows. Assume that a package is importable as ``import my.package``, with
a ``my/package/__init__.py`` module. Then a
``my/package/presentation.mplstyle`` style sheet can be used as
``plt.style.use("my.package.presentation")``. (Note that the implementation
does not actually import ``my.package``, making this process safe against
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
as follows. Assume that a package is importable as ``import my.package``, with
a ``my/package/__init__.py`` module. Then a
``my/package/presentation.mplstyle`` style sheet can be used as
``plt.style.use("my.package.presentation")``. (Note that the implementation
does not actually import ``my.package``, making this process safe against
as follows. Assume that a package is importable as ``import mypackage``, with
a ``mypackage/__init__.py`` module. Then a
``mypackage/presentation.mplstyle`` style sheet can be used as
``plt.style.use("mypackage.presentation")``. (Note that the implementation
does not actually import ``mypackage``, making this process safe against

Let's keep this top-level. Even if a package has subpackages, there should almost never be a reason to hide the styles in lower levels.

You may mention the edge-case in yet to be written documentation.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 26, 2022

I had the idea whether we would want to support a mypackage/default.mplstyle so that one can do mpl.style.use('mypackage'). This could be nicer than forcing packages to name their style. One could have mpl.style.use('seaborn') instead of mpl.style.use('seaborn.seaborn') or other similar constructs.

My general idea is that most packages will want to distribute more than one style file anyways, so e.g. seaborn.darkgrid/seaborn.whitegrid/etc. are fine (but perhaps @mwaskom may want to chime in). I think adding special support for default.mplstyle later doesn't add backcompat issues (or we can declare the whole scheme provisional), so we can always consider that another time.


I've handled your other comments.

@anntzer anntzer force-pushed the style branch 2 times, most recently from d3046d8 to 01c8743 Compare October 26, 2022 09:13
@timhoffm
Copy link
Member

I have minor concerns on ambiguity of strings builtin / style file / package style. We should either think this through or make it provisional so that we don't render us in a corner unintendedly.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 27, 2022

(made the example in customizing.py show a non-dotted package name first as well).

I'm totally fine with making this provisional.

... and use the shorter _rc_params_in_file.
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo two minor doc issues.

@timhoffm timhoffm removed the status: needs comment/discussion needs consensus on next step label Nov 3, 2022
@anntzer
Copy link
Contributor Author

anntzer commented Nov 4, 2022

thanks, fixed both (kept a mention about subpackages but it comes at the end now).

@@ -334,6 +334,11 @@ def make_release_tree(self, base_dir, files):
os.environ.get("CIBUILDWHEEL", "0") != "1"
) else []
),
extras_require={
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not doing what you would expect it to.

Python version specific markers can be achieved with

importlib-resources>=3.2.0; python_version<"3.10"

as per https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#platform-specific-dependencies.

This should be in the main dependencies section, not as an extra.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does actually work, I checked when I had the same thought previously. It is a bit outdated, and what you linked is the more recommended way, but this is valid (if perhaps not fully future-proof, as it is not well documented any more).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Document how to distribute style files in python packages
7 participants