2
2
import contextlib
3
3
from functools import wraps
4
4
import inspect
5
+ from inspect import Signature , Parameter
5
6
import logging
6
7
from numbers import Number
7
8
import re
@@ -84,6 +85,12 @@ def _stale_axes_callback(self, val):
84
85
_XYPair = namedtuple ("_XYPair" , "x y" )
85
86
86
87
88
+ class _Unset :
89
+ def __repr__ (self ):
90
+ return "<UNSET>"
91
+ _UNSET = _Unset ()
92
+
93
+
87
94
class Artist :
88
95
"""
89
96
Abstract base class for objects that render into a FigureCanvas.
@@ -93,6 +100,51 @@ class Artist:
93
100
94
101
zorder = 0
95
102
103
+ def __init_subclass__ (cls ):
104
+ # Inject custom set() methods into the subclass with signature and
105
+ # docstring based on the subclasses' properties.
106
+
107
+ if not hasattr (cls .set , '_autogenerated_signature' ):
108
+ # Don't overwrite cls.set if the subclass or one of its parents
109
+ # has defined a set method set itself.
110
+ # If there was no explicit definition, cls.set is inherited from
111
+ # the hierarchy of auto-generated set methods, which hold the
112
+ # flag _autogenerated_signature.
113
+ return
114
+
115
+ cls .set = lambda self , ** kwargs : Artist .set (self , ** kwargs )
116
+ cls .set .__name__ = "set"
117
+ cls .set .__qualname__ = f"{ cls .__qualname__ } .set"
118
+ cls ._update_set_signature_and_docstring ()
119
+
120
+ _PROPERTIES_EXCLUDED_FROM_SET = [
121
+ 'navigate_mode' , # not a user-facing function
122
+ 'figure' , # changing the figure is such a profound operation
123
+ # that we don't want this in set()
124
+ '3d_properties' , # cannot be used as a keyword due to leading digit
125
+ ]
126
+
127
+ @classmethod
128
+ def _update_set_signature_and_docstring (cls ):
129
+ """
130
+ Update the signature of the set function to list all properties
131
+ as keyword arguments.
132
+
133
+ Property aliases are not listed in the signature for brevity, but
134
+ are still accepted as keyword arguments.
135
+ """
136
+ cls .set .__signature__ = Signature (
137
+ [Parameter ("self" , Parameter .POSITIONAL_OR_KEYWORD ),
138
+ * [Parameter (prop , Parameter .KEYWORD_ONLY , default = _UNSET )
139
+ for prop in ArtistInspector (cls ).get_setters ()
140
+ if prop not in Artist ._PROPERTIES_EXCLUDED_FROM_SET ]])
141
+ cls .set ._autogenerated_signature = True
142
+
143
+ cls .set .__doc__ = (
144
+ "Set multiple properties at once.\n \n "
145
+ "Supported properties are\n \n "
146
+ + kwdoc (cls ))
147
+
96
148
def __init__ (self ):
97
149
self ._stale = True
98
150
self .stale_callback = None
@@ -1096,7 +1148,9 @@ def properties(self):
1096
1148
return ArtistInspector (self ).properties ()
1097
1149
1098
1150
def set (self , ** kwargs ):
1099
- """A property batch setter. Pass *kwargs* to set properties."""
1151
+ # docstring and signature are auto-generated via
1152
+ # Artist._update_set_signature_and_docstring() at the end of the
1153
+ # module.
1100
1154
kwargs = cbook .normalize_kwargs (kwargs , self )
1101
1155
return self .update (kwargs )
1102
1156
@@ -1385,6 +1439,21 @@ def aliased_name(self, s):
1385
1439
aliases = '' .join (' or %s' % x for x in sorted (self .aliasd .get (s , [])))
1386
1440
return s + aliases
1387
1441
1442
+ _NOT_LINKABLE = {
1443
+ # A set of property setter methods that are not available in our
1444
+ # current docs. This is a workaround used to prevent trying to link
1445
+ # these setters which would lead to "target reference not found"
1446
+ # warnings during doc build.
1447
+ 'matplotlib.image._ImageBase.set_alpha' ,
1448
+ 'matplotlib.image._ImageBase.set_array' ,
1449
+ 'matplotlib.image._ImageBase.set_data' ,
1450
+ 'matplotlib.image._ImageBase.set_filternorm' ,
1451
+ 'matplotlib.image._ImageBase.set_filterrad' ,
1452
+ 'matplotlib.image._ImageBase.set_interpolation' ,
1453
+ 'matplotlib.image._ImageBase.set_resample' ,
1454
+ 'matplotlib.text._AnnotationBase.set_annotation_clip' ,
1455
+ }
1456
+
1388
1457
def aliased_name_rest (self , s , target ):
1389
1458
"""
1390
1459
Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
@@ -1394,6 +1463,10 @@ def aliased_name_rest(self, s, target):
1394
1463
alias, return 'markerfacecolor or mfc' and for the transform
1395
1464
property, which does not, return 'transform'.
1396
1465
"""
1466
+ # workaround to prevent "reference target not found"
1467
+ if target in self ._NOT_LINKABLE :
1468
+ return f'``{ s } ``'
1469
+
1397
1470
aliases = '' .join (' or %s' % x for x in sorted (self .aliasd .get (s , [])))
1398
1471
return ':meth:`%s <%s>`%s' % (s , target , aliases )
1399
1472
@@ -1656,3 +1729,7 @@ def kwdoc(artist):
1656
1729
return ('\n ' .join (ai .pprint_setters_rest (leadingspace = 4 ))
1657
1730
if mpl .rcParams ['docstring.hardcopy' ] else
1658
1731
'Properties:\n ' + '\n ' .join (ai .pprint_setters (leadingspace = 4 )))
1732
+
1733
+ # We defer this to the end of them module, because it needs ArtistInspector
1734
+ # to be defined.
1735
+ Artist ._update_set_signature_and_docstring ()
0 commit comments