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

Skip to content

Commit 39a971b

Browse files
committed
Expire _check_savefig_extra_args-related deprecations.
i.e. make print_figure() error on non-supported kwargs. We can further remove the need to decorate each and every print_method by instead doing the wrapping when fetching the method instead; this wrapping is done in a _switching_canvas_and_get_print_method helper.
1 parent 9fb5370 commit 39a971b

File tree

10 files changed

+66
-133
lines changed

10 files changed

+66
-133
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Unknown keyword arguments to ``FigureCanvas.print_foo`` methods
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... now raise a TypeError, instead of being ignored.

lib/matplotlib/backend_bases.py

Lines changed: 47 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,8 @@
3434
import io
3535
import logging
3636
import os
37-
import re
3837
import sys
3938
import time
40-
import traceback
4139
from weakref import WeakKeyDictionary
4240

4341
import numpy as np
@@ -1534,14 +1532,14 @@ class Done(Exception):
15341532

15351533
def _draw(renderer): raise Done(renderer)
15361534

1537-
with cbook._setattr_cm(figure, draw=_draw):
1535+
with cbook._setattr_cm(figure, draw=_draw), ExitStack() as stack:
15381536
orig_canvas = figure.canvas
15391537
if print_method is None:
15401538
fmt = figure.canvas.get_default_filetype()
15411539
# Even for a canvas' default output type, a canvas switch may be
15421540
# needed, e.g. for FigureCanvasBase.
1543-
print_method = getattr(
1544-
figure.canvas._get_output_canvas(None, fmt), f"print_{fmt}")
1541+
print_method = stack.enter_context(
1542+
figure.canvas._switching_canvas_and_get_print_method(fmt))
15451543
try:
15461544
print_method(io.BytesIO())
15471545
except Done as exc:
@@ -1550,8 +1548,6 @@ def _draw(renderer): raise Done(renderer)
15501548
else:
15511549
raise RuntimeError(f"{print_method} did not call Figure.draw, so "
15521550
f"no renderer is available")
1553-
finally:
1554-
figure.canvas = orig_canvas
15551551

15561552

15571553
def _no_output_draw(figure):
@@ -1574,79 +1570,6 @@ def _is_non_interactive_terminal_ipython(ip):
15741570
and getattr(ip.parent, 'interact', None) is False)
15751571

15761572

1577-
def _check_savefig_extra_args(func=None, extra_kwargs=()):
1578-
"""
1579-
Decorator for the final print_* methods that accept keyword arguments.
1580-
1581-
If any unused keyword arguments are left, this decorator will warn about
1582-
them, and as part of the warning, will attempt to specify the function that
1583-
the user actually called, instead of the backend-specific method. If unable
1584-
to determine which function the user called, it will specify `.savefig`.
1585-
1586-
For compatibility across backends, this does not warn about keyword
1587-
arguments added by `FigureCanvasBase.print_figure` for use in a subset of
1588-
backends, because the user would not have added them directly.
1589-
"""
1590-
1591-
if func is None:
1592-
return functools.partial(_check_savefig_extra_args,
1593-
extra_kwargs=extra_kwargs)
1594-
1595-
old_sig = inspect.signature(func)
1596-
1597-
@functools.wraps(func)
1598-
def wrapper(*args, **kwargs):
1599-
name = 'savefig' # Reasonable default guess.
1600-
public_api = re.compile(
1601-
r'^savefig|print_[A-Za-z0-9]+|_no_output_draw$'
1602-
)
1603-
seen_print_figure = False
1604-
for frame, line in traceback.walk_stack(None):
1605-
if frame is None:
1606-
# when called in embedded context may hit frame is None.
1607-
break
1608-
# Work around sphinx-gallery not setting __name__.
1609-
frame_name = frame.f_globals.get('__name__', '')
1610-
if re.match(r'\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))',
1611-
frame_name):
1612-
name = frame.f_code.co_name
1613-
if public_api.match(name):
1614-
if name in ('print_figure', '_no_output_draw'):
1615-
seen_print_figure = True
1616-
1617-
elif frame_name == '_functools':
1618-
# PyPy adds an extra frame without module prefix for this
1619-
# functools wrapper, which we ignore to assume we're still in
1620-
# Matplotlib code.
1621-
continue
1622-
else:
1623-
break
1624-
1625-
accepted_kwargs = {*old_sig.parameters, *extra_kwargs}
1626-
if seen_print_figure:
1627-
for kw in ['dpi', 'facecolor', 'edgecolor', 'orientation',
1628-
'bbox_inches_restore']:
1629-
# Ignore keyword arguments that are passed in by print_figure
1630-
# for the use of other renderers.
1631-
if kw not in accepted_kwargs:
1632-
kwargs.pop(kw, None)
1633-
1634-
for arg in list(kwargs):
1635-
if arg in accepted_kwargs:
1636-
continue
1637-
_api.warn_deprecated(
1638-
'3.3', name=name,
1639-
message='%(name)s() got unexpected keyword argument "'
1640-
+ arg + '" which is no longer supported as of '
1641-
'%(since)s and will become an error '
1642-
'%(removal)s')
1643-
kwargs.pop(arg)
1644-
1645-
return func(*args, **kwargs)
1646-
1647-
return wrapper
1648-
1649-
16501573
class FigureCanvasBase:
16511574
"""
16521575
The canvas the figure renders into.
@@ -2145,21 +2068,30 @@ def get_supported_filetypes_grouped(cls):
21452068
groupings[name].sort()
21462069
return groupings
21472070

2148-
def _get_output_canvas(self, backend, fmt):
2071+
@contextmanager
2072+
def _switching_canvas_and_get_print_method(self, fmt, backend=None):
21492073
"""
2150-
Set the canvas in preparation for saving the figure.
2074+
Context manager temporarily setting the canvas for saving the figure::
2075+
2076+
with canvas._switching_canvas_and_get_print_method(fmt, backend) \\
2077+
as print_method:
2078+
# ``print_method`` is a suitable ``print_{fmt}`` method, and
2079+
# the figure's canvas is temporarily switched to the method's
2080+
# canvas within the with... block. ``print_method`` is also
2081+
# wrapped to suppress extra kwargs passed by ``print_figure``.
21512082
21522083
Parameters
21532084
----------
2154-
backend : str or None
2155-
If not None, switch the figure canvas to the ``FigureCanvas`` class
2156-
of the given backend.
21572085
fmt : str
21582086
If *backend* is None, then determine a suitable canvas class for
21592087
saving to format *fmt* -- either the current canvas class, if it
21602088
supports *fmt*, or whatever `get_registered_canvas_class` returns;
21612089
switch the figure canvas to that canvas class.
2090+
backend : str or None, default: None
2091+
If not None, switch the figure canvas to the ``FigureCanvas`` class
2092+
of the given backend.
21622093
"""
2094+
canvas = None
21632095
if backend is not None:
21642096
# Return a specific canvas class, if requested.
21652097
canvas_class = (
@@ -2170,16 +2102,34 @@ def _get_output_canvas(self, backend, fmt):
21702102
f"The {backend!r} backend does not support {fmt} output")
21712103
elif hasattr(self, f"print_{fmt}"):
21722104
# Return the current canvas if it supports the requested format.
2173-
return self
2105+
canvas = self
2106+
canvas_class = None # Skip call to switch_backends.
21742107
else:
21752108
# Return a default canvas for the requested format, if it exists.
21762109
canvas_class = get_registered_canvas_class(fmt)
21772110
if canvas_class:
2178-
return self.switch_backends(canvas_class)
2179-
# Else report error for unsupported format.
2180-
raise ValueError(
2181-
"Format {!r} is not supported (supported formats: {})"
2182-
.format(fmt, ", ".join(sorted(self.get_supported_filetypes()))))
2111+
canvas = self.switch_backends(canvas_class)
2112+
if canvas is None:
2113+
raise ValueError(
2114+
"Format {!r} is not supported (supported formats: {})".format(
2115+
fmt, ", ".join(sorted(self.get_supported_filetypes()))))
2116+
meth = getattr(canvas, f"print_{fmt}")
2117+
mod = (getattr(meth.func, "__module__", "")
2118+
if hasattr(meth, "func") # partialmethod, e.g. backend_wx.
2119+
else getattr(meth, "__module__", ""))
2120+
if mod.startswith(("matplotlib.", "mpl_toolkits.")):
2121+
optional_kws = { # Passed by print_figure for other renderers.
2122+
"dpi", "facecolor", "edgecolor", "orientation",
2123+
"bbox_inches_restore"}
2124+
skip = optional_kws - {*inspect.signature(meth).parameters}
2125+
print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
2126+
*args, **{k: v for k, v in kwargs.items() if k not in skip}))
2127+
else: # Let third-parties do as they see fit.
2128+
print_method = meth
2129+
try:
2130+
yield print_method
2131+
finally:
2132+
self.figure.canvas = self
21832133

21842134
def print_figure(
21852135
self, filename, dpi=None, facecolor=None, edgecolor=None,
@@ -2247,20 +2197,18 @@ def print_figure(
22472197
filename = filename.rstrip('.') + '.' + format
22482198
format = format.lower()
22492199

2250-
# get canvas object and print method for format
2251-
canvas = self._get_output_canvas(backend, format)
2252-
print_method = getattr(canvas, 'print_%s' % format)
2253-
22542200
if dpi is None:
22552201
dpi = rcParams['savefig.dpi']
22562202
if dpi == 'figure':
22572203
dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)
22582204

22592205
# Remove the figure manager, if any, to avoid resizing the GUI widget.
22602206
with cbook._setattr_cm(self, manager=None), \
2207+
self._switching_canvas_and_get_print_method(format, backend) \
2208+
as print_method, \
22612209
cbook._setattr_cm(self.figure, dpi=dpi), \
2262-
cbook._setattr_cm(canvas, _device_pixel_ratio=1), \
2263-
cbook._setattr_cm(canvas, _is_saving=True), \
2210+
cbook._setattr_cm(self.figure.canvas, _device_pixel_ratio=1), \
2211+
cbook._setattr_cm(self.figure.canvas, _is_saving=True), \
22642212
ExitStack() as stack:
22652213

22662214
for prop in ["facecolor", "edgecolor"]:
@@ -2295,8 +2243,8 @@ def print_figure(
22952243
bbox_inches = bbox_inches.padded(pad_inches)
22962244

22972245
# call adjust_bbox to save only the given area
2298-
restore_bbox = tight_bbox.adjust_bbox(self.figure, bbox_inches,
2299-
canvas.fixed_dpi)
2246+
restore_bbox = tight_bbox.adjust_bbox(
2247+
self.figure, bbox_inches, self.figure.canvas.fixed_dpi)
23002248

23012249
_bbox_inches_restore = (bbox_inches, restore_bbox)
23022250
else:
@@ -2319,7 +2267,6 @@ def print_figure(
23192267
if bbox_inches and restore_bbox:
23202268
restore_bbox()
23212269

2322-
self.figure.set_canvas(self)
23232270
return result
23242271

23252272
@classmethod

lib/matplotlib/backends/backend_agg.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@
3535
from matplotlib import _api, cbook
3636
from matplotlib import colors as mcolors
3737
from matplotlib.backend_bases import (
38-
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
39-
RendererBase)
38+
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase)
4039
from matplotlib.font_manager import findfont, get_font
4140
from matplotlib.ft2font import (LOAD_FORCE_AUTOHINT, LOAD_NO_HINTING,
4241
LOAD_DEFAULT, LOAD_NO_AUTOHINT)
@@ -477,7 +476,6 @@ def buffer_rgba(self):
477476
"""
478477
return self.renderer.buffer_rgba()
479478

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

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

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

573569
print_jpeg = print_jpg
574570

575-
@_check_savefig_extra_args
576571
def print_tif(self, filename_or_obj, *, pil_kwargs=None):
577572
self._print_pil(filename_or_obj, "tiff", pil_kwargs)
578573

579574
print_tiff = print_tif
580575

581-
@_check_savefig_extra_args
582576
def print_webp(self, filename_or_obj, *, pil_kwargs=None):
583577
self._print_pil(filename_or_obj, "webp", pil_kwargs)
584578

lib/matplotlib/backends/backend_cairo.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import matplotlib as mpl
2929
from .. import _api, cbook, font_manager
3030
from matplotlib.backend_bases import (
31-
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
32-
GraphicsContextBase, RendererBase)
31+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
32+
RendererBase)
3333
from matplotlib.font_manager import ttfFontProperty
3434
from matplotlib.mathtext import MathTextParser
3535
from matplotlib.path import Path
@@ -448,11 +448,9 @@ def restore_region(self, region):
448448
surface.mark_dirty_rectangle(
449449
slx.start, sly.start, slx.stop - slx.start, sly.stop - sly.start)
450450

451-
@_check_savefig_extra_args
452451
def print_png(self, fobj):
453452
self._get_printed_image_surface().write_to_png(fobj)
454453

455-
@_check_savefig_extra_args
456454
def print_rgba(self, fobj):
457455
width, height = self.get_width_height()
458456
buf = self._get_printed_image_surface().get_data()
@@ -470,7 +468,6 @@ def _get_printed_image_surface(self):
470468
self.figure.draw(renderer)
471469
return surface
472470

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

lib/matplotlib/backends/backend_pdf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
from matplotlib import _api, _text_helpers, cbook
3030
from matplotlib._pylab_helpers import Gcf
3131
from matplotlib.backend_bases import (
32-
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
33-
GraphicsContextBase, RendererBase)
32+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
33+
RendererBase)
3434
from matplotlib.backends.backend_mixed import MixedModeRenderer
3535
from matplotlib.figure import Figure
3636
from matplotlib.font_manager import findfont, get_font
@@ -2762,7 +2762,6 @@ class FigureCanvasPdf(FigureCanvasBase):
27622762
def get_default_filetype(self):
27632763
return 'pdf'
27642764

2765-
@_check_savefig_extra_args
27662765
@_api.delete_parameter("3.4", "dpi")
27672766
def print_pdf(self, filename, *,
27682767
dpi=None, # dpi to use for images

lib/matplotlib/backends/backend_pgf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import matplotlib as mpl
1919
from matplotlib import _api, cbook, font_manager as fm
2020
from matplotlib.backend_bases import (
21-
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
22-
GraphicsContextBase, RendererBase
21+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
22+
RendererBase
2323
)
2424
from matplotlib.backends.backend_mixed import MixedModeRenderer
2525
from matplotlib.backends.backend_pdf import (
@@ -790,7 +790,6 @@ class FigureCanvasPgf(FigureCanvasBase):
790790
def get_default_filetype(self):
791791
return 'pdf'
792792

793-
@_check_savefig_extra_args
794793
def _print_pgf_to_fh(self, fh, *, bbox_inches_restore=None):
795794

796795
header_text = """%% Creator: Matplotlib, PGF backend

lib/matplotlib/backends/backend_ps.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
from matplotlib import _api, cbook, _path, _text_helpers
2525
from matplotlib.afm import AFM
2626
from matplotlib.backend_bases import (
27-
_Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
28-
GraphicsContextBase, RendererBase)
27+
_Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase,
28+
RendererBase)
2929
from matplotlib.cbook import is_writable_file_like, file_requires_unicode
3030
from matplotlib.font_manager import get_font
3131
from matplotlib.ft2font import LOAD_NO_HINTING, LOAD_NO_SCALE, FT2Font
@@ -885,7 +885,6 @@ def _print_ps(
885885
printer(outfile, format, dpi=dpi, dsc_comments=dsc_comments,
886886
orientation=orientation, papertype=papertype, **kwargs)
887887

888-
@_check_savefig_extra_args
889888
def _print_figure(
890889
self, outfile, format, *,
891890
dpi, dsc_comments, orientation, papertype,
@@ -1023,7 +1022,6 @@ def print_figure_impl(fh):
10231022
file = codecs.getwriter("latin-1")(file)
10241023
print_figure_impl(file)
10251024

1026-
@_check_savefig_extra_args
10271025
def _print_figure_tex(
10281026
self, outfile, format, *,
10291027
dpi, dsc_comments, orientation, papertype,

0 commit comments

Comments
 (0)