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

Skip to content

Expire _check_savefig_extra_args-related deprecations. #21395

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

Merged
merged 1 commit into from
Dec 7, 2021
Merged
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
3 changes: 3 additions & 0 deletions doc/api/next_api_changes/removals/21395-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Unknown keyword arguments to ``savefig`` and ``FigureCanvas.print_foo`` methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
... now raise a TypeError, instead of being ignored.
152 changes: 47 additions & 105 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@
import io
import logging
import os
import re
import sys
import time
import traceback
from weakref import WeakKeyDictionary

import numpy as np
Expand Down Expand Up @@ -1534,14 +1532,14 @@ class Done(Exception):

def _draw(renderer): raise Done(renderer)

with cbook._setattr_cm(figure, draw=_draw):
with cbook._setattr_cm(figure, draw=_draw), ExitStack() as stack:
orig_canvas = figure.canvas
if print_method is None:
fmt = figure.canvas.get_default_filetype()
# Even for a canvas' default output type, a canvas switch may be
# needed, e.g. for FigureCanvasBase.
print_method = getattr(
figure.canvas._get_output_canvas(None, fmt), f"print_{fmt}")
print_method = stack.enter_context(
figure.canvas._switch_canvas_and_return_print_method(fmt))
try:
print_method(io.BytesIO())
except Done as exc:
Expand All @@ -1550,8 +1548,6 @@ def _draw(renderer): raise Done(renderer)
else:
raise RuntimeError(f"{print_method} did not call Figure.draw, so "
f"no renderer is available")
finally:
figure.canvas = orig_canvas


def _no_output_draw(figure):
Expand All @@ -1574,84 +1570,6 @@ def _is_non_interactive_terminal_ipython(ip):
and getattr(ip.parent, 'interact', None) is False)


def _check_savefig_extra_args(func=None, extra_kwargs=()):
"""
Decorator for the final print_* methods that accept keyword arguments.

If any unused keyword arguments are left, this decorator will warn about
them, and as part of the warning, will attempt to specify the function that
the user actually called, instead of the backend-specific method. If unable
to determine which function the user called, it will specify `.savefig`.

For compatibility across backends, this does not warn about keyword
arguments added by `FigureCanvasBase.print_figure` for use in a subset of
backends, because the user would not have added them directly.
"""

if func is None:
return functools.partial(_check_savefig_extra_args,
extra_kwargs=extra_kwargs)

old_sig = inspect.signature(func)

@functools.wraps(func)
def wrapper(*args, **kwargs):
name = 'savefig' # Reasonable default guess.
public_api = re.compile(
r'^savefig|print_[A-Za-z0-9]+|_no_output_draw$'
)
seen_print_figure = False
if sys.version_info < (3, 11):
current_frame = None
else:
import inspect
current_frame = inspect.currentframe()
for frame, line in traceback.walk_stack(current_frame):
if frame is None:
# when called in embedded context may hit frame is None.
break
# Work around sphinx-gallery not setting __name__.
frame_name = frame.f_globals.get('__name__', '')
if re.match(r'\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))',
frame_name):
name = frame.f_code.co_name
if public_api.match(name):
if name in ('print_figure', '_no_output_draw'):
seen_print_figure = True

elif frame_name == '_functools':
# PyPy adds an extra frame without module prefix for this
# functools wrapper, which we ignore to assume we're still in
# Matplotlib code.
continue
else:
break

accepted_kwargs = {*old_sig.parameters, *extra_kwargs}
if seen_print_figure:
for kw in ['dpi', 'facecolor', 'edgecolor', 'orientation',
'bbox_inches_restore']:
# Ignore keyword arguments that are passed in by print_figure
# for the use of other renderers.
if kw not in accepted_kwargs:
kwargs.pop(kw, None)

for arg in list(kwargs):
if arg in accepted_kwargs:
continue
_api.warn_deprecated(
'3.3', name=name, removal='3.6',
message='%(name)s() got unexpected keyword argument "'
+ arg + '" which is no longer supported as of '
'%(since)s and will become an error '
'%(removal)s')
kwargs.pop(arg)

return func(*args, **kwargs)

return wrapper


class FigureCanvasBase:
"""
The canvas the figure renders into.
Expand Down Expand Up @@ -2155,21 +2073,30 @@ def get_supported_filetypes_grouped(cls):
groupings[name].sort()
return groupings

def _get_output_canvas(self, backend, fmt):
@contextmanager
def _switch_canvas_and_return_print_method(self, fmt, backend=None):
"""
Set the canvas in preparation for saving the figure.
Context manager temporarily setting the canvas for saving the figure::

with canvas._switch_canvas_and_return_print_method(fmt, backend) \\
as print_method:
# ``print_method`` is a suitable ``print_{fmt}`` method, and
# the figure's canvas is temporarily switched to the method's
# canvas within the with... block. ``print_method`` is also
# wrapped to suppress extra kwargs passed by ``print_figure``.

Parameters
----------
backend : str or None
If not None, switch the figure canvas to the ``FigureCanvas`` class
of the given backend.
fmt : str
If *backend* is None, then determine a suitable canvas class for
saving to format *fmt* -- either the current canvas class, if it
supports *fmt*, or whatever `get_registered_canvas_class` returns;
switch the figure canvas to that canvas class.
backend : str or None, default: None
If not None, switch the figure canvas to the ``FigureCanvas`` class
of the given backend.
"""
canvas = None
if backend is not None:
# Return a specific canvas class, if requested.
canvas_class = (
Expand All @@ -2180,16 +2107,34 @@ def _get_output_canvas(self, backend, fmt):
f"The {backend!r} backend does not support {fmt} output")
elif hasattr(self, f"print_{fmt}"):
# Return the current canvas if it supports the requested format.
return self
canvas = self
canvas_class = None # Skip call to switch_backends.
else:
# Return a default canvas for the requested format, if it exists.
canvas_class = get_registered_canvas_class(fmt)
if canvas_class:
return self.switch_backends(canvas_class)
# Else report error for unsupported format.
raise ValueError(
"Format {!r} is not supported (supported formats: {})"
.format(fmt, ", ".join(sorted(self.get_supported_filetypes()))))
canvas = self.switch_backends(canvas_class)
if canvas is None:
raise ValueError(
"Format {!r} is not supported (supported formats: {})".format(
fmt, ", ".join(sorted(self.get_supported_filetypes()))))
meth = getattr(canvas, f"print_{fmt}")
mod = (meth.func.__module__
if hasattr(meth, "func") # partialmethod, e.g. backend_wx.
else meth.__module__)
if mod.startswith(("matplotlib.", "mpl_toolkits.")):
optional_kws = { # Passed by print_figure for other renderers.
"dpi", "facecolor", "edgecolor", "orientation",
"bbox_inches_restore"}
skip = optional_kws - {*inspect.signature(meth).parameters}
print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
*args, **{k: v for k, v in kwargs.items() if k not in skip}))
else: # Let third-parties do as they see fit.
print_method = meth
try:
yield print_method
finally:
self.figure.canvas = self

def print_figure(
self, filename, dpi=None, facecolor=None, edgecolor=None,
Expand Down Expand Up @@ -2257,20 +2202,18 @@ def print_figure(
filename = filename.rstrip('.') + '.' + format
format = format.lower()

# get canvas object and print method for format
canvas = self._get_output_canvas(backend, format)
print_method = getattr(canvas, 'print_%s' % format)

if dpi is None:
dpi = rcParams['savefig.dpi']
if dpi == 'figure':
dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)

# Remove the figure manager, if any, to avoid resizing the GUI widget.
with cbook._setattr_cm(self, manager=None), \
self._switch_canvas_and_return_print_method(format, backend) \
as print_method, \
cbook._setattr_cm(self.figure, dpi=dpi), \
cbook._setattr_cm(canvas, _device_pixel_ratio=1), \
cbook._setattr_cm(canvas, _is_saving=True), \
cbook._setattr_cm(self.figure.canvas, _device_pixel_ratio=1), \
cbook._setattr_cm(self.figure.canvas, _is_saving=True), \
ExitStack() as stack:

for prop in ["facecolor", "edgecolor"]:
Expand Down Expand Up @@ -2305,8 +2248,8 @@ def print_figure(
bbox_inches = bbox_inches.padded(pad_inches)

# call adjust_bbox to save only the given area
restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
canvas.fixed_dpi)
restore_bbox = tight_bbox.adjust_bbox(
self.figure, bbox_inches, self.figure.canvas.fixed_dpi)

_bbox_inches_restore = (bbox_inches, restore_bbox)
else:
Expand All @@ -2329,7 +2272,6 @@ def print_figure(
if bbox_inches and restore_bbox:
restore_bbox()

self.figure.set_canvas(self)
return result

@classmethod
Expand Down
10 changes: 2 additions & 8 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@
from matplotlib import _api, cbook
from matplotlib import colors as mcolors
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
RendererBase)
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
from matplotlib.font_manager import findfont, get_font
from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING,
LOAD_DEFAULT, LOAD_NO_AUTOHINT)
Expand Down Expand Up @@ -477,7 +476,6 @@ def buffer_rgba(self):
"""
return self.renderer.buffer_rgba()

@_check_savefig_extra_args
@_api.delete_parameter("3.5", "args")
def print_raw(self, filename_or_obj, *args):
FigureCanvasAgg.draw(self)
Expand All @@ -497,7 +495,6 @@ def _print_pil(self, filename_or_obj, fmt, pil_kwargs, metadata=None):
filename_or_obj, self.buffer_rgba(), format=fmt, origin="upper",
dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs)

@_check_savefig_extra_args
@_api.delete_parameter("3.5", "args")
def print_png(self, filename_or_obj, *args,
metadata=None, pil_kwargs=None):
Expand Down Expand Up @@ -559,9 +556,8 @@ def print_to_buffer(self):
# print_figure(), and the latter ensures that `self.figure.dpi` already
# matches the dpi kwarg (if any).

@_check_savefig_extra_args()
@_api.delete_parameter("3.5", "args")
def print_jpg(self, filename_or_obj, *args, pil_kwargs=None, **kwargs):
def print_jpg(self, filename_or_obj, *args, pil_kwargs=None):
# Remove transparency by alpha-blending on an assumed white background.
r, g, b, a = mcolors.to_rgba(self.figure.get_facecolor())
try:
Expand All @@ -572,13 +568,11 @@ def print_jpg(self, filename_or_obj, *args, pil_kwargs=None, **kwargs):

print_jpeg = print_jpg

@_check_savefig_extra_args
def print_tif(self, filename_or_obj, *, pil_kwargs=None):
self._print_pil(filename_or_obj, "tiff", pil_kwargs)

print_tiff = print_tif

@_check_savefig_extra_args
def print_webp(self, filename_or_obj, *, pil_kwargs=None):
self._print_pil(filename_or_obj, "webp", pil_kwargs)

Expand Down
7 changes: 2 additions & 5 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
import matplotlib as mpl
from .. import _api, cbook, font_manager
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
GraphicsContextBase, RendererBase)
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.font_manager import ttfFontProperty
from matplotlib.mathtext import MathTextParser
from matplotlib.path import Path
Expand Down Expand Up @@ -448,11 +448,9 @@ def restore_region(self, region):
surface.mark_dirty_rectangle(
slx.start, sly.start, slx.stop - slx.start, sly.stop - sly.start)

@_check_savefig_extra_args
def print_png(self, fobj):
self._get_printed_image_surface().write_to_png(fobj)

@_check_savefig_extra_args
def print_rgba(self, fobj):
width, height = self.get_width_height()
buf = self._get_printed_image_surface().get_data()
Expand All @@ -470,7 +468,6 @@ def _get_printed_image_surface(self):
self.figure.draw(renderer)
return surface

@_check_savefig_extra_args
def _save(self, fmt, fobj, *, orientation='portrait'):
# save PDF/PS/SVG

Expand Down
5 changes: 2 additions & 3 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from matplotlib import _api, _text_helpers, cbook
from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
GraphicsContextBase, RendererBase)
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.figure import Figure
from matplotlib.font_manager import findfont, get_font
Expand Down Expand Up @@ -2747,7 +2747,6 @@ class FigureCanvasPdf(FigureCanvasBase):
def get_default_filetype(self):
return 'pdf'

@_check_savefig_extra_args
@_api.delete_parameter("3.4", "dpi")
def print_pdf(self, filename, *,
dpi=None, # dpi to use for images
Expand Down
5 changes: 2 additions & 3 deletions lib/matplotlib/backends/backend_pgf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import matplotlib as mpl
from matplotlib import _api, cbook, font_manager as fm
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
GraphicsContextBase, RendererBase
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase
)
from matplotlib.backends.backend_mixed import MixedModeRenderer
from matplotlib.backends.backend_pdf import (
Expand Down Expand Up @@ -790,7 +790,6 @@ class FigureCanvasPgf(FigureCanvasBase):
def get_default_filetype(self):
return 'pdf'

@_check_savefig_extra_args
def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None):

header_text = """%% Creator: Matplotlib, PGF backend
Expand Down
6 changes: 2 additions & 4 deletions lib/matplotlib/backends/backend_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
from matplotlib import _api, cbook, _path, _text_helpers
from matplotlib.afm import AFM
from matplotlib.backend_bases import (
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
GraphicsContextBase, RendererBase)
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
RendererBase)
from matplotlib.cbook import is_writable_file_like, file_requires_unicode
from matplotlib.font_manager import get_font
from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE, FT2Font
Expand Down Expand Up @@ -888,7 +888,6 @@ def _print_ps(
printer(outfile, format, dpi=dpi, dsc_comments=dsc_comments,
orientation=orientation, papertype=papertype, **kwargs)

@_check_savefig_extra_args
def _print_figure(
self, outfile, format, *,
dpi, dsc_comments, orientation, papertype,
Expand Down Expand Up @@ -1026,7 +1025,6 @@ def print_figure_impl(fh):
file = codecs.getwriter("latin-1")(file)
print_figure_impl(file)

@_check_savefig_extra_args
def _print_figure_tex(
self, outfile, format, *,
dpi, dsc_comments, orientation, papertype,
Expand Down
Loading