hdiffpatch 1.1.0__cp314-cp314-win_amd64.whl
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.
- hdiffpatch/__init__.py +80 -0
- hdiffpatch/_base_config.py +56 -0
- hdiffpatch/_bzip2_config.py +84 -0
- hdiffpatch/_c_extension.cp314-win_amd64.pyd +0 -0
- hdiffpatch/_c_extension.pyi +121 -0
- hdiffpatch/_lzma_config.py +213 -0
- hdiffpatch/_tamp_config.py +83 -0
- hdiffpatch/_zlib_config.py +177 -0
- hdiffpatch/_zstd_config.py +113 -0
- hdiffpatch/py.typed +0 -0
- hdiffpatch-1.1.0.dist-info/DELVEWHEEL +2 -0
- hdiffpatch-1.1.0.dist-info/METADATA +377 -0
- hdiffpatch-1.1.0.dist-info/RECORD +19 -0
- hdiffpatch-1.1.0.dist-info/WHEEL +5 -0
- hdiffpatch-1.1.0.dist-info/licenses/LICENSE +201 -0
- hdiffpatch-1.1.0.dist-info/scm_file_list.json +2282 -0
- hdiffpatch-1.1.0.dist-info/scm_version.json +8 -0
- hdiffpatch-1.1.0.dist-info/top_level.txt +1 -0
- hdiffpatch.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""ZlibConfig class for configuring zlib compression parameters."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
import attrs
|
|
7
|
+
|
|
8
|
+
from ._base_config import BaseConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ZlibStrategy(Enum):
|
|
12
|
+
"""Zlib compression strategies."""
|
|
13
|
+
|
|
14
|
+
DEFAULT = "default"
|
|
15
|
+
FILTERED = "filtered"
|
|
16
|
+
HUFFMAN_ONLY = "huffman_only"
|
|
17
|
+
RLE = "rle"
|
|
18
|
+
FIXED = "fixed"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _convert_strategy(value: Union[ZlibStrategy, str]) -> ZlibStrategy:
|
|
22
|
+
"""Convert and validate strategy parameter.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
value : ZlibStrategy or str
|
|
27
|
+
The strategy value to convert
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
ZlibStrategy
|
|
32
|
+
The validated strategy enum
|
|
33
|
+
|
|
34
|
+
Raises
|
|
35
|
+
------
|
|
36
|
+
TypeError
|
|
37
|
+
If value is not a ZlibStrategy enum or string
|
|
38
|
+
ValueError
|
|
39
|
+
If string value is not a valid strategy
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(value, str):
|
|
42
|
+
try:
|
|
43
|
+
return ZlibStrategy(value.lower())
|
|
44
|
+
except ValueError:
|
|
45
|
+
valid_strategies = [s.value for s in ZlibStrategy]
|
|
46
|
+
raise ValueError(f"Invalid strategy '{value}'. Valid options: {valid_strategies}") from None
|
|
47
|
+
elif isinstance(value, ZlibStrategy):
|
|
48
|
+
return value
|
|
49
|
+
else:
|
|
50
|
+
raise TypeError("strategy must be a ZlibStrategy enum or string")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@attrs.frozen
|
|
54
|
+
class ZlibConfig(BaseConfig):
|
|
55
|
+
"""Configuration for zlib compression parameters.
|
|
56
|
+
|
|
57
|
+
This class allows fine-grained control over zlib compression behavior,
|
|
58
|
+
including compression level, memory usage, window size, and strategy.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
level : int, default=9
|
|
63
|
+
Compression level (0-9). Higher values give better
|
|
64
|
+
compression but are slower. 0 = no compression, 9 = best compression.
|
|
65
|
+
memory_level : int, default=8
|
|
66
|
+
Memory level (1-9). Higher values use more memory but
|
|
67
|
+
may improve compression speed.
|
|
68
|
+
window : int, default=15
|
|
69
|
+
Window size as power of 2 (9-15). Larger windows give
|
|
70
|
+
better compression but use more memory. (32KB window)
|
|
71
|
+
strategy : ZlibStrategy, default=ZlibStrategy.DEFAULT
|
|
72
|
+
Compression strategy optimized for different data types.
|
|
73
|
+
save_window_bits : bool, default=True
|
|
74
|
+
Whether to save window bits in compressed data header.
|
|
75
|
+
|
|
76
|
+
Examples
|
|
77
|
+
--------
|
|
78
|
+
Fast compression with minimal memory usage
|
|
79
|
+
|
|
80
|
+
>>> config = ZlibConfig(level=1, memory_level=1, window=9)
|
|
81
|
+
|
|
82
|
+
Best compression for text data
|
|
83
|
+
|
|
84
|
+
>>> config = ZlibConfig(level=9, strategy=ZlibStrategy.DEFAULT)
|
|
85
|
+
|
|
86
|
+
Optimized for PNG-like data
|
|
87
|
+
|
|
88
|
+
>>> config = ZlibConfig(strategy=ZlibStrategy.RLE)
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
level: int = attrs.field(
|
|
92
|
+
default=9,
|
|
93
|
+
validator=attrs.validators.and_(
|
|
94
|
+
attrs.validators.instance_of(int),
|
|
95
|
+
attrs.validators.ge(0),
|
|
96
|
+
attrs.validators.le(9),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
memory_level: int = attrs.field(
|
|
100
|
+
default=8,
|
|
101
|
+
validator=attrs.validators.and_(
|
|
102
|
+
attrs.validators.instance_of(int),
|
|
103
|
+
attrs.validators.ge(1),
|
|
104
|
+
attrs.validators.le(9),
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
window: int = attrs.field(
|
|
108
|
+
default=15,
|
|
109
|
+
validator=attrs.validators.and_(
|
|
110
|
+
attrs.validators.instance_of(int),
|
|
111
|
+
attrs.validators.ge(9),
|
|
112
|
+
attrs.validators.le(15),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
strategy: ZlibStrategy = attrs.field(
|
|
116
|
+
default=ZlibStrategy.DEFAULT,
|
|
117
|
+
converter=_convert_strategy,
|
|
118
|
+
)
|
|
119
|
+
save_window_bits: bool = attrs.field(
|
|
120
|
+
default=True,
|
|
121
|
+
converter=bool,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def fast(cls) -> "ZlibConfig":
|
|
126
|
+
"""Create a ZlibConfig optimized for speed.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
ZlibConfig
|
|
131
|
+
Configuration optimized for speed
|
|
132
|
+
"""
|
|
133
|
+
return cls(level=1, memory_level=1, window=9)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def balanced(cls) -> "ZlibConfig":
|
|
137
|
+
"""Create a ZlibConfig with balanced speed/compression.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
ZlibConfig
|
|
142
|
+
Configuration with balanced speed/compression tradeoff
|
|
143
|
+
"""
|
|
144
|
+
return cls(level=6, memory_level=8, window=15)
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def best_compression(cls) -> "ZlibConfig":
|
|
148
|
+
"""Create a ZlibConfig optimized for best compression.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
ZlibConfig
|
|
153
|
+
Configuration optimized for best compression
|
|
154
|
+
"""
|
|
155
|
+
return cls(level=9, memory_level=9, window=15)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def minimal_memory(cls) -> "ZlibConfig":
|
|
159
|
+
"""Create a ZlibConfig with minimal memory usage.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
ZlibConfig
|
|
164
|
+
Configuration with minimal memory usage
|
|
165
|
+
"""
|
|
166
|
+
return cls(level=6, memory_level=1, window=9)
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def png_optimized(cls) -> "ZlibConfig":
|
|
170
|
+
"""Create a ZlibConfig optimized for PNG-like data.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
ZlibConfig
|
|
175
|
+
Configuration optimized for PNG-like data
|
|
176
|
+
"""
|
|
177
|
+
return cls(level=9, strategy=ZlibStrategy.RLE)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""ZStdConfig class for configuring zstd compression parameters."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import attrs
|
|
6
|
+
|
|
7
|
+
from ._base_config import BaseConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@attrs.frozen
|
|
11
|
+
class ZStdConfig(BaseConfig):
|
|
12
|
+
"""Configuration for zstd compression parameters.
|
|
13
|
+
|
|
14
|
+
This class allows control over zstd compression behavior,
|
|
15
|
+
including compression level, window size, and threading.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
level : int, default=3
|
|
20
|
+
Compression level (1-22). Higher values give better
|
|
21
|
+
compression but are slower. 1 = fastest, 22 = best compression.
|
|
22
|
+
window : int or None, default=None
|
|
23
|
+
Window size as log2. Must be between 10 and 27.
|
|
24
|
+
Larger windows give better compression but use more memory.
|
|
25
|
+
None uses zstd default based on compression level.
|
|
26
|
+
workers : int, default=0
|
|
27
|
+
Number of worker threads (0-200). 0 = single-threaded.
|
|
28
|
+
|
|
29
|
+
Examples
|
|
30
|
+
--------
|
|
31
|
+
Fast compression with minimal memory usage
|
|
32
|
+
|
|
33
|
+
>>> config = ZStdConfig(level=1, workers=0)
|
|
34
|
+
|
|
35
|
+
Best compression for large files with multiple threads
|
|
36
|
+
|
|
37
|
+
>>> config = ZStdConfig(level=22, workers=4)
|
|
38
|
+
|
|
39
|
+
Balanced compression with custom window size
|
|
40
|
+
|
|
41
|
+
>>> config = ZStdConfig(level=6, window=20, workers=2)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
level: int = attrs.field(
|
|
45
|
+
default=3,
|
|
46
|
+
validator=attrs.validators.and_(
|
|
47
|
+
attrs.validators.instance_of(int),
|
|
48
|
+
attrs.validators.ge(1),
|
|
49
|
+
attrs.validators.le(22),
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
window: Optional[int] = attrs.field(
|
|
53
|
+
default=None,
|
|
54
|
+
validator=attrs.validators.optional(
|
|
55
|
+
attrs.validators.and_(
|
|
56
|
+
attrs.validators.instance_of(int),
|
|
57
|
+
attrs.validators.ge(10),
|
|
58
|
+
attrs.validators.le(27),
|
|
59
|
+
)
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
workers: int = attrs.field(
|
|
63
|
+
default=0,
|
|
64
|
+
validator=attrs.validators.and_(
|
|
65
|
+
attrs.validators.instance_of(int),
|
|
66
|
+
attrs.validators.ge(0),
|
|
67
|
+
attrs.validators.le(200),
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def fast(cls) -> "ZStdConfig":
|
|
73
|
+
"""Create a ZStdConfig optimized for speed.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
ZStdConfig
|
|
78
|
+
Configuration optimized for speed
|
|
79
|
+
"""
|
|
80
|
+
return cls(level=1, workers=0)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def balanced(cls) -> "ZStdConfig":
|
|
84
|
+
"""Create a ZStdConfig with balanced speed/compression.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
ZStdConfig
|
|
89
|
+
Configuration with balanced speed/compression tradeoff
|
|
90
|
+
"""
|
|
91
|
+
return cls(level=6, workers=2)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def best_compression(cls) -> "ZStdConfig":
|
|
95
|
+
"""Create a ZStdConfig optimized for best compression.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
ZStdConfig
|
|
100
|
+
Configuration optimized for best compression
|
|
101
|
+
"""
|
|
102
|
+
return cls(level=22, window=27, workers=4)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def minimal_memory(cls) -> "ZStdConfig":
|
|
106
|
+
"""Create a ZStdConfig with minimal memory usage.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
ZStdConfig
|
|
111
|
+
Configuration with minimal memory usage
|
|
112
|
+
"""
|
|
113
|
+
return cls(level=3, window=10, workers=0)
|
hdiffpatch/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Version: 1.13.0
|
|
2
|
+
Arguments: ['C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-6z3wtab3\\cp314-win_amd64\\build\\venv\\Scripts\\delvewheel', 'repair', '-w', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-6z3wtab3\\cp314-win_amd64\\repaired_wheel', '-v', 'C:\\Users\\runneradmin\\AppData\\Local\\Temp\\cibw-run-6z3wtab3\\cp314-win_amd64\\built_wheel\\hdiffpatch-1.1.0-cp314-cp314-win_amd64.whl']
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hdiffpatch
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Python wrapper around HDiffPatch C++ library for efficient binary diff/patch operations.
|
|
5
|
+
Author: Brian Pugh
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: homepage, https://github.com/BrianPugh/hdiffpatch-python
|
|
8
|
+
Project-URL: repository, https://github.com/BrianPugh/hdiffpatch-python
|
|
9
|
+
Keywords: binary,diff,patch,compression,hdiffpatch,delta,update
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: C++
|
|
21
|
+
Classifier: Programming Language :: Cython
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Topic :: System :: Archiving :: Compression
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: attrs>=21.3.0
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
**hdiffpatch-python** is a Python wrapper around the [HDiffPatch](https://github.com/sisong/HDiffPatch) C++ library, providing binary diff and patch operations with compression support.
|
|
32
|
+
|
|
33
|
+
<div align="center">
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
[](https://pypi.python.org/pypi/hdiffpatch)
|
|
37
|
+
[](https://github.com/BrianPugh/hdiffpatch-python/actions?query=workflow%3Atests)
|
|
38
|
+
[](https://codecov.io/github/BrianPugh/hdiffpatch-python?branch=main)
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
**hdiffpatch** requires Python `>=3.9` and can be installed via:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install hdiffpatch
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For development installation:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/BrianPugh/hdiffpatch-python.git
|
|
54
|
+
cd hdiffpatch-python
|
|
55
|
+
uv sync
|
|
56
|
+
uv run python rebuild.py # Build Cython extensions
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
**hdiffpatch** primarily provides 2 simple functions:
|
|
62
|
+
|
|
63
|
+
* `diff` for creating a patch.
|
|
64
|
+
* `apply` for applying a patch.
|
|
65
|
+
|
|
66
|
+
### Basic Usage
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import hdiffpatch
|
|
70
|
+
|
|
71
|
+
# Create binary data
|
|
72
|
+
old_data = b"Hello, world!"
|
|
73
|
+
new_data = b"Hello, HDiffPatch!"
|
|
74
|
+
|
|
75
|
+
# Create a diff
|
|
76
|
+
diff = hdiffpatch.diff(old_data, new_data)
|
|
77
|
+
|
|
78
|
+
# Apply the diff
|
|
79
|
+
result = hdiffpatch.apply(old_data, diff)
|
|
80
|
+
assert result == new_data
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### With Simple Compression
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import hdiffpatch
|
|
87
|
+
|
|
88
|
+
old_data = b"Large binary data..." * 1000
|
|
89
|
+
new_data = b"Modified binary data..." * 1000
|
|
90
|
+
|
|
91
|
+
# Create a compressed diff
|
|
92
|
+
diff = hdiffpatch.diff(old_data, new_data, compression="zlib")
|
|
93
|
+
|
|
94
|
+
# Apply patch
|
|
95
|
+
result = hdiffpatch.apply(old_data, diff)
|
|
96
|
+
assert result == new_data
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### With Advanced Compression Configuration
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import hdiffpatch
|
|
103
|
+
|
|
104
|
+
old_data = b"Large binary data..." * 1000
|
|
105
|
+
new_data = b"Modified binary data..." * 1000
|
|
106
|
+
|
|
107
|
+
# Use configuration classes for fine-grained control
|
|
108
|
+
config = hdiffpatch.ZlibConfig(level=9, window=12)
|
|
109
|
+
|
|
110
|
+
diff = hdiffpatch.diff(old_data, new_data, compression=config)
|
|
111
|
+
result = hdiffpatch.apply(old_data, diff)
|
|
112
|
+
assert result == new_data
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Recompressing Diffs
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
import hdiffpatch
|
|
119
|
+
|
|
120
|
+
old_data = b"Large binary data..." * 1000
|
|
121
|
+
new_data = b"Modified binary data..." * 1000
|
|
122
|
+
|
|
123
|
+
# Create a diff with zlib compression
|
|
124
|
+
diff_zlib = hdiffpatch.diff(old_data, new_data, compression="zlib")
|
|
125
|
+
|
|
126
|
+
# Recompress the same diff with zstd
|
|
127
|
+
diff_zstd = hdiffpatch.recompress(diff_zlib, compression="zstd")
|
|
128
|
+
|
|
129
|
+
# Remove compression entirely
|
|
130
|
+
diff_uncompressed = hdiffpatch.recompress(diff_zlib, compression="none")
|
|
131
|
+
|
|
132
|
+
# Both diffs produce the same result when applied
|
|
133
|
+
result1 = hdiffpatch.apply(old_data, diff_zlib)
|
|
134
|
+
result2 = hdiffpatch.apply(old_data, diff_zstd)
|
|
135
|
+
assert result1 == result2 == new_data
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## API Reference
|
|
139
|
+
|
|
140
|
+
### Core Functions
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
def diff(old_data, new_data, compression="none", *, validate=True) -> bytes
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Create a binary diff between two byte sequences.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
|
|
150
|
+
* `old_data` (bytes): Original data.
|
|
151
|
+
* `new_data` (bytes): Modified data.
|
|
152
|
+
* `compression` (str or config object): Compression type as string (`"none"`, `"zlib"`, `"lzma"`, `"lzma2"`, `"zstd"`, `"bzip2"`, `"tamp"`) or a compression configuration object.
|
|
153
|
+
* `validate` (bool): Test that the patch successfully converts `old_data` to `new_data`. This is a computationally inexpensive operation. Defaults to `True`.
|
|
154
|
+
|
|
155
|
+
**Returns:** `bytes` - Binary diff data that can be used with `apply()` and `old_data` to generate `new_data`.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
def apply(old_data, diff_data) -> bytes
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Apply a binary patch to reconstruct new data.
|
|
164
|
+
|
|
165
|
+
**Parameters:**
|
|
166
|
+
|
|
167
|
+
* `old_data` (bytes): Original data.
|
|
168
|
+
* `diff_data` (bytes): Patch data from `diff()`.
|
|
169
|
+
|
|
170
|
+
**Returns:** `bytes` - Reconstructed data. The `new_data` that was passed to `diff()`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
def recompress(diff_data, compression=None) -> bytes
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Recompress a diff with a different compression algorithm.
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
|
|
182
|
+
* `diff_data` (bytes): The diff data to recompress.
|
|
183
|
+
* `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.
|
|
184
|
+
|
|
185
|
+
**Returns:** `bytes` - The recompressed diff data
|
|
186
|
+
|
|
187
|
+
### Compression Configuration
|
|
188
|
+
|
|
189
|
+
For advanced compression control, **hdiffpatch** provides configuration classes for each compression algorithm:
|
|
190
|
+
|
|
191
|
+
#### ZStdConfig
|
|
192
|
+
|
|
193
|
+
Fine-grained control over Zstandard compression:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# Basic configuration
|
|
197
|
+
config = hdiffpatch.ZStdConfig(level=15, window=20, workers=2)
|
|
198
|
+
|
|
199
|
+
# Preset configurations
|
|
200
|
+
config = hdiffpatch.ZStdConfig.fast() # Optimized for speed
|
|
201
|
+
config = hdiffpatch.ZStdConfig.balanced() # Balanced speed/compression
|
|
202
|
+
config = hdiffpatch.ZStdConfig.best_compression() # Maximum compression
|
|
203
|
+
config = hdiffpatch.ZStdConfig.minimal_memory() # Minimal memory usage
|
|
204
|
+
|
|
205
|
+
# Use with diff
|
|
206
|
+
diff = hdiffpatch.diff(old_data, new_data, compression=config)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Parameters:**
|
|
210
|
+
|
|
211
|
+
* `level` (1-22): Compression level, higher = better compression
|
|
212
|
+
* `window` (10-27): Window size as log2, larger = better compression
|
|
213
|
+
* `workers` (0-200): Number of threads, 0 = single-threaded
|
|
214
|
+
|
|
215
|
+
#### ZlibConfig
|
|
216
|
+
|
|
217
|
+
Fine-grained control over zlib compression:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# Basic configuration
|
|
221
|
+
config = hdiffpatch.ZlibConfig(
|
|
222
|
+
level=9,
|
|
223
|
+
memory_level=8,
|
|
224
|
+
window=15,
|
|
225
|
+
strategy=hdiffpatch.ZlibStrategy.DEFAULT
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Preset configurations
|
|
229
|
+
config = hdiffpatch.ZlibConfig.fast()
|
|
230
|
+
config = hdiffpatch.ZlibConfig.balanced()
|
|
231
|
+
config = hdiffpatch.ZlibConfig.best_compression()
|
|
232
|
+
config = hdiffpatch.ZlibConfig.minimal_memory()
|
|
233
|
+
config = hdiffpatch.ZlibConfig.png_optimized() # Optimized for PNG-like data
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Parameters:**
|
|
237
|
+
|
|
238
|
+
* `level` (0-9): Compression level
|
|
239
|
+
* `memory_level` (1-9): Memory usage level
|
|
240
|
+
* `window` (9-15): Window size as power of 2
|
|
241
|
+
* `strategy`: Compression strategy (`DEFAULT`, `FILTERED`, `HUFFMAN_ONLY`, `RLE`, `FIXED`)
|
|
242
|
+
|
|
243
|
+
#### LzmaConfig and Lzma2Config
|
|
244
|
+
|
|
245
|
+
Fine-grained control over LZMA compression:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
# LZMA configuration
|
|
249
|
+
config = hdiffpatch.LzmaConfig(level=9, window=23, thread_num=1)
|
|
250
|
+
|
|
251
|
+
# LZMA2 configuration (supports more threads)
|
|
252
|
+
config = hdiffpatch.Lzma2Config(level=9, window=23, thread_num=4)
|
|
253
|
+
|
|
254
|
+
# Preset configurations available for both
|
|
255
|
+
config = hdiffpatch.LzmaConfig.fast()
|
|
256
|
+
config = hdiffpatch.LzmaConfig.balanced()
|
|
257
|
+
config = hdiffpatch.LzmaConfig.best_compression()
|
|
258
|
+
config = hdiffpatch.LzmaConfig.minimal_memory()
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Parameters:**
|
|
262
|
+
|
|
263
|
+
* `level` (0-9): Compression level
|
|
264
|
+
* `window` (12-30): Window size as log2
|
|
265
|
+
* `thread_num`: Number of threads (1-2 for LZMA, 1-64 for LZMA2)
|
|
266
|
+
|
|
267
|
+
#### BZip2Config
|
|
268
|
+
|
|
269
|
+
Fine-grained control over bzip2 compression:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
config = hdiffpatch.BZip2Config(level=9)
|
|
273
|
+
|
|
274
|
+
# Preset configurations
|
|
275
|
+
config = hdiffpatch.BZip2Config.fast()
|
|
276
|
+
config = hdiffpatch.BZip2Config.balanced()
|
|
277
|
+
config = hdiffpatch.BZip2Config.best_compression()
|
|
278
|
+
config = hdiffpatch.BZip2Config.minimal_memory()
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Parameters:**
|
|
282
|
+
|
|
283
|
+
* `level` (1-9): Compression level
|
|
284
|
+
|
|
285
|
+
#### TampConfig
|
|
286
|
+
|
|
287
|
+
Fine-grained control over [Tamp](https://github.com/BrianPugh/tamp) compression (embedded-friendly):
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
config = hdiffpatch.TampConfig(window=10)
|
|
291
|
+
|
|
292
|
+
# Preset configurations
|
|
293
|
+
config = hdiffpatch.TampConfig.fast()
|
|
294
|
+
config = hdiffpatch.TampConfig.balanced()
|
|
295
|
+
config = hdiffpatch.TampConfig.best_compression()
|
|
296
|
+
config = hdiffpatch.TampConfig.minimal_memory()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Parameters:**
|
|
300
|
+
|
|
301
|
+
* `window` (8-15): Window size as power of 2
|
|
302
|
+
|
|
303
|
+
### Exceptions
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
hdiffpatch.HDiffPatchError
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Compression Performance
|
|
310
|
+
|
|
311
|
+
Different compression algorithms offer trade-offs between compression ratio and speed:
|
|
312
|
+
|
|
313
|
+
* **`zlib`**: Good balance of speed and compression. Very common.
|
|
314
|
+
* **`zstd`**: Fast compression with good ratios.
|
|
315
|
+
* **`lzma`/`lzma2`**: Very high compression ratios, slower.
|
|
316
|
+
* **`bzip2`**: Good compression, moderate speed
|
|
317
|
+
* **`tamp`**: Embedded-friendly compression, minimal memory usage.
|
|
318
|
+
|
|
319
|
+
### Basic Compression Comparison
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
import hdiffpatch
|
|
323
|
+
|
|
324
|
+
# Large repetitive data
|
|
325
|
+
old_data = b"A" * 10000 + b"B" * 10000
|
|
326
|
+
new_data = b"A" * 10000 + b"C" * 10000
|
|
327
|
+
|
|
328
|
+
# Compare compression effectiveness
|
|
329
|
+
for compression in ["none", "zlib", "zstd", "lzma", "bzip2", "tamp"]:
|
|
330
|
+
diff = hdiffpatch.diff(old_data, new_data, compression=compression)
|
|
331
|
+
print(f"{compression}: {len(diff)} bytes")
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Advanced Configuration Comparison
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
import hdiffpatch
|
|
338
|
+
|
|
339
|
+
# Compare different configuration approaches
|
|
340
|
+
configs = {
|
|
341
|
+
"zstd_fast": hdiffpatch.ZStdConfig.fast(),
|
|
342
|
+
"zstd_best": hdiffpatch.ZStdConfig.best_compression(),
|
|
343
|
+
"zlib_balanced": hdiffpatch.ZlibConfig.balanced(),
|
|
344
|
+
"lzma2_custom": hdiffpatch.Lzma2Config(level=6, window=20, thread_num=4),
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
for name, config in configs.items():
|
|
348
|
+
diff = hdiffpatch.diff(old_data, new_data, compression=config)
|
|
349
|
+
print(f"{name}: {len(diff)} bytes")
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Real-World Example: MicroPython Firmware
|
|
353
|
+
|
|
354
|
+
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.
|
|
355
|
+
|
|
356
|
+
* [RPI_PICO-20241129-v1.24.1.uf2](https://micropython.org/resources/firmware/RPI_PICO-20241129-v1.24.1.uf2): 651 KB
|
|
357
|
+
* [RPI_PICO-20250415-v1.25.0.uf2](https://micropython.org/resources/firmware/RPI_PICO-20250415-v1.25.0.uf2): 652 KB
|
|
358
|
+
|
|
359
|
+
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?"
|
|
360
|
+
To answer this question, we compare the size of the compressed patch to the compressed firmware.
|
|
361
|
+
|
|
362
|
+
| Algorithm | Size (HDiffPatch) | Size (firmware) | Improvement |
|
|
363
|
+
|-----------|-------------------|-----------------|-------------|
|
|
364
|
+
| none | 209.7 KB | 652.0 KB | 3.11x |
|
|
365
|
+
| tamp | 143.1 KB | 322.8 KB | 2.26x |
|
|
366
|
+
| zstd | 133.4 KB | 277.6 KB | 2.08x |
|
|
367
|
+
| zlib | 125.5 KB | 251.8 KB | 2.01x |
|
|
368
|
+
| bzip2 | 128.6 KB | 246.2 KB | 1.91x |
|
|
369
|
+
| lzma | 116.9 KB | 222.7 KB | 1.91x |
|
|
370
|
+
|
|
371
|
+
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.
|
|
372
|
+
|
|
373
|
+
To reproduce these results:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
uv run python tools/micropython-binary-demo.py
|
|
377
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
hdiffpatch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
hdiffpatch/_base_config.py,sha256=WDY0Pl8bNRxFlA0AJtShmSkBr9nlUsQ-zKEAJjWBnrw,1657
|
|
3
|
+
hdiffpatch/_bzip2_config.py,sha256=6fMohYu5L3_AaMlxjZFhByxL8-JNvoPDcKNj-vZN0L8,2113
|
|
4
|
+
hdiffpatch/_c_extension.cp314-win_amd64.pyd,sha256=4aMSDAbITI84-bqdGrYmijj81zW2gp0rM2_VOB3LBYE,823296
|
|
5
|
+
hdiffpatch/_c_extension.pyi,sha256=cM56jgsEgPJHP_uEiwT74e6Cy6CjJeDbB_YFr5MIDuc,3345
|
|
6
|
+
hdiffpatch/_lzma_config.py,sha256=279rYp6DX_ZYq0TxZ3EsEbL2S2PwRuAJxFo9DFGT0oQ,6339
|
|
7
|
+
hdiffpatch/_tamp_config.py,sha256=MvZXbL6L9qIqTblxLVC3yw5hmK6X0bHhl40xlFe_MPw,2063
|
|
8
|
+
hdiffpatch/_zlib_config.py,sha256=-yBhc0mEj-j1_yAmDTWJ9mwa3fW1pimyFGyCcdRUyYY,5102
|
|
9
|
+
hdiffpatch/_zstd_config.py,sha256=4GWN-8mAOct1JDB6QLe1fX-QTfIrSRuuRxxQgnLTeMs,3238
|
|
10
|
+
hdiffpatch/__init__.py,sha256=PBIQLHNz30-Uae6XAJwW4wNkbTPq6isZDGioJWQH8qI,1630
|
|
11
|
+
hdiffpatch-1.1.0.dist-info/DELVEWHEEL,sha256=-jbqjRgQ-KwUcgGkYKs8BA0UEzvyNeKhvm4hbULgVW0,409
|
|
12
|
+
hdiffpatch-1.1.0.dist-info/METADATA,sha256=dmWChLJ8Y7R3r7xJVOxppUd1fb0d0iWIJF9F-dY5Wj8,12167
|
|
13
|
+
hdiffpatch-1.1.0.dist-info/RECORD,,
|
|
14
|
+
hdiffpatch-1.1.0.dist-info/scm_file_list.json,sha256=FJKC8I5Yw52AvZFZOtiJ3CR6Rzb5btBDNZyva_2Pzt4,153244
|
|
15
|
+
hdiffpatch-1.1.0.dist-info/scm_version.json,sha256=zDDmWuNX4epXvAyPPaE5WyRBD7sNnt0Fq7Uq5xj-VhM,168
|
|
16
|
+
hdiffpatch-1.1.0.dist-info/top_level.txt,sha256=IHwSCeRigLNb80MkH0f87HsMsvxktM_sC_H2Mg246tQ,11
|
|
17
|
+
hdiffpatch-1.1.0.dist-info/WHEEL,sha256=pIUzES5vCYHKBR6AfxXxDFyqjZ9ckCheusePfq1FYSo,101
|
|
18
|
+
hdiffpatch-1.1.0.dist-info/licenses/LICENSE,sha256=NlaN0UJZDwQsLaQ4kMTmn_Q4BogQGPxRbjlWM0D1SSI,11558
|
|
19
|
+
hdiffpatch.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll,sha256=pMIim9wqKmMKzcCVtNhgCOXD47x3cxdDVPPaT1vrnN4,575056
|