Skip to content

Commit e0facf5

Browse files
Fix add_page positioning logic and validation (#13)
* Fix add_page positioning logic and validation BREAKING CHANGE: Correct insert_after_page parameter behavior: - insert_after_page=-1 now inserts at END of document (not beginning) - insert_after_page=0 inserts after first page - insert_after_page=N inserts after page N - Negative values < -1 are now rejected with ValueError Changes: - Update method documentation and examples - Add validation for invalid negative positions - Fix logic for -1 (end insertion) vs specific page positions - Add comprehensive test for invalid position errors - Rename test methods to reflect correct behavior 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Implement correct insertion index logic for add_page method BREAKING CHANGE: Redesign add_page to use proper insertion index semantics: Parameter changes: - insert_after_page → insert_index (renamed parameter) - insert_index=0: Insert before first page (at beginning) - insert_index=1: Insert before second page (after first page) - insert_index=-1: Insert after last page (at end) Implementation changes: - Completely rewrite positioning logic for insertion index semantics - Update method documentation and examples - Rename and update all test cases to match new behavior - Add validation for invalid negative positions (< -1) - Fix API parts construction for correct page insertion 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Fix line length in test_client.py after rebase Breaking up long json_data parameter in mock assertion call to meet line length requirements. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 4abd0e1 commit e0facf5

File tree

3 files changed

+70
-57
lines changed

3 files changed

+70
-57
lines changed

src/nutrient_dws/api/direct.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -609,22 +609,22 @@ def merge_pdfs(
609609
def add_page(
610610
self,
611611
input_file: FileInput,
612-
insert_after_page: int,
612+
insert_index: int,
613613
page_count: int = 1,
614614
page_size: str = "A4",
615615
orientation: str = "portrait",
616616
output_path: str | None = None,
617617
) -> bytes | None:
618618
"""Add blank pages to a PDF document.
619619
620-
Inserts blank pages at the specified position in the document.
621-
The new pages will be inserted after the specified page index.
620+
Inserts blank pages at the specified insertion index in the document.
622621
623622
Args:
624623
input_file: Input PDF file.
625-
insert_after_page: Page index to insert after (0-based).
626-
Use -1 to insert at the beginning.
627-
For end insertion, use the last page index (page count - 1).
624+
insert_index: Position to insert pages (0-based insertion index).
625+
0 = insert before first page (at beginning)
626+
1 = insert before second page (after first page)
627+
-1 = insert after last page (at end)
628628
page_count: Number of blank pages to add (default: 1).
629629
page_size: Page size for new pages. Common values: "A4", "Letter",
630630
"Legal", "A3", "A5" (default: "A4").
@@ -638,25 +638,26 @@ def add_page(
638638
Raises:
639639
AuthenticationError: If API key is missing or invalid.
640640
APIError: For other API errors.
641-
ValueError: If page_count is less than 1.
641+
ValueError: If page_count is less than 1 or if insert_index is
642+
a negative number other than -1.
642643
643644
Examples:
644-
# Add a single blank page after page 2
645-
result = client.add_page("document.pdf", insert_after_page=2)
645+
# Add a single blank page at the beginning
646+
result = client.add_page("document.pdf", insert_index=0)
646647
647-
# Add multiple pages at the beginning
648+
# Add multiple pages at the end
648649
result = client.add_page(
649650
"document.pdf",
650-
insert_after_page=-1, # Insert at beginning
651+
insert_index=-1, # Insert at end
651652
page_count=3,
652653
page_size="Letter",
653654
orientation="landscape"
654655
)
655656
656-
# Add pages at the end and save to file
657+
# Add pages before third page and save to file
657658
client.add_page(
658659
"document.pdf",
659-
insert_after_page=5, # Insert after last page (for 6-page doc)
660+
insert_index=2, # Insert before third page
660661
page_count=2,
661662
output_path="with_blank_pages.pdf"
662663
)
@@ -666,6 +667,8 @@ def add_page(
666667
# Validate inputs
667668
if page_count < 1:
668669
raise ValueError("page_count must be at least 1")
670+
if insert_index < -1:
671+
raise ValueError("insert_index must be -1 (for end) or a non-negative insertion index")
669672

670673
# Prepare file for upload
671674
file_field, file_data = prepare_file_for_upload(input_file, "file")
@@ -674,37 +677,34 @@ def add_page(
674677
# Build parts array
675678
parts: list[dict[str, Any]] = []
676679

677-
if insert_after_page == -1:
680+
# Create new page part
681+
new_page_part = {
682+
"page": "new",
683+
"pageCount": page_count,
684+
"layout": {
685+
"size": page_size,
686+
"orientation": orientation,
687+
},
688+
}
689+
690+
if insert_index == -1:
691+
# Insert at end: add all original pages first, then new pages
692+
parts.append({"file": "file"})
693+
parts.append(new_page_part)
694+
elif insert_index == 0:
678695
# Insert at beginning: add new pages first, then all original pages
679-
new_page_part = {
680-
"page": "new",
681-
"pageCount": page_count,
682-
"layout": {
683-
"size": page_size,
684-
"orientation": orientation,
685-
},
686-
}
687696
parts.append(new_page_part)
688697
parts.append({"file": "file"})
689698
else:
690-
# Insert after a specific page:
691-
# First add pages from start to insertion point (inclusive)
692-
parts.append({"file": "file", "pages": {"start": 0, "end": insert_after_page + 1}})
699+
# Insert at specific position: split original document
700+
# Add pages from start up to insertion point (0 to insert_index-1)
701+
parts.append({"file": "file", "pages": {"start": 0, "end": insert_index}})
693702

694703
# Add new blank pages
695-
new_page_part = {
696-
"page": "new",
697-
"pageCount": page_count,
698-
"layout": {
699-
"size": page_size,
700-
"orientation": orientation,
701-
},
702-
}
703704
parts.append(new_page_part)
704705

705-
# Add remaining pages after insertion point (if any)
706-
# Only add this part if there are pages after the insertion point
707-
parts.append({"file": "file", "pages": {"start": insert_after_page + 1}})
706+
# Add remaining pages from insertion point to end
707+
parts.append({"file": "file", "pages": {"start": insert_index}})
708708

709709
# Build instructions for adding pages
710710
instructions = {"parts": parts, "actions": []}

tests/integration/test_live_api.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,10 @@ def test_convert_to_pdf_from_pdf_passthrough(self, client, sample_pdf_path):
386386
# Verify result is a valid PDF
387387
assert_is_pdf(result)
388388

389-
def test_add_page_basic(self, client, sample_pdf_path):
390-
"""Test add_page method with basic page addition."""
391-
# Test adding a single blank page after first page
392-
result = client.add_page(sample_pdf_path, insert_after_page=0)
389+
def test_add_page_at_beginning(self, client, sample_pdf_path):
390+
"""Test add_page method inserting at the beginning."""
391+
# Test inserting at beginning (insert_index=0)
392+
result = client.add_page(sample_pdf_path, insert_index=0)
393393

394394
assert isinstance(result, bytes)
395395
assert len(result) > 0
@@ -399,30 +399,30 @@ def test_add_page_basic(self, client, sample_pdf_path):
399399

400400
def test_add_page_multiple_pages(self, client, sample_pdf_path):
401401
"""Test add_page method with multiple pages."""
402-
# Test adding multiple blank pages
403-
result = client.add_page(sample_pdf_path, insert_after_page=1, page_count=3)
402+
# Test adding multiple blank pages before second page
403+
result = client.add_page(sample_pdf_path, insert_index=1, page_count=3)
404404

405405
assert isinstance(result, bytes)
406406
assert len(result) > 0
407407

408408
# Verify result is a valid PDF
409409
assert_is_pdf(result)
410410

411-
def test_add_page_at_beginning(self, client, sample_pdf_path):
412-
"""Test add_page method inserting at the beginning."""
413-
# Test inserting at beginning using -1
414-
result = client.add_page(sample_pdf_path, insert_after_page=-1, page_count=2)
411+
def test_add_page_at_end(self, client, sample_pdf_path):
412+
"""Test add_page method inserting at the end."""
413+
# Test inserting at end using -1
414+
result = client.add_page(sample_pdf_path, insert_index=-1, page_count=2)
415415

416416
assert isinstance(result, bytes)
417417
assert len(result) > 0
418418

419419
# Verify result is a valid PDF
420420
assert_is_pdf(result)
421421

422-
def test_add_page_at_end(self, client, sample_pdf_path):
423-
"""Test add_page method inserting at the end."""
424-
# Test inserting at end (sample PDF has 6 pages, so insert after page 4)
425-
result = client.add_page(sample_pdf_path, insert_after_page=4, page_count=1)
422+
def test_add_page_before_specific_page(self, client, sample_pdf_path):
423+
"""Test add_page method inserting before a specific page."""
424+
# Test inserting before page 3 (insert_index=2)
425+
result = client.add_page(sample_pdf_path, insert_index=2, page_count=1)
426426

427427
assert isinstance(result, bytes)
428428
assert len(result) > 0
@@ -432,10 +432,10 @@ def test_add_page_at_end(self, client, sample_pdf_path):
432432

433433
def test_add_page_custom_size_orientation(self, client, sample_pdf_path):
434434
"""Test add_page method with custom page size and orientation."""
435-
# Test adding Letter-sized landscape pages
435+
# Test adding Letter-sized landscape pages at beginning
436436
result = client.add_page(
437437
sample_pdf_path,
438-
insert_after_page=0,
438+
insert_index=0,
439439
page_size="Letter",
440440
orientation="landscape",
441441
page_count=2,
@@ -453,7 +453,7 @@ def test_add_page_with_output_file(self, client, sample_pdf_path, tmp_path):
453453

454454
# Test adding pages and saving to file
455455
result = client.add_page(
456-
sample_pdf_path, insert_after_page=1, page_count=2, output_path=output_path
456+
sample_pdf_path, insert_index=1, page_count=2, output_path=output_path
457457
)
458458

459459
# Should return None when saving to file
@@ -470,7 +470,7 @@ def test_add_page_different_page_sizes(self, client, sample_pdf_path):
470470
page_sizes = ["A4", "Letter", "Legal", "A3", "A5"]
471471

472472
for page_size in page_sizes:
473-
result = client.add_page(sample_pdf_path, insert_after_page=0, page_size=page_size)
473+
result = client.add_page(sample_pdf_path, insert_index=0, page_size=page_size)
474474

475475
assert isinstance(result, bytes)
476476
assert len(result) > 0
@@ -480,8 +480,17 @@ def test_add_page_invalid_page_count_error(self, client, sample_pdf_path):
480480
"""Test add_page method with invalid page_count raises error."""
481481
# Test zero page count
482482
with pytest.raises(ValueError, match="page_count must be at least 1"):
483-
client.add_page(sample_pdf_path, insert_after_page=0, page_count=0)
483+
client.add_page(sample_pdf_path, insert_index=0, page_count=0)
484484

485485
# Test negative page count
486486
with pytest.raises(ValueError, match="page_count must be at least 1"):
487-
client.add_page(sample_pdf_path, insert_after_page=0, page_count=-1)
487+
client.add_page(sample_pdf_path, insert_index=0, page_count=-1)
488+
489+
def test_add_page_invalid_position_error(self, client, sample_pdf_path):
490+
"""Test add_page method with invalid insert_index raises error."""
491+
# Test invalid negative position (anything below -1)
492+
with pytest.raises(ValueError, match="insert_index must be -1"):
493+
client.add_page(sample_pdf_path, insert_index=-2, page_count=1)
494+
495+
with pytest.raises(ValueError, match="insert_index must be -1"):
496+
client.add_page(sample_pdf_path, insert_index=-5, page_count=1)

tests/unit/test_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,11 @@ def test_set_page_label_valid_config():
169169
mock_http_client.post.assert_called_once_with(
170170
"/build",
171171
files={"file": ("filename.pdf", b"mock_file_data", "application/pdf")},
172-
json_data={"parts": [{"file": "file"}], "actions": [], "output": {"labels": expected_normalized_labels}},
172+
json_data={
173+
"parts": [{"file": "file"}],
174+
"actions": [],
175+
"output": {"labels": expected_normalized_labels},
176+
},
173177
)
174178

175179
# Verify result

0 commit comments

Comments
 (0)