From 7ee5442fe83fbf481c181f234efc4929567b3c4d Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Fri, 30 Oct 2026 08:34:57 -0700 Subject: [PATCH] Chore: Import pikepdf and img2pdf lazily so startup does not load them --- src/documents/barcodes.py | 10 +++++++--- src/documents/converters.py | 3 ++- src/documents/double_sided.py | 3 ++- src/documents/pdf_ops.py | 16 ++++++++++++++-- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/documents/barcodes.py b/src/documents/barcodes.py index 65cba9a71..976861a64 100644 --- a/src/documents/barcodes.py +++ b/src/documents/barcodes.py @@ -10,9 +10,6 @@ from typing import TYPE_CHECKING import regex as regex_mod from django.conf import settings from pdf2image import convert_from_path -from pikepdf import Page -from pikepdf import PasswordError -from pikepdf import Pdf from documents.converters import convert_from_tiff_to_pdf from documents.data_models import ConsumableDocument @@ -34,6 +31,7 @@ from documents.utils import maybe_override_pixel_limit from paperless.config import BarcodeConfig if TYPE_CHECKING: + from pikepdf import Page from PIL import Image logger = logging.getLogger("paperless.barcodes") @@ -282,6 +280,8 @@ class BarcodePlugin(ConsumeTaskPlugin): # No op if not a TIFF self.convert_from_tiff_to_pdf() + from pikepdf import PasswordError + try: self.barcodes = scan_pdf( self.pdf_file, @@ -433,6 +433,8 @@ class BarcodePlugin(ConsumeTaskPlugin): document_paths = [] fname: str = self.input_doc.original_file.stem + from pikepdf import Pdf + with Pdf.open(self.pdf_file) as input_pdf: # Start with an empty document current_document: list[Page] = [] @@ -516,6 +518,8 @@ def scan_pdf(pdf_path: Path, settings: BarcodeConfig, work_dir: Path) -> list[Ba """ barcodes: list[Barcode] = [] + from pikepdf import Pdf + with Pdf.open(pdf_path) as pdf: num_of_pages = len(pdf.pages) logger.debug(f"PDF has {num_of_pages} pages") diff --git a/src/documents/converters.py b/src/documents/converters.py index 76f518d7e..bc66925d3 100644 --- a/src/documents/converters.py +++ b/src/documents/converters.py @@ -1,6 +1,5 @@ from pathlib import Path -import img2pdf from PIL import Image from documents.utils import copy_basic_file_stats @@ -17,6 +16,8 @@ def convert_from_tiff_to_pdf(tiff_path: Path, target_directory: Path) -> Path: Returns the path of the PDF created. """ + import img2pdf + # override pixel setting if needed maybe_override_pixel_limit() diff --git a/src/documents/double_sided.py b/src/documents/double_sided.py index 3c3ec4723..9452a4469 100644 --- a/src/documents/double_sided.py +++ b/src/documents/double_sided.py @@ -6,7 +6,6 @@ from pathlib import Path from typing import Final from django.conf import settings -from pikepdf import Pdf from documents.consumer import ConsumerError from documents.converters import convert_from_tiff_to_pdf @@ -85,6 +84,8 @@ class CollatePlugin(NoCleanupPluginMixin, NoSetupPluginMixin, ConsumeTaskPlugin) valid_staging_exists = True if valid_staging_exists: + from pikepdf import Pdf + try: # Collate pages from second PDF in reverse order with Pdf.open(staging) as pdf1, Pdf.open(pdf_file) as pdf2: diff --git a/src/documents/pdf_ops.py b/src/documents/pdf_ops.py index 0f5b696f6..d31a6b31f 100644 --- a/src/documents/pdf_ops.py +++ b/src/documents/pdf_ops.py @@ -14,8 +14,6 @@ from __future__ import annotations from typing import TYPE_CHECKING from typing import NamedTuple -import pikepdf - if TYPE_CHECKING: from collections.abc import Callable from collections.abc import Iterable @@ -43,6 +41,8 @@ def rotate_pdf(src: Path, dst: Path, degrees: int) -> None: Rotate every page relatively on the opened document, not a rebuild, so Info, XMP and outlines are kept. ``src`` is not modified. """ + import pikepdf + with pikepdf.open(src) as pdf: for page in pdf.pages: page.rotate(degrees, relative=True) @@ -58,6 +58,8 @@ def remove_pages(src: Path, dst: Path, pages: Iterable[int]) -> None: Duplicates are ignored. Pages are removed highest first so earlier removals never shift the index of later ones. """ + import pikepdf + unique = sorted(set(pages)) _require_positive(unique) with pikepdf.open(src) as pdf: @@ -81,6 +83,8 @@ def build_pdfs( (Info, XMP, outlines) is not carried over. Returns the written paths in output order. """ + import pikepdf + for specs, _ in outputs: _require_positive(spec.page for spec in specs) @@ -140,6 +144,8 @@ def needs_decrypt(src: Path) -> bool: True if ``src`` is encrypted. A PDF that needs a password to open at all counts as encrypted. """ + import pikepdf + try: with pikepdf.open(src) as pdf: return bool(pdf.is_encrypted) @@ -154,6 +160,8 @@ def decrypt_pdf(src: Path, make_dst: Callable[[], Path], password: str) -> Path: ``make_dst`` is only called once the password has been accepted, so a wrong password never leaves a destination behind. """ + import pikepdf + with pikepdf.open(src, password=password) as pdf: pdf.remove_unreferenced_resources() dst = make_dst() @@ -170,6 +178,8 @@ class PdfMerger: """ def __init__(self) -> None: + import pikepdf + self._pdf = pikepdf.new() self._version: str = self._pdf.pdf_version @@ -185,6 +195,8 @@ class PdfMerger: self._pdf.close() def add(self, path: Path) -> None: + import pikepdf + with pikepdf.open(str(path)) as pdf: self._version = max(self._version, pdf.pdf_version) self._pdf.pages.extend(pdf.pages)