Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,22 @@ To reuse existing coverage JSON without rerunning tests:
uvx nox -s class-coverage -- --no-tests
```

### Live tests

Live tests require `PDFREST_API_KEY`. By default, the test fixture tries the
local service, the development service, and then the production service. To run
against a specific reachable pdfRest deployment first, set
`PDFREST_LIVE_BASE_URL` to its base URL:

```bash
export PDFREST_API_KEY="..."
export PDFREST_LIVE_BASE_URL="https://pdfrest.example.com"
uvx nox -s tests-3.11 -- tests/live
```

If that URL is unavailable, the fixture continues with its normal fallback URLs
and fails only when none are reachable.

## Examples

Run all examples:
Expand Down
3 changes: 2 additions & 1 deletion docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ Use this group to add visible content or remove sensitive content.

- Add overlays:
[add_text_to_pdf][pdfrest.PdfRestClient.add_text_to_pdf],
[add_image_to_pdf][pdfrest.PdfRestClient.add_image_to_pdf]
[add_image_to_pdf][pdfrest.PdfRestClient.add_image_to_pdf],
[add_shapes_to_pdf][pdfrest.PdfRestClient.add_shapes_to_pdf]
- Watermarking:
[watermark_pdf_with_text][pdfrest.PdfRestClient.watermark_pdf_with_text],
[watermark_pdf_with_image][pdfrest.PdfRestClient.watermark_pdf_with_image]
Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ supported interpreter matrix.

## Available Examples

- `examples/add_shapes/add_shapes_to_pdf_example.py` – add a styled rectangle
and divider line to a PDF with accessibility tagging enabled.
- `examples/delete/delete_example.py` – demonstrate file deletion (sync + async
variants).
- `examples/extract_text/extract_pdf_text_example.py` – run `extract_pdf_text`
Expand Down
83 changes: 83 additions & 0 deletions examples/add_shapes/add_shapes_to_pdf_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Add a styled panel and divider line to a PDF.

This sample demonstrates how to:

1. Upload the bundled ``examples/resources/report.pdf`` resource.
2. Describe rectangle and line overlays with typed ``PdfAddShapeObject`` values.
3. Add the shapes to page 1 with accessibility tagging enabled.
4. Print metadata for the PDF returned by pdfRest.

Set ``PDFREST_API_KEY``, then run from the repository root with
``uv run examples/add_shapes/add_shapes_to_pdf_example.py``. The input PDF is
included in the repository, so no additional input files are required.
"""

from __future__ import annotations

from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient
from pdfrest.types import (
PdfAddLineObject,
PdfAddRectangleObject,
PdfAddShapeObject,
)

RESOURCE = Path(__file__).resolve().parents[1] / "resources" / "report.pdf"


def add_shapes_to_report() -> None:
"""Upload the sample report and add a tagged panel and divider line."""
load_dotenv()
shapes: list[PdfAddShapeObject] = [
PdfAddRectangleObject(
type="rectangle",
page=1,
x=54,
y=540,
width=504,
height=108,
fill_color=(245, 247, 250),
stroke_color=(26, 72, 112),
stroke_width=1,
tag_is_artifact=True,
),
PdfAddLineObject(
type="line",
page=1,
x1=72,
y1=510,
x2=540,
y2=510,
stroke_color=(220, 45, 55),
stroke_width=4,
tag_actual_text="Report section divider",
tag_structure_type="Figure",
),
]

with PdfRestClient() as client:
uploaded = client.files.create_from_paths([RESOURCE])[0]
response = client.add_shapes_to_pdf(
uploaded,
shape_objects=shapes,
tag_enabled=True,
output="report-with-shapes",
)

output = response.output_file
print(f"Created {output.name}")
print(f"Output ID: {output.id}")
print(f"MIME type: {output.type}")
print(f"Size: {output.size} bytes")
print(f"Download URL: {output.url}")


if __name__ == "__main__": # pragma: no cover - manual example
add_shapes_to_report()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pdfrest"
version = "1.0.4"
version = "1.1.0"
description = "Python client library for interacting with the pdfRest API"
readme = {file = "README.md", content-type = "text/markdown"}
authors = [
Expand Down
108 changes: 108 additions & 0 deletions src/pdfrest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
OcrPdfPayload,
PdfAddAttachmentPayload,
PdfAddImagePayload,
PdfAddShapesPayload,
PdfAddTextPayload,
PdfBlankPayload,
PdfCompressPayload,
Expand Down Expand Up @@ -144,6 +145,7 @@
HtmlWebLayout,
JpegColorModel,
OcrLanguage,
PdfAddShapeObject,
PdfAddTextObject,
PdfAType,
PdfConversionCompression,
Expand Down Expand Up @@ -3453,6 +3455,59 @@ def add_text_to_pdf(
timeout=timeout,
)

def add_shapes_to_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
shape_objects: PdfAddShapeObject | Sequence[PdfAddShapeObject],
tag_enabled: bool | None = None,
output: str | None = None,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> PdfRestFileBasedResponse:
"""Draw one or more lines or rectangles onto a PDF.

Coordinates use PDF units with the origin in the lower-left corner.
Set ``tag_enabled=True`` when any shape includes tagging metadata.

Args:
file: Uploaded PDF file as a `PdfRestFile` object.
shape_objects: Line or rectangle objects to draw onto the document.
tag_enabled: Enable tagging for newly added shapes.
output: Output filename prefix used by pdfRest when creating files.
extra_query: Additional query parameters merged into the request.
extra_headers: Additional HTTP headers merged into the request.
extra_body: Additional request body fields merged into the payload.
timeout: Request timeout override for this call.

Returns:
Validated file-based response returned by pdfRest.

Raises:
PdfRestError: If request execution fails at the client or API layer.
ValidationError: If local payload validation fails before sending.
"""
payload: dict[str, Any] = {
"files": file,
"shape_objects": shape_objects,
}
if tag_enabled is not None:
payload["tag_enabled"] = tag_enabled
if output is not None:
payload["output"] = output

return self._post_file_operation(
endpoint="/pdf-with-added-shapes",
payload=payload,
payload_model=PdfAddShapesPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

def add_image_to_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down Expand Up @@ -6448,6 +6503,59 @@ async def add_text_to_pdf(
timeout=timeout,
)

async def add_shapes_to_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
shape_objects: PdfAddShapeObject | Sequence[PdfAddShapeObject],
tag_enabled: bool | None = None,
output: str | None = None,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> PdfRestFileBasedResponse:
"""Asynchronous variant of [PdfRestClient.add_shapes_to_pdf][pdfrest.PdfRestClient.add_shapes_to_pdf].

Coordinates use PDF units with the origin in the lower-left corner.
Set ``tag_enabled=True`` when any shape includes tagging metadata.

Args:
file: Uploaded PDF file as a `PdfRestFile` object.
shape_objects: Line or rectangle objects to draw onto the document.
tag_enabled: Enable tagging for newly added shapes.
output: Output filename prefix used by pdfRest when creating files.
extra_query: Additional query parameters merged into the request.
extra_headers: Additional HTTP headers merged into the request.
extra_body: Additional request body fields merged into the payload.
timeout: Request timeout override for this call.

Returns:
Validated file-based response returned by pdfRest.

Raises:
PdfRestError: If request execution fails at the client or API layer.
ValidationError: If local payload validation fails before sending.
"""
payload: dict[str, Any] = {
"files": file,
"shape_objects": shape_objects,
}
if tag_enabled is not None:
payload["tag_enabled"] = tag_enabled
if output is not None:
payload["output"] = output

return await self._post_file_operation(
endpoint="/pdf-with-added-shapes",
payload=payload,
payload_model=PdfAddShapesPayload,
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)

async def add_image_to_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down
Loading
Loading