Package._gather_image_parts appends every part reached through an image
relationship to the ImageParts collection, casting it to ImagePart. When
an image uses a type that does not load as an ImagePart (for example an
SVG, or an image with broken or non-standard EXIF data), the target is a
plain Part with no sha1 attribute.
The next call to add_picture runs ImageParts._get_by_sha1, which reads
.sha1 on every collected part, so it raised
"'Part' object has no attribute 'sha1'" as soon as such a part was
present.
Skip parts that have no sha1 attribute while matching, mirroring the
same fix in python-pptx. Add a unit test covering a non-image Part
placed before the matching ImagePart in the collection.
Closes #1565.
Problem
Package._gather_image_parts walks the package relationships and appends the target of every image relationship to the ImageParts collection, casting it to ImagePart:
The cast is only a type hint. If an image uses a type that does not load as an ImagePart (an SVG, or an image with broken or non-standard EXIF data as in the report), the target part is a plain Part, which has no sha1.
Once such a part is in the collection, the next add_picture call runs ImageParts._get_by_sha1, which reads .sha1 on every collected part, so it raised 'Part' object has no attribute 'sha1'.
Fix
Skip parts that have no sha1 while matching:
This mirrors the same fix already made in python-pptx (_ImageParts._find_by_sha1, commit 4c0fd125), which the issue links to.
Tests
Added it_skips_parts_without_a_sha1_when_matching in tests/test_package.py. It puts a plain Part (no sha1) ahead of the matching ImagePart in the collection and asserts _get_by_sha1 returns the ImagePart rather than raising. The test fails on the current code with the reported AttributeError and passes with the fix.
Verified with pytest tests/test_package.py (9 passed) and ruff check on the changed files. The unrelated collection failures elsewhere in the suite on my machine come from a pyparsing version mismatch in the cxml test helper and are present on a clean checkout too.