Skip to content
Closed
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
41 changes: 37 additions & 4 deletions .agents/skills/pdfrest-client-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,39 @@ before making substantive edits.

Name a helper for the user outcome, not the path or OpenAPI operation ID.

### Decide helper granularity with an applicability matrix

Before choosing one helper or several, derive a matrix from the OpenAPI
contract. Use one row per user-recognizable source type or workflow and record:

- accepted MIME types and filename extensions;
- required inputs and resource cardinality;
- optional fields, classified as universal, subset-only, or variant-exclusive;
- output/response shape and any materially different validation or lifecycle.

Prefer focused helpers when the caller knows the source/workflow before the call
and a combined signature would expose keywords that are invalid for some rows,
depend on a mode or file type for their meaning, require extensive cross-field
runtime rejection, or prevent the type checker/editor from showing the valid
option set. Distinct file-family validation or a meaningful cluster of
row-specific options is strong evidence for a split. The fact that variants
share an HTTP path, OpenAPI operation, or nested wire object is not evidence
that they should share a public helper.

Keep one helper when the rows share one coherent input contract and outcome and
nearly all options apply uniformly. A single helper can also be appropriate when
a natural discriminated `TypedDict`/model union expresses each variant without a
kitchen-sink keyword signature and static typing rejects invalid combinations.
Do not add a synthetic mode discriminator merely to avoid naming clear user
workflows.

When splitting, keep universal keywords and request-customization arguments
consistent across helpers, reuse internal base/nested models for common wire
fields, and give each helper a narrow payload model for its applicable options
and file-family validation. Add tests proving every helper rejects the other
families before transport and never serializes an option that is inapplicable to
its row.

## Versioning new APIs

Adding a public API is a feature release and requires a minor-version bump.
Expand All @@ -70,10 +103,10 @@ commits:
- Name the material source/result or effect: `convert_html_to_pdf`,
`add_text_to_pdf`, and `merge_pdfs`. Include both sides of a conversion.

- Split kitchen-sink routes into distinct helpers when source type, output,
validation, or user workflow differs. `/pdf` correctly maps to helpers such as
`convert_office_to_pdf`, `convert_html_to_pdf`, and `convert_url_to_pdf`, not
one mode-driven endpoint wrapper.
- Split kitchen-sink routes according to the applicability-matrix decision
above. `/pdf` correctly maps to helpers such as `convert_office_to_pdf`,
`convert_html_to_pdf`, and `convert_url_to_pdf`, not one mode-driven endpoint
wrapper.

- Use a qualifier only when it changes the contract or workflow, such as
`preview_redactions` then `apply_redactions`, or text versus image
Expand Down
22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,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 @@ -397,6 +414,11 @@

- 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
7 changes: 6 additions & 1 deletion docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ formats.
[convert_email_to_pdf][pdfrest.PdfRestClient.convert_email_to_pdf],
[convert_image_to_pdf][pdfrest.PdfRestClient.convert_image_to_pdf],
[convert_html_to_pdf][pdfrest.PdfRestClient.convert_html_to_pdf],
[convert_url_to_pdf][pdfrest.PdfRestClient.convert_url_to_pdf]
[convert_url_to_pdf][pdfrest.PdfRestClient.convert_url_to_pdf],
[convert_markdown_to_pdf][pdfrest.PdfRestClient.convert_markdown_to_pdf],
[convert_plain_text_to_pdf][pdfrest.PdfRestClient.convert_plain_text_to_pdf],
[convert_json_to_pdf][pdfrest.PdfRestClient.convert_json_to_pdf],
[convert_xml_to_pdf][pdfrest.PdfRestClient.convert_xml_to_pdf],
[convert_csv_to_pdf][pdfrest.PdfRestClient.convert_csv_to_pdf]
- Out of PDF:
[convert_to_word][pdfrest.PdfRestClient.convert_to_word],
[convert_to_excel][pdfrest.PdfRestClient.convert_to_excel],
Expand Down
3 changes: 3 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ supported interpreter matrix.

- `examples/add_shapes/add_shapes_to_pdf_example.py` – add a styled rectangle
and divider line to a PDF with accessibility tagging enabled.
- `examples/convert_structured_documents/convert_structured_documents_to_pdf_example.py`
– convert Markdown, plain text, JSON, XML, and CSV documents to PDF with
format-specific options.
- `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
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Convert Markdown, plain text, JSON, XML, and CSV documents to PDF.

This sample demonstrates how to:

1. Upload the five deterministic structured documents in ``examples/resources``.
2. Select format-specific conversion options with public typed dictionaries.
3. Generate one PDF from each source through the focused client helpers.
4. Print the returned PDF names, MIME types, sizes, and download URLs.

Set ``PDFREST_API_KEY``, then run from the repository root with
``uv run examples/convert_structured_documents/convert_structured_documents_to_pdf_example.py``.
All required input files are included in the repository.
"""

from __future__ import annotations

from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient
from pdfrest.models import PdfRestFileBasedResponse
from pdfrest.types import (
PdfStructuredTextCsvColumn,
PdfStructuredTextMargin,
PdfStructuredTextPageSetup,
PdfStructuredTextStyle,
PdfStructuredTextTableStyle,
)

RESOURCE_DIRECTORY = Path(__file__).resolve().parents[1] / "resources"
RESOURCE_PATHS = [
RESOURCE_DIRECTORY / "structured-document.md",
RESOURCE_DIRECTORY / "structured-document.txt",
RESOURCE_DIRECTORY / "structured-document.json",
RESOURCE_DIRECTORY / "structured-document.xml",
RESOURCE_DIRECTORY / "structured-document.csv",
]


def _print_result(label: str, response: PdfRestFileBasedResponse) -> None:
output = response.output_file
print(f"{label}: {output.name}")
print(f" MIME type: {output.type}")
print(f" Size: {output.size} bytes")
print(f" Download URL: {output.url}")


def convert_structured_documents() -> None:
"""Upload each sample and convert it with its format-specific helper."""
load_dotenv()
page_setup = PdfStructuredTextPageSetup(
size="Letter",
orientation="portrait",
margin=PdfStructuredTextMargin(top=36, right=36, bottom=36, left=36),
)
style = PdfStructuredTextStyle(
font="Arial",
text_size=11,
text_color_rgb=(32, 42, 54),
heading_scale=1.4,
)
table_style = PdfStructuredTextTableStyle(
show_borders=True,
repeat_headers_on_overflow=True,
header_fill_color_rgb=(34, 93, 131),
header_text_color_rgb=(255, 255, 255),
)
columns = [
PdfStructuredTextCsvColumn(index=0, text_align="left", width_weight=2),
PdfStructuredTextCsvColumn(index=1, text_align="right", width_weight=1),
]

with PdfRestClient() as client:
markdown, plain_text, json_file, xml_file, csv_file = (
client.files.create_from_paths(RESOURCE_PATHS)
)
responses = [
(
"Markdown",
client.convert_markdown_to_pdf(
markdown,
title="Markdown service summary",
enable_tagging=True,
page_setup=page_setup,
style=style,
table_style=table_style,
output="markdown-summary",
),
),
(
"Plain text",
client.convert_plain_text_to_pdf(
plain_text,
line_handling="reflow",
page_setup=page_setup,
style=style,
output="plain-text-summary",
),
),
(
"JSON",
client.convert_json_to_pdf(
json_file,
data_presentation="hierarchy",
page_setup=page_setup,
style=style,
output="json-summary",
),
),
(
"XML",
client.convert_xml_to_pdf(
xml_file,
data_presentation="hierarchy",
page_setup=page_setup,
style=style,
output="xml-summary",
),
),
(
"CSV",
client.convert_csv_to_pdf(
csv_file,
first_row_is_header=True,
columns=columns,
table_style=table_style,
page_setup=page_setup,
style=style,
output="csv-summary",
),
),
]

for label, response in responses:
_print_result(label, response)


if __name__ == "__main__": # pragma: no cover - manual example
convert_structured_documents()
3 changes: 3 additions & 0 deletions examples/resources/structured-document.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
service,availability_percent,status
Document API,99.98,healthy
Conversion API,99.97,healthy
5 changes: 5 additions & 0 deletions examples/resources/structured-document.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"quarter": "Q3",
"availability_percent": 99.98,
"priorities": ["accessibility", "document generation"]
}
10 changes: 10 additions & 0 deletions examples/resources/structured-document.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
**The Importance of Sleep**

Sleep is often underestimated, but it plays a vital role in maintaining health
and well-being. During sleep, the body repairs tissues, consolidates memories,
and restores energy for the next day.

Modern life tends to push sleep aside. Busy schedules, late-night work, and
constant access to technology can all reduce rest.

Prioritizing sleep supports balance and long-term health.
4 changes: 4 additions & 0 deletions examples/resources/structured-document.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Quarterly service summary

Availability improved during the quarter while response times remained stable.
The next release focuses on accessibility and document-generation workflows.
6 changes: 6 additions & 0 deletions examples/resources/structured-document.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<service-summary quarter="Q3">
<availability-percent>99.98</availability-percent>
<priority>accessibility</priority>
<priority>document generation</priority>
</service-summary>
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.1.0"
version = "1.2.0"
description = "Python client library for interacting with the pdfRest API"
readme = {file = "README.md", content-type = "text/markdown"}
authors = [
Expand Down
Loading
Loading