Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 13380e1

Browse filesBrowse files
authored
MNT: Cleanup FontProperties __init__ API (#28843)
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.
1 parent e8e12df commit 13380e1
Copy full SHA for 13380e1

File tree

Expand file treeCollapse file tree

4 files changed

+111
-4
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+111
-4
lines changed
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FontProperties initialization
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
`.FontProperties` initialization is limited to the two call patterns:
5+
6+
- single positional parameter, interpreted as fontconfig pattern
7+
- only keyword parameters for setting individual properties
8+
9+
All other previously supported call patterns are deprecated.

‎lib/matplotlib/font_manager.py

Copy file name to clipboardExpand all lines: lib/matplotlib/font_manager.py
+61-3Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import copy
3333
import dataclasses
3434
from functools import lru_cache
35+
import functools
3536
from io import BytesIO
3637
import json
3738
import logging
@@ -536,6 +537,57 @@ def afmFontProperty(fontpath, font):
536537
return FontEntry(fontpath, name, style, variant, weight, stretch, size)
537538

538539

540+
def _cleanup_fontproperties_init(init_method):
541+
"""
542+
A decorator to limit the call signature to single a positional argument
543+
or alternatively only keyword arguments.
544+
545+
We still accept but deprecate all other call signatures.
546+
547+
When the deprecation expires we can switch the signature to::
548+
549+
__init__(self, pattern=None, /, *, family=None, style=None, ...)
550+
551+
plus a runtime check that pattern is not used alongside with the
552+
keyword arguments. This results eventually in the two possible
553+
call signatures::
554+
555+
FontProperties(pattern)
556+
FontProperties(family=..., size=..., ...)
557+
558+
"""
559+
@functools.wraps(init_method)
560+
def wrapper(self, *args, **kwargs):
561+
# multiple args with at least some positional ones
562+
if len(args) > 1 or len(args) == 1 and kwargs:
563+
# Note: Both cases were previously handled as individual properties.
564+
# Therefore, we do not mention the case of font properties here.
565+
_api.warn_deprecated(
566+
"3.10",
567+
message="Passing individual properties to FontProperties() "
568+
"positionally was deprecated in Matplotlib %(since)s and "
569+
"will be removed in %(removal)s. Please pass all properties "
570+
"via keyword arguments."
571+
)
572+
# single non-string arg -> clearly a family not a pattern
573+
if len(args) == 1 and not kwargs and not cbook.is_scalar_or_string(args[0]):
574+
# Case font-family list passed as single argument
575+
_api.warn_deprecated(
576+
"3.10",
577+
message="Passing family as positional argument to FontProperties() "
578+
"was deprecated in Matplotlib %(since)s and will be removed "
579+
"in %(removal)s. Please pass family names as keyword"
580+
"argument."
581+
)
582+
# Note on single string arg:
583+
# This has been interpreted as pattern so far. We are already raising if a
584+
# non-pattern compatible family string was given. Therefore, we do not need
585+
# to warn for this case.
586+
return init_method(self, *args, **kwargs)
587+
588+
return wrapper
589+
590+
539591
class FontProperties:
540592
"""
541593
A class for storing and manipulating font properties.
@@ -585,9 +637,14 @@ class FontProperties:
585637
approach allows all text sizes to be made larger or smaller based
586638
on the font manager's default font size.
587639
588-
This class will also accept a fontconfig_ pattern_, if it is the only
589-
argument provided. This support does not depend on fontconfig; we are
590-
merely borrowing its pattern syntax for use here.
640+
This class accepts a single positional string as fontconfig_ pattern_,
641+
or alternatively individual properties as keyword arguments::
642+
643+
FontProperties(pattern)
644+
FontProperties(*, family=None, style=None, variant=None, ...)
645+
646+
This support does not depend on fontconfig; we are merely borrowing its
647+
pattern syntax for use here.
591648
592649
.. _fontconfig: https://www.freedesktop.org/wiki/Software/fontconfig/
593650
.. _pattern:
@@ -599,6 +656,7 @@ class FontProperties:
599656
fontconfig.
600657
"""
601658

659+
@_cleanup_fontproperties_init
602660
def __init__(self, family=None, style=None, variant=None, weight=None,
603661
stretch=None, size=None,
604662
fname=None, # if set, it's a hardcoded filename to use

‎lib/matplotlib/tests/test_font_manager.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_font_manager.py
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import numpy as np
1212
import pytest
1313

14+
import matplotlib as mpl
1415
from matplotlib.font_manager import (
1516
findfont, findSystemFonts, FontEntry, FontProperties, fontManager,
1617
json_dump, json_load, get_font, is_opentype_cff_font,
@@ -367,3 +368,42 @@ def inner():
367368
for obj in gc.get_objects():
368369
if isinstance(obj, SomeObject):
369370
pytest.fail("object from inner stack still alive")
371+
372+
373+
def test_fontproperties_init_deprecation():
374+
"""
375+
Test the deprecated API of FontProperties.__init__.
376+
377+
The deprecation does not change behavior, it only adds a deprecation warning
378+
via a decorator. Therefore, the purpose of this test is limited to check
379+
which calls do and do not issue deprecation warnings. Behavior is still
380+
tested via the existing regular tests.
381+
"""
382+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
383+
# multiple positional arguments
384+
FontProperties("Times", "italic")
385+
386+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
387+
# Mixed positional and keyword arguments
388+
FontProperties("Times", size=10)
389+
390+
with pytest.warns(mpl.MatplotlibDeprecationWarning):
391+
# passing a family list positionally
392+
FontProperties(["Times"])
393+
394+
# still accepted:
395+
FontProperties(family="Times", style="italic")
396+
FontProperties(family="Times")
397+
FontProperties("Times") # works as pattern and family
398+
FontProperties("serif-24:style=oblique:weight=bold") # pattern
399+
400+
# also still accepted:
401+
# passing as pattern via family kwarg was not covered by the docs but
402+
# historically worked. This is left unchanged for now.
403+
# AFAICT, we cannot detect this: We can determine whether a string
404+
# works as pattern, but that doesn't help, because there are strings
405+
# that are both pattern and family. We would need to identify, whether
406+
# a string is *not* a valid family.
407+
# Since this case is not covered by docs, I've refrained from jumping
408+
# extra hoops to detect this possible API misuse.
409+
FontProperties(family="serif-24:style=oblique:weight=bold")

‎lib/matplotlib/ticker.py

Copy file name to clipboardExpand all lines: lib/matplotlib/ticker.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ def set_useMathText(self, val):
574574
from matplotlib import font_manager
575575
ufont = font_manager.findfont(
576576
font_manager.FontProperties(
577-
mpl.rcParams["font.family"]
577+
family=mpl.rcParams["font.family"]
578578
),
579579
fallback_to_default=False,
580580
)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.