euinvoice 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- euinvoice-0.1.0/LICENSE +21 -0
- euinvoice-0.1.0/PKG-INFO +212 -0
- euinvoice-0.1.0/README.md +181 -0
- euinvoice-0.1.0/pyproject.toml +138 -0
- euinvoice-0.1.0/src/euinvoice/__init__.py +64 -0
- euinvoice-0.1.0/src/euinvoice/__main__.py +305 -0
- euinvoice-0.1.0/src/euinvoice/_api.py +154 -0
- euinvoice-0.1.0/src/euinvoice/_syntax.py +16 -0
- euinvoice-0.1.0/src/euinvoice/_xml.py +296 -0
- euinvoice-0.1.0/src/euinvoice/calc/__init__.py +74 -0
- euinvoice-0.1.0/src/euinvoice/calc/_categories.py +369 -0
- euinvoice-0.1.0/src/euinvoice/calc/_check.py +243 -0
- euinvoice-0.1.0/src/euinvoice/calc/_common.py +182 -0
- euinvoice-0.1.0/src/euinvoice/calc/_complete.py +230 -0
- euinvoice-0.1.0/src/euinvoice/detection.py +159 -0
- euinvoice-0.1.0/src/euinvoice/errors.py +101 -0
- euinvoice-0.1.0/src/euinvoice/facturx/__init__.py +10 -0
- euinvoice-0.1.0/src/euinvoice/facturx/_embed.py +231 -0
- euinvoice-0.1.0/src/euinvoice/facturx/_extract.py +215 -0
- euinvoice-0.1.0/src/euinvoice/facturx/_pypdf.py +39 -0
- euinvoice-0.1.0/src/euinvoice/facturx/xmp.py +149 -0
- euinvoice-0.1.0/src/euinvoice/model/__init__.py +94 -0
- euinvoice-0.1.0/src/euinvoice/model/_base.py +127 -0
- euinvoice-0.1.0/src/euinvoice/model/allowances.py +106 -0
- euinvoice-0.1.0/src/euinvoice/model/amounts.py +110 -0
- euinvoice-0.1.0/src/euinvoice/model/bt_index.py +110 -0
- euinvoice-0.1.0/src/euinvoice/model/codes/__init__.py +81 -0
- euinvoice-0.1.0/src/euinvoice/model/codes/_generated.py +559 -0
- euinvoice-0.1.0/src/euinvoice/model/codes/derived.py +16 -0
- euinvoice-0.1.0/src/euinvoice/model/codes/enums.py +67 -0
- euinvoice-0.1.0/src/euinvoice/model/datatypes.py +399 -0
- euinvoice-0.1.0/src/euinvoice/model/delivery.py +61 -0
- euinvoice-0.1.0/src/euinvoice/model/documents.py +21 -0
- euinvoice-0.1.0/src/euinvoice/model/invoice.py +168 -0
- euinvoice-0.1.0/src/euinvoice/model/lines.py +149 -0
- euinvoice-0.1.0/src/euinvoice/model/parties.py +180 -0
- euinvoice-0.1.0/src/euinvoice/model/payment.py +68 -0
- euinvoice-0.1.0/src/euinvoice/model/tax.py +31 -0
- euinvoice-0.1.0/src/euinvoice/model/totals.py +38 -0
- euinvoice-0.1.0/src/euinvoice/profiles/__init__.py +41 -0
- euinvoice-0.1.0/src/euinvoice/profiles/_base.py +162 -0
- euinvoice-0.1.0/src/euinvoice/profiles/en16931.py +23 -0
- euinvoice-0.1.0/src/euinvoice/profiles/facturx.py +198 -0
- euinvoice-0.1.0/src/euinvoice/profiles/peppol.py +360 -0
- euinvoice-0.1.0/src/euinvoice/profiles/registry.py +74 -0
- euinvoice-0.1.0/src/euinvoice/profiles/xrechnung.py +455 -0
- euinvoice-0.1.0/src/euinvoice/py.typed +0 -0
- euinvoice-0.1.0/src/euinvoice/report.py +66 -0
- euinvoice-0.1.0/src/euinvoice/syntax/__init__.py +9 -0
- euinvoice-0.1.0/src/euinvoice/syntax/_marks.py +192 -0
- euinvoice-0.1.0/src/euinvoice/syntax/_read_errors.py +55 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/__init__.py +12 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_build.py +188 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_lines.py +137 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_parties.py +142 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_read.py +207 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_read_common.py +69 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_read_lines.py +168 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_read_parties.py +193 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_read_settlement.py +287 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_reader.py +171 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_settlement.py +211 -0
- euinvoice-0.1.0/src/euinvoice/syntax/cii/_write.py +136 -0
- euinvoice-0.1.0/src/euinvoice/syntax/result.py +25 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/__init__.py +13 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_build.py +182 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_cursor.py +292 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_lines.py +186 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_parties.py +183 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_read.py +424 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_read_lines.py +283 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_read_parties.py +277 -0
- euinvoice-0.1.0/src/euinvoice/syntax/ubl/_write.py +378 -0
- euinvoice-0.1.0/src/euinvoice/validation/__init__.py +8 -0
- euinvoice-0.1.0/src/euinvoice/validation/artifacts.py +446 -0
- euinvoice-0.1.0/src/euinvoice/validation/manifest.toml +127 -0
- euinvoice-0.1.0/src/euinvoice/validation/orchestration.py +190 -0
- euinvoice-0.1.0/src/euinvoice/validation/schematron.py +239 -0
- euinvoice-0.1.0/src/euinvoice/validation/xsd.py +158 -0
euinvoice-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Biagio Distefano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
euinvoice-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: euinvoice
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: EN 16931 e-invoicing for Python: build, serialize (UBL/CII), validate, parse and embed (Factur-X) European e-invoices.
|
|
5
|
+
Keywords: e-invoicing,en16931,peppol,xrechnung,factur-x,zugferd,ubl,cii,einvoice
|
|
6
|
+
Author: Biagio Distefano
|
|
7
|
+
Author-email: Biagio Distefano <biagio@biagiodistefano.io>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
18
|
+
Classifier: Topic :: Text Processing :: Markup :: XML
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: lxml>=5.3
|
|
21
|
+
Requires-Dist: pydantic>=2.11
|
|
22
|
+
Requires-Dist: pypdf>=6.1 ; extra == 'pdf'
|
|
23
|
+
Requires-Dist: saxonche>=12.9,!=13.0.0 ; extra == 'validate'
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Homepage, https://github.com/letsrevel/euinvoice
|
|
26
|
+
Project-URL: Issues, https://github.com/letsrevel/euinvoice/issues
|
|
27
|
+
Project-URL: Changelog, https://github.com/letsrevel/euinvoice/blob/main/CHANGELOG.md
|
|
28
|
+
Provides-Extra: pdf
|
|
29
|
+
Provides-Extra: validate
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# euinvoice
|
|
33
|
+
|
|
34
|
+
**EN 16931 e-invoicing for Python.** You can build European e-invoices as typed models, serialize them
|
|
35
|
+
to **UBL** or **CII**, validate them against the **official** rule sets, parse them back, and embed them
|
|
36
|
+
in **Factur-X / ZUGFeRD** PDFs.
|
|
37
|
+
|
|
38
|
+
> **Status: 0.1.0, not yet released.** The API can still change before the release; see
|
|
39
|
+
> [`CHANGELOG.md`](CHANGELOG.md) and the [known limitations](#known-limitations).
|
|
40
|
+
|
|
41
|
+
## Why
|
|
42
|
+
|
|
43
|
+
EU e-invoicing mandates are arriving country by country, and the reference tooling is mostly Java. euinvoice
|
|
44
|
+
is a small, typed, MIT-licensed Python library with three principles:
|
|
45
|
+
|
|
46
|
+
- **One semantic model.** The EN 16931 business terms form a frozen, `Decimal`-only Pydantic model. UBL and CII
|
|
47
|
+
are just two ways of writing it down.
|
|
48
|
+
- **The official rules are the judge.** Validation runs the pinned official XSD and Schematron artifacts (CEN,
|
|
49
|
+
Peppol, KoSIT XRechnung) on SaxonC-HE, not a reimplementation that drifts after the next rule release.
|
|
50
|
+
- **Framework-free and side-effect-free.** Bytes go in and bytes come out, with no Django, no network and no
|
|
51
|
+
file system in the core.
|
|
52
|
+
|
|
53
|
+
## What 0.1.0 supports
|
|
54
|
+
|
|
55
|
+
| Profile | Syntax | Generate | Parse | Validate |
|
|
56
|
+
|---|---|---|---|---|
|
|
57
|
+
| EN 16931 core | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN |
|
|
58
|
+
| Peppol BIS Billing 3.0 | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN + Peppol |
|
|
59
|
+
| XRechnung 3.0 (CIUS) | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN + XRechnung |
|
|
60
|
+
| XRechnung 3.0 Extension | UBL 2.1, CII D16B | ✅ EN 16931 content only | ✅ extension content listed as unmapped; content outside the CEN code lists (e.g. ICD `XR03`, testsuite `04.05a` CII) raises `ParseError` | ⚠️ raw flags ([#49](https://github.com/letsrevel/euinvoice/issues/49)) |
|
|
61
|
+
| XRechnung 3.0 CVD | UBL 2.1, CII D16B | ❌ | ❌ conforming documents (BR-CL-13) | ⚠️ raw flags, see below |
|
|
62
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ EN 16931, XRECHNUNG | CII in PDF/A-3 | ✅ | ✅ | ⚠️ EN 16931 / XRechnung rules only |
|
|
63
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ BASIC, EXTENDED | CII in PDF/A-3 | ❌ | ✅ EXTENDED-only content listed as unmapped | ❌ |
|
|
64
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ MINIMUM, BASIC WL | CII in PDF/A-3 | ❌ | ❌ detected and extracted only | ❌ |
|
|
65
|
+
| ZUGFeRD 2.0 MINIMUM, BASIC, EXTENDED | CII in PDF/A-3 | ❌ | ✅ BASIC, EXTENDED (EXTENDED-only content listed as unmapped; corpus EXTENDED samples with codes outside the CEN lists, ABK BR-CL-19 and 9958 BR-CL-25, raise `ParseError` like their 2.1 twins, [#69](https://github.com/letsrevel/euinvoice/issues/69)); ❌ MINIMUM | ❌ |
|
|
66
|
+
|
|
67
|
+
- **Parse** means read into the EN 16931 model. Readers never drop input silently: every element or attribute
|
|
68
|
+
without a business term is listed in `parse_detailed(...).unmapped`.
|
|
69
|
+
- **Validate** reports the raw severities of the official rule sets. KoSIT's per-scenario severity overrides are
|
|
70
|
+
not applied ([#49](https://github.com/letsrevel/euinvoice/issues/49)): for example, 2 of the 6 official XRechnung Extension instances get fatal findings
|
|
71
|
+
that KoSIT downgrades.
|
|
72
|
+
- **XRechnung CVD:** BR-DE-CVD-03 (fatal, `XRechnung-UBL-validation.sch` lines 560-562) needs an item
|
|
73
|
+
classification with list id `CVD`, which BR-CL-13 (fatal, CEN `EN16931-UBL-codes.sch` lines 67-68) and
|
|
74
|
+
therefore the model refuse. No *conforming* CVD invoice can be built or read. `validate()` rejects every CVD
|
|
75
|
+
document: BR-CL-13 when it carries the `CVD` item classification, BR-DE-CVD-03 (fatal) when it does not
|
|
76
|
+
(KoSIT downgrades BR-CL-13, [#49](https://github.com/letsrevel/euinvoice/issues/49)).
|
|
77
|
+
- **Factur-X:** the rows above cover the Factur-X 1.0 / ZUGFeRD 2.1+ BT-24s. `facturx.embed` / `facturx.extract`
|
|
78
|
+
write and read the PDF container (the `[pdf]` extra). The
|
|
79
|
+
Factur-X XSD and Schematron are not pinned yet ([#42](https://github.com/letsrevel/euinvoice/issues/42)), so
|
|
80
|
+
`validate()` checks the embedded XML of the EN 16931 and XRECHNUNG levels against the EN 16931 / XRechnung
|
|
81
|
+
rules only, and raises `ArtifactsNotAvailableError` for MINIMUM, BASIC WL, BASIC and EXTENDED. MINIMUM and
|
|
82
|
+
BASIC WL carry no invoice lines, so they cannot become an `Invoice`: `parse()` raises `ParseError`
|
|
83
|
+
([#69](https://github.com/letsrevel/euinvoice/issues/69)). The library does not check PDF/A-3 conformance
|
|
84
|
+
(the test suite runs veraPDF on `embed()` output). ZUGFeRD 2.0 PDFs are extract-only (not generated). Their
|
|
85
|
+
MINIMUM, BASIC and EXTENDED BT-24s (`urn:zugferd.de:2p0:*`) and the colon spellings of BASIC and EXTENDED
|
|
86
|
+
(`urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:*`) are not registered as profiles, but `validate()` raises
|
|
87
|
+
`ArtifactsNotAvailableError` for them as for the levels above (CLI exit 2;
|
|
88
|
+
[#98](https://github.com/letsrevel/euinvoice/issues/98)). A ZUGFeRD 2.0 EN 16931 invoice carries the core BT-24
|
|
89
|
+
and is validated as EN 16931 core.
|
|
90
|
+
ZUGFeRD 1.0 PDFs are extract-only (`parse()` raises `UnsupportedDocumentError`).
|
|
91
|
+
|
|
92
|
+
Planned later: ebInterface, more national CIUSes (RO, HR, FR, DK, …), FatturaPA, KSeF, Facturae, and
|
|
93
|
+
clearance/transport integrations.
|
|
94
|
+
|
|
95
|
+
## Install
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
uv add euinvoice # model + UBL/CII serialize/parse
|
|
99
|
+
uv add 'euinvoice[validate]' # + official Schematron validation (SaxonC-HE)
|
|
100
|
+
uv add 'euinvoice[pdf]' # + Factur-X / ZUGFeRD PDF embedding
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Until 0.1.0 is on PyPI: `uv add 'euinvoice @ git+https://github.com/letsrevel/euinvoice'`.
|
|
104
|
+
|
|
105
|
+
The official validation artifacts are **downloaded, not bundled**, because of their licences. Fetch them
|
|
106
|
+
once (pinned and sha256-verified):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python -m euinvoice artifacts fetch # cache: $EUINVOICE_ARTIFACTS_DIR or ~/.cache/euinvoice
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
`draft` is an `euinvoice.InvoiceDraft`: the invoice without its derived totals. These examples run as tests
|
|
115
|
+
(`tests/_readme.py` builds the synthetic XRechnung draft they use); see the [quickstart](docs/quickstart.md) for
|
|
116
|
+
building a draft.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
>>> from euinvoice import calc, detect, parse, parse_detailed, profiles, to_xml, validate
|
|
120
|
+
>>> invoice = calc.complete(draft) # derive line totals, document totals and the VAT breakdown (EN 16931)
|
|
121
|
+
>>> xml = to_xml(invoice, profile=profiles.XRECHNUNG, syntax="cii") # raises PreflightError on blocking findings
|
|
122
|
+
>>> parse(xml) == profiles.XRECHNUNG.prepare(invoice) # lossless; to_xml wrote the profile's BT-24 (prepare)
|
|
123
|
+
True
|
|
124
|
+
>>> parse_detailed(xml).unmapped # XPaths of input with no business term; parse() discards them
|
|
125
|
+
()
|
|
126
|
+
>>> found = detect(xml) # syntax and profile from the root element and BT-24
|
|
127
|
+
>>> found.syntax, found.profile.id
|
|
128
|
+
(<Syntax.CII: 'cii'>, 'xrechnung')
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Validation needs the `[validate]` extra and the fetched artifacts. `validate()` takes XML; for a PDF, pass
|
|
132
|
+
`facturx.extract(pdf).xml` or use the CLI.
|
|
133
|
+
|
|
134
|
+
<!-- readme-doctest: needs-artifacts -->
|
|
135
|
+
```python
|
|
136
|
+
>>> report = validate(xml) # profile auto-detected from BT-24
|
|
137
|
+
>>> report.ok
|
|
138
|
+
True
|
|
139
|
+
>>> for finding in report.findings:
|
|
140
|
+
... print(finding.rule_id, finding.severity, finding.message)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Factur-X / ZUGFeRD needs the `[pdf]` extra:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
>>> from euinvoice import facturx
|
|
147
|
+
>>> # your renderer produces the human-readable PDF/A-3 (e.g. WeasyPrint pdf_variant="pdf/a-3b")
|
|
148
|
+
>>> hybrid_pdf = facturx.embed(rendered_pdf, invoice, profile=profiles.FACTURX_EN16931)
|
|
149
|
+
>>> found = facturx.extract(hybrid_pdf)
|
|
150
|
+
>>> found.filename, found.conformance_level, found.profile.id
|
|
151
|
+
('factur-x.xml', 'EN 16931', 'facturx-en16931')
|
|
152
|
+
>>> parse(hybrid_pdf) == profiles.FACTURX_EN16931.prepare(invoice) # parse() reads PDFs too
|
|
153
|
+
True
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Command line
|
|
157
|
+
|
|
158
|
+
`python -m euinvoice` wraps the same functions. `FILE` is UBL, CII or a Factur-X / ZUGFeRD PDF (`-` reads
|
|
159
|
+
stdin), and `--profile` takes a profile id such as `xrechnung` or `peppol`.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
python -m euinvoice validate invoice.xml [--profile ID] [--json] # one line per finding, then the verdict
|
|
163
|
+
python -m euinvoice convert invoice.xml --to ubl|cii [--profile ID] [-o OUT]
|
|
164
|
+
python -m euinvoice info invoice.pdf [--json] # syntax, BT-24, profile, container, totals
|
|
165
|
+
python -m euinvoice artifacts fetch [--only NAME]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Exit codes: `0` success (warnings allowed); `1` the document was rejected (a `fatal` or `error` finding, a
|
|
169
|
+
refused conversion, an unreadable invoice) or an artifact failed its integrity check; `2` no verdict (usage
|
|
170
|
+
error, unknown profile, unreadable file, failed download, missing artifacts or extras, a Factur-X level whose
|
|
171
|
+
rules are not pinned). `--help` on each subcommand lists them.
|
|
172
|
+
|
|
173
|
+
## Documentation
|
|
174
|
+
|
|
175
|
+
The [`docs/`](docs/index.md) folder holds the guide: [quickstart](docs/quickstart.md),
|
|
176
|
+
[concepts](docs/concepts.md), [validation](docs/validation.md), [Factur-X](docs/facturx.md), a
|
|
177
|
+
[mapping guide](docs/mapping/index.md) with synthetic freelancer and ticketing examples, and the
|
|
178
|
+
[BT mapping](docs/reference/bt-mapping.md). It builds with `make docs` (mkdocs-material). The GitHub Pages site goes live
|
|
179
|
+
once the maintainer enables Pages for the repository and sets the repository variable `DOCS_DEPLOY=true`.
|
|
180
|
+
|
|
181
|
+
## Known limitations
|
|
182
|
+
|
|
183
|
+
Open questions waiting for a maintainer decision (`needs-human`):
|
|
184
|
+
|
|
185
|
+
- [#42](https://github.com/letsrevel/euinvoice/issues/42): the Factur-X / ZUGFeRD XSD and Schematron have no
|
|
186
|
+
pinnable official download, so Factur-X levels are not validated against Factur-X rules (see above).
|
|
187
|
+
- [#69](https://github.com/letsrevel/euinvoice/issues/69): Factur-X MINIMUM and BASIC WL cannot be read into the
|
|
188
|
+
model.
|
|
189
|
+
- [#49](https://github.com/letsrevel/euinvoice/issues/49): KoSIT's XRechnung severity overrides are not applied,
|
|
190
|
+
so the verdict can differ from the KoSIT validator in both directions (e.g. BR-CL-21/23, XRechnung Extension
|
|
191
|
+
and CVD).
|
|
192
|
+
- [#67](https://github.com/letsrevel/euinvoice/issues/67): the XRechnung profiles set no default BT-23; the caller
|
|
193
|
+
must provide it (the pre-flight reports PEPPOL-EN16931-R001 otherwise).
|
|
194
|
+
- [#40](https://github.com/letsrevel/euinvoice/issues/40): the hardened parser rejects a single text node over
|
|
195
|
+
10,000,000 bytes, i.e. a BT-125 attachment over about 7.5 MB.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
make setup # uv sync --all-extras --group dev
|
|
201
|
+
make check # ruff + mypy --strict + file length
|
|
202
|
+
make test # unit tests, ≥95% branch coverage
|
|
203
|
+
make artifacts # fetch official artifacts + corpora
|
|
204
|
+
make conformance # run them
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md). Security reports: [`SECURITY.md`](SECURITY.md).
|
|
208
|
+
|
|
209
|
+
## Licence
|
|
210
|
+
|
|
211
|
+
MIT. Official validation artifacts are third-party works under their own licences (EUPL-1.2,
|
|
212
|
+
Apache-2.0 and others). They are fetched at runtime and never redistributed by this package.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# euinvoice
|
|
2
|
+
|
|
3
|
+
**EN 16931 e-invoicing for Python.** You can build European e-invoices as typed models, serialize them
|
|
4
|
+
to **UBL** or **CII**, validate them against the **official** rule sets, parse them back, and embed them
|
|
5
|
+
in **Factur-X / ZUGFeRD** PDFs.
|
|
6
|
+
|
|
7
|
+
> **Status: 0.1.0, not yet released.** The API can still change before the release; see
|
|
8
|
+
> [`CHANGELOG.md`](CHANGELOG.md) and the [known limitations](#known-limitations).
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
EU e-invoicing mandates are arriving country by country, and the reference tooling is mostly Java. euinvoice
|
|
13
|
+
is a small, typed, MIT-licensed Python library with three principles:
|
|
14
|
+
|
|
15
|
+
- **One semantic model.** The EN 16931 business terms form a frozen, `Decimal`-only Pydantic model. UBL and CII
|
|
16
|
+
are just two ways of writing it down.
|
|
17
|
+
- **The official rules are the judge.** Validation runs the pinned official XSD and Schematron artifacts (CEN,
|
|
18
|
+
Peppol, KoSIT XRechnung) on SaxonC-HE, not a reimplementation that drifts after the next rule release.
|
|
19
|
+
- **Framework-free and side-effect-free.** Bytes go in and bytes come out, with no Django, no network and no
|
|
20
|
+
file system in the core.
|
|
21
|
+
|
|
22
|
+
## What 0.1.0 supports
|
|
23
|
+
|
|
24
|
+
| Profile | Syntax | Generate | Parse | Validate |
|
|
25
|
+
|---|---|---|---|---|
|
|
26
|
+
| EN 16931 core | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN |
|
|
27
|
+
| Peppol BIS Billing 3.0 | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN + Peppol |
|
|
28
|
+
| XRechnung 3.0 (CIUS) | UBL 2.1, CII D16B | ✅ | ✅ | ✅ XSD + CEN + XRechnung |
|
|
29
|
+
| XRechnung 3.0 Extension | UBL 2.1, CII D16B | ✅ EN 16931 content only | ✅ extension content listed as unmapped; content outside the CEN code lists (e.g. ICD `XR03`, testsuite `04.05a` CII) raises `ParseError` | ⚠️ raw flags ([#49](https://github.com/letsrevel/euinvoice/issues/49)) |
|
|
30
|
+
| XRechnung 3.0 CVD | UBL 2.1, CII D16B | ❌ | ❌ conforming documents (BR-CL-13) | ⚠️ raw flags, see below |
|
|
31
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ EN 16931, XRECHNUNG | CII in PDF/A-3 | ✅ | ✅ | ⚠️ EN 16931 / XRechnung rules only |
|
|
32
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ BASIC, EXTENDED | CII in PDF/A-3 | ❌ | ✅ EXTENDED-only content listed as unmapped | ❌ |
|
|
33
|
+
| Factur-X 1.0 / ZUGFeRD 2.1+ MINIMUM, BASIC WL | CII in PDF/A-3 | ❌ | ❌ detected and extracted only | ❌ |
|
|
34
|
+
| ZUGFeRD 2.0 MINIMUM, BASIC, EXTENDED | CII in PDF/A-3 | ❌ | ✅ BASIC, EXTENDED (EXTENDED-only content listed as unmapped; corpus EXTENDED samples with codes outside the CEN lists, ABK BR-CL-19 and 9958 BR-CL-25, raise `ParseError` like their 2.1 twins, [#69](https://github.com/letsrevel/euinvoice/issues/69)); ❌ MINIMUM | ❌ |
|
|
35
|
+
|
|
36
|
+
- **Parse** means read into the EN 16931 model. Readers never drop input silently: every element or attribute
|
|
37
|
+
without a business term is listed in `parse_detailed(...).unmapped`.
|
|
38
|
+
- **Validate** reports the raw severities of the official rule sets. KoSIT's per-scenario severity overrides are
|
|
39
|
+
not applied ([#49](https://github.com/letsrevel/euinvoice/issues/49)): for example, 2 of the 6 official XRechnung Extension instances get fatal findings
|
|
40
|
+
that KoSIT downgrades.
|
|
41
|
+
- **XRechnung CVD:** BR-DE-CVD-03 (fatal, `XRechnung-UBL-validation.sch` lines 560-562) needs an item
|
|
42
|
+
classification with list id `CVD`, which BR-CL-13 (fatal, CEN `EN16931-UBL-codes.sch` lines 67-68) and
|
|
43
|
+
therefore the model refuse. No *conforming* CVD invoice can be built or read. `validate()` rejects every CVD
|
|
44
|
+
document: BR-CL-13 when it carries the `CVD` item classification, BR-DE-CVD-03 (fatal) when it does not
|
|
45
|
+
(KoSIT downgrades BR-CL-13, [#49](https://github.com/letsrevel/euinvoice/issues/49)).
|
|
46
|
+
- **Factur-X:** the rows above cover the Factur-X 1.0 / ZUGFeRD 2.1+ BT-24s. `facturx.embed` / `facturx.extract`
|
|
47
|
+
write and read the PDF container (the `[pdf]` extra). The
|
|
48
|
+
Factur-X XSD and Schematron are not pinned yet ([#42](https://github.com/letsrevel/euinvoice/issues/42)), so
|
|
49
|
+
`validate()` checks the embedded XML of the EN 16931 and XRECHNUNG levels against the EN 16931 / XRechnung
|
|
50
|
+
rules only, and raises `ArtifactsNotAvailableError` for MINIMUM, BASIC WL, BASIC and EXTENDED. MINIMUM and
|
|
51
|
+
BASIC WL carry no invoice lines, so they cannot become an `Invoice`: `parse()` raises `ParseError`
|
|
52
|
+
([#69](https://github.com/letsrevel/euinvoice/issues/69)). The library does not check PDF/A-3 conformance
|
|
53
|
+
(the test suite runs veraPDF on `embed()` output). ZUGFeRD 2.0 PDFs are extract-only (not generated). Their
|
|
54
|
+
MINIMUM, BASIC and EXTENDED BT-24s (`urn:zugferd.de:2p0:*`) and the colon spellings of BASIC and EXTENDED
|
|
55
|
+
(`urn:cen.eu:en16931:2017:compliant:factur-x.eu:1p0:*`) are not registered as profiles, but `validate()` raises
|
|
56
|
+
`ArtifactsNotAvailableError` for them as for the levels above (CLI exit 2;
|
|
57
|
+
[#98](https://github.com/letsrevel/euinvoice/issues/98)). A ZUGFeRD 2.0 EN 16931 invoice carries the core BT-24
|
|
58
|
+
and is validated as EN 16931 core.
|
|
59
|
+
ZUGFeRD 1.0 PDFs are extract-only (`parse()` raises `UnsupportedDocumentError`).
|
|
60
|
+
|
|
61
|
+
Planned later: ebInterface, more national CIUSes (RO, HR, FR, DK, …), FatturaPA, KSeF, Facturae, and
|
|
62
|
+
clearance/transport integrations.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv add euinvoice # model + UBL/CII serialize/parse
|
|
68
|
+
uv add 'euinvoice[validate]' # + official Schematron validation (SaxonC-HE)
|
|
69
|
+
uv add 'euinvoice[pdf]' # + Factur-X / ZUGFeRD PDF embedding
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Until 0.1.0 is on PyPI: `uv add 'euinvoice @ git+https://github.com/letsrevel/euinvoice'`.
|
|
73
|
+
|
|
74
|
+
The official validation artifacts are **downloaded, not bundled**, because of their licences. Fetch them
|
|
75
|
+
once (pinned and sha256-verified):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python -m euinvoice artifacts fetch # cache: $EUINVOICE_ARTIFACTS_DIR or ~/.cache/euinvoice
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
`draft` is an `euinvoice.InvoiceDraft`: the invoice without its derived totals. These examples run as tests
|
|
84
|
+
(`tests/_readme.py` builds the synthetic XRechnung draft they use); see the [quickstart](docs/quickstart.md) for
|
|
85
|
+
building a draft.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
>>> from euinvoice import calc, detect, parse, parse_detailed, profiles, to_xml, validate
|
|
89
|
+
>>> invoice = calc.complete(draft) # derive line totals, document totals and the VAT breakdown (EN 16931)
|
|
90
|
+
>>> xml = to_xml(invoice, profile=profiles.XRECHNUNG, syntax="cii") # raises PreflightError on blocking findings
|
|
91
|
+
>>> parse(xml) == profiles.XRECHNUNG.prepare(invoice) # lossless; to_xml wrote the profile's BT-24 (prepare)
|
|
92
|
+
True
|
|
93
|
+
>>> parse_detailed(xml).unmapped # XPaths of input with no business term; parse() discards them
|
|
94
|
+
()
|
|
95
|
+
>>> found = detect(xml) # syntax and profile from the root element and BT-24
|
|
96
|
+
>>> found.syntax, found.profile.id
|
|
97
|
+
(<Syntax.CII: 'cii'>, 'xrechnung')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Validation needs the `[validate]` extra and the fetched artifacts. `validate()` takes XML; for a PDF, pass
|
|
101
|
+
`facturx.extract(pdf).xml` or use the CLI.
|
|
102
|
+
|
|
103
|
+
<!-- readme-doctest: needs-artifacts -->
|
|
104
|
+
```python
|
|
105
|
+
>>> report = validate(xml) # profile auto-detected from BT-24
|
|
106
|
+
>>> report.ok
|
|
107
|
+
True
|
|
108
|
+
>>> for finding in report.findings:
|
|
109
|
+
... print(finding.rule_id, finding.severity, finding.message)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Factur-X / ZUGFeRD needs the `[pdf]` extra:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
>>> from euinvoice import facturx
|
|
116
|
+
>>> # your renderer produces the human-readable PDF/A-3 (e.g. WeasyPrint pdf_variant="pdf/a-3b")
|
|
117
|
+
>>> hybrid_pdf = facturx.embed(rendered_pdf, invoice, profile=profiles.FACTURX_EN16931)
|
|
118
|
+
>>> found = facturx.extract(hybrid_pdf)
|
|
119
|
+
>>> found.filename, found.conformance_level, found.profile.id
|
|
120
|
+
('factur-x.xml', 'EN 16931', 'facturx-en16931')
|
|
121
|
+
>>> parse(hybrid_pdf) == profiles.FACTURX_EN16931.prepare(invoice) # parse() reads PDFs too
|
|
122
|
+
True
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Command line
|
|
126
|
+
|
|
127
|
+
`python -m euinvoice` wraps the same functions. `FILE` is UBL, CII or a Factur-X / ZUGFeRD PDF (`-` reads
|
|
128
|
+
stdin), and `--profile` takes a profile id such as `xrechnung` or `peppol`.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python -m euinvoice validate invoice.xml [--profile ID] [--json] # one line per finding, then the verdict
|
|
132
|
+
python -m euinvoice convert invoice.xml --to ubl|cii [--profile ID] [-o OUT]
|
|
133
|
+
python -m euinvoice info invoice.pdf [--json] # syntax, BT-24, profile, container, totals
|
|
134
|
+
python -m euinvoice artifacts fetch [--only NAME]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Exit codes: `0` success (warnings allowed); `1` the document was rejected (a `fatal` or `error` finding, a
|
|
138
|
+
refused conversion, an unreadable invoice) or an artifact failed its integrity check; `2` no verdict (usage
|
|
139
|
+
error, unknown profile, unreadable file, failed download, missing artifacts or extras, a Factur-X level whose
|
|
140
|
+
rules are not pinned). `--help` on each subcommand lists them.
|
|
141
|
+
|
|
142
|
+
## Documentation
|
|
143
|
+
|
|
144
|
+
The [`docs/`](docs/index.md) folder holds the guide: [quickstart](docs/quickstart.md),
|
|
145
|
+
[concepts](docs/concepts.md), [validation](docs/validation.md), [Factur-X](docs/facturx.md), a
|
|
146
|
+
[mapping guide](docs/mapping/index.md) with synthetic freelancer and ticketing examples, and the
|
|
147
|
+
[BT mapping](docs/reference/bt-mapping.md). It builds with `make docs` (mkdocs-material). The GitHub Pages site goes live
|
|
148
|
+
once the maintainer enables Pages for the repository and sets the repository variable `DOCS_DEPLOY=true`.
|
|
149
|
+
|
|
150
|
+
## Known limitations
|
|
151
|
+
|
|
152
|
+
Open questions waiting for a maintainer decision (`needs-human`):
|
|
153
|
+
|
|
154
|
+
- [#42](https://github.com/letsrevel/euinvoice/issues/42): the Factur-X / ZUGFeRD XSD and Schematron have no
|
|
155
|
+
pinnable official download, so Factur-X levels are not validated against Factur-X rules (see above).
|
|
156
|
+
- [#69](https://github.com/letsrevel/euinvoice/issues/69): Factur-X MINIMUM and BASIC WL cannot be read into the
|
|
157
|
+
model.
|
|
158
|
+
- [#49](https://github.com/letsrevel/euinvoice/issues/49): KoSIT's XRechnung severity overrides are not applied,
|
|
159
|
+
so the verdict can differ from the KoSIT validator in both directions (e.g. BR-CL-21/23, XRechnung Extension
|
|
160
|
+
and CVD).
|
|
161
|
+
- [#67](https://github.com/letsrevel/euinvoice/issues/67): the XRechnung profiles set no default BT-23; the caller
|
|
162
|
+
must provide it (the pre-flight reports PEPPOL-EN16931-R001 otherwise).
|
|
163
|
+
- [#40](https://github.com/letsrevel/euinvoice/issues/40): the hardened parser rejects a single text node over
|
|
164
|
+
10,000,000 bytes, i.e. a BT-125 attachment over about 7.5 MB.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
make setup # uv sync --all-extras --group dev
|
|
170
|
+
make check # ruff + mypy --strict + file length
|
|
171
|
+
make test # unit tests, ≥95% branch coverage
|
|
172
|
+
make artifacts # fetch official artifacts + corpora
|
|
173
|
+
make conformance # run them
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md). Security reports: [`SECURITY.md`](SECURITY.md).
|
|
177
|
+
|
|
178
|
+
## Licence
|
|
179
|
+
|
|
180
|
+
MIT. Official validation artifacts are third-party works under their own licences (EUPL-1.2,
|
|
181
|
+
Apache-2.0 and others). They are fetched at runtime and never redistributed by this package.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "euinvoice"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "EN 16931 e-invoicing for Python: build, serialize (UBL/CII), validate, parse and embed (Factur-X) European e-invoices."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Biagio Distefano", email = "biagio@biagiodistefano.io" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["e-invoicing", "en16931", "peppol", "xrechnung", "factur-x", "zugferd", "ubl", "cii", "einvoice"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Programming Language :: Python :: 3.14",
|
|
20
|
+
"Topic :: Office/Business :: Financial :: Accounting",
|
|
21
|
+
"Topic :: Text Processing :: Markup :: XML",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
requires-python = ">=3.12"
|
|
25
|
+
dependencies = [
|
|
26
|
+
"lxml>=5.3",
|
|
27
|
+
"pydantic>=2.11",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
# Official Schematron validation (XSLT 3.0). SaxonC-HE is MPL-2.0.
|
|
32
|
+
validate = [
|
|
33
|
+
"saxonche>=12.9,!=13.0.0",
|
|
34
|
+
]
|
|
35
|
+
# Factur-X / ZUGFeRD PDF/A-3 embedding and extraction.
|
|
36
|
+
pdf = [
|
|
37
|
+
"pypdf>=6.1",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/letsrevel/euinvoice"
|
|
42
|
+
Issues = "https://github.com/letsrevel/euinvoice/issues"
|
|
43
|
+
Changelog = "https://github.com/letsrevel/euinvoice/blob/main/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
[build-system]
|
|
46
|
+
requires = ["uv_build>=0.11.8,<0.13.0"]
|
|
47
|
+
build-backend = "uv_build"
|
|
48
|
+
|
|
49
|
+
[dependency-groups]
|
|
50
|
+
dev = [
|
|
51
|
+
"hypothesis>=6.168.4",
|
|
52
|
+
"licensecheck>=2026.0.8",
|
|
53
|
+
"lxml-stubs>=0.5.1",
|
|
54
|
+
"mkdocs-material>=9.7.7",
|
|
55
|
+
"mkdocstrings[python]>=1.0.6",
|
|
56
|
+
"mypy>=2.4.0",
|
|
57
|
+
"pip-audit>=2.10.1",
|
|
58
|
+
"pytest>=9.1.1",
|
|
59
|
+
"pytest-cov>=7.1.0",
|
|
60
|
+
"pytest-xdist>=3.8.0",
|
|
61
|
+
"ruff>=0.16.10",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 120
|
|
66
|
+
target-version = "py312"
|
|
67
|
+
src = ["src", "tests", "scripts"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
preview = true
|
|
71
|
+
# Compared to revel-backend: adds bugbear (B), comprehensions (C4), naming (N), pytest style (PT),
|
|
72
|
+
# pyupgrade (UP), simplify (SIM), ruff-specific (RUF), and flake8-bandit (S), which replaces the
|
|
73
|
+
# separate bandit run. Security rules that matter most here (hardened XML parsing) are enforced
|
|
74
|
+
# by tests, see CLAUDE.md.
|
|
75
|
+
select = ["B", "C4", "C90", "D", "E", "F", "I", "N", "import-private-name", "too-many-nested-blocks", "PT", "RUF", "S", "SIM", "T20", "UP", "W"]
|
|
76
|
+
ignore = ["undocumented-public-module", "undocumented-public-package", "undocumented-magic-method", "undocumented-public-init"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff.lint.per-file-ignores]
|
|
79
|
+
"tests/**" = ["D", "assert", "import-private-name"]
|
|
80
|
+
"scripts/**" = ["print"]
|
|
81
|
+
# The Syntax enum lives in the package root (IMPLEMENTATION_PLAN.md §4: "Syntax enum {UBL, CII}").
|
|
82
|
+
"src/euinvoice/syntax/__init__.py" = ["non-empty-init-module"]
|
|
83
|
+
# The lazy ``facturx`` __getattr__ sits under ``if not TYPE_CHECKING`` so mypy users still get attribute errors (#27).
|
|
84
|
+
"src/euinvoice/__init__.py" = ["non-empty-init-module"]
|
|
85
|
+
# One row per business term (keyed by BT id for the coverage gate #32): long XPaths, kept one row per line.
|
|
86
|
+
"tests/syntax/test_*_write_bts.py" = ["line-too-long"]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.mccabe]
|
|
89
|
+
max-complexity = 10
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.pydocstyle]
|
|
92
|
+
convention = "google"
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.pylint]
|
|
95
|
+
max-nested-blocks = 4
|
|
96
|
+
|
|
97
|
+
[tool.mypy]
|
|
98
|
+
strict = true
|
|
99
|
+
extra_checks = true
|
|
100
|
+
warn_unreachable = true
|
|
101
|
+
warn_unused_ignores = true
|
|
102
|
+
plugins = ["pydantic.mypy"]
|
|
103
|
+
files = ["src", "tests", "scripts"]
|
|
104
|
+
|
|
105
|
+
[[tool.mypy.overrides]]
|
|
106
|
+
# saxonche ships no type information.
|
|
107
|
+
module = "saxonche.*"
|
|
108
|
+
ignore_missing_imports = true
|
|
109
|
+
|
|
110
|
+
[tool.pydantic-mypy]
|
|
111
|
+
init_typed = true
|
|
112
|
+
init_forbid_extra = true
|
|
113
|
+
warn_required_dynamic_aliases = true
|
|
114
|
+
|
|
115
|
+
[tool.pytest.ini_options]
|
|
116
|
+
testpaths = ["tests"]
|
|
117
|
+
# Dev scripts in scripts/ are importable from tests (tests/scripts/test_<name>.py).
|
|
118
|
+
pythonpath = ["scripts", "tests"]
|
|
119
|
+
addopts = "-ra --strict-markers --strict-config -m 'not conformance'"
|
|
120
|
+
xfail_strict = true
|
|
121
|
+
markers = [
|
|
122
|
+
"conformance: runs the official validation artifacts and upstream example corpora (needs `make artifacts`); run with `make conformance`",
|
|
123
|
+
]
|
|
124
|
+
filterwarnings = ["error"]
|
|
125
|
+
|
|
126
|
+
[tool.coverage.run]
|
|
127
|
+
branch = true
|
|
128
|
+
source = ["euinvoice"]
|
|
129
|
+
|
|
130
|
+
[tool.coverage.report]
|
|
131
|
+
fail_under = 95
|
|
132
|
+
show_missing = true
|
|
133
|
+
skip_covered = true
|
|
134
|
+
exclude_also = [
|
|
135
|
+
"if t.TYPE_CHECKING:",
|
|
136
|
+
"raise NotImplementedError",
|
|
137
|
+
"@t.overload",
|
|
138
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""EN 16931 e-invoicing: build, serialize (UBL/CII), validate, parse and embed (Factur-X) European e-invoices.
|
|
2
|
+
|
|
3
|
+
The public API (plan §4): :func:`to_xml`, :func:`parse`, :func:`parse_detailed`, :func:`validate` and
|
|
4
|
+
:func:`detect`, the :class:`Invoice` / :class:`InvoiceDraft` models and the ``calc``, ``profiles`` and
|
|
5
|
+
``facturx`` subpackages. ``facturx`` needs the ``[pdf]`` extra and is imported on first access
|
|
6
|
+
(``euinvoice.facturx`` or ``from euinvoice import facturx``); it is not in ``__all__``, so ``from euinvoice import *``
|
|
7
|
+
works without pypdf. ``import euinvoice`` loads neither pypdf nor saxonche (the ``[validate]`` extra, loaded when
|
|
8
|
+
validating).
|
|
9
|
+
|
|
10
|
+
The functions :func:`detect` and :func:`validate` live in the modules ``euinvoice.detection`` and
|
|
11
|
+
``euinvoice.validation`` (renamed in #93 so the functions shadow no module).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
import typing as t
|
|
16
|
+
from importlib.metadata import version
|
|
17
|
+
|
|
18
|
+
from euinvoice import calc, profiles
|
|
19
|
+
from euinvoice._api import parse, parse_detailed, to_xml
|
|
20
|
+
from euinvoice.detection import detect
|
|
21
|
+
from euinvoice.model import Invoice, InvoiceDraft
|
|
22
|
+
from euinvoice.report import ValidationReport
|
|
23
|
+
from euinvoice.syntax import Syntax
|
|
24
|
+
from euinvoice.syntax.result import ParseResult
|
|
25
|
+
from euinvoice.validation import validate
|
|
26
|
+
|
|
27
|
+
if t.TYPE_CHECKING:
|
|
28
|
+
# The redundant alias marks an explicit re-export, so ruff does not add ``facturx`` to ``__all__`` (which would
|
|
29
|
+
# make ``from euinvoice import *`` need pypdf).
|
|
30
|
+
from euinvoice import facturx as facturx
|
|
31
|
+
|
|
32
|
+
__version__ = version("euinvoice")
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Invoice",
|
|
36
|
+
"InvoiceDraft",
|
|
37
|
+
"ParseResult",
|
|
38
|
+
"Syntax",
|
|
39
|
+
"ValidationReport",
|
|
40
|
+
"__version__",
|
|
41
|
+
"calc",
|
|
42
|
+
"detect",
|
|
43
|
+
"parse",
|
|
44
|
+
"parse_detailed",
|
|
45
|
+
"profiles",
|
|
46
|
+
"to_xml",
|
|
47
|
+
"validate",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Hidden from type checkers so that a typo such as ``euinvoice.facturxx`` is a mypy error, not ``Any``; the
|
|
52
|
+
# ``TYPE_CHECKING`` import above gives them ``facturx``.
|
|
53
|
+
if not t.TYPE_CHECKING:
|
|
54
|
+
|
|
55
|
+
def __getattr__(name: str) -> t.Any:
|
|
56
|
+
"""Import ``euinvoice.facturx`` on first access (it needs the ``[pdf]`` extra).
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
AttributeError: ``name`` is not a lazily imported subpackage.
|
|
60
|
+
ImportError: ``name`` is ``"facturx"`` and pypdf is not installed; the message names the extra.
|
|
61
|
+
"""
|
|
62
|
+
if name == "facturx":
|
|
63
|
+
return importlib.import_module("euinvoice.facturx")
|
|
64
|
+
raise AttributeError(f"module 'euinvoice' has no attribute {name!r}")
|