Skip to main content

privacyforms-pdf

CI Codecov PyPI Python 3.12+ Code style: ruff License: MIT uv

Python library for parsing and filling PDF forms using pypdf.

Features

  • Parse fillable PDFs into a canonical PDFRepresentation schema
  • Fill PDF forms from simple JSON key/value data
  • Extract layout hints and visual row groupings
  • Validate representation JSON against the schema
  • Verify sample data keys against parsed field IDs
  • Extend the CLI through pluggy command entry points

Requirements

  • Python 3.12+
  • pypdf >= 5

Installation

git clone <repo-url>
cd privacyforms.pdf
uv sync

CLI Quick Start

Parse A PDF

pdf-forms parse form.pdf -o representation.json

This writes a compact PDFRepresentation JSON document.

Verify A Representation JSON File

pdf-forms verify-json representation.json

Verify Sample Data Keys Against Parsed Field IDs

pdf-forms verify-data --form-json representation.json --data-json sample-data.json

Preferred format:

  • use field IDs such as f-0 for canonical machine-facing data
  • field names such as Candidate Name remain supported for convenience

Compatibility modes:

  • fill-form accepts --field-keys name|id|auto
  • verify-data accepts --key-mode name|id|auto
  • auto accepts a mixture of field IDs and field names

Fill A PDF Form

pdf-forms fill-form form.pdf data.json -o filled.pdf
pdf-forms fill-form form.pdf data.json -o filled.pdf --no-validate
pdf-forms fill-form form.pdf data.json -o filled.pdf --strict
pdf-forms fill-form form.pdf data.json -o filled.pdf --field-keys id

Recommended fill-form payloads are keyed by field IDs:

{
  "f-0": "John Smith",
  "f-1": "Software Engineer",
  "f-2": "2025-06-01",
  "f-3": true
}

Field names and mixed key styles are still supported through --field-keys name and --field-keys auto.

Check Whether A PDF Contains A Form

pdf-forms info form.pdf

Python API

The package currently exposes two main API layers:

  • read/parse APIs via parse_pdf() and extract_pdf_form()
  • higher-level read/fill/validate APIs via PDFFormService

Parse A PDF Into PDFRepresentation

from privacyforms_pdf import extract_pdf_form

representation = extract_pdf_form("form.pdf")

print(representation.spec_version)
print(representation.source)
print(len(representation.fields))
print(len(representation.rows))

for field in representation.fields:
    print(field.id, field.name, field.type, field.value)

Extract Labels And Nearby Text

Install the optional labels dependency (requires PyMuPDF):

pip install privacyforms.pdf[labels]

CLI:

pdf-forms parse form.pdf --labels -o representation.json

Python API:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
representation = service.extract("form.pdf", extract_labels=True)

for field in representation.fields:
    print(field.title)  # best inferred label
    for block in field.text_blocks:
        print(block.role, block.direction, block.text)

You can also call parse_pdf() directly:

from privacyforms_pdf import parse_pdf

representation = parse_pdf("form.pdf")
json_text = representation.to_compact_json()

Fill And Validate Forms

from privacyforms_pdf import PDFFormService

service = PDFFormService()

has_form = service.has_form("form.pdf")

form_data = {
    "f-0": "John Smith",
    "f-3": True,
}

errors = service.validate_form_data("form.pdf", form_data, key_mode="id")
if errors:
    print(errors)
else:
    service.fill_form("form.pdf", form_data, "filled.pdf", key_mode="id")

You can also fill from a JSON file:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
service.fill_form_from_json("form.pdf", "data.json", "filled.pdf", key_mode="id")

The class also exposes read helpers:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
representation = service.extract("form.pdf")
fields = service.list_fields("form.pdf")
field = service.get_field_by_id("form.pdf", "f-0")
value = service.get_field_value("form.pdf", "Candidate Name")
service.extract_to_json("form.pdf", "representation.json")

Public Objects

Primary exports from privacyforms_pdf:

  • PDFFormService
  • FormFiller
  • parse_pdf
  • extract_pdf_form
  • PDFRepresentation
  • PDFField
  • FieldFlags
  • FieldLayout
  • FieldTextBlock
  • FieldTextRole
  • FieldTextDirection
  • ChoiceOption
  • RowGroup
  • PDFFormError
  • PDFFormNotFoundError
  • FormValidationError
  • FieldNotFoundError

PDFRepresentation Schema

Top-level fields:

  • spec_version: str
  • source: str | None
  • fields: list[PDFField]
  • rows: list[RowGroup]

PDFField

Main fields:

  • name: str
  • title: str | None
  • id: str
  • type: PDFFieldType
  • field_flags: FieldFlags | None
  • layout: FieldLayout | None
  • default_value: str | bool | list[str] | None
  • value: str | bool | list[str] | None
  • choices: list[ChoiceOption]
  • text_blocks: list[FieldTextBlock] — nearby labels, descriptions, helpers
  • format: str | None
  • max_length: int | None
  • textarea_rows: int | None
  • textarea_cols: int | None

Supported field types:

  • textfield
  • textarea
  • datefield
  • checkbox
  • radiobuttongroup
  • combobox
  • listbox
  • signature

FieldLayout

Layout hints are stored in integer PDF coordinates:

  • page: int | None
  • x: int | None
  • y: int | None
  • width: int | None
  • height: int | None

FieldTextBlock

Nearby text associated with a field (populated when extract_labels=True):

  • text: str
  • role: "label" | "description" | "helper" | "instruction" | "unknown"
  • direction: "left" | "right" | "above" | "below" | "inside" | "unknown"
  • layout: FieldLayout | None
  • distance: float | None

RowGroup

Visual rows derived from layout analysis:

  • fields: list[PDFField | str]
  • page_index: int

When serialized, row fields are emitted as field IDs.

JSON Shape

Example parsed representation:

{
  "source": "form.pdf",
  "fields": [
    {
      "name": "Candidate Name",
      "id": "f-0",
      "type": "textfield",
      "layout": {
        "page": 1,
        "x": 53,
        "y": 1077,
        "width": 361,
        "height": 27
      }
    }
  ],
  "rows": [
    {
      "fields": ["f-0"],
      "page_index": 1
    }
  ]
}

Notes:

  • omitted fields are intentionally excluded by compact serialization
  • field_flags only serializes flags set to true
  • rows reference fields by ID in JSON

Exceptions

  • PDFFormError: base exception for form-related failures
  • PDFFormNotFoundError: raised when a PDF does not contain a form
  • FormValidationError: raised when fill-time validation fails
  • FieldNotFoundError: exported for compatibility and field lookup failures

Ratings

Aspect Score Notes
Overall 9/10 Production-grade library with excellent engineering discipline
Security 9/10 Input validation, symlink rejection, size limits, Bandit clean
Architecture 9/10 Clean layers, canonical schema, pluggy extensibility
API Design 8/10 Dual function/class layers, type-safe, minor wrapper leakage
Functionality 9/10 All form types handled, cross-generator radio support, graceful fallback
Code Quality 9/10 100% coverage, strict ruff/ty, complete type hints
Documentation 8/10 Excellent project docs; PDF internals could use more inline depth

Security

  • Symlinks are rejected for both reads and writes to prevent path-traversal issues
  • PDF files are validated via magic-byte header check (%PDF) before parsing
  • Input size limits guard against oversized PDFs (> 50 MB) and JSON (> 10 MB)
  • JSON depth limits prevent stack exhaustion from malicious payloads

Architecture

  • Clean separation of concerns: schemaparserfillerextractorcli
  • Canonical PDFRepresentation schema (Pydantic v2) is the single source of truth
  • CLI commands are loaded dynamically via pluggy entry points — easy to extend
  • Low-level PDF writer (FormFiller) is decoupled from the high-level service (PDFFormService)

API Design

  • Two complementary layers: function-based (parse_pdf, extract_pdf_form) and class-based (PDFFormService)
  • Field IDs are the canonical key format; field names remain supported for convenience
  • key_mode="auto" accepts mixed payloads of IDs and names
  • All public methods have complete type hints and Google-style docstrings

Functionality

  • Handles all common PDF form types: text, textarea, date, checkbox, radio, combo, listbox, signature
  • Radio button state resolution works across different PDF generators
  • Listbox filling includes custom appearance streams so selections are visible in viewers
  • Graceful fallback when pypdf's appearance-stream generation hits edge cases

Code Quality

  • 100% test coverage (426 tests) with pytest and pytest-cov
  • Ruff enforces strict linting (E, W, F, I, N, D, UP, B, C4, SIM, TCH)
  • ty type checker runs in strict mode — complete type hints throughout
  • Bandit security scanner integrated; no high or medium severity issues

Development

Quality Checks

make check
make test
make test-cov

Project Structure

privacyforms.pdf/
├── privacyforms_pdf/
│   ├── __init__.py
│   ├── schema.py
│   ├── schema_layout.py
│   ├── parser.py
│   ├── extractor.py
│   ├── filler.py
│   ├── hooks.py
│   ├── cli.py
│   └── commands/
├── tests/
├── samples/
├── demo/
├── docs/
├── pyproject.toml
└── README.md

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

privacyforms_pdf-0.2.0.tar.gz (66.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

privacyforms_pdf-0.2.0-py3-none-any.whl (46.0 kB view details)

Uploaded Python 3

File details

Details for the file privacyforms_pdf-0.2.0.tar.gz.

File metadata

  • Download URL: privacyforms_pdf-0.2.0.tar.gz
  • Upload date:
  • Size: 66.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for privacyforms_pdf-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ffc436db85c68e67fdd69c14abdc21f4b1318af6ed9fa77a806cc4f463be38f1
MD5 263d84d613c628c27e6772486fef5a52
BLAKE2b-256 6daef4f2db1f5ca6ad964a5d8dd60f957f1e212e05a9d987d0a56490e85614d3

See more details on using hashes here.

Provenance

The following attestation bundles were made for privacyforms_pdf-0.2.0.tar.gz:

Publisher: publish-pypi.yml on zopyx/privacyforms.pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file privacyforms_pdf-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for privacyforms_pdf-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 06295939db70c8cee4826c5e625dbf442b99e50ed1c3127075fe3897229c63c8
MD5 5d690f86213ebda29c1f49036b1bcea5
BLAKE2b-256 55c890bf461adb3ea59d83e3c3052910abd821f061160a0bbdc2f532d0e3ca66

See more details on using hashes here.

Provenance

The following attestation bundles were made for privacyforms_pdf-0.2.0-py3-none-any.whl:

Publisher: publish-pypi.yml on zopyx/privacyforms.pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page