Skip to content

Commit 98aa974

Browse files
authored
BUG: Don't close stream passed to PdfWriter.write() (#2909)
Closes #2905.
1 parent 9e0fce7 commit 98aa974

File tree

2 files changed

+64
-9
lines changed

2 files changed

+64
-9
lines changed

pypdf/_writer.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
StreamType,
6464
_get_max_pdf_version_header,
6565
deprecate,
66+
deprecate_no_replacement,
6667
deprecation_with_replacement,
6768
logger_warning,
6869
)
@@ -236,10 +237,9 @@ def _get_clone_from(
236237
or Path(str(fileobj)).stat().st_size == 0
237238
):
238239
cloning = False
239-
if isinstance(fileobj, (IO, BytesIO)):
240+
if isinstance(fileobj, (IOBase, BytesIO)):
240241
t = fileobj.tell()
241-
fileobj.seek(-1, 2)
242-
if fileobj.tell() == 0:
242+
if fileobj.seek(0, 2) == 0:
243243
cloning = False
244244
fileobj.seek(t, 0)
245245
if cloning:
@@ -250,7 +250,8 @@ def _get_clone_from(
250250
# to prevent overwriting
251251
self.temp_fileobj = fileobj
252252
self.fileobj = ""
253-
self.with_as_usage = False
253+
self._with_as_usage = False
254+
self._cloned = False
254255
# The root of our page tree node.
255256
pages = DictionaryObject()
256257
pages.update(
@@ -268,6 +269,7 @@ def _get_clone_from(
268269
if not isinstance(clone_from, PdfReader):
269270
clone_from = PdfReader(clone_from)
270271
self.clone_document_from_reader(clone_from)
272+
self._cloned = True
271273
else:
272274
self._pages = self._add_object(pages)
273275
# root object
@@ -355,11 +357,23 @@ def xmp_metadata(self, value: Optional[XmpInformation]) -> None:
355357

356358
return self.root_object.xmp_metadata # type: ignore
357359

360+
@property
361+
def with_as_usage(self) -> bool:
362+
deprecate_no_replacement("with_as_usage", "6.0")
363+
return self._with_as_usage
364+
365+
@with_as_usage.setter
366+
def with_as_usage(self, value: bool) -> None:
367+
deprecate_no_replacement("with_as_usage", "6.0")
368+
self._with_as_usage = value
369+
358370
def __enter__(self) -> "PdfWriter":
359-
"""Store that writer is initialized by 'with'."""
371+
"""Store how writer is initialized by 'with'."""
372+
c: bool = self._cloned
360373
t = self.temp_fileobj
361374
self.__init__() # type: ignore
362-
self.with_as_usage = True
375+
self._cloned = c
376+
self._with_as_usage = True
363377
self.fileobj = t # type: ignore
364378
return self
365379

@@ -370,7 +384,7 @@ def __exit__(
370384
traceback: Optional[TracebackType],
371385
) -> None:
372386
"""Write data to the fileobj."""
373-
if self.fileobj:
387+
if self.fileobj and not self._cloned:
374388
self.write(self.fileobj)
375389

376390
def _repr_mimebundle_(
@@ -1406,13 +1420,14 @@ def write(self, stream: Union[Path, StrByteType]) -> Tuple[bool, IO[Any]]:
14061420

14071421
if isinstance(stream, (str, Path)):
14081422
stream = FileIO(stream, "wb")
1409-
self.with_as_usage = True
14101423
my_file = True
14111424

14121425
self.write_stream(stream)
14131426

1414-
if self.with_as_usage:
1427+
if my_file:
14151428
stream.close()
1429+
else:
1430+
stream.flush()
14161431

14171432
return my_file, stream
14181433

tests/test_writer.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,3 +2481,43 @@ def test_append_pdf_with_dest_without_page(caplog):
24812481
writer.append(reader)
24822482
assert "/__WKANCHOR_8" not in writer.named_destinations
24832483
assert len(writer.named_destinations) == 3
2484+
2485+
2486+
def test_stream_not_closed():
2487+
"""Tests for #2905"""
2488+
src = RESOURCE_ROOT / "pdflatex-outline.pdf"
2489+
with NamedTemporaryFile(suffix=".pdf") as tmp:
2490+
with PdfReader(src) as reader, PdfWriter() as writer:
2491+
writer.add_page(reader.pages[0])
2492+
writer.write(tmp)
2493+
assert not tmp.file.closed
2494+
2495+
with NamedTemporaryFile(suffix=".pdf") as target:
2496+
with PdfWriter(target.file) as writer:
2497+
writer.add_blank_page(100, 100)
2498+
assert not target.file.closed
2499+
2500+
with open(src, "rb") as fileobj:
2501+
with PdfWriter(fileobj) as writer:
2502+
pass
2503+
assert not fileobj.closed
2504+
2505+
2506+
def test_auto_write(tmp_path):
2507+
"""Another test for #2905"""
2508+
target = tmp_path / "out.pdf"
2509+
with PdfWriter(target) as writer:
2510+
writer.add_blank_page(100, 100)
2511+
assert target.stat().st_size > 0
2512+
2513+
2514+
def test_deprecate_with_as():
2515+
"""Yet another test for #2905"""
2516+
with PdfWriter() as writer:
2517+
with pytest.warns(DeprecationWarning) as w:
2518+
val = writer.with_as_usage
2519+
assert "with_as_usage is deprecated" in w[0].message.args[0]
2520+
assert val
2521+
with pytest.warns(DeprecationWarning) as w:
2522+
writer.with_as_usage = val # old code allowed setting this, so...
2523+
assert "with_as_usage is deprecated" in w[0].message.args[0]

0 commit comments

Comments
 (0)