Repository navigation
Conversation
c0712a9 to
ac5cef6
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4262 +/- ##
==========================================
+ Coverage 98.40% 98.67% +0.26%
==========================================
Files 59 59
Lines 11992 11967 -25
Branches 2267 2268 +1
==========================================
+ Hits 11801 11808 +7
+ Misses 104 84 -20
+ Partials 87 75 -12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| for copy in (cloned, replicated): | ||
| assert type(copy) is type(annotation) | ||
| assert copy is not annotation | ||
| assert copy.keys() == annotation.keys() |
There was a problem hiding this comment.
Removed it - inspect.signature only raises for C-level builtins like type, never a Python init, so it was dead code.Updated to compare .items() so the assertion checks both keys and values.
| def test_merge_page_with_annotations_of_merged_page(): | ||
| """Annotations of the page passed to merge_page are cloned as well. | ||
|
|
||
| See issue #3467: cloning such a page raised ``TypeError`` because the |
There was a problem hiding this comment.
The old behavior is irrelevant in the test docstrings.
There was a problem hiding this comment.
Removed the historical behavior from both test docstrings. They now describe the behavior being tested.
DictionaryObject.clone() and DictionaryObject.replicate() created the target object via self.__class__(). That fails for every annotation class (Text, FreeText, Link, Polygon, ...), because their constructors require arguments, so PageObject.merge_page() raised TypeError whenever the merged page carried a markup annotation. Create the empty target through a helper that skips __init__ when the class cannot be instantiated without arguments. The clone is populated from the source object right afterwards, so the constructor arguments are not needed; the annotation constructors only store their arguments in the dictionary anyway. Fixes py-pdf#3467. Signed-off-by: Ak4shh <akashposa40@gmail.com>
ac5cef6 to
9bc7dd5
Compare
Linked entries such as /Next, /Prev, /N and /V were still instantiated via `__class__()`, failing for classes like OutlineItem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ak4shh <akashposa40@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ak4shh <akashposa40@gmail.com>
|
Removed the try/except around inspect.signature(). A Python-level init never triggers it, so it was dead code. |
|
Addressed all three points, and also fixed cloning of linked /Next//Prev//N//V objects (with a test). Could you take another look? |
Fixes #3467.
The bug
PageObject.merge_page()clones the annotations of the page passed to it.DictionaryObject.clone()built the clone target withself.__class__(),which is impossible for every annotation class, because their constructors
require arguments:
The same
self.__class__()call was inDictionaryObject.replicate(), so thisaffected any clone/replicate of an annotation dictionary, not just
merge_page.The fix
DictionaryObject._new_for_clone()creates the empty target. When the classcannot be instantiated without arguments, it instantiates it without calling
__init__; the clone is populated from the source object immediatelyafterwards, so the constructor arguments are not needed. Annotation
constructors only store their arguments in the dictionary anyway
(
self["/Vertices"] = ...), which_clonethen copies from the source.The check is signature-based (and cached per class) rather than
try/except TypeError, becauseLink()raisesValueErrorwhen neitherurlnortarget_page_indexis given.Classes that can be built without arguments (e.g.
PageObject, which setspdfin__init__) keep taking the normal construction path.Tests
tests/test_page.py::test_merge_page_with_annotations_of_merged_page— theissue's reproducer: merge a page that carries a
Polygonannotation, thenassert the annotation survives a write/re-read round trip.
tests/test_annotations.py::test_clone_and_replicate_annotation—parametrized over all ten annotation classes (
Ellipse,FreeText,Highlight,Line,Link,Polygon,PolyLine,Popup,Rectangle,Text), asserting thatclone()andreplicate()both return the sameclass with the same keys and that the copy does not share entries with the
source.
Both tests fail on
mainand pass with this change:Full suite
python -m pytest tests -q --basetemp=...on Windows / Python 3.13:31 failed, 2158 passed, 34 skipped, 95 errors— identical failures and errorsto the unmodified tree (all pre-existing and environmental: empty
sample-files/submodule, missingfontTools,pytest-cache/tmp_pathpermission issues).
passedrises by exactly the 11 new test cases.Scope
Three files:
pypdf/generic/_data_structures.py,tests/test_annotations.py,tests/test_page.py.