Skip to content

Commit a6cb787

Browse files
authored
Merge pull request #44 from MHoroszowski/feature/modernization-phase3
chore: pyright CI gate (public API) + Python 3.13 matrix + pyproject cleanup (Modernization Phase 3)
2 parents 5c26976 + 94965d7 commit a6cb787

4 files changed

Lines changed: 43 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,37 @@ jobs:
3636
- name: ruff format check
3737
run: ruff format --check src tests
3838

39+
typecheck:
40+
name: Typecheck (pyright strict — public API)
41+
runs-on: ubuntu-latest
42+
steps:
43+
- uses: actions/checkout@v4
44+
- name: Set up Python
45+
uses: actions/setup-python@v5
46+
with:
47+
python-version: "3.12"
48+
- name: Install package and pyright
49+
run: |
50+
python -m pip install --upgrade pip pyright
51+
python -m pip install -e .
52+
# ---public-API surface: pptx/__init__.py is the literal entrypoint
53+
# ---resolved by `from pptx import Presentation`, plus the modules
54+
# ---it pulls in (api, presentation, util, exc, types).
55+
# ---Strict-mode pyright on the broader codebase still surfaces several
56+
# ---thousand findings (reportUnknownMemberType-heavy in chart and
57+
# ---oxml.simpletypes); those are tracked as future work, not this gate.
58+
- name: pyright (public-API entry points)
59+
run: |
60+
pyright src/pptx/__init__.py src/pptx/api.py src/pptx/presentation.py \
61+
src/pptx/util.py src/pptx/exc.py src/pptx/types.py
62+
3963
test:
4064
name: Test (Python ${{ matrix.python-version }})
4165
runs-on: ubuntu-latest
4266
strategy:
4367
fail-fast: false
4468
matrix:
45-
python-version: ["3.9", "3.10", "3.11", "3.12"]
69+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
4670
steps:
4771
- uses: actions/checkout@v4
4872
- name: Set up Python ${{ matrix.python-version }}

pyproject.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ classifiers = [
1414
"Operating System :: OS Independent",
1515
"Programming Language :: Python",
1616
"Programming Language :: Python :: 3",
17-
"Programming Language :: Python :: 3.8",
1817
"Programming Language :: Python :: 3.9",
1918
"Programming Language :: Python :: 3.10",
2019
"Programming Language :: Python :: 3.11",
2120
"Programming Language :: Python :: 3.12",
21+
"Programming Language :: Python :: 3.13",
2222
"Topic :: Office/Business :: Office Suites",
2323
"Topic :: Software Development :: Libraries",
2424
]
@@ -33,7 +33,7 @@ dynamic = ["version"]
3333
keywords = ["powerpoint", "ppt", "pptx", "openxml", "office"]
3434
license = { text = "MIT" }
3535
readme = "README.rst"
36-
requires-python = ">=3.8"
36+
requires-python = ">=3.9"
3737

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

45-
[tool.black]
46-
line-length = 100
47-
4845
[tool.pyright]
4946
exclude = [
5047
"**/__pycache__",

src/pptx/api.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,21 @@ def Presentation(
2727
|pathlib.Path|) or a file-like object. If *pptx* is missing or ``None``,
2828
the built-in default presentation "template" is loaded.
2929
"""
30+
# ---accept os.PathLike (pathlib.Path, etc.) by coercing to str at the
31+
# ---boundary; collapse the union to (str | IO[bytes]) for downstream---
32+
pkg_file: str | IO[bytes]
3033
if pptx is None:
31-
pptx = _default_pptx_path()
34+
pkg_file = _default_pptx_path()
35+
elif isinstance(pptx, os.PathLike):
36+
pkg_file = os.fspath(pptx)
37+
else:
38+
pkg_file = pptx
3239

33-
# ---accept os.PathLike (pathlib.Path, etc.) by coercing to str at the boundary---
34-
if hasattr(pptx, "__fspath__"):
35-
pptx = os.fspath(pptx)
36-
37-
presentation_part = Package.open(pptx).main_document_part
40+
presentation_part = Package.open(pkg_file).main_document_part
3841

3942
if not _is_pptx_package(presentation_part):
4043
tmpl = "file '%s' is not a PowerPoint file, content type is '%s'"
41-
raise ValueError(tmpl % (pptx, presentation_part.content_type))
44+
raise ValueError(tmpl % (pkg_file, presentation_part.content_type))
4245

4346
return presentation_part.presentation
4447

src/pptx/presentation.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ def save(self, file: str | os.PathLike[str] | IO[bytes]):
7373
`file` can be a file-path (|str| or any |os.PathLike| object such as
7474
|pathlib.Path|) or a file-like object open for writing bytes.
7575
"""
76-
# ---accept os.PathLike (pathlib.Path etc.) by coercing to str---
77-
if hasattr(file, "__fspath__"):
78-
file = os.fspath(file)
79-
self.part.save(file)
76+
# ---accept os.PathLike (pathlib.Path etc.) by coercing to str at the
77+
# ---boundary; collapse the union to (str | IO[bytes]) for downstream---
78+
pkg_file: str | IO[bytes] = os.fspath(file) if isinstance(file, os.PathLike) else file
79+
self.part.save(pkg_file)
8080

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

162162
return _Sections(self)
163163

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

207207
if slide_indexes is None:
208208
source_slides = list(other_pres.slides)

0 commit comments

Comments
 (0)