https://github.com/brianpugh/hdiffpatch-python

hdiffpatch-python is a python wrapper around the HDiffPatch C++ library, providing binary diff and patch operations with compression support.

https://github.com/brianpugh/hdiffpatch-python

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.5%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

hdiffpatch-python is a python wrapper around the HDiffPatch C++ library, providing binary diff and patch operations with compression support.

Basic Info
  • Host: GitHub
  • Owner: BrianPugh
  • License: apache-2.0
  • Language: Python
  • Default Branch: main
  • Size: 914 KB
Statistics
  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • Open Issues: 0
  • Releases: 1
Created 12 months ago · Last pushed 12 months ago
Metadata Files
Readme Contributing License

README.md

hdiffpatch-python is a Python wrapper around the HDiffPatch C++ library, providing binary diff and patch operations with compression support.

![Python compat](https://img.shields.io/badge/%3E=python-3.9-blue.svg) [![PyPi](https://img.shields.io/pypi/v/hdiffpatch.svg)](https://pypi.python.org/pypi/hdiffpatch) [![GHA Status](https://github.com/BrianPugh/hdiffpatch-python/actions/workflows/tests.yaml/badge.svg?branch=main)](https://github.com/BrianPugh/hdiffpatch-python/actions?query=workflow%3Atests) [![Coverage](https://codecov.io/github/BrianPugh/hdiffpatch-python/coverage.svg?branch=main)](https://codecov.io/github/BrianPugh/hdiffpatch-python?branch=main)

Installation

hdiffpatch requires Python >=3.9 and can be installed via:

bash pip install hdiffpatch

For development installation:

bash git clone https://github.com/BrianPugh/hdiffpatch-python.git cd hdiffpatch-python uv sync uv run python rebuild.py # Build Cython extensions

Quick Start

hdiffpatch primarily provides 2 simple functions:

  • diff for creating a patch.
  • apply for applying a patch.

Basic Usage

```python import hdiffpatch

Create binary data

olddata = b"Hello, world!" newdata = b"Hello, HDiffPatch!"

Create a diff

diff = hdiffpatch.diff(olddata, newdata)

Apply the diff

result = hdiffpatch.apply(olddata, diff) assert result == newdata ```

With Simple Compression

```python import hdiffpatch

olddata = b"Large binary data..." * 1000 newdata = b"Modified binary data..." * 1000

Create a compressed diff

diff = hdiffpatch.diff(olddata, newdata, compression="zlib")

Apply patch

result = hdiffpatch.apply(olddata, diff) assert result == newdata ```

With Advanced Compression Configuration

```python import hdiffpatch

olddata = b"Large binary data..." * 1000 newdata = b"Modified binary data..." * 1000

Use configuration classes for fine-grained control

config = hdiffpatch.ZlibConfig(level=9, window=12)

diff = hdiffpatch.diff(olddata, newdata, compression=config) result = hdiffpatch.apply(olddata, diff) assert result == newdata ```

Recompressing Diffs

```python import hdiffpatch

olddata = b"Large binary data..." * 1000 newdata = b"Modified binary data..." * 1000

Create a diff with zlib compression

diffzlib = hdiffpatch.diff(olddata, new_data, compression="zlib")

Recompress the same diff with zstd

diffzstd = hdiffpatch.recompress(diffzlib, compression="zstd")

Remove compression entirely

diffuncompressed = hdiffpatch.recompress(diffzlib, compression="none")

Both diffs produce the same result when applied

result1 = hdiffpatch.apply(olddata, diffzlib) result2 = hdiffpatch.apply(olddata, diffzstd) assert result1 == result2 == new_data ```

API Reference

Core Functions

python def diff(old_data, new_data, compression="none", *, validate=True) -> bytes

Create a binary diff between two byte sequences.

Parameters:

  • old_data (bytes): Original data.
  • new_data (bytes): Modified data.
  • compression (str or config object): Compression type as string ("none", "zlib", "lzma", "lzma2", "zstd", "bzip2", "tamp") or a compression configuration object.
  • validate (bool): Test that the patch successfully converts old_data to new_data. This is a computationally inexpensive operation. Defaults to True.

Returns: bytes - Binary diff data that can be used with apply() and old_data to generate new_data.


python def apply(old_data, diff_data) -> bytes

Apply a binary patch to reconstruct new data.

Parameters:

  • old_data (bytes): Original data.
  • diff_data (bytes): Patch data from diff().

Returns: bytes - Reconstructed data. The new_data that was passed to diff().


python def recompress(diff_data, compression=None) -> bytes

Recompress a diff with a different compression algorithm.

Parameters:

  • diff_data (bytes): The diff data to recompress.
  • compression (str or config object, optional): Target compression type as string ("none", "zlib", "lzma", "lzma2", "zstd", "bzip2", "tamp") or a compression configuration object. If None, removes compression.

Returns: bytes - The recompressed diff data

Compression Configuration

For advanced compression control, hdiffpatch provides configuration classes for each compression algorithm:

ZStdConfig

Fine-grained control over Zstandard compression:

```python

Basic configuration

config = hdiffpatch.ZStdConfig(level=15, window=20, workers=2)

Preset configurations

config = hdiffpatch.ZStdConfig.fast() # Optimized for speed config = hdiffpatch.ZStdConfig.balanced() # Balanced speed/compression config = hdiffpatch.ZStdConfig.bestcompression() # Maximum compression config = hdiffpatch.ZStdConfig.minimalmemory() # Minimal memory usage

Use with diff

diff = hdiffpatch.diff(olddata, newdata, compression=config) ```

Parameters:

  • level (1-22): Compression level, higher = better compression
  • window (10-27): Window size as log2, larger = better compression
  • workers (0-200): Number of threads, 0 = single-threaded

ZlibConfig

Fine-grained control over zlib compression:

```python

Basic configuration

config = hdiffpatch.ZlibConfig( level=9, memory_level=8, window=15, strategy=hdiffpatch.ZlibStrategy.DEFAULT )

Preset configurations

config = hdiffpatch.ZlibConfig.fast() config = hdiffpatch.ZlibConfig.balanced() config = hdiffpatch.ZlibConfig.bestcompression() config = hdiffpatch.ZlibConfig.minimalmemory() config = hdiffpatch.ZlibConfig.png_optimized() # Optimized for PNG-like data ```

Parameters:

  • level (0-9): Compression level
  • memory_level (1-9): Memory usage level
  • window (9-15): Window size as power of 2
  • strategy: Compression strategy (DEFAULT, FILTERED, HUFFMAN_ONLY, RLE, FIXED)

LzmaConfig and Lzma2Config

Fine-grained control over LZMA compression:

```python

LZMA configuration

config = hdiffpatch.LzmaConfig(level=9, window=23, thread_num=1)

LZMA2 configuration (supports more threads)

config = hdiffpatch.Lzma2Config(level=9, window=23, thread_num=4)

Preset configurations available for both

config = hdiffpatch.LzmaConfig.fast() config = hdiffpatch.LzmaConfig.balanced() config = hdiffpatch.LzmaConfig.bestcompression() config = hdiffpatch.LzmaConfig.minimalmemory() ```

Parameters:

  • level (0-9): Compression level
  • window (12-30): Window size as log2
  • thread_num: Number of threads (1-2 for LZMA, 1-64 for LZMA2)

BZip2Config

Fine-grained control over bzip2 compression:

```python config = hdiffpatch.BZip2Config(level=9, work_factor=30)

Preset configurations

config = hdiffpatch.BZip2Config.fast() config = hdiffpatch.BZip2Config.balanced() config = hdiffpatch.BZip2Config.bestcompression() config = hdiffpatch.BZip2Config.minimalmemory() ```

Parameters:

  • level (1-9): Compression level
  • work_factor (0-250): Work factor for worst-case scenarios

TampConfig

Fine-grained control over Tamp compression (embedded-friendly):

```python config = hdiffpatch.TampConfig(window=10)

Preset configurations

config = hdiffpatch.TampConfig.fast() config = hdiffpatch.TampConfig.balanced() config = hdiffpatch.TampConfig.bestcompression() config = hdiffpatch.TampConfig.minimalmemory() ```

Parameters:

  • window (8-15): Window size as power of 2

Exceptions

python hdiffpatch.HDiffPatchError

Compression Performance

Different compression algorithms offer trade-offs between compression ratio and speed:

  • zlib: Good balance of speed and compression. Very common.
  • zstd: Fast compression with good ratios.
  • lzma/lzma2: Very high compression ratios, slower.
  • bzip2: Good compression, moderate speed
  • tamp: Embedded-friendly compression, minimal memory usage.

Basic Compression Comparison

```python import hdiffpatch

Large repetitive data

olddata = b"A" * 10000 + b"B" * 10000 newdata = b"A" * 10000 + b"C" * 10000

Compare compression effectiveness

for compression in ["none", "zlib", "zstd", "lzma", "bzip2", "tamp"]: diff = hdiffpatch.diff(olddata, newdata, compression=compression) print(f"{compression}: {len(diff)} bytes") ```

Advanced Configuration Comparison

```python import hdiffpatch

Compare different configuration approaches

configs = { "zstdfast": hdiffpatch.ZStdConfig.fast(), "zstdbest": hdiffpatch.ZStdConfig.bestcompression(), "zlibbalanced": hdiffpatch.ZlibConfig.balanced(), "lzma2custom": hdiffpatch.Lzma2Config(level=6, window=20, threadnum=4), }

for name, config in configs.items(): diff = hdiffpatch.diff(olddata, newdata, compression=config) print(f"{name}: {len(diff)} bytes") ```

Real-World Example: MicroPython Firmware

Here's a comprehensive comparison using actual MicroPython firmware files with a 12-bit window size (4096 bytes). This window size was chosen because it is typically a good trade-off between memory-usage and compression-performance for embedded targets.

Since we're using compression for the diff, a natural question would be: "If I'm adding a decompression library to my target project, then how much smaller is the patch compared to just compressing the firmware?" To answer this question, we compare the size of the compressed patch to the compressed firmware.

| Algorithm | Size (HDiffPatch) | Size (firmware) | Improvement | |-----------|-------------------|-----------------|-------------| | none | 209.7 KB | 652.0 KB | 3.11x | | tamp | 143.1 KB | 322.8 KB | 2.26x | | zstd | 133.4 KB | 277.6 KB | 2.08x | | zlib | 125.5 KB | 251.8 KB | 2.01x | | bzip2 | 128.6 KB | 246.2 KB | 1.91x | | lzma | 116.9 KB | 222.7 KB | 1.91x |

In this example, using hdiffpatch resulted in a ~3x smaller update when compared to a naive uncompressed firmware update, and ~2x smaller when comparing against an equivalently-compressed firmware update.

To reproduce these results:

bash uv run python tools/micropython-binary-demo.py

Owner

  • Name: Brian Pugh
  • Login: BrianPugh
  • Kind: user
  • Location: Washington D.C.

Deep Learning Scientist and blockchain enthusiast

GitHub Events

Total
  • Push event: 10
  • Pull request event: 3
  • Create event: 1
Last Year
  • Push event: 10
  • Pull request event: 3
  • Create event: 1

Committers

Last synced: 12 months ago

All Time
  • Total Commits: 12
  • Total Committers: 1
  • Avg Commits per committer: 12.0
  • Development Distribution Score (DDS): 0.0
Past Year
  • Commits: 12
  • Committers: 1
  • Avg Commits per committer: 12.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Brian Pugh b****7@g****m 12

Issues and Pull Requests

Last synced: 10 months ago

Packages

  • Total packages: 1
  • Total downloads:
    • pypi 848 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 2
  • Total maintainers: 1
pypi.org: hdiffpatch

Python wrapper around HDiffPatch C++ library for efficient binary diff/patch operations.

  • Versions: 2
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 848 Last month
Rankings
Dependent packages count: 8.8%
Average: 29.3%
Dependent repos count: 49.7%
Maintainers (1)
Last synced: 10 months ago

Dependencies

.github/workflows/build_wheels.yaml actions
  • actions/cache v4 composite
  • actions/checkout v4 composite
  • actions/download-artifact v4 composite
  • actions/setup-python v5 composite
  • actions/upload-artifact v4 composite
  • docker/setup-qemu-action v3 composite
  • pypa/cibuildwheel v2.23.3 composite
  • pypa/gh-action-pypi-publish release/v1 composite
  • snok/install-poetry v1 composite
.github/workflows/tests.yaml actions
  • actions/cache v4 composite
  • actions/checkout v4 composite
  • actions/setup-python v5 composite
  • andstor/file-existence-action v3 composite
  • codecov/codecov-action v4 composite
  • snok/install-poetry v1 composite
poetry.lock pypi
  • asttokens 3.0.0
  • attrs 25.3.0
  • cfgv 3.4.0
  • colorama 0.4.6
  • coverage 7.9.2
  • cython 3.1.2
  • decorator 5.2.1
  • distlib 0.3.9
  • exceptiongroup 1.3.0
  • executing 2.2.0
  • filelock 3.18.0
  • identify 2.6.12
  • iniconfig 2.1.0
  • ipdb 0.13.13
  • ipython 8.18.1
  • jedi 0.19.2
  • line-profiler 4.2.0
  • matplotlib-inline 0.1.7
  • nodeenv 1.9.1
  • packaging 25.0
  • parso 0.8.4
  • pexpect 4.9.0
  • platformdirs 4.3.8
  • pluggy 1.6.0
  • pre-commit 4.2.0
  • prompt-toolkit 3.0.51
  • ptyprocess 0.7.0
  • pure-eval 0.2.3
  • pygments 2.19.2
  • pytest 8.4.1
  • pytest-cov 6.2.1
  • pytest-mock 3.14.1
  • pyyaml 6.0.2
  • stack-data 0.6.3
  • tomli 2.2.1
  • traitlets 5.14.3
  • typing-extensions 4.14.1
  • virtualenv 20.31.2
  • wcwidth 0.2.13
pyproject.toml pypi
  • ipdb >=0.13.9 debug
  • line_profiler >=3.5.1 debug
  • coverage >=5.1 develop
  • cython >=1.0.0 develop
  • pre_commit >=2.16.0 develop
  • pytest >=7.1.2 develop
  • pytest-cov >=3.0.0 develop
  • pytest-mock >=3.7.0 develop
  • attrs >=21.3.0
  • python ^3.9