Skip to content

feat(ergonomics): PathLike + PERCENT_40 typo + Slide.background.element fix (modernization Phase 1)#39

Merged
MHoroszowski merged 1 commit intomasterfrom
feature/modernization-phase1
May 8, 2026
Merged

feat(ergonomics): PathLike + PERCENT_40 typo + Slide.background.element fix (modernization Phase 1)#39
MHoroszowski merged 1 commit intomasterfrom
feature/modernization-phase1

Conversation

@MHoroszowski
Copy link
Copy Markdown
Owner

@MHoroszowski MHoroszowski commented May 8, 2026

Modernization Phase 1 — PathLike + PERCENT_40 typo + Slide.background.element fix

Refs #29. Phase 1 of the Modernization & Ergonomics epic. Bundles three small, mostly-orthogonal wins the issue called out as "trivial cherry-picks" plus a long-standing correctness bug that pollutes the power-user surface.

What this PR adds / fixes

1. pathlib.Path / os.PathLike support across the API

Closes upstream PR scanny#1123. The four entry points users most commonly hit with a Path now accept it without a TypeError:

from pathlib import Path
from pptx import Presentation
from pptx.util import Inches

prs = Presentation(Path("deck.pptx"))             # ← used to TypeError
prs.save(Path("out.pptx"))                         # ← used to TypeError
slide.shapes.add_picture(Path("logo.png"), ...)    # ← used to AttributeError on .seek
Image.from_file(Path("logo.png"))                  # ← used to AttributeError on .seek

Each accepts any os.PathLike[str] (pathlib.Path, custom subclasses with __fspath__, etc.) by coercing via os.fspath(...) at the boundary. Existing str and file-like-object callers are unaffected.

2. MSO_PATTERN_TYPE.PERCENT_40 typo fixed

Closes upstream scanny#1131. The enum member was misspelled ERCENT_40 (missing leading P). Renamed to the correct PERCENT_40 with no value change (xml_value remains pct40, integer value remains 6). Code that referenced the broken name needs to update — the broken name is gone deliberately, callers will get a clear AttributeError.

3. slide.background.element returns the <p:bg> element

Closes upstream issue scanny#1126. Previously slide.background.element (and the private ._element) returned the parent <p:cSld> element instead of the actual <p:bg> background. Power users introspecting the XML now get the right node. The <p:bg> is materialized on construction (matching the legacy destructive behavior of accessing .fill); since slide.background is a lazyproperty, this only fires on first slide-background access, not on slide load.

Out of scope (Phase 2 follow-up)

Test coverage

  • 15 new unit tests in tests/test_modernization_phase1.py:
    • 4 tests on Presentation() accepting Path/str/BytesIO/PathLike-subclass
    • 2 tests on Presentation.save(Path) round-trip
    • 1 test on add_picture(Path)
    • 3 tests on Image.from_file(Path|str|stream)
    • 2 tests on the PERCENT_40 typo fix
    • 3 tests on slide.background.element (returns <p:bg>, fill still works, RGB round-trip survives)
  • 4 new behave scenarios in features/modernization-phase1.feature
  • UAT uat_modernization_phase1.py (untracked per repo §6) — programmer ergonomics, no visual change to inspect, so the script prints per-fix verification. UAT signoff: ✓ — also confirmed in the wild with a real add_picture(Path) traceback that this PR resolves.

Verification

$ python3 -m pytest tests/ -q | tail -3
3237 passed in 4.54s

$ ruff check src tests | tail -3
All checks passed!

$ python3 -m behave features/ --no-color | tail -3
1003 scenarios passed, 0 failed, 0 skipped
3010 steps passed, 0 failed, 0 skipped

…nt fix (modernization Phase 1)

Phase 1 of issue #29 (Modernization & Ergonomics epic). Bundles three
small, mostly-orthogonal wins that the issue called out as "trivial
cherry-picks" plus a long-standing correctness bug that pollutes the
power-user surface. Defers `Font.color` no-mutate-on-read (closes
upstream scanny#1111/scanny#1074), `collections.abc` import sweep, and dev-tooling
modernization (uv / pyright strict) to Phase 2.

What this PR adds / fixes
-------------------------

1. **`pathlib.Path` / `os.PathLike` support across the API.** Closes
   upstream PR scanny#1123. The four entry points users
   most commonly hit with a `Path` now accept it without a TypeError:

   - `pptx.Presentation(path)` — open a deck from a Path
   - `prs.save(path)` — write a deck to a Path
   - `slide.shapes.add_picture(path, ...)` — embed an image by Path
   - `pptx.parts.image.Image.from_file(path)` — lower-level loader

   Each accepts any `os.PathLike[str]` (Path, custom subclasses, etc.)
   by coercing via `os.fspath(...)` at the boundary. Existing `str`
   and file-like-object callers are unaffected.

2. **`MSO_PATTERN_TYPE.PERCENT_40` typo fixed.** Closes upstream
   scanny#1131. The enum member was misspelled `ERCENT_40` (missing leading
   `P`). The fix renames to the correct `PERCENT_40` with no value
   change (xml_value remains `pct40`, integer value remains 6). Code
   that referenced the broken name needs to update — the broken name
   is gone deliberately, callers will get a clear AttributeError.

3. **`slide.background.element` returns the `<p:bg>` element.** Closes
   upstream issue scanny#1126. Previously `slide.background.element` (and
   the private `._element`) returned the parent `<p:cSld>` element
   instead of the actual `<p:bg>` background. Power users
   introspecting the XML now get the right node. The `<p:bg>` is
   materialized on construction (matching the legacy destructive
   behavior of accessing `.fill`); since `slide.background` is a
   lazyproperty, this only fires on first slide-background access,
   not on slide load.

Out of scope (for explicit Phase 2 follow-up)
---------------------------------------------

- **`Font.color` mutate-on-read** (closes upstream scanny#1111, scanny#1074) —
  the getter currently materializes `<a:solidFill>` on access, which
  means READING a font's color modifies the file. The fix requires a
  lazy ColorFormat wrapper that delays solidFill creation until the
  setter actually fires; that's a larger refactor that needs its own
  PR with careful review against the existing color/fill test suite.
- **`collections.abc` import sweep** (closes upstream scanny#771) — no
  remaining offenders in our fork's source tree (the upstream PR was
  authored against an older codebase). If any creep in via vendored
  fixes, Phase 2 will sweep.
- **Dev-tooling modernization** (uv, pyright strict, pytest-syrupy)
  — covered in the issue body but deserves its own PR per the
  issue's own "patch 1 / 2 / 3" framing.

Test coverage
-------------

- 15 new unit tests in `tests/test_modernization_phase1.py`:
  * 4 tests on `Presentation()` accepting Path/str/BytesIO/PathLike-subclass
  * 2 tests on `Presentation.save(Path)` round-trip
  * 1 test on `add_picture(Path)`
  * 3 tests on `Image.from_file(Path|str|stream)`
  * 2 tests on the PERCENT_40 typo fix
  * 3 tests on `slide.background.element` (returns `<p:bg>`,
    fill still works, RGB round-trip survives)
- 4 new behave scenarios in `features/modernization-phase1.feature`
- New `uat_modernization_phase1.py` (untracked per repo §6) — these
  are programmer ergonomics, not visual changes, so the UAT prints
  per-fix verification rather than producing a deck for visual review.

Verification
------------
```
$ python3 -m pytest tests/ -q | tail -3
3237 passed in 4.54s

$ ruff check src tests | tail -3
All checks passed!

$ python3 -m behave features/ --no-color | tail -3
1003 scenarios passed, 0 failed, 0 skipped
3010 steps passed, 0 failed, 0 skipped
```

Refs #29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant