From fc89634507fe2bb6f93011090d6420b69d69b296 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 29 Mar 2026 14:43:46 +0200 Subject: [PATCH] Support PathLike file inputs --- src/pptx/opc/package.py | 11 ++++++----- src/pptx/parts/image.py | 7 ++++--- tests/opc/test_package.py | 9 +++++++++ tests/parts/test_image.py | 11 +++++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/pptx/opc/package.py b/src/pptx/opc/package.py index 713759c54..6c19bbd0c 100644 --- a/src/pptx/opc/package.py +++ b/src/pptx/opc/package.py @@ -7,6 +7,7 @@ from __future__ import annotations import collections +import os from typing import IO, TYPE_CHECKING, DefaultDict, Iterator, Mapping, Set, cast from pptx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM @@ -360,11 +361,11 @@ def rels(self) -> _Relationships: # --- this must be public to allow the part graph to be traversed --- return self._rels - def _blob_from_file(self, file: str | IO[bytes]) -> bytes: - """Return bytes of `file`, which is either a str path or a file-like object.""" - # --- a str `file` is assumed to be a path --- - if isinstance(file, str): - with open(file, "rb") as f: + def _blob_from_file(self, file: str | os.PathLike[str] | IO[bytes]) -> bytes: + """Return bytes of `file`, which is either a path or a file-like object.""" + # --- a path-like `file` is assumed to be a path --- + if isinstance(file, (str, os.PathLike)): + with open(os.fspath(file), "rb") as f: return f.read() # --- otherwise, assume `file` is a file-like object diff --git a/src/pptx/parts/image.py b/src/pptx/parts/image.py index 9be5d02d6..ccf74041b 100644 --- a/src/pptx/parts/image.py +++ b/src/pptx/parts/image.py @@ -158,11 +158,12 @@ def from_file(cls, image_file: str | IO[bytes]) -> Image: `image_file` can be either a path (str) or a file-like object. """ - if isinstance(image_file, str): + if isinstance(image_file, (str, os.PathLike)): # treat image_file as a path - with open(image_file, "rb") as f: + image_path = os.fspath(image_file) + with open(image_path, "rb") as f: blob = f.read() - filename = os.path.basename(image_file) + filename = os.path.basename(image_path) else: # assume image_file is a file-like object # ---reposition file cursor if it has one--- diff --git a/tests/opc/test_package.py b/tests/opc/test_package.py index 8c0e95809..9702637e0 100644 --- a/tests/opc/test_package.py +++ b/tests/opc/test_package.py @@ -7,6 +7,7 @@ import collections import io import itertools +from pathlib import Path from typing import Any import pytest @@ -431,6 +432,14 @@ def it_can_load_a_blob_from_a_file_path_to_help(self): assert part._blob_from_file(path) == file_bytes + def it_can_load_a_blob_from_a_pathlike_to_help(self): + path = Path(absjoin(test_file_dir, "minimal.pptx")) + with open(path, "rb") as f: + file_bytes = f.read() + part = Part(None, None, None, None) + + assert part._blob_from_file(path) == file_bytes + def it_can_load_a_blob_from_a_file_like_object_to_help(self): part = Part(None, None, None, None) assert part._blob_from_file(io.BytesIO(b"012345")) == b"012345" diff --git a/tests/parts/test_image.py b/tests/parts/test_image.py index 386e3fce9..b8ccbb72c 100644 --- a/tests/parts/test_image.py +++ b/tests/parts/test_image.py @@ -3,6 +3,7 @@ from __future__ import annotations import io +from pathlib import Path import pytest @@ -101,6 +102,16 @@ def it_can_construct_from_a_path(self, from_blob_, image_): Image.from_blob.assert_called_once_with(blob, "python-icon.jpeg") assert image is image_ + def it_can_construct_from_a_pathlike(self, from_blob_, image_): + with open(test_image_path, "rb") as f: + blob = f.read() + from_blob_.return_value = image_ + + image = Image.from_file(Path(test_image_path)) + + Image.from_blob.assert_called_once_with(blob, "python-icon.jpeg") + assert image is image_ + def it_can_construct_from_a_stream(self, from_stream_fixture): image_file, blob, image_ = from_stream_fixture image = Image.from_file(image_file)