From b19120079aa4e52c54b365857e472ebbc45585aa Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:50:55 +0200 Subject: [PATCH] MNT: Cleanup FontProperties __init__ API The cleanup approach is to not modify code logic during a deprecation period, but only detect deprecated call patterns through a decorator and warn on them. --- .../deprecations/28843-TH.rst | 9 +++ lib/matplotlib/font_manager.py | 64 ++++++++++++++++++- lib/matplotlib/ticker.py | 2 +- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/28843-TH.rst diff --git a/doc/api/next_api_changes/deprecations/28843-TH.rst b/doc/api/next_api_changes/deprecations/28843-TH.rst new file mode 100644 index 000000000000..a144a665811c --- /dev/null +++ b/doc/api/next_api_changes/deprecations/28843-TH.rst @@ -0,0 +1,9 @@ +FontProperties initialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`.FontProperties.` initialization is limited to the two call patterns: + +- single positional parameter, interpreted as fontconfig pattern +- only keyword parameters for setting individual properties + +All other previously supported call patterns are deprecated. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index d9560ec0cc0f..b1c0943a902e 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -29,9 +29,11 @@ from base64 import b64encode from collections import namedtuple +from collections.abc import Iterable import copy import dataclasses from functools import lru_cache +import functools from io import BytesIO import json import logging @@ -536,6 +538,56 @@ def afmFontProperty(fontpath, font): return FontEntry(fontpath, name, style, variant, weight, stretch, size) +def _cleanup_fontproperties_init(init_method): + """ + A decorator to limit the call signature to single a positional argument + or alternatively only keyword arguments. + + We still accept but deprecate all other call signatures. + + When the deprecation expires we can switch the signature to:: + + __init__(self, pattern=None, /, *, family=None, style=None, ...) + + plus a runtime check that pattern is not used alongside with the + keyword arguments. This results eventually in the two possible + call signatures:: + + FontProperties(pattern) + FontProperties(family=..., size=..., ...) + + """ + @functools.wraps(init_method) + def wrapper(self, *args, **kwargs): + # multiple args with at least some positional ones + if len(args) > 1 or len(args) == 1 and kwargs: + # Note: Both cases were previously handled as individual properties. + # Therefore, we do not mention the case of font properties here. + _api.warn_deprecated( + "3.10", + message="Passing individual properties to FontProperties() " + "positionally is deprecated. Please pass all properties " + "via keyword arguments." + ) + # single non-string arg -> clearly a family not a pattern + if (len(args) == 1 and not kwargs + and isinstance(args[0], Iterable) and not isinstance(args[0], str)): + # Case font-family list passed as single argument + _api.warn_deprecated( + "3.10", + message="Passing family as positional argument to FontProperties() " + "is deprecated. Please pass family names as keyword" + "argument." + ) + # Note on single string arg: + # This has been interpreted as pattern so far. We are already raising if a + # non-pattern compatible family string was given. Therefore, we do not need + # to warn for this case. + return init_method(self, *args, **kwargs) + + return wrapper + + class FontProperties: """ A class for storing and manipulating font properties. @@ -585,9 +637,14 @@ class FontProperties: approach allows all text sizes to be made larger or smaller based on the font manager's default font size. - This class will also accept a fontconfig_ pattern_, if it is the only - argument provided. This support does not depend on fontconfig; we are - merely borrowing its pattern syntax for use here. + This class accepts a single positional string as fontconfig_ pattern_, + or alternatively individual properties as keyword arguments:: + + FontProperties(pattern) + FontProperties(*, family=None, style=None, variant=None, ...) + + This support does not depend on fontconfig; we are merely borrowing its + pattern syntax for use here. .. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/ .. _pattern: @@ -599,6 +656,7 @@ class FontProperties: fontconfig. """ + @_cleanup_fontproperties_init def __init__(self, family=None, style=None, variant=None, weight=None, stretch=None, size=None, fname=None, # if set, it's a hardcoded filename to use diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index d824bbe3b6e2..cd40641de4d3 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -574,7 +574,7 @@ def set_useMathText(self, val): from matplotlib import font_manager ufont = font_manager.findfont( font_manager.FontProperties( - mpl.rcParams["font.family"] + family=mpl.rcParams["font.family"] ), fallback_to_default=False, )