Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3fa5b27
skills: Add pdfRest client API guidance
datalogics-kam Aug 22, 2026
a53dc07
pdfrest-client-api: Document API payload test coverage
datalogics-kam Aug 23, 2026
014a6b7
pdfrest-client-api: Document unified PDF color inputs
datalogics-kam Aug 23, 2026
a0b2324
pdfrest-client-api: Document uv version bumps
datalogics-kam Aug 25, 2026
34b5aa6
pdfrest-client-api: Define Jira branch setup
datalogics-kam Aug 25, 2026
a800ecf
CONTRIBUTING.md: Document client API skill usage
datalogics-kam Aug 25, 2026
4b863c2
pdfrest-client-api: Improve generated API reference guidance
datalogics-kam Aug 25, 2026
e7bfc49
AGENTS.md: Add runnable example guidance
datalogics-kam Aug 26, 2026
2438665
pdfrest-client-api: Require runnable API examples
datalogics-kam Aug 26, 2026
8811b91
pdfrest-client-api: Prefer TypedDict constructors
datalogics-kam Aug 26, 2026
cf5cb08
pdfrest-client-api: Add helper granularity decision rules
datalogics-kam Aug 28, 2026
5f4f6ce
pdfrest-client-api: Document literal contract tests
datalogics-kam Aug 29, 2026
d3d7126
client: Add PDF shape overlays
datalogics-kam Aug 22, 2026
feea388
types: Qualify shape API doc links
datalogics-kam Aug 22, 2026
fe6c976
tests: Cover shape overlay validation boundaries
datalogics-kam Aug 23, 2026
89f7d08
types: Simplify PDF shape color inputs
datalogics-kam Aug 23, 2026
f114cf9
pyproject: Bump release to 1.1.0
datalogics-kam Aug 25, 2026
4cffeff
types: Document PDF shape object contracts
datalogics-kam Aug 25, 2026
8fe5700
conftest: Allow live test base URL override
datalogics-kam Aug 25, 2026
4ef4925
examples: Add shape overlay example
datalogics-kam Aug 26, 2026
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
324 changes: 324 additions & 0 deletions .agents/skills/pdfrest-client-api/SKILL.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .agents/skills/pdfrest-client-api/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "pdfRest Client API"
short_description: "Add or evolve typed pdfRest client API helpers."
default_prompt: "Use $pdfrest-client-api to add or modify a forward-compatible API helper on PdfRestClient and AsyncPdfRestClient, using PDFCloud-API as the contract source and complete unit/live coverage."
122 changes: 122 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
pushing.
- `uv run pytest` — execute the suite with the active interpreter.
- `uv build` — produce wheels and sdists identical to the release workflow.
- `uv version --bump <major|minor|patch>` — update the project version; use this
command instead of editing the version manually in `pyproject.toml`.
- `uvx nox -s tests` — create matrix virtualenvs via nox and execute the pytest
session.
- `nox` executes pytest sessions with built-in parallelism; when invoking pytest
Expand Down Expand Up @@ -79,6 +81,23 @@
payload models (`model_validate`). Avoid duplicating payload validation in
client methods or raising configuration errors for payload-shape issues that
Pydantic validators can enforce.
- Decide public helper granularity from an applicability matrix, not from the
number of HTTP routes. List each user-recognizable source/workflow variant
against its accepted MIME types/extensions, required inputs, optional fields,
output shape, and validation rules; classify every option as universal,
subset-only, or variant-exclusive.
- Split one server operation into focused helpers when the source/workflow is
known before the call and a combined signature would expose options that are
invalid for some variants, require mode-dependent runtime checks, or weaken
editor/type-checker guidance. Distinct file-family validation or a meaningful
cluster of variant-only options is strong evidence for a split; a shared path
or wire object is not evidence for one public helper.
- Keep one helper when the variants share one coherent input contract and
outcome, or when a natural discriminated public input can make every valid
combination statically explicit without a kitchen-sink keyword signature. When
helpers are split, keep universal keywords consistent, share internal
base/nested payload models, and give each helper its own narrow payload model
that rejects other variants before transport execution.
- Prefer Pydantic-backed JSON serialization for performance: use
`model_dump_json()` for Pydantic models, and use `pydantic_core.to_json()` for
non-model payloads instead of `json.dumps()` where practical.
Expand Down Expand Up @@ -124,6 +143,12 @@
import them from `pdfrest.types` (e.g., `PdfInfoQuery`) instead of reaching
into underscored modules. Treat that package as the public surface for shared
type contracts consumed by both clients and tests.
- Document every public text `Literal` alias with a PEP 258 docstring
immediately after its `TypeAlias` assignment. State what the option controls,
then use an `Accepted values:` list with one Markdown bullet per literal
spelling and its user-visible meaning. Derive meanings from the OpenAPI
contract or observed server behavior; do not leave callers to infer semantics
from the strings.
- Payload models that reference uploaded resources should accept
`list[PdfRestFile]` with explicit length bounds and serialize IDs for the
allowed cardinality (`serialization_alias="id"` plus a serializer that emits
Expand Down Expand Up @@ -168,6 +193,12 @@
remain the default approach. Add custom validators only when they provide
behavior native constraints cannot (for example, parsing alternate wire
formats or enforcing cross-field dependencies).
- When pdfRest exposes separate RGB/CMYK wire fields for one semantic color,
expose one public `<name>_color: PdfColor` input instead of separate
`<name>_color_rgb`/`<name>_color_cmyk` inputs. Use a channel-count
`BeforeValidator` with shared `validation_alias` and distinct serialization
aliases to route three channels to RGB and four to CMYK; keep those wire-field
names internal to the payload model.
- Keep `BeforeValidator`/`AfterValidator` helpers and field serializers short
and shape-focused. They should primarily adapt nonconforming inputs or handle
pdfRest wire quirks (for example, splitting comma-separated values or
Expand Down Expand Up @@ -229,6 +260,15 @@
assertion through `PdfRestClient` and `AsyncPdfRestClient` so sync/async
behaviour stays independently verifiable.

- For endpoints that accept discriminated JSON objects, test every discriminator
through both client transports and assert the model's exact JSON-ready
serialization directly. Parameterize each constrained field at its accepted
boundaries and immediately outside them; test MIME and single-resource
cardinality failures through both transports with a transport that fails if
local validation does not short-circuit. When a helper accepts `timeout`,
capture `request.extensions["timeout"]` in both customization tests and assert
every timeout component.

- When endpoints may raise `PdfRestErrorGroup` (or any future pdfRest-specific
exception groups), assert them with `pytest.RaisesGroup`/`pytest.RaisesExc`,
and use the `check=` hook to confirm the outer group is the expected class so
Expand Down Expand Up @@ -287,6 +327,16 @@
invalid values (e.g., bogus literals or mixed lists) alongside boundary
failures so the server-side error messaging is exercised.

- Treat a public `Literal` as an enumerated contract, not as representative
option coverage. Parameterize every accepted spelling with readable
`pytest.param(..., id=...)` cases in the focused payload and client tests. For
a helper exposed by both clients, distinct sync and async test functions must
send every value; matching live tests must also exercise every value through
both transports. Include a server-rejected invalid spelling through
`extra_body` when local validation would otherwise prevent that request. The
`pr-review-auditor` checks the declared literal values against these cases, so
a single happy-path value is insufficient.

- Provide live integration tests under `tests/live/` (with an `__init__.py` so
pytest discovers the package) that introspect payload models to enumerate
valid/invalid literal values and numeric boundaries. These tests should vary a
Expand All @@ -309,10 +359,82 @@
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.,
`client: Add document merge service`).
- Name the commit scope after the primary file, directory, or domain object
affected by the change, such as `AGENTS`, `pdfrest-client-api`, `client`,
`models`, `tests`, `examples`, `docs`, or `pyproject`. Do not use generic
category or intent labels such as `guidance`, `changes`, `maintenance`, or
`misc`.
- Keep commit messages imperative and focused; squash fixups before opening a
PR.
- Reference related issues or tickets in the PR description, and highlight
Expand Down
36 changes: 36 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ uv run pre-commit install
uv run python -c "import pdfrest; print(pdfrest.__version__)"
```

## Adding or evolving a client API

When asking Codex to add a documented pdfRest endpoint or a compatible parameter
to an existing endpoint, begin the prompt with the repository-local
`$pdfrest-client-api` skill. Include the Jira work item key when one exists. The
PDFCloud-API checkout and its documented OpenAPI operation must be available
before using the skill. Prefer adding the neighboring PDFCloud-API directory to
the Codex project so the skill can read the contract directly. For example:

```text
$pdfrest-client-api PDFCLOUD-6233: Add the documented PDFCloud-API operation
for ...
```

The skill uses the PDFCloud-API OpenAPI specification as the contract; keeps the
sync and async clients aligned; puts wire serialization and validation in
Pydantic payload models; preserves existing caller behavior; and requires
focused unit coverage plus matching live endpoint tests. It also handles the
versioning required for a newly added public API.

## Code quality checks

Run these before opening a PR:
Expand Down Expand Up @@ -58,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