Skip to content
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
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,37 @@ jobs:
- name: ruff format check
run: ruff format --check src tests

typecheck:
name: Typecheck (pyright strict — public API)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install package and pyright
run: |
python -m pip install --upgrade pip pyright
python -m pip install -e .
# ---public-API surface: pptx/__init__.py is the literal entrypoint
# ---resolved by `from pptx import Presentation`, plus the modules
# ---it pulls in (api, presentation, util, exc, types).
# ---Strict-mode pyright on the broader codebase still surfaces several
# ---thousand findings (reportUnknownMemberType-heavy in chart and
# ---oxml.simpletypes); those are tracked as future work, not this gate.
- name: pyright (public-API entry points)
run: |
pyright src/pptx/__init__.py src/pptx/api.py src/pptx/presentation.py \
src/pptx/util.py src/pptx/exc.py src/pptx/types.py

test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
Expand Down
7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Office/Business :: Office Suites",
"Topic :: Software Development :: Libraries",
]
Expand All @@ -33,7 +33,7 @@ dynamic = ["version"]
keywords = ["powerpoint", "ppt", "pptx", "openxml", "office"]
license = { text = "MIT" }
readme = "README.rst"
requires-python = ">=3.8"
requires-python = ">=3.9"

[project.urls]
Changelog = "https://github.com/MHoroszowski/python-pptx/blob/master/HISTORY.rst"
Expand All @@ -42,9 +42,6 @@ Homepage = "https://github.com/MHoroszowski/python-pptx"
Repository = "https://github.com/MHoroszowski/python-pptx"
Upstream = "https://github.com/scanny/python-pptx"

[tool.black]
line-length = 100

[tool.pyright]
exclude = [
"**/__pycache__",
Expand Down
17 changes: 10 additions & 7 deletions src/pptx/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@ def Presentation(
|pathlib.Path|) or a file-like object. If *pptx* is missing or ``None``,
the built-in default presentation "template" is loaded.
"""
# ---accept os.PathLike (pathlib.Path, etc.) by coercing to str at the
# ---boundary; collapse the union to (str | IO[bytes]) for downstream---
pkg_file: str | IO[bytes]
if pptx is None:
pptx = _default_pptx_path()
pkg_file = _default_pptx_path()
elif isinstance(pptx, os.PathLike):
pkg_file = os.fspath(pptx)
else:
pkg_file = pptx

# ---accept os.PathLike (pathlib.Path, etc.) by coercing to str at the boundary---
if hasattr(pptx, "__fspath__"):
pptx = os.fspath(pptx)

presentation_part = Package.open(pptx).main_document_part
presentation_part = Package.open(pkg_file).main_document_part

if not _is_pptx_package(presentation_part):
tmpl = "file '%s' is not a PowerPoint file, content type is '%s'"
raise ValueError(tmpl % (pptx, presentation_part.content_type))
raise ValueError(tmpl % (pkg_file, presentation_part.content_type))

return presentation_part.presentation

Expand Down
12 changes: 6 additions & 6 deletions src/pptx/presentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ def save(self, file: str | os.PathLike[str] | IO[bytes]):
`file` can be a file-path (|str| or any |os.PathLike| object such as
|pathlib.Path|) or a file-like object open for writing bytes.
"""
# ---accept os.PathLike (pathlib.Path etc.) by coercing to str---
if hasattr(file, "__fspath__"):
file = os.fspath(file)
self.part.save(file)
# ---accept os.PathLike (pathlib.Path etc.) by coercing to str at the
# ---boundary; collapse the union to (str | IO[bytes]) for downstream---
pkg_file: str | IO[bytes] = os.fspath(file) if isinstance(file, os.PathLike) else file
self.part.save(pkg_file)

@property
def slide_height(self) -> Length | None:
Expand Down Expand Up @@ -157,7 +157,7 @@ def sections(self):
elements into existence; the wrapping XML is created on the first
``add_section`` call.
"""
from pptx.sections import _Sections
from pptx.sections import _Sections # pyright: ignore[reportPrivateUsage]

return _Sections(self)

Expand Down Expand Up @@ -202,7 +202,7 @@ def append_from(
Raises |IndexError| if any value in `slide_indexes` is out of
range for ``other_pres.slides``.
"""
from pptx.parts.slide import _PortContext, duplicate_notes_slide_for # noqa: F401
from pptx.parts.slide import _PortContext # pyright: ignore[reportPrivateUsage]

if slide_indexes is None:
source_slides = list(other_pres.slides)
Expand Down
Loading