xmlvalid 1.0.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.
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .venv/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ uv.lock
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - First release: `Client` and `AsyncClient` with `validate()`, `submit()`, `wait_for()`,
6
+ `validation()`, `cancel()`, `report()`, `share()`, `usage()`, `schemas()`; typed results and
7
+ exceptions; `webhooks.verify()`.
xmlvalid-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xmlvalid.com
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.
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.5
2
+ Name: xmlvalid
3
+ Version: 1.0.0
4
+ Summary: Python client for the xmlvalid API: validate XML and e-invoices (Peppol, XRechnung, Factur-X, UBL, CII) against XSD, DTD, RelaxNG and Schematron business rules
5
+ Project-URL: Homepage, https://xmlvalid.com
6
+ Project-URL: Documentation, https://xmlvalid.com/docs/api
7
+ Project-URL: Source, https://github.com/xmlvalid/xmlvalid-python
8
+ Project-URL: Issues, https://github.com/xmlvalid/xmlvalid-python/issues
9
+ Project-URL: Changelog, https://github.com/xmlvalid/xmlvalid-python/blob/main/CHANGELOG.md
10
+ Author-email: xmlvalid <support@xmlvalid.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: e-invoice,en16931,factur-x,peppol,schematron,ubl,validation,xml,xrechnung,xsd
14
+ Classifier: Development Status :: 5 - Production/Stable
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Text Processing :: Markup :: XML
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx<1,>=0.27
23
+ Description-Content-Type: text/markdown
24
+
25
+ # xmlvalid for Python
26
+
27
+ Python client for the [xmlvalid API](https://xmlvalid.com/docs/api): validate XML documents and
28
+ e-invoices against XSD, DTD, RelaxNG and Schematron business rules. Peppol BIS, XRechnung,
29
+ Factur-X / ZUGFeRD, EN 16931, UBL, CII and national rules (France, Italy, Croatia, Romania and
30
+ more) are in the [schema catalog](https://xmlvalid.com/schemas); your own schemas work too.
31
+
32
+ - Python 3.10+, sync and async (asyncio, trio), built on [httpx](https://www.python-httpx.org)
33
+ - Large files are streamed, not loaded into memory
34
+ - Waits for the result for you, retries when rate limited
35
+ - Typed results and exceptions, webhook signature checks
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install xmlvalid
41
+ ```
42
+
43
+ Create an API key under [Dashboard → API keys](https://xmlvalid.com/dashboard/api).
44
+
45
+ ## Validate
46
+
47
+ ```python
48
+ from xmlvalid import Client
49
+
50
+ xmlvalid = Client("xv_…") # or set XMLVALID_API_KEY
51
+
52
+ validation = xmlvalid.validate("invoice.xml", schema="peppol-bis-3-invoice")
53
+
54
+ if validation.is_valid:
55
+ print("Valid")
56
+ else:
57
+ for error in validation.errors:
58
+ print(error) # Line 139: [BR-CO-16] Amount due for payment (BT-115) = …
59
+ ```
60
+
61
+ The document can be:
62
+
63
+ ```python
64
+ xmlvalid.validate("invoice.xml") # a path (XML, .gz, .zip, EPUB, Factur-X PDF)
65
+ xmlvalid.validate(Path("invoice.xml")) # a Path
66
+ xmlvalid.validate(data, name="invoice.xml") # bytes
67
+ xmlvalid.validate(open("invoice.xml", "rb")) # a binary file object
68
+ xmlvalid.validate(text="<Invoice>…</Invoice>") # a string
69
+ xmlvalid.validate(url="https://example.com/feed.xml") # a public URL, downloaded by xmlvalid
70
+ ```
71
+
72
+ The schema:
73
+
74
+ | Argument | Validates against |
75
+ |---|---|
76
+ | `schema="peppol-bis-3-invoice"` | A [catalog](https://xmlvalid.com/schemas) entry, with its business rules |
77
+ | `schema="auto"` | The catalog profile an e-invoice names (BT-24), for mixed invoices |
78
+ | `schema="my-12"` | One of your saved schemas |
79
+ | `schema_file="order.xsd"` | An XSD, DTD or RelaxNG file sent with the document |
80
+ | `doctype=True` | The DOCTYPE the document declares |
81
+ | none | Well-formedness only |
82
+
83
+ Your own Schematron runs after the schema check: `rules="rules.sch"`, or for rules with
84
+ includes, relative path: file: `rules={"main.sch": "/path/main.sch", "parts/totals.sch": "/path/parts/totals.sch"}`.
85
+
86
+ ## Async
87
+
88
+ ```python
89
+ from xmlvalid import AsyncClient
90
+
91
+ async with AsyncClient() as xmlvalid:
92
+ validation = await xmlvalid.validate("invoice.xml", schema="xrechnung-3-ubl")
93
+ ```
94
+
95
+ Every method of `Client` is there, awaited.
96
+
97
+ ## The result
98
+
99
+ ```python
100
+ validation.status # done, failed (on xmlvalid's side) or cancelled
101
+ validation.result.outcome # valid, warnings, invalid, schema, input or failed
102
+ validation.result.errors_total # all of them; the first 500 are listed
103
+ for error in validation.errors:
104
+ error.line, error.column, error.level, error.type # xsd, schematron, …
105
+ error.rule # BR-CO-16 for a business rule
106
+ error.message
107
+ error.rule_url # the rule's page on xmlvalid.com
108
+ error.help_url # how to fix this kind of error
109
+ validation.warnings
110
+ validation.raw # the API's JSON, as a dict
111
+ ```
112
+
113
+ For a .zip, `validation.result.items` has one result per document; for a PDF,
114
+ `validation.result.pdf` names the invoice found in it.
115
+
116
+ ## Long validations, reports and the account
117
+
118
+ `validate()` waits up to 15 minutes (`timeout=`); a `ValidationTimeoutError` carries the
119
+ validation, which goes on on the server. To queue and come back later:
120
+
121
+ ```python
122
+ validation = xmlvalid.submit("big.xml", schema="ubl-2.1-invoice")
123
+ # …
124
+ validation = xmlvalid.wait_for(validation.id) # or xmlvalid.validation(id), xmlvalid.cancel(id)
125
+
126
+ html = xmlvalid.report(validation) # the HTML report
127
+ link = xmlvalid.share([validation]) # a public link for 30 days (Pro): link.url
128
+
129
+ usage = xmlvalid.usage() # usage.used, usage.limit, usage.remaining
130
+ schemas = xmlvalid.schemas() # your saved schemas, then the catalog
131
+ ```
132
+
133
+ ## Errors
134
+
135
+ Every exception extends `xmlvalid.XmlvalidError`. API refusals are `ApiError`s with
136
+ `status_code`, `error_code` and, for `invalid_request`, `fields`:
137
+
138
+ | Exception | When |
139
+ |---|---|
140
+ | `AuthenticationError` | 401: no key, a revoked key |
141
+ | `PlanLimitError` | 403: not in your plan |
142
+ | `NotFoundError` | 404: no such validation, or it expired (24 hours) |
143
+ | `NotFinishedError` | 409: the report of a validation still running |
144
+ | `DocumentTooLargeError` | 413: over your plan's size limit |
145
+ | `InvalidRequestError` | 422: unknown schema, a bad URL, … |
146
+ | `QuotaExceededError` | 429: this month's validations are used up |
147
+ | `RateLimitedError` | 429: still limited after the retries; `retry_after` seconds |
148
+ | `ServerError` | 5xx |
149
+ | `TransportError` | The request could not be sent or its answer read |
150
+ | `ValidationTimeoutError` | Still running after `timeout=` (also a `TimeoutError`) |
151
+
152
+ ## Webhooks
153
+
154
+ On Pro, xmlvalid can call your endpoint when a validation finishes or a URL monitor changes
155
+ state. Check the signature with the endpoint's secret and the raw body:
156
+
157
+ ```python
158
+ from xmlvalid import Event, webhooks
159
+
160
+ event = webhooks.verify(
161
+ request.body, # the raw bytes, as received
162
+ request.headers.get("Xmlvalid-Signature"),
163
+ os.environ["XMLVALID_WEBHOOK_SECRET"],
164
+ ) # raises SignatureVerificationError
165
+
166
+ if event.type == Event.VALIDATION_COMPLETED:
167
+ validation = event.validation
168
+ ```
169
+
170
+ A retried delivery has the same `event.id`. The other events are `monitor.failed`,
171
+ `monitor.recovered` (`event.data["monitor"]`, `event.data["check"]`) and `ping`.
172
+
173
+ ## Options
174
+
175
+ ```python
176
+ Client(
177
+ api_key="xv_…", # default: XMLVALID_API_KEY
178
+ base_url="https://xmlvalid.com/api/v1",
179
+ timeout=900, # seconds validate() waits in all
180
+ wait=10, # seconds the API holds a new request (0-60)
181
+ max_retries=3, # retries of a 429 rate_limited
182
+ http_client=httpx.Client(...), # proxies, certificates, a mock transport in tests
183
+ )
184
+ ```
185
+
186
+ Use it as a context manager, or call `close()`, to close the connection pool.
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ uv sync
192
+ uv run pytest
193
+ uv run mypy
194
+ uv run ruff check && uv run ruff format --check
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT
@@ -0,0 +1,175 @@
1
+ # xmlvalid for Python
2
+
3
+ Python client for the [xmlvalid API](https://xmlvalid.com/docs/api): validate XML documents and
4
+ e-invoices against XSD, DTD, RelaxNG and Schematron business rules. Peppol BIS, XRechnung,
5
+ Factur-X / ZUGFeRD, EN 16931, UBL, CII and national rules (France, Italy, Croatia, Romania and
6
+ more) are in the [schema catalog](https://xmlvalid.com/schemas); your own schemas work too.
7
+
8
+ - Python 3.10+, sync and async (asyncio, trio), built on [httpx](https://www.python-httpx.org)
9
+ - Large files are streamed, not loaded into memory
10
+ - Waits for the result for you, retries when rate limited
11
+ - Typed results and exceptions, webhook signature checks
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install xmlvalid
17
+ ```
18
+
19
+ Create an API key under [Dashboard → API keys](https://xmlvalid.com/dashboard/api).
20
+
21
+ ## Validate
22
+
23
+ ```python
24
+ from xmlvalid import Client
25
+
26
+ xmlvalid = Client("xv_…") # or set XMLVALID_API_KEY
27
+
28
+ validation = xmlvalid.validate("invoice.xml", schema="peppol-bis-3-invoice")
29
+
30
+ if validation.is_valid:
31
+ print("Valid")
32
+ else:
33
+ for error in validation.errors:
34
+ print(error) # Line 139: [BR-CO-16] Amount due for payment (BT-115) = …
35
+ ```
36
+
37
+ The document can be:
38
+
39
+ ```python
40
+ xmlvalid.validate("invoice.xml") # a path (XML, .gz, .zip, EPUB, Factur-X PDF)
41
+ xmlvalid.validate(Path("invoice.xml")) # a Path
42
+ xmlvalid.validate(data, name="invoice.xml") # bytes
43
+ xmlvalid.validate(open("invoice.xml", "rb")) # a binary file object
44
+ xmlvalid.validate(text="<Invoice>…</Invoice>") # a string
45
+ xmlvalid.validate(url="https://example.com/feed.xml") # a public URL, downloaded by xmlvalid
46
+ ```
47
+
48
+ The schema:
49
+
50
+ | Argument | Validates against |
51
+ |---|---|
52
+ | `schema="peppol-bis-3-invoice"` | A [catalog](https://xmlvalid.com/schemas) entry, with its business rules |
53
+ | `schema="auto"` | The catalog profile an e-invoice names (BT-24), for mixed invoices |
54
+ | `schema="my-12"` | One of your saved schemas |
55
+ | `schema_file="order.xsd"` | An XSD, DTD or RelaxNG file sent with the document |
56
+ | `doctype=True` | The DOCTYPE the document declares |
57
+ | none | Well-formedness only |
58
+
59
+ Your own Schematron runs after the schema check: `rules="rules.sch"`, or for rules with
60
+ includes, relative path: file: `rules={"main.sch": "/path/main.sch", "parts/totals.sch": "/path/parts/totals.sch"}`.
61
+
62
+ ## Async
63
+
64
+ ```python
65
+ from xmlvalid import AsyncClient
66
+
67
+ async with AsyncClient() as xmlvalid:
68
+ validation = await xmlvalid.validate("invoice.xml", schema="xrechnung-3-ubl")
69
+ ```
70
+
71
+ Every method of `Client` is there, awaited.
72
+
73
+ ## The result
74
+
75
+ ```python
76
+ validation.status # done, failed (on xmlvalid's side) or cancelled
77
+ validation.result.outcome # valid, warnings, invalid, schema, input or failed
78
+ validation.result.errors_total # all of them; the first 500 are listed
79
+ for error in validation.errors:
80
+ error.line, error.column, error.level, error.type # xsd, schematron, …
81
+ error.rule # BR-CO-16 for a business rule
82
+ error.message
83
+ error.rule_url # the rule's page on xmlvalid.com
84
+ error.help_url # how to fix this kind of error
85
+ validation.warnings
86
+ validation.raw # the API's JSON, as a dict
87
+ ```
88
+
89
+ For a .zip, `validation.result.items` has one result per document; for a PDF,
90
+ `validation.result.pdf` names the invoice found in it.
91
+
92
+ ## Long validations, reports and the account
93
+
94
+ `validate()` waits up to 15 minutes (`timeout=`); a `ValidationTimeoutError` carries the
95
+ validation, which goes on on the server. To queue and come back later:
96
+
97
+ ```python
98
+ validation = xmlvalid.submit("big.xml", schema="ubl-2.1-invoice")
99
+ # …
100
+ validation = xmlvalid.wait_for(validation.id) # or xmlvalid.validation(id), xmlvalid.cancel(id)
101
+
102
+ html = xmlvalid.report(validation) # the HTML report
103
+ link = xmlvalid.share([validation]) # a public link for 30 days (Pro): link.url
104
+
105
+ usage = xmlvalid.usage() # usage.used, usage.limit, usage.remaining
106
+ schemas = xmlvalid.schemas() # your saved schemas, then the catalog
107
+ ```
108
+
109
+ ## Errors
110
+
111
+ Every exception extends `xmlvalid.XmlvalidError`. API refusals are `ApiError`s with
112
+ `status_code`, `error_code` and, for `invalid_request`, `fields`:
113
+
114
+ | Exception | When |
115
+ |---|---|
116
+ | `AuthenticationError` | 401: no key, a revoked key |
117
+ | `PlanLimitError` | 403: not in your plan |
118
+ | `NotFoundError` | 404: no such validation, or it expired (24 hours) |
119
+ | `NotFinishedError` | 409: the report of a validation still running |
120
+ | `DocumentTooLargeError` | 413: over your plan's size limit |
121
+ | `InvalidRequestError` | 422: unknown schema, a bad URL, … |
122
+ | `QuotaExceededError` | 429: this month's validations are used up |
123
+ | `RateLimitedError` | 429: still limited after the retries; `retry_after` seconds |
124
+ | `ServerError` | 5xx |
125
+ | `TransportError` | The request could not be sent or its answer read |
126
+ | `ValidationTimeoutError` | Still running after `timeout=` (also a `TimeoutError`) |
127
+
128
+ ## Webhooks
129
+
130
+ On Pro, xmlvalid can call your endpoint when a validation finishes or a URL monitor changes
131
+ state. Check the signature with the endpoint's secret and the raw body:
132
+
133
+ ```python
134
+ from xmlvalid import Event, webhooks
135
+
136
+ event = webhooks.verify(
137
+ request.body, # the raw bytes, as received
138
+ request.headers.get("Xmlvalid-Signature"),
139
+ os.environ["XMLVALID_WEBHOOK_SECRET"],
140
+ ) # raises SignatureVerificationError
141
+
142
+ if event.type == Event.VALIDATION_COMPLETED:
143
+ validation = event.validation
144
+ ```
145
+
146
+ A retried delivery has the same `event.id`. The other events are `monitor.failed`,
147
+ `monitor.recovered` (`event.data["monitor"]`, `event.data["check"]`) and `ping`.
148
+
149
+ ## Options
150
+
151
+ ```python
152
+ Client(
153
+ api_key="xv_…", # default: XMLVALID_API_KEY
154
+ base_url="https://xmlvalid.com/api/v1",
155
+ timeout=900, # seconds validate() waits in all
156
+ wait=10, # seconds the API holds a new request (0-60)
157
+ max_retries=3, # retries of a 429 rate_limited
158
+ http_client=httpx.Client(...), # proxies, certificates, a mock transport in tests
159
+ )
160
+ ```
161
+
162
+ Use it as a context manager, or call `close()`, to close the connection pool.
163
+
164
+ ## Development
165
+
166
+ ```bash
167
+ uv sync
168
+ uv run pytest
169
+ uv run mypy
170
+ uv run ruff check && uv run ruff format --check
171
+ ```
172
+
173
+ ## License
174
+
175
+ MIT
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "xmlvalid"
7
+ dynamic = ["version"]
8
+ description = "Python client for the xmlvalid API: validate XML and e-invoices (Peppol, XRechnung, Factur-X, UBL, CII) against XSD, DTD, RelaxNG and Schematron business rules"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "xmlvalid", email = "support@xmlvalid.com" }]
14
+ keywords = ["xml", "validation", "xsd", "schematron", "e-invoice", "peppol", "xrechnung", "factur-x", "ubl", "en16931"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Topic :: Text Processing :: Markup :: XML",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = ["httpx>=0.27,<1"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://xmlvalid.com"
28
+ Documentation = "https://xmlvalid.com/docs/api"
29
+ Source = "https://github.com/xmlvalid/xmlvalid-python"
30
+ Issues = "https://github.com/xmlvalid/xmlvalid-python/issues"
31
+ Changelog = "https://github.com/xmlvalid/xmlvalid-python/blob/main/CHANGELOG.md"
32
+
33
+ [dependency-groups]
34
+ dev = ["pytest>=8", "mypy>=1.13", "ruff>=0.8"]
35
+
36
+ [tool.hatch.version]
37
+ path = "src/xmlvalid/_version.py"
38
+
39
+ [tool.hatch.build.targets.sdist]
40
+ include = ["src", "tests", "README.md", "CHANGELOG.md", "LICENSE"]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
44
+ filterwarnings = ["error"]
45
+
46
+ [tool.mypy]
47
+ strict = true
48
+ files = ["src", "tests"]
49
+
50
+ [tool.ruff]
51
+ line-length = 120
52
+ target-version = "py310"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
@@ -0,0 +1,54 @@
1
+ """Python client for the xmlvalid API: https://xmlvalid.com/docs/api"""
2
+
3
+ from . import webhooks
4
+ from ._client import DEFAULT_BASE_URL, AsyncClient, Client, DocumentInput, Rules
5
+ from ._version import __version__
6
+ from .errors import (
7
+ ApiError,
8
+ AuthenticationError,
9
+ DocumentTooLargeError,
10
+ InvalidRequestError,
11
+ NotFinishedError,
12
+ NotFoundError,
13
+ PlanLimitError,
14
+ QuotaExceededError,
15
+ RateLimitedError,
16
+ ServerError,
17
+ SignatureVerificationError,
18
+ TransportError,
19
+ ValidationTimeoutError,
20
+ XmlvalidError,
21
+ )
22
+ from .models import ArchiveItem, Diagnostic, Event, ReportLink, Result, Schema, Usage, Validation
23
+
24
+ __all__ = [
25
+ "DEFAULT_BASE_URL",
26
+ "ApiError",
27
+ "ArchiveItem",
28
+ "AsyncClient",
29
+ "AuthenticationError",
30
+ "Client",
31
+ "Diagnostic",
32
+ "DocumentInput",
33
+ "DocumentTooLargeError",
34
+ "Event",
35
+ "InvalidRequestError",
36
+ "NotFinishedError",
37
+ "NotFoundError",
38
+ "PlanLimitError",
39
+ "QuotaExceededError",
40
+ "RateLimitedError",
41
+ "ReportLink",
42
+ "Result",
43
+ "Rules",
44
+ "Schema",
45
+ "ServerError",
46
+ "SignatureVerificationError",
47
+ "TransportError",
48
+ "Usage",
49
+ "Validation",
50
+ "ValidationTimeoutError",
51
+ "XmlvalidError",
52
+ "__version__",
53
+ "webhooks",
54
+ ]