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

Skip to content

Commit a87c18e

Browse files
committed
Deprecate PdfPages(keep_empty=True).
... because 0-page pdfs are not valid pdf files. Explicitly passing keep_empty=False remains supported for now to help transitioning to the new behavior. I also delayed file creation in backend_pdf.PdfPages as that seems simpler than re-deleting the file at closure if needed. I further dropped `__slots__` as the micro-optimization of these classes seem pointless (backend_pdf.PdfPages is wrapping a PdfFile object which is much larger anyways).
1 parent e4905bf commit a87c18e

File tree

5 files changed

+130
-52
lines changed

5 files changed

+130
-52
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
``PdfPages(keep_empty=True)``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
A zero-page pdf is not valid, thus passing ``keep_empty=True`` to
4+
`.backend_pdf.PdfPages` and `.backend_pgf.PdfPages`, and the ``keep_empty``
5+
attribute of these classes, are deprecated. Currently, these classes default
6+
to keeping empty outputs, but that behavior is deprecated too. Explicitly
7+
passing ``keep_empty=False`` remains supported for now to help transition to
8+
the new behavior.
9+
10+
Furthermore, `.backend_pdf.PdfPages` no longer immediately creates the target
11+
file upon instantiation, but only when the first figure is saved. To fully
12+
control file creation, directly pass an opened file object as argument
13+
(``with open(path, "wb") as file, PdfPages(file) as pdf: ...``).

lib/matplotlib/backends/backend_pdf.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,18 +2669,19 @@ class PdfPages:
26692669
In reality `PdfPages` is a thin wrapper around `PdfFile`, in order to avoid
26702670
confusion when using `~.pyplot.savefig` and forgetting the format argument.
26712671
"""
2672-
__slots__ = ('_file', 'keep_empty')
26732672

2674-
def __init__(self, filename, keep_empty=True, metadata=None):
2673+
_UNSET = object()
2674+
2675+
def __init__(self, filename, keep_empty=_UNSET, metadata=None):
26752676
"""
26762677
Create a new PdfPages object.
26772678
26782679
Parameters
26792680
----------
26802681
filename : str or path-like or file-like
2681-
Plots using `PdfPages.savefig` will be written to a file at this
2682-
location. The file is opened at once and any older file with the
2683-
same name is overwritten.
2682+
Plots using `PdfPages.savefig` will be written to a file at this location.
2683+
The file is opened when a figure is saved for the first time (overwriting
2684+
any older file with the same name).
26842685
26852686
keep_empty : bool, optional
26862687
If set to False, then empty pdf files will be deleted automatically
@@ -2696,8 +2697,16 @@ def __init__(self, filename, keep_empty=True, metadata=None):
26962697
'Trapped'. Values have been predefined for 'Creator', 'Producer'
26972698
and 'CreationDate'. They can be removed by setting them to `None`.
26982699
"""
2699-
self._file = PdfFile(filename, metadata=metadata)
2700-
self.keep_empty = keep_empty
2700+
self._filename = filename
2701+
self._metadata = metadata
2702+
self._file = None
2703+
if keep_empty and keep_empty is not self._UNSET:
2704+
_api.warn_deprecated("3.8", message=(
2705+
"Keeping empty pdf files is deprecated since %(since)s and support "
2706+
"will be removed %(removal)s."))
2707+
self._keep_empty = keep_empty
2708+
2709+
keep_empty = _api.deprecate_privatize_attribute("3.8")
27012710

27022711
def __enter__(self):
27032712
return self
@@ -2710,12 +2719,15 @@ def close(self):
27102719
Finalize this object, making the underlying file a complete
27112720
PDF file.
27122721
"""
2713-
self._file.finalize()
2714-
self._file.close()
2715-
if (self.get_pagecount() == 0 and not self.keep_empty and
2716-
not self._file.passed_in_file_object):
2717-
os.remove(self._file.fh.name)
2718-
self._file = None
2722+
if self._file is not None:
2723+
self._file.finalize()
2724+
self._file.close()
2725+
self._file = None
2726+
elif self._keep_empty: # True *or* UNSET.
2727+
_api.warn_deprecated("3.8", message=(
2728+
"Keeping empty pdf files is deprecated since %(since)s and support "
2729+
"will be removed %(removal)s."))
2730+
PdfFile(self._filename, metadata=self._metadata) # touch the file.
27192731

27202732
def infodict(self):
27212733
"""
@@ -2744,6 +2756,8 @@ def savefig(self, figure=None, **kwargs):
27442756
if manager is None:
27452757
raise ValueError(f"No figure {figure}")
27462758
figure = manager.canvas.figure
2759+
if self._file is None:
2760+
self._file = PdfFile(self._filename, metadata=self._metadata) # init.
27472761
# Force use of pdf backend, as PdfPages is tightly coupled with it.
27482762
with cbook._setattr_cm(figure, canvas=FigureCanvasPdf(figure)):
27492763
figure.savefig(self, format="pdf", **kwargs)

lib/matplotlib/backends/backend_pgf.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from PIL import Image
1515

1616
import matplotlib as mpl
17-
from matplotlib import cbook, font_manager as fm
17+
from matplotlib import _api, cbook, font_manager as fm
1818
from matplotlib.backend_bases import (
1919
_Backend, FigureCanvasBase, FigureManagerBase, RendererBase
2020
)
@@ -874,16 +874,10 @@ class PdfPages:
874874
... # When no figure is specified the current figure is saved
875875
... pdf.savefig()
876876
"""
877-
__slots__ = (
878-
'_output_name',
879-
'keep_empty',
880-
'_n_figures',
881-
'_file',
882-
'_info_dict',
883-
'_metadata',
884-
)
885877

886-
def __init__(self, filename, *, keep_empty=True, metadata=None):
878+
_UNSET = object()
879+
880+
def __init__(self, filename, *, keep_empty=_UNSET, metadata=None):
887881
"""
888882
Create a new PdfPages object.
889883
@@ -912,11 +906,17 @@ def __init__(self, filename, *, keep_empty=True, metadata=None):
912906
"""
913907
self._output_name = filename
914908
self._n_figures = 0
915-
self.keep_empty = keep_empty
909+
if keep_empty and keep_empty is not self._UNSET:
910+
_api.warn_deprecated("3.8", message=(
911+
"Keeping empty pdf files is deprecated since %(since)s and support "
912+
"will be removed %(removal)s."))
913+
self._keep_empty = keep_empty
916914
self._metadata = (metadata or {}).copy()
917915
self._info_dict = _create_pdf_info_dict('pgf', self._metadata)
918916
self._file = BytesIO()
919917

918+
keep_empty = _api.deprecate_privatize_attribute("3.8")
919+
920920
def _write_header(self, width_inches, height_inches):
921921
pdfinfo = ','.join(
922922
_metadata_to_str(k, v) for k, v in self._info_dict.items())
@@ -946,7 +946,10 @@ def close(self):
946946
self._file.write(rb'\end{document}\n')
947947
if self._n_figures > 0:
948948
self._run_latex()
949-
elif self.keep_empty:
949+
elif self._keep_empty:
950+
_api.warn_deprecated("3.8", message=(
951+
"Keeping empty pdf files is deprecated since %(since)s and support "
952+
"will be removed %(removal)s."))
950953
open(self._output_name, 'wb').close()
951954
self._file.close()
952955

lib/matplotlib/tests/test_backend_pdf.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import io
44
import os
55
from pathlib import Path
6-
from tempfile import NamedTemporaryFile
76

87
import numpy as np
98
import pytest
@@ -81,35 +80,44 @@ def test_multipage_properfinalize():
8180
assert len(s) < 40000
8281

8382

84-
def test_multipage_keep_empty():
83+
def test_multipage_keep_empty(tmp_path):
84+
os.chdir(tmp_path)
85+
8586
# test empty pdf files
86-
# test that an empty pdf is left behind with keep_empty=True (default)
87-
with NamedTemporaryFile(delete=False) as tmp:
88-
with PdfPages(tmp) as pdf:
89-
filename = pdf._file.fh.name
90-
assert os.path.exists(filename)
91-
os.remove(filename)
92-
# test if an empty pdf is deleting itself afterwards with keep_empty=False
93-
with PdfPages(filename, keep_empty=False) as pdf:
87+
88+
# an empty pdf is left behind with keep_empty unset
89+
with pytest.warns(mpl.MatplotlibDeprecationWarning), PdfPages("a.pdf") as pdf:
90+
pass
91+
assert os.path.exists("a.pdf")
92+
93+
# an empty pdf is left behind with keep_empty=True
94+
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
95+
PdfPages("b.pdf", keep_empty=True) as pdf:
9496
pass
95-
assert not os.path.exists(filename)
97+
assert os.path.exists("b.pdf")
98+
99+
# an empty pdf deletes itself afterwards with keep_empty=False
100+
with PdfPages("c.pdf", keep_empty=False) as pdf:
101+
pass
102+
assert not os.path.exists("c.pdf")
103+
96104
# test pdf files with content, they should never be deleted
97-
fig, ax = plt.subplots()
98-
ax.plot([1, 2, 3])
99-
# test that a non-empty pdf is left behind with keep_empty=True (default)
100-
with NamedTemporaryFile(delete=False) as tmp:
101-
with PdfPages(tmp) as pdf:
102-
filename = pdf._file.fh.name
103-
pdf.savefig()
104-
assert os.path.exists(filename)
105-
os.remove(filename)
106-
# test that a non-empty pdf is left behind with keep_empty=False
107-
with NamedTemporaryFile(delete=False) as tmp:
108-
with PdfPages(tmp, keep_empty=False) as pdf:
109-
filename = pdf._file.fh.name
110-
pdf.savefig()
111-
assert os.path.exists(filename)
112-
os.remove(filename)
105+
106+
# a non-empty pdf is left behind with keep_empty unset
107+
with PdfPages("d.pdf") as pdf:
108+
pdf.savefig(plt.figure())
109+
assert os.path.exists("d.pdf")
110+
111+
# a non-empty pdf is left behind with keep_empty=True
112+
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
113+
PdfPages("e.pdf", keep_empty=True) as pdf:
114+
pdf.savefig(plt.figure())
115+
assert os.path.exists("e.pdf")
116+
117+
# a non-empty pdf is left behind with keep_empty=False
118+
with PdfPages("f.pdf", keep_empty=False) as pdf:
119+
pdf.savefig(plt.figure())
120+
assert os.path.exists("f.pdf")
113121

114122

115123
def test_composite_image():

lib/matplotlib/tests/test_backend_pgf.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,46 @@ def test_pdf_pages_metadata_check(monkeypatch, system):
286286
}
287287

288288

289+
def test_multipage_keep_empty(tmp_path):
290+
os.chdir(tmp_path)
291+
292+
# test empty pdf files
293+
294+
# an empty pdf is left behind with keep_empty unset
295+
with pytest.warns(mpl.MatplotlibDeprecationWarning), PdfPages("a.pdf") as pdf:
296+
pass
297+
assert os.path.exists("a.pdf")
298+
299+
# an empty pdf is left behind with keep_empty=True
300+
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
301+
PdfPages("b.pdf", keep_empty=True) as pdf:
302+
pass
303+
assert os.path.exists("b.pdf")
304+
305+
# an empty pdf deletes itself afterwards with keep_empty=False
306+
with PdfPages("c.pdf", keep_empty=False) as pdf:
307+
pass
308+
assert not os.path.exists("c.pdf")
309+
310+
# test pdf files with content, they should never be deleted
311+
312+
# a non-empty pdf is left behind with keep_empty unset
313+
with PdfPages("d.pdf") as pdf:
314+
pdf.savefig(plt.figure())
315+
assert os.path.exists("d.pdf")
316+
317+
# a non-empty pdf is left behind with keep_empty=True
318+
with pytest.warns(mpl.MatplotlibDeprecationWarning), \
319+
PdfPages("e.pdf", keep_empty=True) as pdf:
320+
pdf.savefig(plt.figure())
321+
assert os.path.exists("e.pdf")
322+
323+
# a non-empty pdf is left behind with keep_empty=False
324+
with PdfPages("f.pdf", keep_empty=False) as pdf:
325+
pdf.savefig(plt.figure())
326+
assert os.path.exists("f.pdf")
327+
328+
289329
@needs_pgf_xelatex
290330
def test_tex_restart_after_error():
291331
fig = plt.figure()

0 commit comments

Comments
 (0)