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.
- bloomfilter_py-2.0.0/CHANGELOG.md +22 -0
- bloomfilter_py-2.0.0/MANIFEST.in +4 -0
- bloomfilter_py-2.0.0/PKG-INFO +410 -0
- bloomfilter_py-2.0.0/README.md +356 -0
- bloomfilter_py-2.0.0/benchmarks/benchmark_backends.py +134 -0
- bloomfilter_py-2.0.0/bloomfilter/__init__.py +23 -0
- bloomfilter_py-2.0.0/bloomfilter/_bitarray.py +300 -0
- bloomfilter_py-2.0.0/bloomfilter/_murmur3.py +63 -0
- bloomfilter_py-2.0.0/bloomfilter/bloomfilter.py +528 -0
- bloomfilter_py-2.0.0/bloomfilter/bloomfilter_strategy.py +186 -0
- bloomfilter_py-2.0.0/bloomfilter/funnel.py +132 -0
- bloomfilter_py-2.0.0/bloomfilter/py.typed +0 -0
- bloomfilter_py-2.0.0/bloomfilter_py.egg-info/PKG-INFO +410 -0
- bloomfilter_py-2.0.0/bloomfilter_py.egg-info/SOURCES.txt +27 -0
- bloomfilter_py-2.0.0/bloomfilter_py.egg-info/requires.txt +4 -0
- {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/pyproject.toml +19 -8
- bloomfilter_py-2.0.0/scripts/check_api_compatibility.py +259 -0
- bloomfilter_py-2.0.0/scripts/verify_distribution.py +93 -0
- bloomfilter_py-2.0.0/tests/__init__.py +16 -0
- bloomfilter_py-2.0.0/tests/data/murmur3-vectors.json +3642 -0
- bloomfilter_py-2.0.0/tests/java/GuavaBloomFilterInterop.java +301 -0
- bloomfilter_py-2.0.0/tests/test_backends.py +454 -0
- bloomfilter_py-2.0.0/tests/test_bloomfilter.py +576 -0
- bloomfilter_py-2.0.0/tests/test_filter_operations.py +172 -0
- bloomfilter_py-2.0.0/tests/test_guava_interop.py +262 -0
- bloomfilter_py-1.2.0/PKG-INFO +0 -179
- bloomfilter_py-1.2.0/README.md +0 -124
- bloomfilter_py-1.2.0/bloomfilter/__init__.py +0 -3
- bloomfilter_py-1.2.0/bloomfilter/bloomfilter.py +0 -246
- bloomfilter_py-1.2.0/bloomfilter/bloomfilter_strategy.py +0 -129
- bloomfilter_py-1.2.0/bloomfilter_py.egg-info/PKG-INFO +0 -179
- bloomfilter_py-1.2.0/bloomfilter_py.egg-info/SOURCES.txt +0 -12
- bloomfilter_py-1.2.0/bloomfilter_py.egg-info/requires.txt +0 -10
- bloomfilter_py-1.2.0/tests/test_bloomfilter.py +0 -242
- {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/LICENSE +0 -0
- {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/dependency_links.txt +0 -0
- {bloomfilter_py-1.2.0 → bloomfilter_py-2.0.0}/bloomfilter_py.egg-info/top_level.txt +0 -0
- {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,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
|
+

|
|
57
|
+

|
|
58
|
+
[](https://codecov.io/gh/OldPanda/bloomfilter-py)
|
|
59
|
+
[](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.
|