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
68 changes: 65 additions & 3 deletions .agents/skills/pdfrest-client-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,68 @@ Follow `TESTING_GUIDELINES.md` and the live-test requirements in `AGENTS.md`.
- Update the API guide, public exports, and user-facing examples when the new
capability or parameter changes discoverability or usage.

### Runnable example requirement

Every new public endpoint/helper requires a runnable example; it is part of the
API deliverable, not optional follow-up documentation. For a compatible change
to an existing helper, extend or add an example when the new parameter changes a
user workflow or demonstrates behavior that is not otherwise discoverable.

- Before creating the example, identify every input asset it needs and inspect
`examples/resources/` for a suitable deterministic, redistributable fixture.
If any required file is absent, ask the user to provide it, naming the needed
file type and relevant characteristics (for example, a signed PDF, a
multi-page TIFF, a font, or a profile JSON). Do not fabricate, download, or
substitute a semantically unsuitable input. Stop if a required fixture cannot
be obtained.

- Add one endpoint-oriented script at
`examples/<capability>/<descriptive_name>_example.py` and list it in
`examples/README.md`. Reuse checked-in assets via a path derived from
`Path(__file__)`; place an approved new shared asset under
`examples/resources/`.

- Start the script at line one with the repository's single-line PEP 723 header:

```python
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
```

Adjust the Python constraint and declare every third-party import. Do not add
a shebang before the metadata. Inline metadata isolates `uv run` from the
project environment, and `noxfile.py` parses this exact line-one structure.

- Immediately follow the header with a module docstring that explains the user
outcome, enumerates the upload/API/result steps, names `PDFREST_API_KEY` and
all input prerequisites, and provides the repository-root command
`uv run examples/<capability>/<descriptive_name>_example.py`.

- Use the public SDK exactly as a customer would: load the API key environment,
enter the sync or async client context manager, upload local assets first,
call the new helper with `PdfRestFile` values, and print concise,
endpoint-relevant response details. Keep it deterministic, repeatable, and
independent of third-party URLs.

- When a public `TypedDict` represents a structured API input, construct it in
examples with its keyword constructor, such as `PdfAddLineObject(...)`,
instead of an anonymous dictionary literal. Annotate heterogeneous collections
with the public union alias, such as `list[PdfAddShapeObject]`. Reserve
dictionary literals for dynamic data, intentionally invalid input, and raw
wire-format overrides.

- Use `python-X.Y/<same_name>.py` plus an extending `ruff.toml` only when an
older interpreter needs a distinct implementation. Otherwise keep one script
compatible across the supported range.

- Validate the example against the local checkout with
`uvx nox -s run-example -- examples/<capability>/<script>.py`. When practical,
run `uvx nox -s examples` to cover Python 3.10-3.14; direct
`uv run examples/<capability>/<script>.py` is also required once the published
`pdfrest` release contains the new API.

### Generated API-reference contracts

The API reference is generated from the public source. Keep request-shape
Expand Down Expand Up @@ -210,6 +272,6 @@ documentation with its types; do not hand-copy a shape schema into Markdown.

Run targeted unit and live tests first, then the relevant Ruff and type checks.
Run the full pytest suite and `uvx nox -s tests` when practical. For API
reference changes, also run the strict docs build. Report the OpenAPI operation
inspected, files changed, checks run, checks skipped, and any live-validation
limitation.
reference changes, also run the strict docs build. Run the focused example and
the example matrix as described above. Report the OpenAPI operation inspected,
files changed, checks run, checks skipped, and any live-validation limitation.
67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,73 @@
to `.env`) in temporary scripts to drive the in-flight client against live
endpoints and capture responses for test data and assertions.

## Example Guidelines

- Every new public endpoint/helper must include a runnable example under
`examples/`. Group examples by capability in an endpoint-oriented directory
such as `examples/extract_text/`, and use a descriptive `*_example.py`
filename. Add the script to the inventory in `examples/README.md` and update
relevant docs links or usage guidance when the new capability changes
discoverability.

- Make each example a standalone uv script. Its first lines must be a PEP 723
metadata block in the single-line form understood by the Nox example discovery
code:

```python
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
```

Set `requires-python` to the widest supported range the example actually
supports and list every third-party import in `dependencies`. Keep the block
first (do not put a shebang above it), because `noxfile.py` reads metadata
starting at line one. PEP 723 metadata gives `uv run` an isolated environment;
do not rely on undeclared project or development dependencies.

- Follow the metadata with a module docstring that states the user outcome,
lists the important upload/API/output steps, and gives the exact command to
run from the repository root, for example
`uv run examples/extract_text/extract_pdf_text_example.py`. Name required
environment variables, input files, and any expected setup in that docstring.

- Prefer deterministic, redistributable inputs under `examples/resources/` and
resolve them relative to `Path(__file__)`, never the caller's working
directory. Reuse a suitable checked-in resource when possible. Before adding a
new binary or specialized input, confirm its provenance, redistribution
suitability, and expected API behavior; ask the contributor for the required
asset when those cannot be established.

- Examples exercise the real service, load `PDFREST_API_KEY` from the
environment (optionally through `python-dotenv`), upload local inputs through
`client.files.create_from_paths`, and use client context managers. Keep the
flow short and instructional while printing enough typed response data for a
user and CI to confirm success.

- When a public `TypedDict` represents a structured API input, construct it in
examples with its keyword constructor, such as `PdfAddLineObject(...)`,
instead of an anonymous dictionary literal. Annotate heterogeneous collections
with the public union alias, such as `list[PdfAddShapeObject]`, so readers and
type checkers can see the supported contract. Use dictionary literals when
demonstrating dynamic data, intentionally invalid input, or raw wire-format
overrides.

- Put interpreter-specific alternatives beside the base script as
`python-X.Y/<same_name>.py`, with a local `ruff.toml` extending the parent
configuration, only when syntax or compatibility requires a distinct script.
The base script remains the default for newer supported interpreters.

- Validate a new or changed example directly with `uv run <script>` when the
published SDK contains the demonstrated API. During development, validate
against the local checkout with
`uvx nox -s run-example -- examples/<capability>/<script>.py`; run
`uvx nox -s examples` for the Python 3.10-3.14 matrix when practical. The CI
`examples` job runs every discovered script against the live service on each
supported interpreter and gates publishing, so examples must be safe to run
repeatedly and must not depend on third-party network resources.

## Commit & Pull Request Guidelines

- Follow the `area: summary` convention seen in `pdfassistant-chatbot` (e.g.,
Expand Down
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
Loading
Loading