Skip to content

PDFCLOUD-6570 Add PDF-to-PostScript conversion - #55

Open
datalogics-tsmith wants to merge 6 commits into
mainfrom
pdfcloud-6570-add-pdf-to-postscript-conversion
Open

datalogics-tsmith wants to merge 6 commits into
mainfrom
pdfcloud-6570-add-pdf-to-postscript-conversion

Conversation

@datalogics-tsmith

Copy link
Copy Markdown
Contributor

PDFCLOUD-6570

Why this change

The Python SDK did not expose pdfRest's PDF-to-PostScript operation, requiring callers to assemble requests outside the typed client. This adds a supported sync and async workflow for converting uploaded PDFs into print-oriented PostScript.

What changed (high level)

The new convert_pdf_to_postscript helpers accept uploaded PdfRestFile resources and delegate request validation and serialization to a dedicated Pydantic payload model. A public PostScriptLevel type makes the supported language levels discoverable, while the existing file-operation machinery resolves the generated resource into a typed response.

Documentation and runnable sync and async examples demonstrate the upload, conversion, download, and verification flow. The package version moves to 1.4.0 for the additive public API.

Behavior changes

Callers can now select PostScript level 2 or 3, page ranges, binary or text-safe output, scaling, rotation, shrink-to-fit behavior, printable annotations, and an optional output prefix. Omitting the output prefix leaves server-generated naming intact.

Invalid MIME types, multiple input resources, unsupported language levels, malformed page ranges, non-positive or non-finite scales, and invalid output prefixes fail locally before transport. There are no breaking changes to existing APIs.

Validation

Focused validation passed with 46 unit tests and 24 live sync/async tests against the local pdfRest service. The live suite isolates each route option and verifies concrete PostScript markers for text-safe output, language level, page selection, and the legal scale boundary.

The Python 3.10–3.14 non-live Nox matrix passed with 2,969 tests and 10 expected skips per interpreter at 95% overall coverage. The class-function coverage gate checked 165 functions and passed its 90% threshold. Ruff, configured basedpyright, strict MkDocs, package build, and diff checks also passed. The final rebase was patch-identical by git range-diff, followed by another focused unit and live run.

The pre-commit file checks passed except that its basedpyright subprocess could not discover installed dependencies in the sandbox; an equivalent full configured basedpyright run completed with zero errors or warnings.

Risks and follow-ups

The public API is additive and uses the existing request and response infrastructure, so migration risk is low. Live assertions use deterministic checked-in PDFs; rotation, shrink-to-fit, and annotation toggles are isolated and exercised through both transports, while binary-output and scale effects have direct output assertions. CI should provide the final clean-environment verification of the pre-commit hook and interpreter matrix.

- Export the supported PostScript language levels for client callers.
- Validate uploaded PDFs, page ranges, scale values, and output names.
- Serialize the payload into the fields expected by the service.

Assisted-by: Codex
- Expose sync and async helpers with typed conversion options.
- Cover request serialization, validation, responses, and timeouts.
- Bump the feature release version to 1.4.0.

Assisted-by: Codex
- Exercise both language levels through sync and async clients.
- Assert output metadata, page count, and language-level markers.
- Confirm the service rejects invalid levels, ranges, and scales.

Assisted-by: Codex
- Add runnable sync and async examples for PostScript levels 3 and 2.
- Verify generated headers, language levels, page counts, and metadata.
- Link the route and examples from the public documentation indexes.

Assisted-by: Codex
- Verify default output serialization through both client transports.
- Isolate live options and assert observable PostScript markers.
- Derive live scale boundary cases from the payload schema.

Assisted-by: Codex
@netlify

netlify Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for pdfrest-python ready!

Name Link
🔨 Latest commit b92ea91
🔍 Latest deploy log https://app.netlify.com/projects/pdfrest-python/deploys/6ad7c10f0fef652f907ce2c7
😎 Deploy Preview https://deploy-preview-55--pdfrest-python.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@datalogics-kam datalogics-kam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focused review by Codex using the repository's pr-review-auditor guidance. Scope: the new PostScript page-range validation and opportunities to reuse existing Pydantic compositions.

Please replace the duplicated page-range parser/checks with a composition of the existing constrained range types and the smallest endpoint-specific adaptations. This follows AGENTS.md's requirement to favor declarative Pydantic validation and keep custom validators shape-focused.

Validation at 1efad81: the focused PostScript and shared graphic-payload suites passed (146 tests, Python 3.11); the committed diff passed git diff --check. A separate composition probe accepted and serialized the documented selections all, 1, 1,3-5, last, and 14-last. Live tests and the full interpreter matrix were not run for this focused validation-design review.

Comment thread src/pdfrest/models/_internal.py Outdated
)


def _validate_postscript_page_range(value: str) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Compose page-range validation from the existing Pydantic types

This introduces another regex/parser and manual range checks even though PageNumber, _AscendingPageRangeTuple, and AscendingPageRange already express positive page numbers, valid endpoint combinations, parsing, and ascending-order validation. The documented page selections can reuse those types with the existing comma-list parser/serializer and an all alternative. AGENTS.md explicitly prefers declarative Pydantic composition and native constraints over bespoke normalization/checking code.

Please build the payload field from those reusable Annotated aliases, unions, and constrained collections, following _AscendingPageRangeTuple: let Pydantic validate each endpoint, use a short BeforeValidator only for input adaptation, and retain an AfterValidator only for relations native constraints cannot express. Add the verified PostScript-specific syntax through narrow adapters or declarative constraints; preserve its accepted syntax and wire behavior without duplicating the shared rules or broadening other endpoints. Cover the composed payload and local rejection through both clients, including the endpoint-specific differences.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b92ea91. The PostScript field now parses into a list of the existing AscendingPageRange union, which composes PageNumber and _AscendingPageRangeTuple. The PostScript-specific adapter is limited to splitting/serializing the comma-delimited wire value and preserving the established user-facing validation message.

@datalogics-kam datalogics-kam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review by Codex using the repository's pr-review-auditor guidance, against 1efad816aab02cd62f5936cabbb3ac56c1013253.

Please address the two inline findings: the unsupported all,1 success case and missing assertions for the effects of rotation, shrink-to-fit, and annotation controls. The page-range finding is an acceptance criterion for the declarative composition already requested in the existing review; it does not prescribe retaining or modifying the separate regex/parser.

Validation on this unchanged head: the earlier review run passed 46 focused unit tests and 24 live tests on Python 3.11, plus Ruff, basedpyright, and diff checks. A live probe nevertheless showed that all,1 passes local validation and receives HTTP 400. The additional composition probe confirms that AscendingPageRange excludes all, while a standalone all alternative outside the collection can preserve that keyword and reject a mixed all selection. No source files were changed. The full local interpreter/example matrices were not rerun; current-head CI passed those checks.

Comment thread tests/test_convert_pdf_to_postscript.py Outdated
pytest.param("1-3", id="ascending-range"),
pytest.param("6-last", id="range-to-last"),
pytest.param("1, 3-5, 12, 14-last", id="mixed-selection"),
pytest.param("all,1", id="all-with-selection"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Reject mixed all selections instead of certifying them as supported

This case passes the current payload model, but a live /postscript request with page_range="all,1" returns HTTP 400. Change it to a rejection case and verify that both clients fail before transport, consistent with AGENTS.md:122-127.

This complements the existing request to reuse the constrained page-range types: treat standalone all as an alternative to the collection of page selections, rather than a permitted collection member. AscendingPageRange itself excludes all; the surrounding composition can enforce this distinction without another parser. Preserve verified valid syntax and serialization while adding this regression coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b92ea91. all is now a standalone union branch, so all,1 is rejected locally. The payload suite covers the rejection, and the sync and async client tests both verify that this input short-circuits before transport.

3,
1,
)
if expected_marker is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Assert the effects of rotation, fitting, and annotation options

The rotate=True, shrink_to_fit=True, and print_annotations=False cases all supply expected_marker=None, so this branch skips the option-specific assertion. Both sync and async tests then check only generic metadata, the header, language level, and page count. Default output would satisfy those checks even if the service ignored the requested flags.

Use inputs where each flag changes the result and assert that effect through both transports: for example, printable annotations for the annotation toggle and suitable page geometry/scaling for rotation and fitting. This follows TESTING_GUIDELINES.md:232-235, which requires validating that the server honors optional arguments.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in b92ea91.

  • print_annotations now has distinct sync and async live tests using a deterministic PDF with a printable annotation. Each test requests both values and verifies that the normalized PostScript output changes.
  • I investigated rotate and shrink_to_fit directly against the current production service at api.pdfrest.com using five varied PDFs. The service accepts both options, but the tested outputs retain the same bounding boxes and rasterize identically with each option enabled or disabled. The previous smoke cases therefore could not prove those options worked and have been removed.
  • The production API defect is tracked in PDFCLOUD-6597. Live effect assertions for these two options can be restored after the service honors them.

@datalogics-kam

Copy link
Copy Markdown
Contributor

I had Codex update the skill, see #56

- Compose PostScript page ranges from shared ascending selection types and
  reject combinations that mix all with explicit selections.
- Replace annotation option smoke coverage with sync and async assertions
  against a deterministic printable annotation fixture.

Assisted-by: Codex

This branch was successfully deployed

1 active deployment
ci-live — b92ea915 Deployed Oct 20, 2026 by datalogics-tsmith via Live Tests (Python 3.11) #580
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants