Skip to content

Commit 629047d

Browse files
PDF to Markdown: Use PdfRestFileBasedResponse
Assisted-by: Codex
1 parent ce5d126 commit 629047d

3 files changed

Lines changed: 102 additions & 69 deletions

File tree

src/pdfrest/client.py

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
translate_httpx_error,
6161
)
6262
from .models import (
63-
ConvertToMarkdownResponse,
6463
PdfRestDeletionResponse,
6564
PdfRestErrorResponse,
6665
PdfRestFile,
@@ -115,7 +114,6 @@
115114
PdfXType,
116115
SummaryFormat,
117116
SummaryOutputFormat,
118-
SummaryOutputType,
119117
TranslateOutputFormat,
120118
)
121119

@@ -2222,19 +2220,18 @@ def convert_to_markdown(
22222220
file: PdfRestFile | Sequence[PdfRestFile],
22232221
*,
22242222
pages: PdfPageSelection | None = None,
2225-
output_type: SummaryOutputType = "json",
22262223
page_break_comments: Literal["on", "off"] | None = None,
22272224
output: str | None = None,
22282225
extra_query: Query | None = None,
22292226
extra_headers: AnyMapping | None = None,
22302227
extra_body: Body | None = None,
22312228
timeout: TimeoutTypes | None = None,
2232-
) -> ConvertToMarkdownResponse:
2233-
"""Convert a PDF to Markdown."""
2229+
) -> PdfRestFileBasedResponse:
2230+
"""Convert a PDF to Markdown and return a file-based response."""
22342231

22352232
payload: dict[str, Any] = {
22362233
"files": file,
2237-
"output_type": output_type,
2234+
"output_type": "file",
22382235
}
22392236
if pages is not None:
22402237
payload["pages"] = pages
@@ -2243,20 +2240,15 @@ def convert_to_markdown(
22432240
if output is not None:
22442241
payload["output"] = output
22452242

2246-
validated_payload = ConvertToMarkdownPayload.model_validate(payload)
2247-
request = self.prepare_request(
2248-
"POST",
2249-
"/markdown",
2250-
json_body=validated_payload.model_dump(
2251-
mode="json", by_alias=True, exclude_none=True, exclude_unset=True
2252-
),
2243+
return self._post_file_operation(
2244+
endpoint="/markdown",
2245+
payload=payload,
2246+
payload_model=ConvertToMarkdownPayload,
22532247
extra_query=extra_query,
22542248
extra_headers=extra_headers,
22552249
extra_body=extra_body,
22562250
timeout=timeout,
22572251
)
2258-
raw_payload = self._send_request(request)
2259-
return ConvertToMarkdownResponse.model_validate(raw_payload)
22602252

22612253
def ocr_pdf(
22622254
self,
@@ -3208,19 +3200,18 @@ async def convert_to_markdown(
32083200
file: PdfRestFile | Sequence[PdfRestFile],
32093201
*,
32103202
pages: PdfPageSelection | None = None,
3211-
output_type: SummaryOutputType = "json",
32123203
page_break_comments: Literal["on", "off"] | None = None,
32133204
output: str | None = None,
32143205
extra_query: Query | None = None,
32153206
extra_headers: AnyMapping | None = None,
32163207
extra_body: Body | None = None,
32173208
timeout: TimeoutTypes | None = None,
3218-
) -> ConvertToMarkdownResponse:
3219-
"""Convert a PDF to Markdown."""
3209+
) -> PdfRestFileBasedResponse:
3210+
"""Convert a PDF to Markdown and return a file-based response."""
32203211

32213212
payload: dict[str, Any] = {
32223213
"files": file,
3223-
"output_type": output_type,
3214+
"output_type": "file",
32243215
}
32253216
if pages is not None:
32263217
payload["pages"] = pages
@@ -3229,20 +3220,15 @@ async def convert_to_markdown(
32293220
if output is not None:
32303221
payload["output"] = output
32313222

3232-
validated_payload = ConvertToMarkdownPayload.model_validate(payload)
3233-
request = self.prepare_request(
3234-
"POST",
3235-
"/markdown",
3236-
json_body=validated_payload.model_dump(
3237-
mode="json", by_alias=True, exclude_none=True, exclude_unset=True
3238-
),
3223+
return await self._post_file_operation(
3224+
endpoint="/markdown",
3225+
payload=payload,
3226+
payload_model=ConvertToMarkdownPayload,
32393227
extra_query=extra_query,
32403228
extra_headers=extra_headers,
32413229
extra_body=extra_body,
32423230
timeout=timeout,
32433231
)
3244-
raw_payload = await self._send_request(request)
3245-
return ConvertToMarkdownResponse.model_validate(raw_payload)
32463232

32473233
async def ocr_pdf(
32483234
self,

tests/live/test_live_convert_to_markdown.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44

55
from pdfrest import PdfRestApiError, PdfRestClient
6-
from pdfrest.models import ConvertToMarkdownResponse
6+
from pdfrest.models import PdfRestFileBasedResponse
77

88
from ..resources import get_test_resource_path
99

@@ -18,13 +18,10 @@ def test_live_convert_to_markdown_success(
1818
base_url=pdfrest_live_base_url,
1919
) as client:
2020
uploaded = client.files.create_from_paths([resource])[0]
21-
response = client.convert_to_markdown(
22-
uploaded,
23-
output_type="json",
24-
)
21+
response = client.convert_to_markdown(uploaded)
2522

26-
assert isinstance(response, ConvertToMarkdownResponse)
27-
assert response.markdown
23+
assert isinstance(response, PdfRestFileBasedResponse)
24+
assert response.output_files
2825
assert response.input_id == uploaded.id
2926

3027

tests/test_convert_to_markdown.py

Lines changed: 84 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,30 @@
77
from pydantic import ValidationError
88

99
from pdfrest import AsyncPdfRestClient, PdfRestClient
10-
from pdfrest.models import ConvertToMarkdownResponse, PdfRestFile, PdfRestFileID
10+
from pdfrest.models import (
11+
PdfRestFile,
12+
PdfRestFileBasedResponse,
13+
PdfRestFileID,
14+
)
1115
from pdfrest.models._internal import ConvertToMarkdownPayload
1216

1317
from .graphics_test_helpers import ASYNC_API_KEY, VALID_API_KEY, make_pdf_file
1418

1519

20+
def _make_markdown_file(file_id: str, name: str = "markdown.md") -> PdfRestFile:
21+
return PdfRestFile.model_validate(
22+
{
23+
"id": file_id,
24+
"name": name,
25+
"url": f"https://api.pdfrest.com/resource/{file_id}",
26+
"type": "text/markdown",
27+
"size": 64,
28+
"modified": "2024-01-01T00:00:00Z",
29+
"scheduledDeletionTimeUtc": None,
30+
}
31+
)
32+
33+
1634
def test_convert_to_markdown_payload_rejects_non_pdf() -> None:
1735
file_id = str(PdfRestFileID.generate())
1836
text_file = PdfRestFile.model_validate(
@@ -48,20 +66,21 @@ def test_convert_to_markdown_payload_invalid_page_break_comments() -> None:
4866
)
4967

5068

51-
def test_convert_to_markdown_json_success(monkeypatch: pytest.MonkeyPatch) -> None:
69+
def test_convert_to_markdown_success(monkeypatch: pytest.MonkeyPatch) -> None:
5270
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
5371
input_file = make_pdf_file(PdfRestFileID.generate(1))
72+
output_id = str(PdfRestFileID.generate())
5473
payload_dump = ConvertToMarkdownPayload.model_validate(
5574
{
5675
"files": [input_file],
5776
"pages": ["1-3"],
5877
"output": "md",
59-
"output_type": "json",
78+
"output_type": "file",
6079
"page_break_comments": "on",
6180
}
6281
).model_dump(mode="json", by_alias=True, exclude_none=True, exclude_unset=True)
6382

64-
seen: dict[str, int] = {"post": 0}
83+
seen: dict[str, int] = {"post": 0, "get": 0}
6584

6685
def handler(request: httpx.Request) -> httpx.Response:
6786
if request.method == "POST" and request.url.path == "/markdown":
@@ -72,10 +91,19 @@ def handler(request: httpx.Request) -> httpx.Response:
7291
return httpx.Response(
7392
200,
7493
json={
75-
"markdown": "# Title",
76-
"inputId": str(input_file.id),
94+
"inputId": [str(input_file.id)],
95+
"outputId": [output_id],
7796
},
7897
)
98+
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
99+
seen["get"] += 1
100+
assert request.url.params["format"] == "info"
101+
return httpx.Response(
102+
200,
103+
json=_make_markdown_file(output_id).model_dump(
104+
mode="json", by_alias=True
105+
),
106+
)
79107
msg = f"Unexpected request {request.method} {request.url}"
80108
raise AssertionError(msg)
81109

@@ -85,16 +113,13 @@ def handler(request: httpx.Request) -> httpx.Response:
85113
input_file,
86114
pages=["1-3"],
87115
output="md",
88-
output_type="json",
89116
page_break_comments="on",
90117
)
91118

92-
assert seen == {"post": 1}
93-
assert isinstance(response, ConvertToMarkdownResponse)
94-
assert response.markdown == "# Title"
119+
assert seen == {"post": 1, "get": 1}
120+
assert isinstance(response, PdfRestFileBasedResponse)
95121
assert response.input_id == input_file.id
96-
assert response.output_id is None
97-
assert response.output_url is None
122+
assert len(response.output_files) == 1
98123

99124

100125
def test_convert_to_markdown_request_customization(
@@ -116,45 +141,61 @@ def handler(request: httpx.Request) -> httpx.Response:
116141
if request.method == "POST" and request.url.path == "/markdown":
117142
assert request.url.params["trace"] == "true"
118143
assert request.headers["X-Debug"] == "sync"
119-
captured_timeout["value"] = request.extensions.get("timeout")
144+
captured_timeout["post"] = request.extensions.get("timeout")
120145
payload = json.loads(request.content.decode("utf-8"))
121146
for key, value in payload_dump.items():
122147
assert payload[key] == value
123148
assert payload["debug"] is True
124149
return httpx.Response(
125150
200,
126151
json={
127-
"outputUrl": f"https://api.pdfrest.com/resource/{output_id}?format=file",
128-
"outputId": output_id,
129-
"inputId": str(input_file.id),
152+
"inputId": [str(input_file.id)],
153+
"outputId": [output_id],
130154
},
131155
)
156+
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
157+
assert request.url.params["format"] == "info"
158+
assert request.url.params["trace"] == "true"
159+
assert request.headers["X-Debug"] == "sync"
160+
captured_timeout["get"] = request.extensions.get("timeout")
161+
return httpx.Response(
162+
200,
163+
json=_make_markdown_file(output_id, "debug.md").model_dump(
164+
mode="json", by_alias=True
165+
),
166+
)
132167
msg = f"Unexpected request {request.method} {request.url}"
133168
raise AssertionError(msg)
134169

135170
transport = httpx.MockTransport(handler)
136171
with PdfRestClient(api_key=VALID_API_KEY, transport=transport) as client:
137172
response = client.convert_to_markdown(
138173
input_file,
139-
output_type="file",
140174
extra_query={"trace": "true"},
141175
extra_headers={"X-Debug": "sync"},
142176
extra_body={"debug": True},
143177
timeout=0.4,
144178
page_break_comments="off",
145179
)
146180

147-
assert isinstance(response, ConvertToMarkdownResponse)
148-
assert response.output_id == output_id
149-
assert response.output_url
150-
timeout_value = captured_timeout["value"]
151-
assert timeout_value is not None
152-
if isinstance(timeout_value, dict):
181+
assert isinstance(response, PdfRestFileBasedResponse)
182+
assert len(response.output_files) == 1
183+
post_timeout = captured_timeout["post"]
184+
get_timeout = captured_timeout["get"]
185+
assert post_timeout is not None
186+
assert get_timeout is not None
187+
if isinstance(post_timeout, dict):
188+
assert all(
189+
component == pytest.approx(0.4) for component in post_timeout.values()
190+
)
191+
else:
192+
assert post_timeout == pytest.approx(0.4)
193+
if isinstance(get_timeout, dict):
153194
assert all(
154-
component == pytest.approx(0.4) for component in timeout_value.values()
195+
component == pytest.approx(0.4) for component in get_timeout.values()
155196
)
156197
else:
157-
assert timeout_value == pytest.approx(0.4)
198+
assert get_timeout == pytest.approx(0.4)
158199

159200

160201
@pytest.mark.asyncio
@@ -163,11 +204,12 @@ async def test_async_convert_to_markdown_success(
163204
) -> None:
164205
monkeypatch.delenv("PDFREST_API_KEY", raising=False)
165206
input_file = make_pdf_file(PdfRestFileID.generate(2))
207+
output_id = str(PdfRestFileID.generate())
166208
payload_dump = ConvertToMarkdownPayload.model_validate(
167-
{"files": [input_file], "output_type": "json", "page_break_comments": "off"}
209+
{"files": [input_file], "output_type": "file", "page_break_comments": "off"}
168210
).model_dump(mode="json", by_alias=True, exclude_none=True, exclude_unset=True)
169211

170-
seen: dict[str, int] = {"post": 0}
212+
seen: dict[str, int] = {"post": 0, "get": 0}
171213

172214
def handler(request: httpx.Request) -> httpx.Response:
173215
if request.method == "POST" and request.url.path == "/markdown":
@@ -178,20 +220,28 @@ def handler(request: httpx.Request) -> httpx.Response:
178220
return httpx.Response(
179221
200,
180222
json={
181-
"markdown": "Async md",
182-
"inputId": str(input_file.id),
223+
"inputId": [str(input_file.id)],
224+
"outputId": [output_id],
183225
},
184226
)
227+
if request.method == "GET" and request.url.path == f"/resource/{output_id}":
228+
seen["get"] += 1
229+
assert request.url.params["format"] == "info"
230+
return httpx.Response(
231+
200,
232+
json=_make_markdown_file(output_id, "async.md").model_dump(
233+
mode="json", by_alias=True
234+
),
235+
)
185236
msg = f"Unexpected request {request.method} {request.url}"
186237
raise AssertionError(msg)
187238

188239
transport = httpx.MockTransport(handler)
189240
async with AsyncPdfRestClient(api_key=ASYNC_API_KEY, transport=transport) as client:
190241
response = await client.convert_to_markdown(
191-
input_file, output_type="json", page_break_comments="off"
242+
input_file, page_break_comments="off"
192243
)
193244

194-
assert seen == {"post": 1}
195-
assert isinstance(response, ConvertToMarkdownResponse)
196-
assert response.markdown == "Async md"
197-
assert response.input_id == input_file.id
245+
assert seen == {"post": 1, "get": 1}
246+
assert isinstance(response, PdfRestFileBasedResponse)
247+
assert len(response.output_files) == 1

0 commit comments

Comments
 (0)