Thursday, August 27, 2026
1 change · 19.0
Resolved issues and error corrections
This fixes a compatibility issue that prevented documents from being signed when running Odoo on Python 3.13 or newer. The PDF handling layer now supports the expected page box fields, allowing the signing flow to complete normally.
Original PR description
On Python 3.13 and above, `requirements.txt` installs `pypdf` instead of `PyPDF2`: ``` PyPDF2==2.12.1 ; python_version > '3.10' and python_version < '3.13' # (Noble and below) PyPDF==5.4.0 ;…
On Python 3.13 and above, `requirements.txt` installs `pypdf` instead of `PyPDF2`:
```
PyPDF2==2.12.1 ; python_version > '3.10' and python_version < '3.13' # (Noble and below)
PyPDF==5.4.0 ; python_version >= '3.13' # (Trixie)
```
so `odoo.tools.pdf` resolves to the `_pypdf` backend. That backend back-fills the PyPDF2 1.x camelCase API onto pypdf so callers do not have to branch on the installed library, but the alias set is incomplete in two ways:
- `cropBox` is missing entirely, while `mediaBox` is aliased.
- `mediaBox` is a getter-only property, so assigning to it raises.
## Impact
`sign/models/sign_document.py` needs all three forms, and dies on the first:
```python
box = page.cropBox if page.get('/CropBox') else page.mediaBox # 436
...
overlay_page.mediaBox = page.mediaBox # 444
if page.get("/CropBox"):
overlay_page.cropBox = page.cropBox # 446
```
```
File ".../sign/models/sign_document.py", line 436, in render_document_with_items
box = page.cropBox if page.get('/CropBox') else page.mediaBox
AttributeError: 'PageObject' object has no attribute 'cropBox'
```
`render_document_with_items` is reached from `sign_request_item.sign` → `_post_fill_request_item` → `_send_completed_documents` → `_generate_completed_documents`, so **signing any document fails on Python 3.13+**.
Fixing the shim rather than the caller keeps the design the module already follows: `_pypdf2_2.py` does the same normalisation for the 2.x backend, and `sign` is written against the camelCase API on purpose.
## Why CI does not catch it
The test that covers exactly this branch, `sign/tests/test_sign_request.py::test_origin_offset_translation`, patches the backend by module path:
```python
with patch('PyPDF2._page.PageObject.add_transformation', create=True) as mock_add, \
patch('PyPDF2._page.PageObject.cropBox', new_callable=PropertyMock, return_value=offset_box):
```
On 3.13+ `PyPDF2` is not installed at all, so the target never resolves and the branch is never exercised. A follow-up on the enterprise side should patch through the backend `odoo.tools.pdf` resolved instead of naming `PyPDF2`; that change is what would keep this from regressing.
## Verification
Reproduced and fixed on Python 3.14.4 with pypdf 5.4.0, running the exact `sign_document.py` sequence:
```
L436 read cropBox/mediaBox -> (0.0, 0.0)
L444/L446 writes -> OK
round-trip cropbox: (0.0, 0.0)
```
Before the patch, line 436 raises the `AttributeError` above.
## Forward-port
Not needed. On `master` the monkeypatch block is gone and the remaining shims carry `@deprecated("PyPDF2 1.x compatibility shims are deprecated, switch to modern API")`, so callers there are expected to move to the modern API rather than lean on these aliases. This is a 19.0-only stable-branch fix.