vdi2770
Read a VDI 2770 handover-documentation container and get back a typed model — without extracting anything to disk, without opening a socket, and without any dependencies.
pip install vdi2770
import vdi2770
box = vdi2770.read_container_file("handover.zip")
for c in box.walk():
if c.metadata_bytes is None:
continue
doc = vdi2770.build_document(vdi2770.parse_xml(c.metadata_bytes), c.where)
print(c.path, [(i.domain_id, i.id) for i in doc.identifiers],
[k.class_id for k in doc.classifications])
handover.zip [('SUPPLIER', 'DOC-2024-0001')] ['03-01']
handover.zip!/pumps.zip [('SUPPLIER', 'DOC-2024-0002')] ['02-04']
It decides nothing
There is no is_valid() here, on purpose. Whether a container is correct is a
question about VDI 2770, and the answer depends on which supplement your customer
sent you. This library tells you what is in the file and where it is written; the
opinion is yours to supply.
If you want an opinion supplied for you, vdi2770-validate
is this library plus a rule set, as a command-line tool.
Three properties, each tested rather than promised
Nothing is extracted to disk. Members are decompressed into memory under a budget and dropped. There is no temporary directory to clean up and no path traversal to get wrong, because no path is ever joined.
Nothing is fetched. No socket is opened for any input, ever — including XML that asks for one. An entity declaration is refused outright rather than resolved-but-locally, so there is no parser setting to get wrong later.
A refusal is reported, not raised. A member that blows a budget becomes a
Defect on the container and the read continues, so one hostile file inside a
supplier archive does not cost you the other four hundred.
What comes back
read_container(data, name) returns a Container:
path |
handover.zip!/pumps.zip — the JAR convention, so it stays greppable |
kind |
DOCUMENTATION, DOCUMENT, UNKNOWN, or UNREADABLE |
members, file_names |
what the reader can open — the budget filter and the readability sweep have both run |
present |
every file name the archive declares, including members that were refused. Whether a name is there is a fact about the directory; being unable to inflate the bytes behind it does not unsay it |
metadata_bytes, metadata_name |
the metadata that was found, if any |
children, walk() |
inner containers, opened to three levels |
defects |
what the reader could not do, and why |
rejected |
members present in the archive but refused, and why |
near_misses |
reserved name → (kind, the name that nearly matched), kind being in-a-subfolder, path-prefixed or case-differs. vdi2770_metadata.xml in an archive with no metadata is worth saying; how to say it is yours, not ours |
duplicate_names |
a ZIP may carry the same name twice; readers disagree about which one wins |
build_document(node, where) returns a Document whose every node carries a
Location with the line and column it was written at, which is the reason this
package parses XML itself instead of handing you an ElementTree.
read_pdf(data) returns four facts and no verdict: is_pdf, header,
encrypted, and pdfa_claim — the last being what the file's own metadata
claims, such as "2b". Nothing here verifies that claim. Verifying PDF/A takes
a PDF/A validator, and this is not one.
Defect kinds
not-a-zip, too-many-members, unsafe-member-name, member-too-large,
suspicious-compression, archive-too-large, metadata-too-large,
metadata-unreadable, member-unreadable, nesting-too-deep,
container-budget-exhausted, decompression-budget-exhausted,
member-budget-exhausted, ambiguous-name.
These strings are part of the public surface; a test in this package fails if the code grows a kind that this list does not name.
vdi2770.REFUSAL_KINDS is the subset of those that can name a member in
Container.rejected — what a caller needs a sentence for. Working that subset
out by reading this module's source is how two of them came to be missed.
The last three are the budgets that span the whole read rather than one archive: a
documentation container may legitimately hold hundreds of inner containers, and
their metadata is held for as long as you walk the tree. Ten thousand of them,
each with sixteen megabytes of metadata, is a permitted input under every
per-archive limit and about 156 GiB of memory — and the same tree can ask the
readability sweep to inflate terabytes while no single member is over its cap.
MAX_CONTAINERS and MAX_TOTAL_METADATA_BYTES bound the first,
MAX_TOTAL_DECOMPRESSED the second. MAX_TOTAL_MEMBERS bounds a third thing
the other two do not: this package keeps one record per entry named anywhere
in the tree, and ten thousand entries in each of a thousand archives is ten
million of them whatever their bytes weigh. Hitting any of them is reported
rather than silently truncating the tree.
Supported
Python 3.9 and up. The budgets are module constants in vdi2770.zipread — per
archive: MAX_MEMBERS, MAX_MEMBER_BYTES, MAX_TOTAL_BYTES, MAX_RATIO with
its MIN_SUSPICIOUS_BYTES floor, MAX_METADATA_BYTES, MAX_CONTAINER_LEVELS;
across one read: MAX_CONTAINERS, MAX_TOTAL_METADATA_BYTES,
MAX_TOTAL_DECOMPRESSED, MAX_TOTAL_MEMBERS. vdi2770.pdfread has seven of its own for the PDF scan:
MAX_STREAMS, MAX_STREAM_SCAN, MAX_INFLATED_PER_STREAM,
MAX_INFLATED_TOTAL, MAX_XMP_PACKETS, MAX_PDFA_PREFIXES,
MAX_TRAILER_SCAN. A test fails if
either module grows one this list does not name. You can read them all, and they
are deliberately not arguments, so a caller cannot turn them off by accident.
Unofficial
Not affiliated with, endorsed by, or connected to VDI, the Digital Data Chain Consortium, or IDTA. VDI 2770 is a guideline published by the Verein Deutscher Ingenieure; this is an independent reader for the container format it describes, written without access to the guideline text, which is sold rather than published. What that means for what this library can and cannot claim is spelled out in the validator's scope note.
Apache-2.0.
Release files for vdi2770 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vdi2770-0.6.0.tar.gz | 50.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vdi2770-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.1 kB
Release files / vdi2770-0.6.0.tar.gz
| Download URL | vdi2770-0.6.0.tar.gz |
|---|---|
| Size | 50.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1eb5abc018131339944f571dc78d4ad321f3c2d71b0f99e486f6b6119ec84933
|
|
BLAKE2b-256 checksum How to use checksums |
65cb9cd5cc07fc855df2e649d474e75f92dc5e5cf2376d21d51b90202f8b22a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / vdi2770-0.6.0-py3-none-any.whl
| Download URL | vdi2770-0.6.0-py3-none-any.whl |
|---|---|
| Size | 30.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4861c6441bde0fa5fe2ee2a073bcc053a3b2a631526821d046d0f8caeff42548
|
|
BLAKE2b-256 checksum How to use checksums |
2857685a6e28f0c5c4a3a2c8757c5be0f0eeab7af4952e361d778e2309ecef25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log