Skip to content

PDFCLOUD-6571 Add ZUGFeRD PDF creation - #57

Merged
datalogics-kam merged 15 commits into
mainfrom
pdfcloud-6571-add-zugferd-pdf-creation
Nov 11, 2026
Merged

datalogics-kam merged 15 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 f8efb8f
🔍 Latest deploy log https://app.netlify.com/projects/pdfrest-python/deploys/6af4989e04e713ce4a7cdc4a
😎 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
- Pair synchronous and asynchronous checks for render options and bounds.
- Assert request routes, serialized payloads, and concrete responses.
- Cover open font names and resource cardinality failures.

Assisted-by: Codex
- Derive live literal catalogs from the public SDK types.
- Verify requested fonts, isolated visual controls, and replacement output.
- Add a licensed two-item invoice fixture for alternate-row evidence.

Assisted-by: Codex
- Extend compatibility checks to runtime and static type contracts.
- Require unchanged unrelated tests and self-contained literal docs.
- Add explicit base comparisons and rendered-reference inspection.

Assisted-by: Codex
@datalogics-tsmith

Copy link
Copy Markdown
Contributor Author

Addressed the compatibility and API-skill review on 6fdaeed in e43a87e and b6fac14.

  • e43a87e restores PdfStructuredTextKnownFont to the original flat Literal[...] runtime shape, restores the existing structured-document parametrization to get_args(PdfStructuredTextKnownFont), and adds a focused regression for get_origin(), get_args(), and the dependent PdfStructuredTextFontName union structure.
  • The same commit makes the PdfStructuredTextKnownFont and PdfZugferdKnownFont generated documentation self-contained with every accepted spelling and meaning. I inspected the rendered API-reference HTML after a successful strict MkDocs build to confirm both complete lists render at their own entries.
  • ZUGFeRD-specific Webdings rejection remains local to the ZUGFeRD payload validation.
  • b6fac14 updates the endpoint-creation skill to require runtime and static compatibility for existing public exports, unchanged unrelated tests, self-contained literal documentation, base-branch comparisons, and rendered-reference inspection.

Validation included the unchanged structured-document tests, the full Python 3.10-3.14 non-live Nox matrix, basedpyright, Ruff format/check, strict MkDocs, client-class coverage gates, package build, and the focused ZUGFeRD live module.

@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

Changes requested on f6689bc42022d52b773a688a7f9c703d95a35039. Three remaining coverage findings are attached to the current changed code in this new review:

  • P2 — Live rendering controls: cover the remaining four color controls and date format with isolated sync/async result assertions, plus a rejected language case.
  • P2 — Supplied-PDF unit parity: add sync regeneration and async preservation success assertions. Forcing sync regeneration off still passes all 462 focused unit tests.
  • P3 — Interior precision boundaries: add legal precision values 1 and 3. Rejecting precision 1 still passes all 462 focused unit tests.

These are outstanding regression-protection gaps. No production correctness or CI regression was reproduced. The earlier compatibility, restored unrelated tests, self-contained literal documentation, API-skill corrections, helper split, regeneration guard, language validation, and version bump are verified addressed. The fresh inline threads in this review identify the remaining work; the older coverage discussion is historical context.

Validation of this committed head: full non-live Nox matrix passed on Python 3.10–3.14 with 3,432 passed and 10 expected skips each; all four client-class function coverage gates passed across the matrix; 68 focused live cases passed on Python 3.11; both changed examples passed on Python 3.10. Ruff formatting/lint, basedpyright, runtime/static public-contract comparisons, strict MkDocs and rendered literal-list inspection, wheel/sdist builds, and whitespace checks passed. The two passing mutation probes expose test gaps; both targets were restored byte-for-byte.

The complete service-wide live suite, full local example matrix, and mutating local pre-commit hooks were not repeated. The focused endpoint/examples ran and exact-head CI is green. No project source or skill files were changed or pushed.

Comment thread tests/test_create_zugferd_pdf.py Resolved
Comment thread tests/test_create_zugferd_pdf.py Resolved
- Pair preservation and regeneration success paths across both clients.
- Exercise precision boundary interiors through payload and client tests.
- Verify remaining render controls and language rejection with live calls.

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

Changes requested on f8efb8fabd942cabafd33ce3a1ee037c75fec63f for the remaining live enum-behavior assertion gap attached below.

The three findings from the preceding requested review are addressed. This additional policy check found that the live enum tests verify accepted requests and PDF metadata without establishing the requested logo position or localized label/currency/numeric/precision result. A focused live mutation that removed those requested settings still passed all 10 selected sync/async cases.

My earlier recommendation to approve was incorrect under this repository's required review policy. I had classified the missing semantic assertions as an optional follow-up and marked the corresponding rules pass using evidence for different rendering controls. TESTING_GUIDELINES.md:232–235 explicitly requires validating that the server honors these optional arguments, and AGENTS.md:241–251 makes server-observable live coverage a release gate. This new review makes the remaining work visible; it does not revise older review comments.

Required change: add paired semantic output assertions that distinguish each requested enum setting from defaults, and show that ignoring those settings causes the relevant cases to fail. The inline finding lists the affected options, exact evidence and policy citations.

Validation on this same head remains: 3,440 non-live tests passed with 10 expected skips on each Python 3.10–3.14 interpreter; all four client-class coverage gates passed; 84 unmodified focused live cases passed; Ruff/type checks and exact-head CI are green. Additional hermetic and differential checks passed (44 and 1,613 respectively). Those successful checks do not close this policy gap: the new deliberate enum-dropping mutation also passed 10 live cases. No production defect was reproduced, and no tracked source file was edited or pushed.

@datalogics-kam
datalogics-kam dismissed their stale review November 11, 2026 22:49

Withdrawing this finding: the paired SDK MockTransport tests already assert these serialized options. The live-only mutation did not demonstrate missing SDK interface coverage; verifying pdfRest renderer internals is outside this SDK PR.

@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

Approved on f8efb8fabd942cabafd33ce3a1ee037c75fec63f.

The requested SDK changes are addressed: focused XML and supplied-PDF helpers, explicit regeneration permission, language normalization and validation surfaces, preserved existing public type contracts and self-contained literal documentation, and paired sync/async request and boundary coverage. The final update completes supplied-PDF success parity and interior precision cases. All review threads are resolved.

This approval covers the SDK interface: input validation, normalization, exact request serialization, response/error handling, both transports, and live interoperability. Existing MockTransport tests detect dropped render-option values; additional assertions about pdfRest's rendering implementation are not required to approve this SDK change. No remaining actionable SDK finding was identified.

Validation on this head: the full non-live Python 3.10–3.14 Nox matrix passed with 3,440 tests passed and 10 expected skips per interpreter; all four client-class function coverage gates passed across the matrix; 84 focused live cases passed on Python 3.11; Ruff formatting/lint and basedpyright passed. Additional interface and compatibility probes passed, and deliberate SDK option-dropping mutations were detected by the existing unit tests.

Exact-head CI is green, including the complete test/example matrices, live job, docs, and pre-commit. The full service-wide live suite and full example matrix were not repeated locally; focused live validation and the exact-head CI provide that coverage boundary. No source changes were made during review.

@datalogics-kam
datalogics-kam merged commit e635461 into main Nov 11, 2026
19 checks passed
@datalogics-kam
datalogics-kam deleted the pdfcloud-6571-add-zugferd-pdf-creation branch November 11, 2026 22:55

This branch was previously deployed

1 inactive deployment
ci-live — f8efb8fa Deployed Nov 11, 2026 by datalogics-tsmith via Examples (Python 3.14) #618
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