Skip to content

PDFCLOUD-6571 Add ZUGFeRD PDF creation - #57

Open
datalogics-tsmith wants to merge 9 commits into
mainfrom
pdfcloud-6571-add-zugferd-pdf-creation
Open

datalogics-tsmith wants to merge 9 commits into
mainfrom
pdfcloud-6571-add-zugferd-pdf-creation

Conversation

@datalogics-tsmith

@datalogics-tsmith datalogics-tsmith commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

PDFCLOUD-6571

Dependency update: #55 has merged, and this branch is now updated with current main. The former stack dependency is satisfied.

Why this change

The Python SDK does not expose pdfRest's ZUGFeRD PDF operation, so callers cannot use the typed clients to turn CII invoice XML into a validated ZUGFeRD or Factur-X PDF/A-3 invoice or safely combine that XML with an existing visual PDF. This adds both workflows to the synchronous and asynchronous clients while making the replacement risk explicit.

What changed (high level)

The public API is split by caller intent. convert_invoice_xml_to_zugferd_pdf always renders a PDF from uploaded CII XML and accepts appearance controls. convert_pdf_to_zugferd_pdf starts from an uploaded PDF, preserves it by default, and requires allow_regeneration=True before replacement-only logo or rendering options are accepted.

Dedicated Pydantic payload models own resource validation and wire serialization. ZUGFeRD label languages use constrained strings plus canonicalization for English, German, and French aliases, and the structured-document and ZUGFeRD APIs now share one readable-font catalog while retaining their existing public aliases.

The package version advances to 1.5.0. Runnable examples and API guidance cover each workflow separately.

Behavior changes

Callers choose XML rendering or supplied-PDF preservation before making the request, so invalid option combinations are rejected locally instead of being exposed through one mode-dependent signature. The supplied-PDF workflow rejects logo and rendering controls unless regeneration is explicitly permitted. If regeneration is not permitted and visual consistency cannot be confirmed, the service error is surfaced.

Supported two-letter, three-letter, and regional label-language forms are canonicalized to en, de, or fr; explicit None, unsupported languages, malformed tags, unsupported symbol fonts, invalid MIME types, multiple single-resource inputs, malformed colors, and unsafe output prefixes fail as Pydantic validation errors before transport.

These changes replace the provisional create_zugferd_pdf helper introduced only within this unmerged PR; no released SDK API is removed.

Validation

The complete non-live suite passed on Python 3.10 through 3.14 with 3,308 tests passed, 10 expected skips, and 96% overall coverage on each interpreter. The client class-function coverage gate passed at its 90% threshold.

The endpoint-specific live suite exercised all closed render-option values, server-side invalid boundaries, visual rendering changes, matching-PDF preservation, mismatch rejection, explicitly permitted replacement, response metadata, and warnings through both sync and async clients. All 56 live cases were verified across the full run and focused correction rerun. Both standalone examples completed against the live service.

Ruff formatting and linting, basedpyright, the complete pre-commit suite, strict MkDocs build, and wheel/sdist build all passed. Pre-commit and package build were rerun after merging current main. The full repository-wide live suite was not run; live validation was scoped to this endpoint.

Risks and follow-ups

The former #55 stack dependency is resolved and the branch includes current main. Font availability remains deployment-specific; published names are discoverable suggestions while installed text-capable font names remain accepted. Main-repository CI provides the final clean-environment verification.

@netlify

netlify Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for pdfrest-python ready!

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

- Add typed rendering options for invoice appearance and localization.
- Validate uploaded resources, color channels, fonts, and regeneration.
- Serialize the public input shape into the pdfRest wire contract.

Assisted-by: Codex
- Add synchronous and asynchronous helpers for uploaded invoice XML.
- Support optional PDFs, logos, rendering controls, and request options.
- Cover serialization, validation, response mapping, and both transports.

Assisted-by: Codex
- Exercise every closed rendering option through both client transports.
- Verify regenerated PDFs, response metadata, and server-side rejection.
- Add a deterministic minimum-profile invoice fixture.

Assisted-by: Codex
- Add a standalone typed example with a deterministic invoice fixture.
- Verify the generated PDF and print useful response metadata.
- Link the helper from the API guide and example inventories.

Assisted-by: Codex

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

PDFCLOUD-6571

Requesting changes on 7d951b6291e4dad1395286f1ea5b20bfa2833f89. Please split XML generation from supplied-PDF conversion before publishing the API. Two helpers are preferred:

  • convert_invoice_xml_to_zugferd_pdf(invoice, ...)
  • convert_pdf_to_zugferd_pdf(pdf, *, invoice, allow_regeneration=False, ...)

The supplied-PDF helper should reject logo/render options when regeneration permission is disabled, and explain that those options apply only if replacement actually occurs. A three-helper alternative with preservation-only conversion plus convert_pdf_to_zugferd_pdf_allowing_regeneration is acceptable. The existing naming/design thread now records the complete proposal, shared request/payload requirements, and the still-open convention discussion. This is the requested design direction; the guidelines' coherent-contract exception supports combining preservation and permitted replacement in the required-PDF helper.

The other findings are covered without duplicate threads:

  • Null-language exception and accepted aliases: the existing language-validation thread now includes the reproduced sync/async label_language=None AttributeError, the required validation-error behavior, and the previously reported alias/canonicalization fix.
  • Unit validation/MIME/boundary coverage: the existing coverage thread now specifies the missing paired resource guards, supported MIME variants, optional serialization, response assertions and request counts.
  • Live contract coverage: that same coverage thread now includes isolated options, invalid catalogs/bounds, observable font/render assertions, preservation, mismatch rejection and permitted replacement. Existing positive live literals pass; successful conversion alone does not establish every rendering behavior.
  • Prefer built-in string constraints: one new, lower-priority cleanup thread requests constrained internal string aliases and small semantic validators, including the verified Pydantic union-ordering caveat. Existing color/resource adaptation remains appropriate custom logic.

The earlier required minor-version bump and shared published-font catalog comments remain applicable and were left unchanged. Apply the final API consistently to both transports, examples, tests and documentation. Preserve compatibility for any names already released.

Validation during this review on the unchanged HEAD: the non-live Nox matrix passed on Python 3.10–3.14 with 3,231 passes and 10 expected skips per interpreter; all 12 focused ZUGFeRD live tests passed on Python 3.11; the new example passed on Python 3.10; all four client-class function gates passed across the five interpreters. Read-only Ruff formatting, changed-file Ruff lint, basedpyright, strict docs and diff whitespace checks passed. Public MockTransport probes reproduced the null-language exception through both clients. Repository-wide Ruff encounters 39 errors in an existing untracked local notebook outside this PR.

The full service-wide live suite, full local example matrix and mutating local pre-commit hooks were not repeated; the new live module/example and relevant read-only checks were exercised, and recorded PR CI on this HEAD is successful for the full test/example matrices, live job and pre-commit. This update edits the existing changes-requested review and relevant threads rather than submitting another overlapping review. No source files were edited.

Comment thread src/pdfrest/models/_internal.py Resolved
Comment thread src/pdfrest/client.py Resolved
Comment thread tests/test_create_zugferd_pdf.py Resolved
Comment thread src/pdfrest/types/public.py Resolved
Comment thread src/pdfrest/client.py Resolved
Comment thread src/pdfrest/models/_internal.py Resolved
- Separate XML rendering from supplied-PDF preservation and require
  explicit permission for replacement-only options.
- Canonicalize supported label-language aliases and share the readable
  font catalog across conversion APIs.
- Cover sync and async serialization, validation, response handling, and
  request customization.

Assisted-by: Codex
- Exercise every closed render-option value through sync and async clients.
- Verify visual preservation, observable rendering changes, mismatch
  rejection, and explicitly permitted replacement.
- Send invalid option boundaries to the service and assert response metadata
  and warnings.

Assisted-by: Codex
- Rename the XML generation example:
  create_zugferd_pdf/ -> convert_invoice_xml_to_zugferd_pdf/
- Add a supplied-PDF example that makes regeneration permission explicit.
- Explain preservation and replacement behavior in the API guidance.

Assisted-by: Codex
- Record the new public ZUGFeRD conversion helpers as a minor release.
- Keep the locked editable package metadata synchronized.

Assisted-by: Codex
@datalogics-tsmith

Copy link
Copy Markdown
Contributor Author

Reopening immediately to trigger GitHub Actions for the updated head commit; the review fixes are pushed and all review threads are resolved.

- Incorporate the Apache-2.0 licensing and contributor documentation from
  the 1.4.1 release preparation.
- Retain version 1.5.0 for the new public conversion workflows and keep
  lock metadata synchronized.

Assisted-by: Codex

This branch was successfully deployed

1 active deployment
ci-live — 6fdaeed8 Deployed Nov 2, 2026 by datalogics-tsmith via Examples (Python 3.11) #601
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