Skip to content

Handle errors

Files go missing and not every file is a valid PDF. ozaru reports these problems with standard Python exceptions:

Exception When it happens
FileNotFoundError The path does not exist.
PermissionError You don't have permission to read the file.
IsADirectoryError The path is a folder, not a file.
NotADirectoryError Part of the path is a file used as if it were a folder.
OSError The file could not be read for another reason.
ValueError The file is not a valid PDF, or the path contains a null byte.

extract_text also raises ValueError for a PDF that needs a password.

Catch them with try and except:

from pathlib import Path

import ozaru


def read_pdf(path):
    try:
        return ozaru.extract_text(path)
    except FileNotFoundError:
        print(f"{path}: file not found")
    except ValueError:
        print(f"{path}: not a readable PDF")
    return None


Path("notes.txt").write_text("This is not a PDF.", encoding="utf-8")

read_pdf("missing.pdf")
read_pdf("notes.txt")

text = read_pdf("report.pdf")
print(f"report.pdf: {len(text)} characters")

Output:

missing.pdf: file not found
notes.txt: not a readable PDF
report.pdf: 217 characters

Tip

The text of an exception may change between releases. Rely on the exception type, as this example does, and not on the message.

File errors

FileNotFoundError, PermissionError, IsADirectoryError and NotADirectoryError are the same exceptions Python's open() raises. They are all subclasses of OSError, so one except OSError catches any problem with the file itself, and error.filename tells you which path failed:

from pathlib import Path

import ozaru

Path("reports").mkdir()

for path in ["missing.pdf", "reports"]:
    try:
        ozaru.page_count(path)
    except OSError as error:
        print(f"{type(error).__name__}: {error.filename}")

Output:

FileNotFoundError: missing.pdf
IsADirectoryError: reports

Pages that can't be read

Sometimes a PDF opens but one of its pages is damaged. ozaru doesn't throw away the whole document for that: extract_text returns the text of every page it could read, leaves the damaged page empty, and emits an ozaru.PageWarning for it.

The page stays in the result as an empty string, so split("\f") still gives one item per page and the page numbers don't shift.

Download the damaged report, whose third page is broken, and catch the warnings with the standard warnings module:

import warnings

import ozaru

with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always", ozaru.PageWarning)
    text = ozaru.extract_text("damaged-page.pdf")

for warning in caught:
    print(f"Page {warning.message.page} could not be read")

for number, page in enumerate(text.split("\f"), start=1):
    print(f"Page {number}: {len(page)} characters")

Output:

Page 3 could not be read
Page 1: 82 characters
Page 2: 79 characters
Page 3: 0 characters

Each warning has two attributes:

Attribute Type Description
page int Number of the page that failed, starting at 1
reason str Why the page could not be read

If you don't catch them, Python prints the warnings to the terminal and your program keeps running.

Stop at the first damaged page

When a partial result is not acceptable, turn the warning into an error. Then extract_text raises ozaru.PageWarning instead of returning:

import warnings

import ozaru

warnings.simplefilter("error", ozaru.PageWarning)

try:
    text = ozaru.extract_text("damaged-page.pdf")
except ozaru.PageWarning as error:
    print(f"Stopped: page {error.page} could not be read")

Output:

Stopped: page 3 could not be read

Next: process a folder.