Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Pyplot getter setter automatization #30199

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 66 additions & 75 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2095,82 +2095,7 @@

## Axis ##


def xlim(*args, **kwargs) -> tuple[float, float]:
"""
Get or set the x limits of the current Axes.

Call signatures::

left, right = xlim() # return the current xlim
xlim((left, right)) # set the xlim to left, right
xlim(left, right) # set the xlim to left, right

If you do not specify args, you can pass *left* or *right* as kwargs,
i.e.::

xlim(right=3) # adjust the right leaving left unchanged
xlim(left=1) # adjust the left leaving right unchanged

Setting limits turns autoscaling off for the x-axis.

Returns
-------
left, right
A tuple of the new x-axis limits.

Notes
-----
Calling this function with no arguments (e.g. ``xlim()``) is the pyplot
equivalent of calling `~.Axes.get_xlim` on the current Axes.
Calling this function with arguments is the pyplot equivalent of calling
`~.Axes.set_xlim` on the current Axes. All arguments are passed though.
"""
ax = gca()
if not args and not kwargs:
return ax.get_xlim()
ret = ax.set_xlim(*args, **kwargs)
return ret


def ylim(*args, **kwargs) -> tuple[float, float]:
"""
Get or set the y-limits of the current Axes.

Call signatures::

bottom, top = ylim() # return the current ylim
ylim((bottom, top)) # set the ylim to bottom, top
ylim(bottom, top) # set the ylim to bottom, top

If you do not specify args, you can alternatively pass *bottom* or
*top* as kwargs, i.e.::

ylim(top=3) # adjust the top leaving bottom unchanged
ylim(bottom=1) # adjust the bottom leaving top unchanged

Setting limits turns autoscaling off for the y-axis.

Returns
-------
bottom, top
A tuple of the new y-axis limits.

Notes
-----
Calling this function with no arguments (e.g. ``ylim()``) is the pyplot
equivalent of calling `~.Axes.get_ylim` on the current Axes.
Calling this function with arguments is the pyplot equivalent of calling
`~.Axes.set_ylim` on the current Axes. All arguments are passed though.
"""
ax = gca()
if not args and not kwargs:
return ax.get_ylim()
ret = ax.set_ylim(*args, **kwargs)
return ret


def xticks(

Check failure on line 2098 in lib/matplotlib/pyplot.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Expected 2 blank lines, found 1 Raw Output: message:"Expected 2 blank lines, found 1" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/pyplot.py" range:{start:{line:2098 column:1} end:{line:2098 column:4}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"E302" url:"https://docs.astral.sh/ruff/rules/blank-lines-top-level"} suggestions:{range:{start:{line:2097 column:1} end:{line:2098 column:1}} text:"\n\n"}
ticks: ArrayLike | None = None,
labels: Sequence[str] | None = None,
*,
Expand Down Expand Up @@ -4457,6 +4382,72 @@
gca().set_yscale(value, **kwargs)


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@overload
@_copy_docstring_and_deprecators(Axes.get_xlim)
def xlim() -> tuple[float, float]:
...

Check warning on line 4389 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4389

Added line #L4389 was not covered by tests


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@overload
@_copy_docstring_and_deprecators(Axes.set_xlim)
def xlim(
left: float | tuple[float, float] | None = None,
right: float | None = None,
*,
emit: bool = True,
auto: bool | None = False,
xmin: float | None = None,
xmax: float | None = None,
) -> tuple[float, float]:
...

Check warning on line 4404 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4404

Added line #L4404 was not covered by tests


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@_copy_docstring_and_deprecators(Axes.get_xlim)
def xlim(*args, **kwargs):
ax = gca()
if not args and not kwargs:
return ax.get_xlim()

Check warning on line 4412 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4412

Added line #L4412 was not covered by tests

ret = ax.set_xlim(*args, **kwargs)
return ret


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@overload
@_copy_docstring_and_deprecators(Axes.get_ylim)
def ylim() -> tuple[float, float]:
...

Check warning on line 4422 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4422

Added line #L4422 was not covered by tests


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@overload
@_copy_docstring_and_deprecators(Axes.set_ylim)
def ylim(
bottom: float | tuple[float, float] | None = None,
top: float | None = None,
*,
emit: bool = True,
auto: bool | None = False,
ymin: float | None = None,
ymax: float | None = None,
) -> tuple[float, float]:
...

Check warning on line 4437 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4437

Added line #L4437 was not covered by tests


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
@_copy_docstring_and_deprecators(Axes.get_ylim)
def ylim(*args, **kwargs):
ax = gca()
if not args and not kwargs:
return ax.get_ylim()

Check warning on line 4445 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4445

Added line #L4445 was not covered by tests

ret = ax.set_ylim(*args, **kwargs)
return ret


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
def autumn() -> None:
"""
Expand Down
124 changes: 91 additions & 33 deletions tools/boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@
{return_statement}gca().{called_name}{call}
"""

AXES_GETTER_SETTER_TEMPLATE = AUTOGEN_MSG + """
@overload
@_copy_docstring_and_deprecators(Axes.get_{called_name})
def {name}() -> {get_return_type}: ...
""" + AUTOGEN_MSG + """
@overload
@_copy_docstring_and_deprecators(Axes.set_{called_name})
def {name}{signature}: ...
""" + AUTOGEN_MSG + """
@_copy_docstring_and_deprecators(Axes.get_{called_name})
def {name}(*args, **kwargs):
ax = gca()
if not args and not kwargs:
return ax.get_{called_name}()

Check failure on line 71 in tools/boilerplate.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Blank line contains whitespace Raw Output: message:"Blank line contains whitespace" location:{path:"/home/runner/work/matplotlib/matplotlib/tools/boilerplate.py" range:{start:{line:71 column:1} end:{line:71 column:5}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"W293" url:"https://docs.astral.sh/ruff/rules/blank-line-with-whitespace"} suggestions:{range:{start:{line:71 column:1} end:{line:71 column:5}}}
ret = ax.set_{called_name}(*args, **kwargs)
return ret
"""

FIGURE_METHOD_TEMPLATE = AUTOGEN_MSG + """
@_copy_docstring_and_deprecators(Figure.{called_name})
def {name}{signature}:
Expand Down Expand Up @@ -102,14 +121,21 @@
"""
A placeholder class to destringify annotations from ast
"""

def __init__(self, value):
self._repr = value

def __repr__(self):
return self._repr


def generate_function(name, called_fullname, template, **kwargs):
def generate_function(
name,
called_fullname,
template,
gettersetter=False,
**kwargs
):
"""
Create a wrapper function *pyplot_name* calling *call_name*.

Expand All @@ -127,6 +153,11 @@
- signature: The function signature (including parentheses).
- called_name: The name of the called function.
- call: Parameters passed to *called_name* (including parentheses).
gettersetter : bool
Indicate if the method to be wrapped is correponding to a getter and setter. A new placeholdr is filled :

Check failure on line 157 in tools/boilerplate.py

View workflow job for this annotation

GitHub Actions / ruff

[rdjson] reported by reviewdog 🐶 Line too long (113 > 88) Raw Output: message:"Line too long (113 > 88)" location:{path:"/home/runner/work/matplotlib/matplotlib/tools/boilerplate.py" range:{start:{line:157 column:89} end:{line:157 column:114}}} source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"E501" url:"https://docs.astral.sh/ruff/rules/line-too-long"}

- get_return_type: The type returned by the getter
- set_return_type: The type returned by the setter

**kwargs
Additional parameters are passed to ``template.format()``.
Expand All @@ -135,15 +166,14 @@
class_name, called_name = called_fullname.split('.')
class_ = {'Axes': Axes, 'Figure': Figure}[class_name]

meth = getattr(class_, called_name)
decorator = _api.deprecation.DECORATORS.get(meth)
# Generate the wrapper with the non-kwonly signature, as it will get
# redecorated with make_keyword_only by _copy_docstring_and_deprecators.
if decorator and decorator.func is _api.make_keyword_only:
meth = meth.__wrapped__
if not gettersetter:
signature = get_signature(class_, called_name)
else:
getter_signature = get_signature(class_, f"get_{called_name}")
kwargs.setdefault("get_return_type", str(getter_signature.return_annotation))

annotated_trees = get_ast_mro_trees(class_)
signature = get_matching_signature(meth, annotated_trees)
signature = get_signature(class_, f"set_{called_name}")
kwargs.setdefault('return_type', str(signature.return_annotation))

# Replace self argument.
params = list(signature.parameters.values())[1:]
Expand All @@ -152,30 +182,32 @@
param.replace(default=value_formatter(param.default))
if param.default is not param.empty else param
for param in params]))

# How to call the wrapped function.
call = '(' + ', '.join((
# Pass "intended-as-positional" parameters positionally to avoid
# forcing third-party subclasses to reproduce the parameter names.
'{0}'
if param.kind in [
Parameter.POSITIONAL_OR_KEYWORD]
and param.default is Parameter.empty else
# Only pass the data kwarg if it is actually set, to avoid forcing
# third-party subclasses to support it.
'**({{"data": data}} if data is not None else {{}})'
if param.name == "data" else
'{0}={0}'
if param.kind in [
Parameter.POSITIONAL_OR_KEYWORD,
Parameter.KEYWORD_ONLY] else
'{0}'
if param.kind is Parameter.POSITIONAL_ONLY else
'*{0}'
if param.kind is Parameter.VAR_POSITIONAL else
'**{0}'
if param.kind is Parameter.VAR_KEYWORD else
None).format(param.name)
for param in params) + ')'

def call_param(param: Parameter):
match param.kind:
# Pass "intended-as-positional" parameters positionally to avoid
# forcing third-party subclasses to reproduce the parameter names.
case Parameter.POSITIONAL_OR_KEYWORD if param.default is Parameter.empty:
return '{0}'
# Only pass the data kwarg if it is actually set, to avoid forcing
# third-party subclasses to support it.
case _ if param.name == "data":
return '**({{"data": data}} if data is not None else {{}})'
case Parameter.POSITIONAL_OR_KEYWORD | Parameter.KEYWORD_ONLY:
return '{0}={0}'
case Parameter.POSITIONAL_ONLY:
return '{0}'
case Parameter.VAR_POSITIONAL:
return '*{0}'
case Parameter.VAR_KEYWORD:
return '**{0}'
return None

call = '(' + ', '.join(
(call_param(param)).format(param.name) for param in params
) + ')'
return_statement = 'return ' if has_return_value else ''
# Bail out in case of name collision.
for reserved in ('gca', 'gci', 'gcf', '__ret'):
Expand Down Expand Up @@ -286,7 +318,12 @@
'xlabel:set_xlabel',
'ylabel:set_ylabel',
'xscale:set_xscale',
'yscale:set_yscale',
'yscale:set_yscale'
)

_axes_getter_setters = (
'xlim',
'ylim',
)

cmappable = {
Expand Down Expand Up @@ -341,6 +378,14 @@
yield generate_function(name, f'Axes.{called_name}', template,
sci_command=cmappable.get(name))

for spec in _axes_getter_setters:
if ':' in spec:
name, called_name = spec.split(':')
else:
name = called_name = spec
yield generate_function(name, f'Axes.{called_name}',
AXES_GETTER_SETTER_TEMPLATE, True)

cmaps = (
'autumn',
'bone',
Expand Down Expand Up @@ -405,6 +450,19 @@
return [get_ast_tree(c) for c in cls.__mro__ if c.__module__ != "builtins"]


def get_signature(class_, name):
meth = getattr(class_, name)

decorator = _api.deprecation.DECORATORS.get(meth)
# Generate the wrapper with the non-kwonly signature, as it will get
# redecorated with make_keyword_only by _copy_docstring_and_deprecators.
if decorator and decorator.func is _api.make_keyword_only:
meth = meth.__wrapped__

annotated_trees = get_ast_mro_trees(class_)
return get_matching_signature(meth, annotated_trees)


def get_matching_signature(method, trees):
sig = inspect.signature(method)
for tree in trees:
Expand Down
Loading