document-version-metadata-diff-python is a runnable Python demo that answers one question fast: what changed in a document's metadata between two revisions? Install groupdocs-metadata-net==26.5, run python main.py, and the seeded pair of DOCX revisions produces a classified property diff, two forensic reports, and JSON plus CSV exports, each step asserted with a PASS line.
- Flatten a file's complete property tree into one Python dict with a single call
- Classify metadata differences into added, removed, and changed with both values kept
- Isolate ownership signals (Creator, Editor, Manager, Company) using tag predicates
- Isolate the editing timeline: revision counters, editing time, created/modified/printed stamps
- Ship findings as a stable JSON schema and a four-column CSV
- About This Repository
- Key Features
- Prerequisites
- Repository Structure
- Implementation Examples
- Related Resources
- Keywords
This repository shows metadata version comparison end to end using GroupDocs.Metadata for Python via .NET. The library reads built-in fields, custom properties, and XMP through one find_properties call across 170+ formats, per the product documentation, and its tag system classifies identity and time properties without format-specific names. The examples target engineers who need change detection inside document workflows, from automated intake to dispute support. Everything downstream of the API stays in plain dicts, so the audit logic reads like ordinary Python. I keep this project around as my smoke test after library upgrades; if the six asserts pass, the metadata layer behaves.
| Feature | Description |
|---|---|
| Whole-tree search | find_properties walks every metadata package behind one predicate |
| Tag classification | Tags.person, Tags.corporate, and Tags.time mark properties by meaning |
| Interpreted values | dates and enumerations arrive human-readable, not as raw serials |
| Format breadth | the same calls serve DOCX, PDF, XLSX, images, and audio |
| Write support | properties can be updated or removed with the same API family |
A complete two-revision diff pipeline in six small functions. Tag-driven detectors handle the ownership and revision questions, audit-grade exports feed dashboards (JSON) and spreadsheets or SIEMs (CSV), and a self-asserting main.py verifies every step on the seeded sample pair.
You need Python 3 (any actively supported CPython with pip) and the package: pip install groupdocs-metadata-net==26.5. A license is optional. Evaluation mode runs everything here, and setting LICENSE_PATH in main.py lifts the evaluation limits.
document-version-metadata-diff-python/
β
βββ main.py
βββ requirements.txt
βββ methods/
β βββ __init__.py
β βββ compare_metadata_sets.py
β βββ detect_ownership_changes.py
β βββ detect_revision_history.py
β βββ export_diff_to_csv.py
β βββ export_diff_to_json.py
β βββ extract_all_metadata.py
βββ resources/
βββ document-v1.docx
βββ document-v2.docx
- main.py β drives all six functions against the sample pair and asserts each result
- requirements.txt β pins
groupdocs-metadata-net==26.5 - methods/extract_all_metadata.py β property tree to dict
- methods/compare_metadata_sets.py β the
MetadataDiffbuilder - methods/detect_ownership_changes.py β identity-change detector
- methods/detect_revision_history.py β editing-timeline detector
- methods/export_diff_to_json.py / export_diff_to_csv.py β report writers
- resources/ β seeded
document-v1.docxanddocument-v2.docx
The foundation call. One pass collects built-in, custom, and XMP properties keyed by qualified name, preferring interpreted_value so the dict is readable as-is.
result = {}
with Metadata(document_path) as metadata:
for prop in metadata.find_properties(lambda p: p.name is not None):
key = prop.name
value = (str(prop.interpreted_value) if prop.interpreted_value is not None
else (str(prop.value) if prop.value is not None else ""))
result[key] = value
return resultWhat this example shows:
find_properties with a name predicate is the whole extraction story; there is no per-layer code. The returned dict is what every later function consumes, and its size on the sample files is asserted to be non-zero by main.py.
The core diff. Both revisions go through the extractor, then set logic fills a MetadataDiff value object with three maps.
v1 = extract_all_metadata(path_v1)
v2 = extract_all_metadata(path_v2)
diff = MetadataDiff()
for k, v in v2.items():
if k not in v1:
diff.added[k] = v
elif v1[k] != v:
diff.changed[k] = (v1[k], v)
for k, v in v1.items():
if k not in v2:
diff.removed[k] = v
return diffWhat this example shows:
Changed entries keep old and new values as a pair, so the result is a finding rather than a hint. total_changes on the object sums all three maps for quick thresholds.
The identity question, answered directly. The module-level _read_ownership helper collects only properties tagged as person or company, and the delta loop reports differences with <missing> marking one-sided fields.
v1 = _read_ownership(path_v1)
v2 = _read_ownership(path_v2)
all_keys = set(v1.keys()) | set(v2.keys())
changes = {}
for k in all_keys:
old_v = v1.get(k, "<missing>")
new_v = v2.get(k, "<missing>")
if old_v != new_v:
changes[k] = (old_v, new_v)
return changesWhat this example shows:
Tag predicates make the detector format-independent: no property is named, yet Creator, LastSavedBy, Manager, and Company are all covered. A disappeared identity field surfaces just as loudly as a changed one.
The timing question. _read_revision mixes Tags.time predicates with name rules for Revision and EditTime counters, catching classified timestamps and counter fields together.
v1 = _read_revision(path_v1)
v2 = _read_revision(path_v2)
all_keys = set(v1.keys()) | set(v2.keys())
changes = {}
for k in all_keys:
old_v = v1.get(k, "<missing>")
new_v = v2.get(k, "<missing>")
if old_v != new_v:
changes[k] = (old_v, new_v)
return changesWhat this example shows:
RevisionNumber, TotalEditingTime, and LastPrinted move with every editing session even when the visible text is untouched. This detector surfaces that invisible activity as from/to pairs.
Machine-facing output. The three maps serialize into a stable schema, with changed entries expanded into from/to objects.
payload = {
"added": diff.added,
"removed": diff.removed,
"changed": {k: {"from": v[0], "to": v[1]} for k, v in diff.changed.items()},
}
with open(output_path, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2, ensure_ascii=False)What this example shows:
A dashboard or case-management API ingests diff.json without transformation, and because the schema never varies, reports from different runs line up into a timeline.
People-facing output. Four columns, one row per change, ready for Excel or a SIEM.
with open(output_path, "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["change_type", "property", "old_value", "new_value"])
for k, v in diff.added.items():
writer.writerow(["added", k, "", v])
for k, v in diff.removed.items():
writer.writerow(["removed", k, v, ""])
for k, (old_v, new_v) in diff.changed.items():
writer.writerow(["changed", k, old_v, new_v])What this example shows:
The flattening rule is fixed: added rows leave old_value empty, removed rows leave new_value empty, changed rows carry both. No parsing code needed on the receiving end.
Four tag groups: Tags.person.creator covers Author and LastSavedBy, Tags.person.editor covers the last editor, Tags.person.manager maps to Manager, and Tags.corporate.company maps to Company. The detector reports any of them whose value differs between versions, including fields present on only one side. Everything else, timestamps included, belongs to the revision detector instead. That split keeps each report readable.
Explore these additional resources to deepen your understanding of metadata version comparison:
-
Step-by-step use case guide in the documentation β the same pipeline as a tutorial series: Read the guide β
-
In-depth blog article about this project β the quick-start walkthrough built on this repo: Read the article β
-
How to Compare Document Metadata Between Versions in Java β the same audit approach on the Java platform: Read the article β
-
Edit Metadata in Python Applications β the wider read/update/remove API surface: Read the article β
-
Best Practices in Metadata Management β the policy context around property hygiene: Read the article β
document metadata diff, compare metadata versions, python metadata comparison, detect authorship changes, revision number tracking, TotalEditingTime, LastPrinted, document forensics python, e-discovery metadata, metadata audit report, csv audit export, json diff schema, groupdocs metadata python, python via .net, find_properties, metadata tags, docx properties, document version control, compliance snapshot, metadata tampering detection
Need help? Get Free Support | Read the Docs