Skip to content

Commit 74d31c1

Browse files
committed
finish on testing
1 parent 86e8e95 commit 74d31c1

File tree

16 files changed

+1454
-932
lines changed

16 files changed

+1454
-932
lines changed

src/nutrient_dws/builder/builder.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,7 @@ def _is_action_with_file_input(
151151
Returns:
152152
True if action needs file registration
153153
"""
154-
return (
155-
hasattr(action, "__needsFileRegistration")
156-
and hasattr(action, "fileInput")
157-
and hasattr(action, "createAction")
158-
)
154+
return hasattr(action, "createAction")
159155

160156
async def _prepare_files(self) -> dict[str, NormalizedFileData]:
161157
"""Prepare files for the request concurrently.

src/nutrient_dws/builder/staged_builders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class BufferOutput(TypedDict):
5858

5959

6060
class ContentOutput(TypedDict):
61-
content: str
61+
content: bytes
6262
mimeType: str
6363
filename: str | None
6464

src/nutrient_dws/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
WorkflowWithPartsStage,
1616
)
1717
from nutrient_dws.errors import NutrientError, ValidationError
18-
from nutrient_dws.http import NutrientClientOptions, SignRequestOptions, send_request
18+
from nutrient_dws.http import (
19+
NutrientClientOptions,
20+
SignRequestData,
21+
SignRequestOptions,
22+
send_request,
23+
)
1924
from nutrient_dws.inputs import (
2025
FileInput,
2126
get_pdf_page_count,
@@ -362,7 +367,7 @@ async def sign(
362367
{
363368
"method": "POST",
364369
"endpoint": "/sign",
365-
"data": cast("Any", request_data),
370+
"data": cast("SignRequestData", request_data),
366371
"headers": None,
367372
},
368373
self.options,

src/nutrient_dws/http.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,10 @@ def prepare_request_body(
256256
files, "graphicImage", config["data"]["graphicImage"]
257257
)
258258

259+
request_config["files"] = files
260+
259261
data = {}
260-
if "data" in config["data"]:
262+
if "data" in config["data"] and config["data"]["data"] is not None:
261263
data["data"] = json.dumps(config["data"]["data"])
262264
else:
263265
data["data"] = json.dumps(
@@ -267,25 +269,22 @@ def prepare_request_body(
267269
}
268270
)
269271

270-
request_config["files"] = files
271272
request_config["data"] = data
272273

273274
return request_config
274275

275276
if is_post_ai_redact_request_config(config):
276-
typed_config = config
277-
278-
if "file" in typed_config["data"] and "fileKey" in typed_config["data"]:
277+
if "file" in config["data"] and "fileKey" in config["data"]:
279278
files = {}
280279
append_file_to_form_data(
281-
files, typed_config["data"]["fileKey"], typed_config["data"]["file"]
280+
files, config["data"]["fileKey"], config["data"]["file"]
282281
)
283282

284283
request_config["files"] = files
285-
request_config["data"] = {"data": json.dumps(typed_config["data"]["data"])}
284+
request_config["data"] = {"data": json.dumps(config["data"]["data"])}
286285
else:
287286
# JSON only request
288-
request_config["json"] = typed_config["data"]["data"]
287+
request_config["json"] = config["data"]["data"]
289288

290289
return request_config
291290

src/nutrient_dws/types/build_output.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
from typing import Literal, Optional, TypedDict, Union
1+
from typing import Literal, TypedDict, Union
22

33
from typing_extensions import NotRequired
44

55
from nutrient_dws.types.misc import OcrLanguage, PageRange
66

7-
Title = Optional[str]
8-
97

108
class Metadata(TypedDict):
11-
title: NotRequired[Title | None]
9+
title: NotRequired[str]
1210
author: NotRequired[str]
1311

1412

@@ -59,7 +57,9 @@ class PDFOutput(BasePDFOutput):
5957

6058
class PDFAOutputOptions(PDFOutputOptions):
6159
conformance: NotRequired[
62-
Literal["pdfa-1a", "pdfa-1b", "pdfa-2a", "pdfa-2u", "pdfa-2b", "pdfa-3a", "pdfa-3u"]
60+
Literal[
61+
"pdfa-1a", "pdfa-1b", "pdfa-2a", "pdfa-2u", "pdfa-2b", "pdfa-3a", "pdfa-3u"
62+
]
6363
]
6464
vectorization: NotRequired[bool]
6565
rasterization: NotRequired[bool]

src/nutrient_dws/types/instant_json/form_field.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ class FormFieldAdditionalActionsInput(TypedDict):
4949
onFormat: NotRequired[Action]
5050

5151

52-
class AdditionalActions(FormFieldAdditionalActionsEvent, FormFieldAdditionalActionsInput):
52+
class AdditionalActions(
53+
FormFieldAdditionalActionsEvent, FormFieldAdditionalActionsInput
54+
):
5355
pass
5456

5557

src/nutrient_dws/types/misc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class Margin(TypedDict):
2828
class PageLayout(TypedDict):
2929
orientation: NotRequired[Literal["portrait", "landscape"]]
3030
size: NotRequired[
31-
Literal["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "Letter", "Legal"] | Size
31+
Literal["A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "Letter", "Legal"]
32+
| Size
3233
]
3334
margin: NotRequired[Margin]
3435

@@ -247,7 +248,9 @@ class WatermarkDimension(TypedDict):
247248
"MeasurementScale",
248249
{
249250
"unitFrom": NotRequired[Literal["in", "mm", "cm", "pt"]],
250-
"unitTo": NotRequired[Literal["in", "mm", "cm", "pt", "ft", "m", "yd", "km", "mi"]],
251+
"unitTo": NotRequired[
252+
Literal["in", "mm", "cm", "pt", "ft", "m", "yd", "km", "mi"]
253+
],
251254
"from": NotRequired[float],
252255
"to": NotRequired[float],
253256
},

src/nutrient_dws/types/sign_request.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55

66
class Appearance(TypedDict):
7-
mode: NotRequired[Literal["signatureOnly", "signatureAndDescription", "descriptionOnly"]]
7+
mode: NotRequired[
8+
Literal["signatureOnly", "signatureAndDescription", "descriptionOnly"]
9+
]
810
contentType: NotRequired[str]
911
showWatermark: NotRequired[bool]
1012
showSignDate: NotRequired[bool]

tests/helpers.py

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
"""Test utilities and helpers for Nutrient DWS Python Client tests."""
2+
23
from datetime import datetime, timezone
34
import json
45
from typing import Any, Optional, TypedDict, Literal, List
56
from pathlib import Path
67

8+
79
class XfdfAnnotation(TypedDict):
8-
type: Literal['highlight', 'text', 'square', 'circle']
10+
type: Literal["highlight", "text", "square", "circle"]
911
page: int
1012
rect: List[int]
1113
content: Optional[str]
1214
color: Optional[str]
1315

16+
1417
class TestDocumentGenerator:
1518
"""Generate test documents and content for testing purposes."""
1619

@@ -65,10 +68,17 @@ def generate_pdf_with_table() -> bytes:
6568
return TestDocumentGenerator.generate_simple_pdf_content(content)
6669

6770
@staticmethod
68-
def generate_html_content(title: str = "Test Document", include_styles: bool = True, include_table: bool = False, include_images: bool = False, include_form: bool = False) -> bytes:
71+
def generate_html_content(
72+
title: str = "Test Document",
73+
include_styles: bool = True,
74+
include_table: bool = False,
75+
include_images: bool = False,
76+
include_form: bool = False,
77+
) -> bytes:
6978
"""Generate HTML content for testing."""
7079

71-
styles = """<style>
80+
styles = (
81+
"""<style>
7282
body {
7383
font-family: Arial, sans-serif;
7484
margin: 40px;
@@ -110,8 +120,12 @@ def generate_html_content(title: str = "Test Document", include_styles: bool = T
110120
border: 1px solid #ddd;
111121
border-radius: 4px;
112122
}
113-
</style>""" if include_styles else ""
114-
tables = """<h2>Data Table</h2>
123+
</style>"""
124+
if include_styles
125+
else ""
126+
)
127+
tables = (
128+
"""<h2>Data Table</h2>
115129
<table>
116130
<thead>
117131
<tr>
@@ -141,13 +155,21 @@ def generate_html_content(title: str = "Test Document", include_styles: bool = T
141155
<td>$40.00</td>
142156
</tr>
143157
</tbody>
144-
</table>""" if include_table else ""
145-
images = """<h2>Images</h2>
158+
</table>"""
159+
if include_table
160+
else ""
161+
)
162+
images = (
163+
"""<h2>Images</h2>
146164
<p>Below is a placeholder for image content:</p>
147165
<div style="width: 200px; height: 200px; background-color: #e0e0e0; display: flex; align-items: center; justify-content: center; margin: 20px 0;">
148166
<span style="color: #666;">Image Placeholder</span>
149-
</div>""" if include_images else ""
150-
form = """<h2>Form Example</h2>
167+
</div>"""
168+
if include_images
169+
else ""
170+
)
171+
form = (
172+
"""<h2>Form Example</h2>
151173
<form>
152174
<div class="form-group">
153175
<label for="name">Name:</label>
@@ -161,7 +183,10 @@ def generate_html_content(title: str = "Test Document", include_styles: bool = T
161183
<label for="message">Message:</label>
162184
<textarea id="message" name="message" rows="4" placeholder="Enter your message"></textarea>
163185
</div>
164-
</form>""" if include_form else ""
186+
</form>"""
187+
if include_form
188+
else ""
189+
)
165190

166191
html = f"""<!DOCTYPE html>
167192
<html lang="en">
@@ -180,17 +205,19 @@ def generate_html_content(title: str = "Test Document", include_styles: bool = T
180205
return html.encode("utf-8")
181206

182207
@staticmethod
183-
def generate_xfdf_content(annotations: Optional[list[XfdfAnnotation]] = None) -> bytes:
208+
def generate_xfdf_content(
209+
annotations: Optional[list[XfdfAnnotation]] = None,
210+
) -> bytes:
184211
"""Generate XFDF annotation content."""
185212

186213
if annotations is None:
187214
annotations = [
188215
{
189-
"type": 'highlight',
216+
"type": "highlight",
190217
"page": 0,
191218
"rect": [100, 100, 200, 150],
192-
"color": '#FFFF00',
193-
"content": 'Important text',
219+
"color": "#FFFF00",
220+
"content": "Important text",
194221
},
195222
]
196223

@@ -201,11 +228,11 @@ def generate_xfdf_content(annotations: Optional[list[XfdfAnnotation]] = None) ->
201228
color = annot["color"] or "#FFFF00"
202229
if annot["type"] == "highlight":
203230
inner_xfdf = f"""<highlight page="${annot["page"]}" rect="${rectStr}" color="${color}">
204-
<contents>${annot.get("content", 'Highlighted text')}</contents>
231+
<contents>${annot.get("content", "Highlighted text")}</contents>
205232
</highlight>"""
206233
elif annot["type"] == "text":
207234
inner_xfdf = f"""<text page="${annot["page"]}" rect="${rectStr}" color="${color}">
208-
<contents>${annot.get("content", 'Note')}</contents>
235+
<contents>${annot.get("content", "Note")}</contents>
209236
</text>"""
210237
elif annot["type"] == "square":
211238
inner_xfdf = f"""<square page="{annot["page"]}" rect="{rectStr}" color="{color}" />"""
@@ -224,17 +251,19 @@ def generate_xfdf_content(annotations: Optional[list[XfdfAnnotation]] = None) ->
224251
@staticmethod
225252
def generate_instant_json_content(annotations: Optional[list] = None) -> bytes:
226253
"""Generate Instant JSON annotation content."""
227-
annotations = annotations or [{
228-
"v": 2,
229-
"type": 'pspdfkit/text',
230-
"pageIndex": 0,
231-
"bbox": [100, 100, 200, 150],
232-
"content": 'Test annotation',
233-
"fontSize": 14,
234-
"opacity": 1,
235-
"horizontalAlign": 'left',
236-
"verticalAlign": 'top',
237-
}]
254+
annotations = annotations or [
255+
{
256+
"v": 2,
257+
"type": "pspdfkit/text",
258+
"pageIndex": 0,
259+
"bbox": [100, 100, 200, 150],
260+
"content": "Test annotation",
261+
"fontSize": 14,
262+
"opacity": 1,
263+
"horizontalAlign": "left",
264+
"verticalAlign": "top",
265+
}
266+
]
238267
instant_data = {
239268
"format": "https://pspdfkit.com/instant-json/v1",
240269
"annotations": [],
@@ -282,7 +311,7 @@ def validate_pdf_output(result: Any) -> None:
282311

283312
@staticmethod
284313
def validate_office_output(
285-
result: Any, format: Literal["docx", "xlsx", "pptx"]
314+
result: Any, format: Literal["docx", "xlsx", "pptx"]
286315
) -> None:
287316
"""Validates Office document output"""
288317
mime_types = {
@@ -291,7 +320,11 @@ def validate_office_output(
291320
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
292321
}
293322

294-
if not isinstance(result, dict) or not result.get("success") or "output" not in result:
323+
if (
324+
not isinstance(result, dict)
325+
or not result.get("success")
326+
or "output" not in result
327+
):
295328
raise ValueError("Result must be successful with output")
296329

297330
output = result["output"]
@@ -307,7 +340,11 @@ def validate_image_output(
307340
result: Any, format: Literal["png", "jpeg", "jpg", "webp"] | None = None
308341
) -> None:
309342
"""Validates image output"""
310-
if not isinstance(result, dict) or not result.get("success") or "output" not in result:
343+
if (
344+
not isinstance(result, dict)
345+
or not result.get("success")
346+
or "output" not in result
347+
):
311348
raise ValueError("Result must be successful with output")
312349

313350
output = result["output"]
@@ -325,15 +362,23 @@ def validate_image_output(
325362
}
326363
valid_mimes = format_mime_types.get(format, [f"image/{format}"])
327364
if output.get("mimeType") not in valid_mimes:
328-
raise ValueError(f"Expected format {format}, got {output.get('mimeType')}")
365+
raise ValueError(
366+
f"Expected format {format}, got {output.get('mimeType')}"
367+
)
329368
else:
330-
if not isinstance(output.get("mimeType"), str) or not output["mimeType"].startswith("image/"):
369+
if not isinstance(output.get("mimeType"), str) or not output[
370+
"mimeType"
371+
].startswith("image/"):
331372
raise ValueError("Expected image MIME type")
332373

333374
@staticmethod
334375
def validate_json_output(result: Any) -> None:
335376
"""Validates JSON extraction output"""
336-
if not isinstance(result, dict) or not result.get("success") or "output" not in result:
377+
if (
378+
not isinstance(result, dict)
379+
or not result.get("success")
380+
or "output" not in result
381+
):
337382
raise ValueError("Result must be successful with output")
338383

339384
output = result["output"]
@@ -343,7 +388,9 @@ def validate_json_output(result: Any) -> None:
343388
raise ValueError("Output data must be an object")
344389

345390
@staticmethod
346-
def validate_error_response(result: Any, expected_error_type: str | None = None) -> None:
391+
def validate_error_response(
392+
result: Any, expected_error_type: str | None = None
393+
) -> None:
347394
"""Validates error response"""
348395
if not isinstance(result, dict):
349396
raise ValueError("Result must be a dictionary")

0 commit comments

Comments
 (0)