Skip to content

PDFCLOUD-6254: Convert structured documents to PDF - #47

Open
datalogics-kam wants to merge 18 commits into
mainfrom
pdfcloud-6254-add-structured-document-conversions
Open

datalogics-kam wants to merge 18 commits into
mainfrom
pdfcloud-6254-add-structured-document-conversions

Conversation

@datalogics-kam

@datalogics-kam datalogics-kam commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

PDFCLOUD-6254

Why this change

The SDK could not convert structured Markdown, plain text, JSON, XML, or CSV documents through the documented POST /pdf capability. Callers had to construct untyped requests and could combine options that the service accepts only for particular source formats.

What changed (high level)

The synchronous and asynchronous clients now provide five format-specific helpers backed by Pydantic payload models and public structured-document types. Shared page, typography, table, color, and output settings serialize to structured_text_options, while format-specific signatures keep incompatible options separate. Markdown image mappings accept uploaded resources and internally deduplicate their IDs. A runnable example, fixtures, and generated API-guide links make the feature discoverable.

Behavior changes

Callers upload a source document and pass its PdfRestFile to convert_markdown_to_pdf, convert_plain_text_to_pdf, convert_json_to_pdf, convert_xml_to_pdf, or convert_csv_to_pdf. Local validation rejects incorrect source formats, multiple source files, unsupported Markdown image MIME types, and invalid literal or numeric options before transport. CSV delimiters and image alternative text remain verbatim; page-size names, title, language, and font names follow the API's whitespace trimming. This feature release updates version 1.1.0 to 1.2.0 without changing existing public APIs.

Validation

  • Focused unit suite: uv run pytest -n auto --maxschedchunk 2 tests/test_convert_structured_documents_to_pdf.py — 1,141 passed; 10 existing matching-format skips.
  • Non-live compatibility matrix: uvx nox -s tests -- -m 'not live' — 2,838 passed and 10 matching-format skips on each of Python 3.10–3.14. The 90% class-function coverage gates passed for all four client classes on every interpreter.
  • Focused live suite against the integration service: uv run pytest -n auto --maxschedchunk 2 tests/live/test_live_convert_structured_documents_to_pdf.py — 426 passed; four space/tab delimiter cases skipped for the service limitation described below. The live numeric matrix derives bounds from the payload schemas, varies one numeric value per request, and exercises both transports. Invalid values reach the server through extra_body. Positive page dimensions too small for the required usable area are checked as rendering rejections.
  • Ruff formatting/checks, Basedpyright, and uv run pre-commit run --all-files passed on the completed changes in a clean checkout. The full live suite and example matrix were not rerun locally because this remediation changes only structured-conversion validation and its tests.

Risks and follow-ups

This PR targets main. Four live tests remain skipped because the service currently ignores explicit space and tab CSV delimiters. These tests use genuinely whitespace-separated CSV input and an index-1 column override, so a successful PDF response alone cannot mask incorrect parsing. The service follow-up must preserve explicit single-character whitespace delimiters and verify both parsed columns and their exact values. After corrected delimiter handling is available, remove the skips from both test_live_convert_csv_to_pdf_whitespace_delimiter and test_live_async_convert_csv_to_pdf_whitespace_delimiter and require all four cases to pass with the second-column override intact. The SDK accepts and preserves valid whitespace delimiters; end-to-end parsing verification remains pending that service correction.

Workflows are unchanged. CI runs non-live tests, class-function coverage, and examples on Python 3.10–3.14; live tests run on Python 3.11 for pull requests and scheduled runs. Diff coverage and docs-preview artifacts are PR-only.

@datalogics-kam
datalogics-kam force-pushed the pdfcloud-6254-add-structured-document-conversions branch from ce87afd to 8a5b039 Compare August 29, 2026 14:57
@datalogics-kam
datalogics-kam force-pushed the pdfcloud-6243-pdf-with-added-shapes branch from aad4964 to 4ef4925 Compare August 29, 2026 14:58
@datalogics-kam
datalogics-kam changed the base branch from pdfcloud-6243-pdf-with-added-shapes to main September 8, 2026 19:33

@datalogics-kam datalogics-kam left a comment

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.

Blocking dependency: PDFCLOUD-6287 blocks this work because the client
currently reproduces the upstream structured-text contract's open-ended
strings instead of preventing invalid requests.

In this PR, PdfStructuredTextPageSetup.size is str; the five font selectors
in PdfStructuredTextStyle are str/Sequence[str]; and the internal
Pydantic models enforce only min_length=1. language is likewise only a
non-empty string even though the OpenAPI description merely says it is
"typically" BCP 47. Consequently, values such as arialbold and Executive
pass client-side validation even though they are not valid structured-text font
and page-size identifiers.

This matters because the public font page documents watermark/add-text-style
names and API tokens, while the structured-text converter uses a different
APDFL catalog. See the upstream contract work and complete discrepancy list:
https://datalogics-jira.atlassian.net/browse/PDFCLOUD-6287

Please keep this PR blocked until PDFCLOUD-6287 publishes the authoritative
OpenAPI enums and validation. Then consume those contracts here as public
Literal aliases with accepted-values documentation, validate them in the
payload models, and add exhaustive accepted-value plus representative rejection
tests for sync and async callers.

GitHub does not permit the PR author to submit a Request changes review, so this
is recorded as a review comment; PDFCLOUD-6287 is formally linked as blocking
PDFCLOUD-6254 in Jira.

@datalogics-kam
datalogics-kam marked this pull request as ready for review September 29, 2026 17:12
@datalogics-kam
datalogics-kam force-pushed the pdfcloud-6254-add-structured-document-conversions branch from 8a5b039 to 375c2e2 Compare October 7, 2026 22:31
@netlify

netlify Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for pdfrest-python ready!

Name Link
🔨 Latest commit bafcf62
🔍 Latest deploy log https://app.netlify.com/projects/pdfrest-python/deploys/6ac94fed947c950008a4b603
😎 Deploy Preview https://deploy-preview-47--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.

- Export public structured document types for page, style, table, and
  format-specific options.
- Add format-specific payload validation and nested wire serialization.
- Deduplicate uploaded Markdown images into ordered resource IDs.

Assisted-by: Codex
- Add synchronous and asynchronous helpers for Markdown, plain text,
  JSON, XML, and CSV inputs.
- Cover exact request serialization, validation boundaries, transport
  behavior, and request customization.

Assisted-by: Codex
- Exercise all five structured formats through synchronous and
  asynchronous clients.
- Verify Markdown image resources, response metadata, and server-side
  invalid-option handling against deterministic fixtures.

Assisted-by: Codex
- Add a runnable upload-first example covering Markdown, plain text,
  JSON, XML, and CSV helpers.
- Add deterministic source documents and register the example in the
  inventory.

Assisted-by: Codex
- Add all five format-specific conversion helpers to the Into PDF API
  guide.
- Link generated method signatures to their public structured option
  types.

Assisted-by: Codex
- Release the structured document conversion helpers as a minor feature
  update.
- Keep the project metadata and lockfile package version synchronized.

Assisted-by: Codex
- Explain data presentation, page orientation, image alt-text policy,
  line handling, and CSV alignment values.
- Keep value documentation on public aliases for generated API reference
  reuse.

Assisted-by: Codex
Exercise every structured-document literal through both client transports
against the live service, including shared page orientation.

Assisted-by: Codex
Verify every helper rejects every other structured source before
transport, and covers sync and async request customization timeouts.

Assisted-by: Codex

@datalogics-tsmith datalogics-tsmith 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.

Requesting changes for one confirmed API-compatibility defect and two repository-policy gaps. The CSV delimiter issue prevents callers from using a valid PDFCloud API input; the accompanying tests should cover the corrected behavior and the remaining declared constraints. Please also resolve or explicitly document the production private-import exception.

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


class _StrictStructuredTextModel(BaseModel):
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)

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] Preserve whitespace CSV delimiters. PDFCloud-API validates csv.delimiter with z.string().length(1) and does not trim it, but this shared configuration strips " " and "\t" before _StructuredTextCsvOptions.delimiter checks their length. I confirmed both values pass the API schema, and a live /pdf conversion with a space delimiter succeeded. The typed helper currently rejects that valid request before transport. Please preserve delimiter text verbatim, limit trimming to fields the API actually trims, and add sync/async unit plus live cases for whitespace delimiters. The same global stripping also makes whitespace-only image_alt_text stricter than the API, so please make that divergence intentional or align it too.

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 the SDK validation and serialization issue in 0706b7f. CSV delimiters and image alternative text are preserved verbatim; trimming is limited to title, language, and font names. Payload tests and distinct sync/async request tests cover space/tab delimiters, padded and whitespace-only alternative text, retained trimming, and invalid delimiter lengths rejected before transport.

The requested sync/async space/tab live tests are retained with an explicit skip. A stronger probe using genuinely space/tab-separated CSV input and a second-column override uncovered a separate CLU defect: CsvAdapter.ResolveDelimiter() treats whitespace delimiters as unspecified and falls back to comma. The second-column override then produces HTTP 400 because only one column was parsed. A successful PDF response alone therefore does not establish correct delimiter handling. The skip reason names that defect and directs re-enabling these cases after the CLU fix is deployed.

Validation at this commit: 394 focused unit tests passed (10 existing format-matching skips), and 52 live tests passed against http://sleipnir:3000 (four CLU-blocked cases skipped).

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.

Thanks—the SDK delimiter and image-alt-text behavior is fixed, and the stronger live probes correctly expose the separate CLU defect. This thread is not fully resolved because AGENTS.md:234-244 requires behavior that cannot be exercised live to be called out in the PR description with the reason and a follow-up plan. The current description does not mention the four skipped space/tab cases, the CLU blocker, or a tracking plan; it also has stale validation counts and base-branch information. Please update the PR description and link the concrete CLU follow-up before resolving this thread.

@datalogics-kam datalogics-kam Oct 9, 2026 •

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.

The PR description documents the current main base, validation counts, and all four skipped sync/async space/tab delimiter cases. Explicit whitespace delimiters are currently ignored by the service. The retained tests use whitespace-separated CSV input and an index-1 column override to verify that two columns are parsed.

After corrected behavior is available, verify both parsed columns and remove the skips from test_live_convert_csv_to_pdf_whitespace_delimiter and test_live_async_convert_csv_to_pdf_whitespace_delimiter. All four cases must pass with the second-column override intact.

bafcf62 contains the public-disclosure guidance in AGENTS.md and the corresponding skip-reason cleanup. The description and skip reason use observable API behavior, and the description references only the primary work item. This is separate from the page-size fix and live boundary coverage.

[
pytest.param({"page_setup": {"width": 0.1, "height": 0.1}}, id="page-min"),
pytest.param({"page_setup": {"margin": {"top": 0}}}, id="margin-min"),
pytest.param({"style": {"text_size": 6}}, id="text-size-min"),

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] Complete the required constraint-boundary matrix. TESTING_GUIDELINES.md:134-138 requires explicit tests for every ge, le, gt, lt, or Annotated constraint at the documented bound, just inside it, and outside it; the AGENTS testing rules repeat that requirement. This table still lacks text_size just-inside cases and an upper-outside value, and the suite lacks empty-value cases for column_width_weights, fallback_fonts, CSV columns, title, and language. Please add those cases so every newly declared constraint has 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 two reviewable commits: 3f8fd3e covers numeric and RGB boundaries, and 5ebce31 covers text, collections, and resource cardinality.

The matrix now includes text_size values 6, 7, 71, and 72, with 5 and 73 rejected; empty column_width_weights, fallback_fonts, CSV columns, title, and language; minimum-length successes and whitespace rejection where trimming applies; every declared numeric and RGB channel boundary; and source/image collection and image-index bounds. Public options are exercised through payload serialization and distinct sync/async client paths, with a fail-on-use transport confirming invalid inputs never reach HTTP execution. Internal image index and image ID bounds have direct payload tests.

Validation: the final focused module passed 1,138 tests with 10 existing format-matching skips. The non-live Nox matrix passed 2,835 tests with the same 10 skips on each of Python 3.10–3.14; the class-function coverage gates passed on all five versions. Ruff and Basedpyright passed for the remediation, and all applicable commit/pre-push hooks passed.

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.

The expanded unit matrix is thorough and resolves the specific unit omissions called out above. This thread remains open because the live half of the repository requirement is still absent: AGENTS.md:349-358 requires live tests to enumerate valid and invalid numeric boundaries, vary one parameter per request, and use extra_body to reach server-side rejection paths. The live structured-conversion module currently exercises representative numeric values but not the declared numeric bounds. Please add distinct sync and async live boundary cases for the newly constrained fields.

@datalogics-kam datalogics-kam Oct 9, 2026 •

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 3e09db4. This commit contains only the live numeric/RGB boundary matrix, CSV column and Markdown image-index coverage, and the two supporting fixtures. Page-size regressions are in a separate commit.

Schema-derived cases vary one value per request through distinct sync and async tests. Invalid values use extra_body to reach server validation; schema-valid page dimensions that violate usable-area requirements have explicit rejection checks.

The same final file tree previously passed 426 focused live tests, with four existing delimiter skips, and the Python 3.10–3.14 non-live matrix. After the history split, all 430 live cases collected successfully and pre-commit checks passed. The full matrix and live requests were not repeated because the file contents are unchanged.

Comment thread src/pdfrest/client.py
from .models._internal import (
BasePdfRestGraphicPayload,
BmpPdfRestPayload,
ConvertCsvToPdfPayload,

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.

[P3] Resolve the private-module import policy conflict. AGENTS.md:223-226 says production code must not import modules whose names begin with an underscore. This import block predates the PR, but these new payload imports extend that violation. Please expose the payloads through an approved non-private production module, or document a narrow exception for the client-to-payload relationship before adding more imports here.

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 5fd61fc. AGENTS.md now explicitly permits private-module and private-symbol imports within the package, including the client-to-payload relationship. External dependencies must be consumed through their supported public APIs; another company-owned package still counts as external. Package internals remain outside public exports unless deliberately promoted. This preserves the intended public/private boundary without exposing the payload models as supported SDK APIs. The commit's applicable hooks passed.

Allow imports of private modules and symbols within the package while
requiring supported public APIs for external dependencies, including
other company-owned packages. Keep internal contracts out of public
exports and permit focused internal tests when needed.

Assisted-by: Codex
Restrict whitespace trimming to titles, languages, and font names.
Preserve CSV delimiters and image alternative text verbatim, and reject
invalid delimiter lengths before executing either client transport.

Add payload and sync/async request regressions. Retain live space/tab
cases with an explicit skip until the CLU delimiter fix is deployed.

Assisted-by: Codex
Exercise accepted and rejected dimensions, margins, text sizes, heading
scales, table borders, padding, column weights, and column indices.
Check every RGB channel at its limits and reject invalid tuple lengths.

Verify exact payload serialization through distinct sync and async
request paths, with invalid inputs rejected before transport execution.

Assisted-by: Codex
Exercise minimum text lengths, whitespace rejection, and nonempty
collection bounds through payloads and both client transports.
Verify exact output and image alternative text serialization.

Reject empty or multiple sources for every structured document format
before transport execution. Cover image index and image ID list bounds
on the internal Markdown payload contract.

Assisted-by: Codex

@datalogics-tsmith datalogics-tsmith 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.

Requesting changes because the remediation is not complete: page-size whitespace normalization regressed, the constraint matrix still lacks the required live numeric-boundary coverage, and the CLU-blocked whitespace-delimiter live cases are not disclosed in the PR description with a concrete follow-up plan. I continued the two existing discussions for the partially addressed findings and opened one new inline comment for the newly introduced regression.

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


class _StructuredTextPageSetup(_StrictStructuredTextModel):
size: PdfStructuredTextPageSize | None = 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] Restore the API's page-size whitespace normalization. The remediation removed model-wide whitespace stripping, but page_setup.size was not given a field-specific normalizer. PDFCloud-API defines this field as z.string().trim().min(1): its schema converts " Letter " to "Letter", and I confirmed the live /pdf endpoint accepts that request. This SDK model now rejects it with a literal validation error. Please apply the same pre-strip used for the other API-normalized text fields and add payload, sync, and async regression cases for a padded named size.

@datalogics-kam datalogics-kam Oct 9, 2026 •

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 fa6ba23. This commit contains only the page-size whitespace fix and its payload, sync/async request, and live regressions. The field-specific pre-strip preserves the accepted named-size spellings; the public type remains a Literal.

The focused unit module was rerun after splitting the commits: 1,141 passed, with 10 existing format-matching skips. The live page-size cases previously passed, and the final file tree is identical to that validated version. Pre-commit checks passed.

Normalize padded named sizes before literal validation. Keep payload,
sync/async request, and live page-size regressions with the fix.

Assisted-by: Codex
Derive numeric and RGB cases from payload schemas and exercise accepted
and rejected values through both transports. Cover CSV column options
and image indices with deterministic table and two-image fixtures.

Assisted-by: Codex
Keep repository content and review communication suitable for public
sharing. Describe blocked delimiter coverage through observable service
behavior and limit work-item references to the primary PR item.

Assisted-by: Codex

This branch was successfully deployed

1 active deployment
ci-live — bafcf626 Deployed Oct 9, 2026 by datalogics-kam via Examples (Python 3.10) #549
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