@@ -54,6 +54,25 @@ def {name}{signature}:
5454 {return_statement}gca().{called_name}{call}
5555"""
5656
57+ AXES_GETTER_SETTER_TEMPLATE = AUTOGEN_MSG + """
58+ @overload
59+ @_copy_docstring_and_deprecators(Axes.get_{called_name})
60+ def {name}() -> {get_return_type}: ...
61+ """ + AUTOGEN_MSG + """
62+ @overload
63+ @_copy_docstring_and_deprecators(Axes.set_{called_name})
64+ def {name}{signature}: ...
65+ """ + AUTOGEN_MSG + """
66+ @_copy_docstring_and_deprecators(Axes.get_{called_name})
67+ def {name}(*args, **kwargs):
68+ ax = gca()
69+ if not args and not kwargs:
70+ return ax.get_{called_name}()
71+
72+ ret = ax.set_{called_name}(*args, **kwargs)
73+ return ret
74+ """
75+
5776FIGURE_METHOD_TEMPLATE = AUTOGEN_MSG + """
5877@_copy_docstring_and_deprecators(Figure.{called_name})
5978def {name}{signature}:
@@ -102,14 +121,15 @@ class direct_repr:
102121 """
103122 A placeholder class to destringify annotations from ast
104123 """
124+
105125 def __init__ (self , value ):
106126 self ._repr = value
107127
108128 def __repr__ (self ):
109129 return self ._repr
110130
111131
112- def generate_function (name , called_fullname , template , ** kwargs ):
132+ def generate_function (name , called_fullname , template , gettersetter = False , ** kwargs ):
113133 """
114134 Create a wrapper function *pyplot_name* calling *call_name*.
115135
@@ -127,6 +147,11 @@ def generate_function(name, called_fullname, template, **kwargs):
127147 - signature: The function signature (including parentheses).
128148 - called_name: The name of the called function.
129149 - call: Parameters passed to *called_name* (including parentheses).
150+ gettersetter : bool
151+ Indicate if the method to be wrapped is correponding to a getter and setter. A new placeholdr is filled :
152+
153+ - get_return_type: The type returned by the getter
154+ - set_return_type: The type returned by the setter
130155
131156 **kwargs
132157 Additional parameters are passed to ``template.format()``.
@@ -135,15 +160,14 @@ def generate_function(name, called_fullname, template, **kwargs):
135160 class_name , called_name = called_fullname .split ('.' )
136161 class_ = {'Axes' : Axes , 'Figure' : Figure }[class_name ]
137162
138- meth = getattr (class_ , called_name )
139- decorator = _api .deprecation .DECORATORS .get (meth )
140- # Generate the wrapper with the non-kwonly signature, as it will get
141- # redecorated with make_keyword_only by _copy_docstring_and_deprecators.
142- if decorator and decorator .func is _api .make_keyword_only :
143- meth = meth .__wrapped__
163+ if not gettersetter :
164+ signature = get_signature (class_ , called_name )
165+ else :
166+ getter_signature = get_signature (class_ , f"get_{ called_name } " )
167+ kwargs .setdefault ("get_return_type" , str (getter_signature .return_annotation ))
144168
145- annotated_trees = get_ast_mro_trees (class_ )
146- signature = get_matching_signature ( meth , annotated_trees )
169+ signature = get_signature (class_ , f"set_ { called_name } " )
170+ kwargs . setdefault ( 'return_type' , str ( signature . return_annotation ) )
147171
148172 # Replace self argument.
149173 params = list (signature .parameters .values ())[1 :]
@@ -152,30 +176,30 @@ def generate_function(name, called_fullname, template, **kwargs):
152176 param .replace (default = value_formatter (param .default ))
153177 if param .default is not param .empty else param
154178 for param in params ]))
179+
155180 # How to call the wrapped function.
156- call = '(' + ', ' .join ((
157- # Pass "intended-as-positional" parameters positionally to avoid
158- # forcing third-party subclasses to reproduce the parameter names.
159- '{0}'
160- if param .kind in [
161- Parameter .POSITIONAL_OR_KEYWORD ]
162- and param .default is Parameter .empty else
163- # Only pass the data kwarg if it is actually set, to avoid forcing
164- # third-party subclasses to support it.
165- '**({{"data": data}} if data is not None else {{}})'
166- if param .name == "data" else
167- '{0}={0}'
168- if param .kind in [
169- Parameter .POSITIONAL_OR_KEYWORD ,
170- Parameter .KEYWORD_ONLY ] else
171- '{0}'
172- if param .kind is Parameter .POSITIONAL_ONLY else
173- '*{0}'
174- if param .kind is Parameter .VAR_POSITIONAL else
175- '**{0}'
176- if param .kind is Parameter .VAR_KEYWORD else
177- None ).format (param .name )
178- for param in params ) + ')'
181+
182+ def call_param (param : Parameter ):
183+ match param .kind :
184+ # Pass "intended-as-positional" parameters positionally to avoid
185+ # forcing third-party subclasses to reproduce the parameter names.
186+ case Parameter .POSITIONAL_OR_KEYWORD if param .default is Parameter .empty :
187+ return '{0}'
188+ # Only pass the data kwarg if it is actually set, to avoid forcing
189+ # third-party subclasses to support it.
190+ case _ if param .name == "data" :
191+ return '**({{"data": data}} if data is not None else {{}})'
192+ case Parameter .POSITIONAL_OR_KEYWORD | Parameter .KEYWORD_ONLY :
193+ return '{0}={0}'
194+ case Parameter .POSITIONAL_ONLY :
195+ return '{0}'
196+ case Parameter .VAR_POSITIONAL :
197+ return '*{0}'
198+ case Parameter .VAR_KEYWORD :
199+ return '**{0}'
200+ return None
201+
202+ call = '(' + ', ' .join ((call_param (param )).format (param .name ) for param in params ) + ')'
179203 return_statement = 'return ' if has_return_value else ''
180204 # Bail out in case of name collision.
181205 for reserved in ('gca' , 'gci' , 'gcf' , '__ret' ):
@@ -286,7 +310,12 @@ def boilerplate_gen():
286310 'xlabel:set_xlabel' ,
287311 'ylabel:set_ylabel' ,
288312 'xscale:set_xscale' ,
289- 'yscale:set_yscale' ,
313+ 'yscale:set_yscale'
314+ )
315+
316+ _axes_getter_setters = (
317+ 'xlim' ,
318+ 'ylim' ,
290319 )
291320
292321 cmappable = {
@@ -341,6 +370,14 @@ def boilerplate_gen():
341370 yield generate_function (name , f'Axes.{ called_name } ' , template ,
342371 sci_command = cmappable .get (name ))
343372
373+ for spec in _axes_getter_setters :
374+ if ':' in spec :
375+ name , called_name = spec .split (':' )
376+ else :
377+ name = called_name = spec
378+ yield generate_function (name , f'Axes.{ called_name } ' ,
379+ AXES_GETTER_SETTER_TEMPLATE , True )
380+
344381 cmaps = (
345382 'autumn' ,
346383 'bone' ,
@@ -405,6 +442,19 @@ def get_ast_mro_trees(cls):
405442 return [get_ast_tree (c ) for c in cls .__mro__ if c .__module__ != "builtins" ]
406443
407444
445+ def get_signature (class_ , name ):
446+ meth = getattr (class_ , name )
447+
448+ decorator = _api .deprecation .DECORATORS .get (meth )
449+ # Generate the wrapper with the non-kwonly signature, as it will get
450+ # redecorated with make_keyword_only by _copy_docstring_and_deprecators.
451+ if decorator and decorator .func is _api .make_keyword_only :
452+ meth = meth .__wrapped__
453+
454+ annotated_trees = get_ast_mro_trees (class_ )
455+ return get_matching_signature (meth , annotated_trees )
456+
457+
408458def get_matching_signature (method , trees ):
409459 sig = inspect .signature (method )
410460 for tree in trees :
@@ -460,10 +510,10 @@ def update_sig_from_node(node, sig):
460510 if len (sys .argv ) > 1 :
461511 pyplot_path = Path (sys .argv [1 ])
462512 else :
463- mpl_path = (Path (__file__ ).parent / ".." / "lib" / "matplotlib" ).resolve ()
513+ mpl_path = (Path (__file__ ).parent / ".." / "lib" / "matplotlib" ).resolve ()
464514 pyplot_path = mpl_path / "pyplot.py"
465515 for cls in [Axes , Figure ]:
466- if mpl_path not in Path (inspect .getfile (cls )).parents :
516+ if mpl_path not in Path (inspect .getfile (cls )).parents :
467517 raise RuntimeError (
468518 f"{ cls .__name__ } import path is not { mpl_path } .\n "
469519 "Please make sure your Matplotlib installation "
0 commit comments