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
5 changes: 4 additions & 1 deletion docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ Use this group when users need metadata or text intelligence.
[translate_pdf_text][pdfrest.PdfRestClient.translate_pdf_text],
[translate_pdf_text_to_file][pdfrest.PdfRestClient.translate_pdf_text_to_file]
- Markdown output:
[convert_to_markdown][pdfrest.PdfRestClient.convert_to_markdown]
[convert_to_markdown_inline][pdfrest.PdfRestClient.convert_to_markdown_inline]
returns generated Markdown directly, while
[convert_to_markdown][pdfrest.PdfRestClient.convert_to_markdown] returns a
downloadable Markdown file

## Compose, split, merge, and package

Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ supported interpreter matrix.
- `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/convert_to_markdown/convert_pdf_to_markdown_example.py` – convert a
PDF to inline Markdown and a downloadable Markdown file.
- `examples/convert_postscript_to_pdf/convert_postscript_with_job_options_example.py`
– convert PostScript to PDF with an uploaded custom `.joboptions` profile.
- `examples/convert_to_image/convert_password_protected_pdf_to_png_example.py` –
Expand Down
38 changes: 38 additions & 0 deletions examples/convert_to_markdown/convert_pdf_to_markdown_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["pdfrest", "python-dotenv"]
# ///
"""Convert a PDF to inline Markdown and a downloadable Markdown file.

This example loads ``PDFREST_API_KEY`` (optionally from ``.env``), uploads the
checked-in ``examples/resources/report.pdf`` fixture, requests inline Markdown,
then requests the file-output workflow and prints both results.

Run from the repository root with:
``uv run examples/convert_to_markdown/convert_pdf_to_markdown_example.py``.
"""

import os
from pathlib import Path

from dotenv import load_dotenv

from pdfrest import PdfRestClient

load_dotenv()

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

with PdfRestClient(api_key=os.environ["PDFREST_API_KEY"]) as client:
source_pdf = client.files.create_from_paths([resource])[0]
inline_response = client.convert_to_markdown_inline(source_pdf)
file_response = client.convert_to_markdown(
source_pdf,
output="report-markdown",
)

print("Inline Markdown preview:")
print(inline_response.markdown[:500])
print(f"Created file: {file_response.output_file.name}")
print(f"MIME type: {file_response.output_file.type}")
print(f"URL: {file_response.output_file.url}")
103 changes: 101 additions & 2 deletions src/pdfrest/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
translate_httpx_error,
)
from .models import (
ConvertToMarkdownResponse,
ExtractedTextDocument,
PdfRestDeletionResponse,
PdfRestErrorResponse,
Expand All @@ -87,6 +88,7 @@
ConvertPdfToPostscriptPayload,
ConvertPlainTextToPdfPayload,
ConvertPostscriptToPdfPayload,
ConvertToMarkdownInlinePayload,
ConvertToMarkdownPayload,
ConvertUrlToPdfPayload,
ConvertXmlToPdfPayload,
Expand Down Expand Up @@ -2957,7 +2959,9 @@ def convert_to_markdown(
) -> PdfRestFileBasedResponse:
"""Convert a PDF to Markdown and return a file-based response.

Converts document content into Markdown and supports either inline text output or file output. This is useful when preparing PDFs for LLM/RAG indexing and markdown-centric tooling.
Converts document content into a downloadable Markdown file. Use
[convert_to_markdown_inline][pdfrest.PdfRestClient.convert_to_markdown_inline]
when the generated Markdown should be returned directly in the response.

Args:
file: Uploaded input file or files as `PdfRestFile` objects.
Expand Down Expand Up @@ -2996,6 +3000,59 @@ def convert_to_markdown(
timeout=timeout,
)

def convert_to_markdown_inline(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
pages: PdfPageSelection | None = None,
page_break_comments: bool = False,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> ConvertToMarkdownResponse:
"""Convert a PDF to Markdown and return the content inline.

Args:
file: Uploaded PDF as a `PdfRestFile` object.
pages: Page selection to constrain processing to specific pages.
page_break_comments: When true, inserts page-break marker comments
between source pages.
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:
Generated Markdown and the input resource ID.

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,
"output_type": "json",
"page_break_comments": page_break_comments,
}
if pages is not None:
payload["pages"] = pages

validated_payload = ConvertToMarkdownInlinePayload.model_validate(payload)
request = self.prepare_request(
"POST",
"/markdown",
json_body=validated_payload.model_dump(
mode="json", by_alias=True, exclude_none=True, exclude_unset=True
),
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)
raw_payload = self._send_request(request)
return ConvertToMarkdownResponse.model_validate(raw_payload)

def ocr_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down Expand Up @@ -6729,7 +6786,9 @@ async def convert_to_markdown(
) -> PdfRestFileBasedResponse:
"""Asynchronous variant of [PdfRestClient.convert_to_markdown][pdfrest.PdfRestClient.convert_to_markdown].

Converts document content into Markdown and supports either inline text output or file output. This is useful when preparing PDFs for LLM/RAG indexing and markdown-centric tooling.
Converts document content into a downloadable Markdown file. Use
[convert_to_markdown_inline][pdfrest.AsyncPdfRestClient.convert_to_markdown_inline]
when the generated Markdown should be returned directly in the response.

Args:
file: Uploaded input file or files as `PdfRestFile` objects.
Expand Down Expand Up @@ -6768,6 +6827,46 @@ async def convert_to_markdown(
timeout=timeout,
)

async def convert_to_markdown_inline(
self,
file: PdfRestFile | Sequence[PdfRestFile],
*,
pages: PdfPageSelection | None = None,
page_break_comments: bool = False,
extra_query: Query | None = None,
extra_headers: AnyMapping | None = None,
extra_body: Body | None = None,
timeout: TimeoutTypes | None = None,
) -> ConvertToMarkdownResponse:
"""Asynchronously convert a PDF to Markdown returned inline.

See
[PdfRestClient.convert_to_markdown_inline][pdfrest.PdfRestClient.convert_to_markdown_inline]
for argument and response details.
"""
payload: dict[str, Any] = {
"files": file,
"output_type": "json",
"page_break_comments": page_break_comments,
}
if pages is not None:
payload["pages"] = pages

validated_payload = ConvertToMarkdownInlinePayload.model_validate(payload)
request = self.prepare_request(
"POST",
"/markdown",
json_body=validated_payload.model_dump(
mode="json", by_alias=True, exclude_none=True, exclude_unset=True
),
extra_query=extra_query,
extra_headers=extra_headers,
extra_body=extra_body,
timeout=timeout,
)
raw_payload = await self._send_request(request)
return ConvertToMarkdownResponse.model_validate(raw_payload)

async def ocr_pdf(
self,
file: PdfRestFile | Sequence[PdfRestFile],
Expand Down
2 changes: 2 additions & 0 deletions src/pdfrest/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Public model exports for the `pdfrest.models` package."""

from .public import (
ConvertToMarkdownResponse,
ExtractedTextDocument,
ExtractedTextFullText,
ExtractedTextFullTextPage,
Expand All @@ -27,6 +28,7 @@
)

__all__ = [
"ConvertToMarkdownResponse",
"ExtractTextResponse",
"ExtractedTextDocument",
"ExtractedTextFullText",
Expand Down
23 changes: 18 additions & 5 deletions src/pdfrest/models/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,8 +877,8 @@ class ExtractTextPayload(BaseModel):
] = None


class ConvertToMarkdownPayload(BaseModel):
"""Adapt caller options into a pdfRest-ready markdown conversion payload."""
class _BaseConvertToMarkdownPayload(BaseModel):
"""Adapt shared caller options into a pdfRest-ready Markdown payload."""

files: Annotated[
list[PdfRestFile],
Expand All @@ -902,14 +902,27 @@ class ConvertToMarkdownPayload(BaseModel):
BeforeValidator(_int_to_string),
PlainSerializer(_serialize_page_ranges),
] = None
output_type: Annotated[
SummaryOutputType, Field(serialization_alias="output_type", default="json")
] = "json"
page_break_comments: Annotated[
Literal["on", "off"] | None,
Field(serialization_alias="page_break_comments", default=None),
BeforeValidator(_bool_to_on_off),
] = None


class ConvertToMarkdownInlinePayload(_BaseConvertToMarkdownPayload):
"""Validate a PDF-to-Markdown request that returns inline content."""

output_type: Annotated[
Literal["json"], Field(serialization_alias="output_type", default="json")
] = "json"


class ConvertToMarkdownPayload(_BaseConvertToMarkdownPayload):
"""Validate a PDF-to-Markdown request that returns a file."""

output_type: Annotated[
Literal["file"], Field(serialization_alias="output_type", default="file")
] = "file"
output: Annotated[
str | None,
Field(serialization_alias="output", min_length=1, default=None),
Expand Down
42 changes: 6 additions & 36 deletions src/pdfrest/models/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)

__all__ = (
"ConvertToMarkdownResponse",
"ExtractTextResponse",
"ExtractedTextDocument",
"ExtractedTextFullText",
Expand Down Expand Up @@ -866,18 +867,15 @@ class ExtractedTextDocument(BaseModel):


class ConvertToMarkdownResponse(BaseModel):
"""Response returned by the markdown conversion tool."""
"""Inline Markdown returned by PDF-to-Markdown conversion."""

model_config = ConfigDict(extra="allow")

markdown: Annotated[
str | None,
Field(
description="Inline markdown content when output_type is json.",
default=None,
),
] = None
"""Inline markdown content when output_type is json."""
str,
Field(description="Markdown content generated from the input PDF."),
]
"""Markdown content generated from the input PDF."""

input_id: Annotated[
PdfRestFileID,
Expand All @@ -888,34 +886,6 @@ class ConvertToMarkdownResponse(BaseModel):
]
"""The id of the input file."""

output_url: Annotated[
HttpUrl | None,
Field(
alias="outputUrl",
validation_alias=AliasChoices("output_url", "outputUrl"),
description="Download URL for file output.",
default=None,
),
] = None
"""Download URL for file output."""

output_id: Annotated[
PdfRestFileID | None,
Field(
alias="outputId",
validation_alias=AliasChoices("output_id", "outputId"),
description="The id of the generated output when output_type is file.",
default=None,
),
] = None
"""The id of the generated output when output_type is file."""

warning: Annotated[
str | None,
Field(description="A warning that was generated during markdown conversion."),
] = None
"""A warning that was generated during markdown conversion."""


class PdfRestInfoResponse(BaseModel):
"""A response containing the output from the /info route."""
Expand Down
Loading