BloomFilter-py 1.2.0__tar.gz → 2.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.
Files changed (38) hide show
  1. bloomfilter_py-2.0.0/CHANGELOG.md +22 -0
  2. bloomfilter_py-2.0.0/MANIFEST.in +4 -0
  3. bloomfilter_py-2.0.0/PKG-INFO +410 -0
  4. bloomfilter_py-2.0.0/README.md +356 -0
  5. bloomfilter_py-2.0.0/benchmarks/benchmark_backends.py +134 -0
  6. bloomfilter_py-2.0.0/bloomfilter/__init__.py +23 -0
  7. bloomfilter_py-2.0.0/bloomfilter/_bitarray.py +300 -0
  8. bloomfilter_py-2.0.0/bloomfilter/_murmur3.py +63 -0
  9. bloomfilter_py-2.0.0/bloomfilter/bloomfilter.py +528 -0
  10. bloomfilter_py-2.0.0/bloomfilter/bloomfilter_strategy.py +186 -0
  11. bloomfilter_py-2.0.0/bloomfilter/funnel.py +132 -0
  12. bloomfilter_py-2.0.0/bloomfilter/py.typed +0 -0
  13. bloomfilter_py-2.0.0/bloomfilter_py.egg-info/PKG-INFO +410 -0
  14. bloomfilter_py-2.0.0/bloomfilter_py.egg-info/SOURCES.txt +27 -0
  15. bloomfilter_py-2.0.0/bloomfilter_py.egg-info/requires.txt +4 -0
  16. {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/pyproject.toml +19 -8
  17. bloomfilter_py-2.0.0/scripts/check_api_compatibility.py +259 -0
  18. bloomfilter_py-2.0.0/scripts/verify_distribution.py +93 -0
  19. bloomfilter_py-2.0.0/tests/__init__.py +16 -0
  20. bloomfilter_py-2.0.0/tests/data/murmur3-vectors.json +3642 -0
  21. bloomfilter_py-2.0.0/tests/java/GuavaBloomFilterInterop.java +301 -0
  22. bloomfilter_py-2.0.0/tests/test_backends.py +454 -0
  23. bloomfilter_py-2.0.0/tests/test_bloomfilter.py +576 -0
  24. bloomfilter_py-2.0.0/tests/test_filter_operations.py +172 -0
  25. bloomfilter_py-2.0.0/tests/test_guava_interop.py +262 -0
  26. bloomfilter_py-1.2.0/PKG-INFO +0 -179
  27. bloomfilter_py-1.2.0/README.md +0 -124
  28. bloomfilter_py-1.2.0/bloomfilter/__init__.py +0 -3
  29. bloomfilter_py-1.2.0/bloomfilter/bloomfilter.py +0 -246
  30. bloomfilter_py-1.2.0/bloomfilter/bloomfilter_strategy.py +0 -129
  31. bloomfilter_py-1.2.0/bloomfilter_py.egg-info/PKG-INFO +0 -179
  32. bloomfilter_py-1.2.0/bloomfilter_py.egg-info/SOURCES.txt +0 -12
  33. bloomfilter_py-1.2.0/bloomfilter_py.egg-info/requires.txt +0 -10
  34. bloomfilter_py-1.2.0/tests/test_bloomfilter.py +0 -242
  35. {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/LICENSE +0 -0
  36. {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/dependency_links.txt +0 -0
  37. {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/top_level.txt +0 -0
  38. {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/setup.cfg +0 -0
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ - Retain master security checks that bound hexadecimal and Base64 decoding
6
+ before allocating the complete serialized payload.
7
+ - Remove all mandatory runtime dependencies; ship packed bit storage and a
8
+ seed-zero MurmurHash3 x64_128 implementation supporting Python 3.8+.
9
+ - Add explicit `backend="python"` (default) and `backend="native"` modes.
10
+ Native acceleration is available through the `native` installation extra.
11
+ - Preserve both hashing strategies, existing funnels and the Guava compact
12
+ serialization format. Support cross-backend comparison and merging.
13
+ - Replace per-word serialization objects with bounded chunk transformations;
14
+ chunk large counts and merges as well.
15
+ - Export a stable `BitArray` facade. `setup` copies external third-party
16
+ bitarray inputs; third-party-only data methods and aliasing need migration.
17
+ - Reject non-finite error rates, nonintegral sizing and misbehaving streams.
18
+ - Add fixed and random hash differential tests, packed-bit/backend tests,
19
+ memory checks, packaging validation, benchmarks and a dual-backend CI matrix.
20
+
21
+ Public bit storage and external-array aliasing changes require a major version.
22
+ The version is prepared locally; publishing requires a separate release action.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md CHANGELOG.md
2
+ recursive-include tests *.py *.json *.java
3
+ recursive-include benchmarks *.py
4
+ recursive-include scripts *.py
@@ -0,0 +1,410 @@
1
+ Metadata-Version: 2.4
2
+ Name: bloomfilter-py
3
+ Version: 2.0.0
4
+ Summary: Yet another bloomfilter implementation in Python
5
+ Author-email: OldPanda <me@old-panda.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2020 OldPanda
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Documentation, https://github.com/OldPanda/bloomfilter-py/blob/master/README.md
29
+ Project-URL: Homepage, https://github.com/OldPanda/bloomfilter-py
30
+ Project-URL: Repository, https://github.com/OldPanda/bloomfilter-py.git
31
+ Keywords: bloomfilter
32
+ Classifier: Development Status :: 5 - Production/Stable
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3 :: Only
38
+ Classifier: Programming Language :: Python :: 3.8
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Programming Language :: Python :: 3.13
44
+ Classifier: Programming Language :: Python :: 3.14
45
+ Classifier: Operating System :: OS Independent
46
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
47
+ Requires-Python: >=3.8
48
+ Description-Content-Type: text/markdown
49
+ License-File: LICENSE
50
+ Provides-Extra: native
51
+ Requires-Dist: bitarray<4,>=3.0; extra == "native"
52
+ Requires-Dist: mmh3<6,>=5.0; extra == "native"
53
+ Dynamic: license-file
54
+
55
+ # bloomfilter-py
56
+ ![](https://img.shields.io/pypi/v/bloomfilter-py.svg)
57
+ ![](https://img.shields.io/pypi/pyversions/bloomfilter-py.svg)
58
+ [![codecov](https://codecov.io/gh/OldPanda/bloomfilter-py/branch/master/graph/badge.svg?token=RBX1JK7P7O)](https://codecov.io/gh/OldPanda/bloomfilter-py)
59
+ [![Downloads](https://pepy.tech/badge/bloomfilter-py)](https://pepy.tech/project/bloomfilter-py)
60
+
61
+ ## Overview
62
+ Yet another Bloomfilter implementation in Python, compatible with Java's Guava library.
63
+
64
+ Compatibility targets Guava's compact `BloomFilter.writeTo` / `readFrom` format.
65
+ Java `ObjectOutputStream` / `ObjectInputStream` serialization is a different
66
+ format and is not supported. The compact format does not include the funnel;
67
+ callers must provide the matching encoder when loading a filter.
68
+
69
+ I was looking for a Python library which is capable of reading what Bloomfilter of Java's Guava library serializes and is also able to output byte array which is recognizable by Java. But unfortunately failed. Hence I developed this library by borrowing how Guava implements Bloomfilter serialization/deserialization a lot to deal with Bloomfilters on both Python and Java sides.
70
+
71
+ As for Bloomfilter usage in Java world, please refer to [this post](https://www.baeldung.com/guava-bloom-filter).
72
+
73
+ Here's a brief [introduction](https://en.wikipedia.org/wiki/Bloom_filter) to Bloomfilter.
74
+
75
+ ## Requirements
76
+ * Python 3.8+
77
+
78
+ ## Install
79
+ ```
80
+ pip install bloomfilter-py
81
+ ```
82
+
83
+ ## Backends and migration from 1.3
84
+
85
+ Version 2.0 runs on the Python standard library alone. `bitarray` and `mmh3`
86
+ are no longer required to install or import this package. The default is always
87
+ `backend="python"`, even if those packages are already installed. For explicit
88
+ native acceleration:
89
+
90
+ ```bash
91
+ pip install 'bloomfilter-py[native]'
92
+ ```
93
+
94
+ ```python
95
+ from bloomfilter import BloomFilter
96
+
97
+ fast = BloomFilter(500, 0.01, backend="native")
98
+ fast.put("example")
99
+ loaded = BloomFilter.loads(fast.dumps(), backend="native")
100
+ assert "example" in loaded
101
+ assert loaded.backend == "native"
102
+ ```
103
+
104
+ The constructor and all loading methods accept the keyword-only `backend`
105
+ argument (`"python"` or `"native"`). An invalid name raises `ValueError`;
106
+ requesting native mode without **both** optional packages raises `ImportError`
107
+ with installation instructions. There is no automatic fallback. `copy()`
108
+ preserves the backend. Filters using different backends can be compared and
109
+ merged when their strategies, funnels, sizes, and hash counts are compatible.
110
+ Backend choice is not serialized: all old dumps remain readable, and new dumps
111
+ use exactly the same Guava compact format.
112
+
113
+ Pure Python hashing is slower than the C extension, especially on long keys.
114
+ Native mode is available for throughput-sensitive workloads. Serialization,
115
+ counting, and merging process at most 64 KiB of payload per chunk; copies and
116
+ returned dumps necessarily allocate their complete result. `dumps()` and
117
+ `loads()` also retain full input/output bytes, so use stream methods for large
118
+ filters. Backing storage remains packed (roughly one byte per eight bits).
119
+
120
+ ### Bit storage and custom strategies
121
+
122
+ `filter.data` is now a `bloomfilter.BitArray` facade with consistent semantics
123
+ on either backend. It supports `len`, iteration, negative/integer indexing,
124
+ slice copies, scalar or equal-length iterable slice assignment, `setall`,
125
+ `count(0/1)`, `copy`, equality, `|=`, `+=`, `frombytes`, and `tobytes`.
126
+ Slices preserve the backend; slice assignment cannot resize. Appending via
127
+ `frombytes` or `+=` works even at non-byte-aligned lengths. `tobytes()` uses
128
+ most-significant-bit-first bytes with zero padding at the end. The buffer
129
+ protocol and the complete third-party `bitarray` API are **not** provided.
130
+
131
+ Existing `setup(hash_count, data, strategy, funnel)` calls accept this facade.
132
+ For migration, a third-party `bitarray.bitarray` is also accepted when installed;
133
+ it is copied into Python-backed storage and normalized to logical bit order,
134
+ including little-endian inputs. It is no longer aliased to `filter.data`.
135
+ Callers relying on that alias or on third-party-only methods must update.
136
+ Passing a `BitArray` to `setup` retains it, as before, so external mutations
137
+ remain visible. `backend` reflects the current data object.
138
+
139
+ Custom strategies still receive the same positional `put` / `might_contain`
140
+ arguments, now with the facade as their data argument. Overrides of built-in
141
+ strategies' `hash_key` are honored on both backends. Strategies must not depend
142
+ on private storage fields or a specific third-party data type. Direct calls to built-in strategies with legacy third-party bit arrays remain
143
+ supported. Direct calls to `Strategy.hash_key` always use the standard-library MurmurHash3 implementation.
144
+
145
+ ## Usage Examples
146
+
147
+ ### Basic Usage
148
+ ```Python
149
+ >>> from bloomfilter import BloomFilter
150
+ >>> bloom_filter = BloomFilter(expected_insertions=500, err_rate=0.01)
151
+ >>> for i in range(100):
152
+ ... bloom_filter.put(i)
153
+ ...
154
+ >>> 1 in bloom_filter
155
+ True
156
+ >>> 100 in bloom_filter
157
+ False
158
+ >>>
159
+ ```
160
+
161
+ ### Guava Funnel Compatibility
162
+
163
+ Guava's compact BloomFilter format stores the hash strategy and bit array, but
164
+ does not store its `Funnel`. To query a filter produced by Guava, select the
165
+ same funnel that was used in Java:
166
+
167
+ ```Python
168
+ from bloomfilter import BloomFilter, INTEGER_FUNNEL
169
+
170
+ with open("guava-filter.out", "rb") as f:
171
+ bloom_filter = BloomFilter.loads(f.read(), funnel=INTEGER_FUNNEL)
172
+ ```
173
+
174
+ The built-in mappings are:
175
+
176
+ | Java funnel | bloomfilter-py funnel |
177
+ | --- | --- |
178
+ | `Funnels.integerFunnel()` | `INTEGER_FUNNEL` |
179
+ | `Funnels.longFunnel()` | `LONG_FUNNEL` |
180
+ | `Funnels.stringFunnel(StandardCharsets.UTF_8)` | `UTF8_STRING_FUNNEL` |
181
+ | `Funnels.stringFunnel(StandardCharsets.UTF_16)` | `StringFunnel("UTF-16")` |
182
+ | `Funnels.stringFunnel(StandardCharsets.UTF_16BE)` | `StringFunnel("UTF-16BE")` |
183
+ | `Funnels.stringFunnel(StandardCharsets.UTF_16LE)` | `StringFunnel("UTF-16LE")` |
184
+ | `Funnels.byteArrayFunnel()` | `BYTE_ARRAY_FUNNEL` |
185
+
186
+ `StringFunnel` supports Java's six standard charsets: US-ASCII, ISO-8859-1,
187
+ UTF-8, UTF-16, UTF-16BE, and UTF-16LE. Custom Guava funnels are not encoded in
188
+ the serialized filter; use a matching custom Python `Funnel` implementation
189
+ when reading them.
190
+
191
+ Use the same funnel when creating a filter that Java will read:
192
+
193
+ ```Python
194
+ from bloomfilter import BloomFilter, LONG_FUNNEL
195
+
196
+ bloom_filter = BloomFilter(500, 0.01, funnel=LONG_FUNNEL)
197
+ bloom_filter.put(1) # Encoded as a Java long even though 1 fits in an int.
198
+ ```
199
+
200
+ Calls that omit `funnel` retain the historical bloomfilter-py behavior, which
201
+ chooses integer width from each value and therefore is not suitable for all
202
+ Guava interoperability scenarios.
203
+
204
+ ### Serialize Bloomfilter
205
+ You can easily serialize `BloomFilter` instance to a byte array
206
+ ```Python
207
+ >>> dumps = bloom_filter.dumps()
208
+ >>> with open("dumps.out", "wb") as f:
209
+ ... f.write(dumps)
210
+ ...
211
+ >>>
212
+ ```
213
+
214
+ or to a hex string
215
+ ```Python
216
+ >>> hex_str = bloom_filter.dumps_to_hex()
217
+ ```
218
+
219
+ or to a base64 encoded bytes
220
+ ```Python
221
+ base64_bytes = bloom_filter.dumps_to_base64()
222
+ ```
223
+
224
+ ### Deserialize Bloomfilter
225
+ And you can easily initialize a `BloomFilter` instance from a byte array
226
+ ```Python
227
+ >>> with open("dumps.out", "rb") as f:
228
+ ... bf = BloomFilter.loads(f.read())
229
+ ...
230
+ >>> 1 in bf
231
+ True
232
+ >>> 100 in bf
233
+ False
234
+ >>>
235
+ ```
236
+
237
+ or from a hex string
238
+ ```Python
239
+ >>> bf = BloomFilter.loads_from_hex(hex_str)
240
+ >>> 1 in bf
241
+ True
242
+ >>> 100 in bf
243
+ False
244
+ ```
245
+
246
+ or from a base64 encoded bytes
247
+ ```Python
248
+ >>> bf = BloomFilter.loads_from_base64(base64_bytes)
249
+ >>> 100 in bf
250
+ False
251
+ >>> 200 in bf
252
+ False
253
+ >>> 1 in bf
254
+ True
255
+ >>> 99 in bf
256
+ True
257
+ ```
258
+
259
+ Deserialization limits the backing bit array to 2³⁰ bits by default. For a
260
+ trusted larger Guava filter, pass an explicit limit to any loading method:
261
+
262
+ ```Python
263
+ bf = BloomFilter.loads(dumps, funnel=INTEGER_FUNNEL, max_num_bits=2**31)
264
+ ```
265
+
266
+ ### Stream I/O
267
+
268
+ Read and write compact Guava data directly with binary streams:
269
+
270
+ ```Python
271
+ with open("guava-filter.out", "rb") as stream:
272
+ bf = BloomFilter.read_from(stream, funnel=INTEGER_FUNNEL)
273
+
274
+ with open("python-filter.out", "wb") as stream:
275
+ bf.write_to(stream)
276
+
277
+ byte_count = bf.serialized_size()
278
+ ```
279
+
280
+ `read_from` reads exactly one filter, leaving any following stream data unread.
281
+ It accepts the same `max_num_bits` limit as `loads`, and validates the header
282
+ before reading the payload. `loads` requires exactly one filter with no trailing
283
+ bytes. Neither stream method closes the caller's stream. Both handle partial
284
+ reads or writes on blocking binary streams.
285
+
286
+ ### Copies, Merging, and Statistics
287
+
288
+ ```Python
289
+ snapshot = bf.copy()
290
+ other = BloomFilter(500, 0.01, funnel=INTEGER_FUNNEL)
291
+ other.put(123)
292
+
293
+ if bf.is_compatible(other):
294
+ bf.put_all(other)
295
+
296
+ false_positive_probability = bf.expected_fpp()
297
+ estimated_distinct_elements = bf.approximate_element_count()
298
+ ```
299
+
300
+ `copy` duplicates the backing bit array and preserves the loading limit. It
301
+ shares the funnel and stateless strategy, as Guava does; custom funnels should
302
+ be immutable. `put_all` performs an in-place union without modifying the source.
303
+ Compatibility requires distinct instances with the same bit-array size, number
304
+ of hashes, strategy, and equal funnels. Custom funnels must define equality
305
+ when separate instances represent the same encoding.
306
+
307
+ Statistics use the current bit occupancy, so they also work on loaded and merged
308
+ filters. A fully saturated filter has an expected FPP of `1.0`; its element count
309
+ cannot be estimated and raises `OverflowError`, corresponding to Guava's
310
+ `ArithmeticException`. Filter equality compares configuration, funnel, and bit
311
+ contents. Mutable filters are unhashable in Python.
312
+
313
+ Unlike Guava's atomic implementation, this library does not guarantee safe
314
+ concurrent mutations. Use an external lock around mutations, queries, copies,
315
+ statistics, and serialization when sharing a filter between threads. Java's
316
+ deprecated predicate aliases and stream collectors are language-specific APIs;
317
+ use `might_contain` (or `in`) and a loop calling `put` in Python.
318
+
319
+ ## Development and Testing
320
+
321
+ From the repository root, create an isolated virtual environment and install the
322
+ project in editable mode with pytest:
323
+
324
+ ```bash
325
+ python -m venv .venv
326
+ source .venv/bin/activate
327
+ python -m pip install --upgrade pip
328
+ python -m pip install -e . pytest
329
+ ```
330
+
331
+ Run the complete test suite:
332
+
333
+ ```bash
334
+ python -m pytest -v
335
+ ```
336
+
337
+ The bidirectional Java integration test requires a JDK and a Guava JAR. Point
338
+ `GUAVA_JAR` at the JAR to compile the included Java helper and verify both
339
+ Guava-to-Python and Python-to-Guava serialization:
340
+
341
+ ```bash
342
+ GUAVA_JAR=/path/to/guava-33.7.2-jre.jar \
343
+ python -m pytest -v tests/test_guava_interop.py
344
+ ```
345
+
346
+ The test is skipped when those prerequisites are unavailable. CI downloads the
347
+ pinned Guava release, verifies its SHA-256 checksum, and runs the test as a
348
+ required separate job.
349
+
350
+ To run the same formatting, type, and security checks used during development,
351
+ install the additional tools and execute:
352
+
353
+ ```bash
354
+ python -m pip install black mypy bandit
355
+ python -m black --check bloomfilter tests benchmarks scripts
356
+ python -m mypy bloomfilter --strict
357
+ python -m bandit -r bloomfilter
358
+ ```
359
+
360
+ On Windows PowerShell, activate the virtual environment with
361
+ `.venv\Scripts\Activate.ps1` instead of `source .venv/bin/activate`.
362
+
363
+ ### Reproducible backend benchmarks
364
+
365
+ ```bash
366
+ python benchmarks/benchmark_backends.py --backend python --output python.json
367
+ python benchmarks/benchmark_backends.py --backend native --output native.json
368
+ # Against a separate checkout of 1.3.0, with its dependencies installed:
369
+ python benchmarks/benchmark_backends.py --backend baseline \
370
+ --package-path /path/to/1.3.0 --output baseline.json
371
+ ```
372
+
373
+ The script reports median timings, raw repeats, workload sizes, and interpreter
374
+ metadata. It covers fresh and repeated insertions, hits and misses, short and
375
+ long byte keys, serialization, statistics, and merging. Results describe the
376
+ specified workload rather than a universal throughput guarantee.
377
+
378
+ For the zero-dependency source suite run `python -S -m unittest discover`.
379
+ Install `.[native]` to enable optional differential tests against the C packages.
380
+ The checked-in hash vectors were generated with mmh3 5.3.1 and cover every
381
+ length from 0 through 512 plus larger block boundaries.
382
+ Run Java integration tests for both backends with `BLOOMFILTER_TEST_BACKEND`
383
+ set to `python` and `native` respectively. CI covers Python 3.8 through 3.14.
384
+
385
+ Build and verify release artifacts with `python -m build` followed by
386
+ `python scripts/verify_distribution.py dist`. The latter installs the wheel
387
+ offline into a clean temporary environment and verifies it outside the source
388
+ path, including the absence of mandatory runtime dependencies.
389
+
390
+ ### Auditing compatibility against an earlier checkout
391
+
392
+ Use an environment containing the baseline's dependencies to compare actual
393
+ behavior in separate processes:
394
+
395
+ ```bash
396
+ python scripts/check_api_compatibility.py --baseline /path/to/baseline \
397
+ --candidate . --output compatibility.json
398
+ ```
399
+
400
+ The audit checks twelve strategy/funnel combinations, serialized bytes, query
401
+ results, copies/merges/statistics, streams, custom hashes and existing call
402
+ signatures. It separately reports changes to bit-storage type, third-party
403
+ methods, buffer support and `setup` input aliasing. A successful audit command
404
+ means the comparison completed; inspect `fully_backward_compatible` for the
405
+ compatibility verdict. Version 2.0 preserves the tested core operations but
406
+ changes these public storage behaviors, so it is a major release.
407
+
408
+ Hex and Base64 loaders include the master allocation-bound security update:
409
+ encoded sizes and headers are checked before full Base64 decoding, and hex
410
+ inputs are decoded in bounded chunks even with substantial whitespace.