Observed since 20 Oct, 19:53 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Génération, validation et assemblage de factures électroniques Factur-X / EN 16931 en Python.
ads-facturxGénération, validation et assemblage de factures électroniques Factur-X / EN 16931 en Python.
eFacturePy est le dépôt de travail du package Python ads-facturx, un outil qui permet de :
urn:cen.eu:en16931:2017 ;factur-x) ;dict métiereFacturePy/
├─ 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
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 parvalidate_pdf_a3. Le reste de la chaîne fonctionne sans.
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_pdfseul produit un PDF ordinaire : c'estadd_iccqui pose l'OutputIntent etgenerate_facturxqui é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 dansdocs/pdf/examples/sample_data.py.main.pyexécute cette chaîne de bout en bout.
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 · RightText · ImageElement ·
HorizontalLine · Theme · TableStyles · FontName · BODY_FONT ·
BOLD_FONT · NonEmbeddableFontError ·
TableConfig · TableColumn · TableBox · ParagraphConfig ·
KeepInFrameConfig · FormatText · FormatDate · ExtraRow ·
ExtraRowCell · DocConfig · mm · A4 · colors.
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=...), CenteredText(font=...) et RightText(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 :
Theme / TableStyles via
export_pdf(structure=...). Les clés template et doc_config y sont
requises, header et footer facultatives.Helvetica & consorts sont refuséesTheme(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 :
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.
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.
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 :
386 / 503 sont refusés hors profil FR.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.
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.
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/
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) |
validate_pdf_a3(pdf_path) | conformité PDF/A-3 via veraPDF |
Les Schematron livrés sont dans
ads_facturx/Validators/xslt/ :
| Fichier | Ce qu'il vérifie |
|---|---|
EN16931-CII-validation.xslt | la norme EN 16931 — appliqué par défaut, profil EU |
BR-FR-Flux2-Schematron-CII_V1.4.0.xslt | les règles du flux 2 français (SIREN vendeur, mentions PMT/PMD/AAB, endpoints) |
BR-FR-Flux2-Schematron-CII_V1.3.0.xslt | la révision précédente des mêmes règles |
FACTUR-X_EXTENDED.xslt | les règles Factur-X du niveau étendu, dont les PEPPOL-EN16931-* |
Ils se sélectionnent via xslt_path=, un appel par fichier. Aucun ne couvre
l'autre : un document FR gagne à passer BR-FR et Factur-X EXTENDED, le
premier portant les règles françaises, le second celles du niveau déclaré par
l'URN. C'est ce que fait main.py.
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.
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.
Antoine Ducoulombier — Alchimie Data Solutions · antoine.ducoulombier@alchimiedatasolutions.com
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.7.19 | 25 Sept 2026 | 10 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 19:53 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:b994f3a0ff7e68902cffcd1a08085c1debd445687130f305f40edae735a7517b