Skip to main content

eFacturePy — ads-facturx

Génération, validation et assemblage de factures électroniques Factur-X / EN 16931 en Python.

Python Version Build Status


🧭 Sommaire


📖 À propos

eFacturePy est le dépôt de travail du package Python ads-facturx, un outil qui permet de :

  • produire un XML Cross Industry Invoice conforme au profil urn:cen.eu:en16931:2017 ;
  • générer le PDF/A-3 associé à l’aide de ReportLab ;
  • embarquer ce XML dans le PDF pour obtenir une Factur-X valide (via factur-x) ;
  • valider la facture via XSD (EN16931) et Schematron (moteur SaxonC-HE).

✨ Fonctionnalités

  • ✅ Génération d’un XML Factur-X / EN 16931 à partir d’un dict métier
  • ✅ Deux profils : EU (EN16931) et FR (flux B2B français, BR-FR)
  • ✅ Génération d’un PDF de facture (template par défaut + logo personnalisable)
  • ✅ Fusion PDF + XML → fichier PDF/A-3 conforme
  • ✅ Validation XSD, Schematron (SaxonC) et PDF/A-3 (veraPDF)
  • ✅ Modèles Pydantic v2 pour typer et valider les données métier
  • ✅ Polices et profil ICC embarqués — rien à installer à côté

🏗️ Architecture du dépôt

eFacturePy/
├─ ads_facturx/              # Package distribué sur PyPI
│  ├─ Builders/
│  │  ├─ xml/                # Sérialisation CII (mapper, policy, profils)
│  │  └─ pdf/                # Composants, styles, rendu, polices, ICC
│  ├─ Validators/            # XSD, Schematron (XSLT EN16931-CII), veraPDF
│  └─ models/                # Schémas Pydantic (DevisData, Invoice, Profile…)
├─ tests/                    # Tests unitaires pytest
├─ docs/                     # Documentation + exemples exécutables
├─ scripts/                  # Outillage (smoke test « comme PyPI »)
├─ archive/                  # Ressources brutes : XSD, XSL, Schematron, Saxon
├─ output/                   # Sorties de `main.py` (PDF/XML générés)
├─ main.py                   # Démonstration de bout en bout
├─ pyproject.toml
└─ poetry.lock

📦 Installation

Depuis PyPI :

pip install ads-facturx

Prérequis : Python ≥ 3.10. SaxonC-HE est installé automatiquement via la dépendance saxonche.

Optionnel : veraPDF dans le PATH, requis uniquement par validate_pdf_a3. Le reste de la chaîne fonctionne sans.


🚀 Démarrage rapide

Pipeline complet : validation → XML → contrôles XSD/Schematron → PDF → ICC → Factur-X → contrôle PDF/A-3.

import os

from ads_facturx import (
    DevisData,
    Profile,
    add_icc,
    export_pdf,
    export_xml,
    generate_facturx,
    validate_pdf_a3,
    write_xml_from_string,
    xml_check_schematron,
    xml_check_xsd,
)

DESTINATION = "output"
PROFILE = Profile.EU  # ou Profile.FR pour le flux B2B français

# 1. Données métier (dict) → modèle validé, contrôlé selon le profil
data = {...}  # voir docs/pdf/examples/sample_data.py
validated = DevisData.model_validate(data, context={"profile": PROFILE})

invoice_name = validated.invoice.name
pdf_name = f"{invoice_name}.pdf"
pdf_path = os.path.join(DESTINATION, pdf_name)

# 2. XML EN 16931 + validations
xml_string = export_xml(validated, PROFILE)
xml_path = write_xml_from_string(
    xml_string=xml_string,
    destination_folder=DESTINATION,
    file_name=f"{invoice_name}.xml",
)
xml_check_xsd(xml_string, PROFILE)
print(xml_check_schematron(xml_path=xml_path))

# 3. PDF (template par défaut)
export_pdf(title=invoice_name, output_path=pdf_path, data=validated)

# 4. Profil ICC / OutputIntent — prérequis PDF/A, à faire AVANT la Factur-X.
#    pikepdf n'écrit pas en place : on passe par un fichier temporaire.
tmp = pdf_path + ".icc.pdf"
add_icc(pdf_path, tmp)
os.replace(tmp, pdf_path)

# 5. PDF + XML → Factur-X (c'est ici que le PDF devient PDF/A-3)
generate_facturx(file_name=pdf_name, destination_folder=DESTINATION, xml=xml_string)

# 6. Contrôle de conformité (nécessite veraPDF dans le PATH)
print("PDF/A-3 :", validate_pdf_a3(pdf_path))

⚠️ Les étapes 4 et 5 ne sont pas optionnelles. export_pdf seul produit un PDF ordinaire : c'est add_icc qui pose l'OutputIntent et generate_facturx qui écrit les métadonnées PDF/A-3. Valider avant elles renverra toujours « non conforme ».

Un jeu de données complet, validable tel quel par DevisData, est fourni dans docs/pdf/examples/sample_data.py. main.py exécute cette chaîne de bout en bout.


🔌 API publique

Exposée via from ads_facturx import ... (cf. ads_facturx/__init__.py).

Pipeline principal

Symbole Rôle
DevisData Modèle Pydantic v2 d'entrée (validation EN 16931).
Profile EU (EN16931) ou FR (flux B2B français, extensions BR-FR).
export_xml(data, profile) Construit la chaîne XML CII pour le profil demandé.
write_xml_from_string Sérialise l'XML sur disque (destination_folder, file_name).
xml_check_xsd Valide l'XML avec le XSD facturx au niveau dicté par le profil.
xml_check_schematron Applique un Schematron via SaxonC (EN16931-CII, ou BR-FR).
export_pdf Génère le PDF de facture (ReportLab — template par défaut ou custom).
add_icc Ajoute le profil ICC / OutputIntent — prérequis PDF/A.
generate_facturx Embarque le XML dans le PDF pour produire la Factur-X finale.
validate_pdf_a3 Contrôle la conformité PDF/A-3 via veraPDF.
register_fonts Déclare des polices tierces, embarquées dans le PDF.

Briques pour personnaliser le PDF (détails dans la doc PDF)

Template · ParagraphComponent · SpacerComponent · PageBreakComponent · BoxComponent · TableComponent · KeepInFrameComponent · DiyComponent · Header · Footer · TextElement · CenteredText · ImageElement · HorizontalLine · Theme · TableStyles · FontName · BODY_FONT · BOLD_FONT · NonEmbeddableFontError · TableConfig · TableColumn · TableBox · ParagraphConfig · KeepInFrameConfig · FormatText · FormatDate · ExtraRow · ExtraRowCell · DocConfig · mm · A4 · colors.


🔤 Utiliser une autre police

La librairie embarque DejaVu (régulier + gras) et s'en sert par défaut. Trois écritures équivalentes pour la désigner :

from ads_facturx import BODY_FONT, BOLD_FONT, FontName, Theme

Theme(name="titre", fontName=FontName.DejaVuBold)  # enum, auto-complété
Theme(name="titre", fontName=BOLD_FONT)  # constante
Theme(name="titre", fontName="DejaVu-Bold")  # littéral

Ces noms s'emploient partout où une police est attendue : Theme(fontName=...), TableStyles(body_font=..., header_font=...), PageNumberConfig(font=...), TextElement(font=...) et CenteredText(font=...).

Pour employer une police à soi, il suffit de la déclarer à register_fonts — inutile d'importer ReportLab :

from ads_facturx import Theme, export_pdf, register_fonts
from ads_facturx.Builders.pdf.config.config import DocConfig

register_fonts(extra={"Inter": "fonts/Inter-Regular.ttf"})

titre = Theme(name="titre", fontName="Inter", fontSize=14)

Le nom logique ("Inter") se réutilise ensuite dans Theme(fontName=...), TableStyles(body_font=..., header_font=...) et PageNumberConfig(font=...). Un fichier .ttf introuvable lève un FileNotFoundError immédiatement, plutôt qu'au moment du rendu.

Trois points à connaître :

  • La police est embarquée en sous-ensemble dans le PDF, ce qu'exige PDF/A-3. Un document rendu entièrement dans une police tierce reste conforme.
  • Le template par défaut reste en DejaVu. Pour changer la police de tout le document, il faut fournir ses propres Theme / TableStyles via export_pdf(structure=...). Les clés template et doc_config y sont requises, header et footer facultatives.
  • Les 14 polices standard du PDF sont refusées (voir ci-dessous).

Pourquoi Helvetica & consorts sont refusées

Theme(fontName="Helvetica") lève désormais un NonEmbeddableFontError, de même que Times, Courier, Symbol, ZapfDingbats et leurs variantes de casse ("Helvetica-bold"). Ces 14 polices sont fournies par le lecteur de PDF, donc jamais écrites dans le fichier. Deux conséquences :

  • PDF/A l'interdit — « the font programs for all fonts used for rendering within a conforming file shall be embedded » (ISO 19005-3, clause 6.2.11.4.1). veraPDF rejette le document, et la facture avec.
  • Helvetica, Times et Courier sont des marques déposées : ads-facturx ne peut pas en distribuer les fichiers pour contourner le point précédent.

Le piège, c'est qu'un tel PDF s'ouvre et s'imprime normalement : sans ce contrôle, rien n'échoue avant la validation de conformité — souvent chez le destinataire. La substitution (register_fonts(extra={"Helvetica": ...})) est refusée elle aussi : ReportLab l'ignore silencieusement dès qu'il a déjà instancié la police d'origine, ce qui rendrait la conformité dépendante de l'ordre des appels.

Le remède est toujours le même — une police libre déclarée à register_fonts, ou les DejaVu livrées avec la librairie.


🧾 Modèle de données

Schémas Pydantic v2 dans ads_facturx/models : DevisData (racine), Partner, Address, Invoice, InvoiceLine, InvoiceSummary, LegalInformation, plus les énumérations normatives (InvoiceType, BusinessProcess, CountryCode, CurrencyCode, UnitCode, VatCategory).

DevisData.model_validate(data) applique les contraintes EN 16931 critiques (formats de dates YYYYMMDD, codes pays/devise/unité, type TVA, …).

Détail des champs et exemple JSON complet : docs/pdf/03-input-data.md.


🇫🇷 Profils EU et FR

Le profil se choisit à la validation et à l'export, pas dans les données :

validated = DevisData.model_validate(data, context={"profile": Profile.FR})
xml_string = export_xml(validated, Profile.FR)
Profile.EU Profile.FR
URN déclaré urn:cen.eu:en16931:2017 …:1p0:extended (conformant BR-FR)
Niveau XSD (xsd_level) en16931 extended
Schematron EN16931-CII-validation.xslt BR-FR-Flux2-Schematron-CII_V1.3.0.xslt
Champs requis en plus legal_information, seller_siren, les 4 xml_*_scheme / xml_*_endpoint_id, business_process
Types de facture (BT-3) 380, 381, 389 + 386 (acompte), 503 (avoir d'acompte)

Deux contrôles métier sont appliqués à la validation, avant toute génération :

  • UC-BT-3 — les codes acompte 386 / 503 sont refusés hors profil FR.
  • UC-BT-23 — une facture d'acompte n'admet que les cadres de facturation B1, S1, M1, B2, S2, M2.

Un champ requis manquant lève une ValidationError nommant précisément le champ, et non une erreur Schematron obscure en fin de chaîne.

Facture antérieure référencée (BG-3)

invoice.linked_document_number (BT-25) et invoice.linked_document_date (BT-26) émettent ram:InvoiceReferencedDocument dans les deux profils. Les deux sont optionnels — mais BR-FR-CO-05 exige le couple complet pour les avoirs (261, 381, 396, 502, 503) : le numéro seul laisse la règle en échec, la date étant lue en qdt:DateTimeString.

La date est normalisée à la validation : 2025-04-01 comme 20250401 ressortent en 20250401. Une date jour-en-tête (01/04/2025) est refusée plutôt que réinterprétée.


📚 Documentation détaillée

La génération PDF dispose de sa propre documentation, en 12 sections progressives :

docs/pdf/README.md — table des matières.

Au menu : quickstart, mental model, schéma DevisData, construction de templates, catalogue de composants, personnalisation (style / config), header & footer, composants DIY, défauts fournis, cookbook, erreurs courantes, référence API.

Tous les exemples sont exécutables :

python docs/pdf/examples/run_examples.py
# → PDFs écrits dans docs/pdf/examples/out/

✅ Validation EN 16931

Trois contrôles indépendants, à trois niveaux :

Niveau Fonction Ce qui est vérifié
Données DevisData.model_validate(data, context=…) types, énumérations normatives, complétude du profil, BT-3 × BT-23
XML xml_check_xsd(xml, profile) grammaire — flavor="facturx", niveau dicté par le profil
XML xml_check_schematron(xml_path, xslt_path=…) règles métier — renvoie les failed-assert (id, location, message)
PDF validate_pdf_a3(pdf_path) conformité PDF/A-3 via veraPDF

Les deux Schematron livrés sont dans ads_facturx/Validators/xslt/ : EN16931-CII-validation.xslt (EU, appliqué par défaut) et BR-FR-Flux2-Schematron-CII_V1.3.0.xslt (FR). Le second se sélectionne via xslt_path= — voir main.py.


🧪 Développement

pip install -e ".[dev]"     # pytest, ruff, poetry, build, twine
pytest                      # suite complète
ruff check . && ruff format .
python docs/pdf/examples/run_examples.py   # tous les exemples de la doc
python scripts/smoke_install.py            # test « comme si ça venait de PyPI »

Le dernier construit la roue, l'installe dans un venv vierge et exécute un scénario complet depuis un dossier étranger au dépôt : c'est ce qui attrape une ressource oubliée dans le packaging. Détails dans docs/tester-comme-pypi.md.


📚 Dépendances

factur-x, saxonche, pydantic, reportlab, pikepdf (voir pyproject.toml et poetry.lock).

Le package embarque ses propres ressources : polices DejaVu, profil ICC sRGB2014, XSLT Schematron et logo. Aucun téléchargement ni chemin système n'est requis à l'exécution.

👤 Auteur

Antoine DucoulombierAlchimie Data Solutions · antoine.ducoulombier@alchimiedatasolutions.com

Release files for ads-facturx 0.7.16

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ads-facturx 0.7.16
File Size Uploaded
ads_facturx-0.7.16.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for ads-facturx 0.7.16
File Interpreter ABI Platform
ads_facturx-0.7.16-py3-none-any.whl Python 3 none any Details

Total release size:2.1 MB

Release files / ads_facturx-0.7.16.tar.gz

Download URL ads_facturx-0.7.16.tar.gz
Size 1.1 MB
Tags Source
SHA-256 checksum
How to use checksums
92b422d05f1fa1ea26c5d6251a5629c1f09609ed9977e7ae67cf2511f074b016
BLAKE2b-256 checksum
How to use checksums
f609832747294a394ff1df01b6185aff4a54bd4d0b79f2d3105a28bff4ea9b59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.4.3 CPython/3.10.11 Windows/10

Release files / ads_facturx-0.7.16-py3-none-any.whl

Download URL ads_facturx-0.7.16-py3-none-any.whl
Size 1.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
fc13dfe45962c3fa6e3b4a5cdb195f07f95b626c8d0569278122771d86a48400
BLAKE2b-256 checksum
How to use checksums
5cea397d3963e3f410a4ccefec0d8d048b751dd3fb8737584ac7b76bd2aa35b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/2.4.3 CPython/3.10.11 Windows/10

Release history Release notifications | RSS feed

This release

0.7.16 This release

2 release files

0.7.15

2 release files

0.7.12

2 release files

0.7.11

2 release files

0.7.10

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.7.6

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.16

2 release files

0.6.15

2 release files

0.6.14

2 release files

0.6.13

2 release files

0.6.12

2 release files

0.6.11

2 release files

0.6.10

2 release files

0.6.9

2 release files

0.6.8

2 release files

0.6.7

2 release files

0.6.6

2 release files

0.6.5

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page