fuzzy-search-rs 0.1.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.
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "crossbeam-deque"
|
|
7
|
+
version = "0.8.8"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"crossbeam-epoch",
|
|
12
|
+
"crossbeam-utils",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[[package]]
|
|
16
|
+
name = "crossbeam-epoch"
|
|
17
|
+
version = "0.9.21"
|
|
18
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
19
|
+
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
|
20
|
+
dependencies = [
|
|
21
|
+
"crossbeam-utils",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[[package]]
|
|
25
|
+
name = "crossbeam-utils"
|
|
26
|
+
version = "0.8.23"
|
|
27
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
28
|
+
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
|
29
|
+
|
|
30
|
+
[[package]]
|
|
31
|
+
name = "either"
|
|
32
|
+
version = "1.19.0"
|
|
33
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
34
|
+
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
|
35
|
+
|
|
36
|
+
[[package]]
|
|
37
|
+
name = "fuzzy-search-rs"
|
|
38
|
+
version = "0.1.0"
|
|
39
|
+
dependencies = [
|
|
40
|
+
"pyo3",
|
|
41
|
+
"rayon",
|
|
42
|
+
"rustc-hash",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "heck"
|
|
47
|
+
version = "0.5.0"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
50
|
+
|
|
51
|
+
[[package]]
|
|
52
|
+
name = "libc"
|
|
53
|
+
version = "0.2.191"
|
|
54
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
55
|
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
|
56
|
+
|
|
57
|
+
[[package]]
|
|
58
|
+
name = "once_cell"
|
|
59
|
+
version = "1.21.4"
|
|
60
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
61
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
62
|
+
|
|
63
|
+
[[package]]
|
|
64
|
+
name = "portable-atomic"
|
|
65
|
+
version = "1.15.0"
|
|
66
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
67
|
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
68
|
+
|
|
69
|
+
[[package]]
|
|
70
|
+
name = "proc-macro2"
|
|
71
|
+
version = "1.0.107"
|
|
72
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
73
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
74
|
+
dependencies = [
|
|
75
|
+
"unicode-ident",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[[package]]
|
|
79
|
+
name = "pyo3"
|
|
80
|
+
version = "0.30.1"
|
|
81
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
82
|
+
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
|
83
|
+
dependencies = [
|
|
84
|
+
"libc",
|
|
85
|
+
"once_cell",
|
|
86
|
+
"portable-atomic",
|
|
87
|
+
"pyo3-build-config",
|
|
88
|
+
"pyo3-ffi",
|
|
89
|
+
"pyo3-macros",
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
[[package]]
|
|
93
|
+
name = "pyo3-build-config"
|
|
94
|
+
version = "0.30.1"
|
|
95
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
96
|
+
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
|
97
|
+
dependencies = [
|
|
98
|
+
"target-lexicon",
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
[[package]]
|
|
102
|
+
name = "pyo3-ffi"
|
|
103
|
+
version = "0.30.1"
|
|
104
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
105
|
+
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
|
106
|
+
dependencies = [
|
|
107
|
+
"libc",
|
|
108
|
+
"pyo3-build-config",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[[package]]
|
|
112
|
+
name = "pyo3-macros"
|
|
113
|
+
version = "0.30.1"
|
|
114
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
|
+
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
|
116
|
+
dependencies = [
|
|
117
|
+
"proc-macro2",
|
|
118
|
+
"pyo3-macros-backend",
|
|
119
|
+
"quote",
|
|
120
|
+
"syn",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[[package]]
|
|
124
|
+
name = "pyo3-macros-backend"
|
|
125
|
+
version = "0.30.1"
|
|
126
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
127
|
+
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
|
128
|
+
dependencies = [
|
|
129
|
+
"heck",
|
|
130
|
+
"proc-macro2",
|
|
131
|
+
"quote",
|
|
132
|
+
"syn",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[[package]]
|
|
136
|
+
name = "quote"
|
|
137
|
+
version = "1.0.47"
|
|
138
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
139
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
140
|
+
dependencies = [
|
|
141
|
+
"proc-macro2",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
[[package]]
|
|
145
|
+
name = "rayon"
|
|
146
|
+
version = "1.12.0"
|
|
147
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
148
|
+
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
|
149
|
+
dependencies = [
|
|
150
|
+
"either",
|
|
151
|
+
"rayon-core",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
[[package]]
|
|
155
|
+
name = "rayon-core"
|
|
156
|
+
version = "1.13.0"
|
|
157
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
158
|
+
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
|
159
|
+
dependencies = [
|
|
160
|
+
"crossbeam-deque",
|
|
161
|
+
"crossbeam-utils",
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
[[package]]
|
|
165
|
+
name = "rustc-hash"
|
|
166
|
+
version = "2.1.3"
|
|
167
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
168
|
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "syn"
|
|
172
|
+
version = "3.0.10"
|
|
173
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
174
|
+
checksum = "4ae9ccbea3a7b0b09443036490c8f34a8b1b760fd345e5c0dae0aac718faea79"
|
|
175
|
+
dependencies = [
|
|
176
|
+
"proc-macro2",
|
|
177
|
+
"quote",
|
|
178
|
+
"unicode-ident",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
[[package]]
|
|
182
|
+
name = "target-lexicon"
|
|
183
|
+
version = "0.13.5"
|
|
184
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
185
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
186
|
+
|
|
187
|
+
[[package]]
|
|
188
|
+
name = "unicode-ident"
|
|
189
|
+
version = "1.0.27"
|
|
190
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
191
|
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "fuzzy-search-rs"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Rust backend for the skipgram scan and candidate building of fuzzy-search"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
name = "fuzzy_search_rs"
|
|
11
|
+
crate-type = ["cdylib", "rlib"]
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
pyo3 = { version = "0.30", features = ["extension-module", "abi3-py310"] }
|
|
15
|
+
rayon = "1"
|
|
16
|
+
rustc-hash = "2"
|
|
17
|
+
|
|
18
|
+
[profile.release]
|
|
19
|
+
lto = "fat"
|
|
20
|
+
codegen-units = 1
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# fuzzy-search-rs
|
|
2
|
+
|
|
3
|
+
Optional Rust backend for `FuzzyPhraseSearcher` in [fuzzy-search](../README.md). It returns exactly the
|
|
4
|
+
same matches as the Python implementation, about 35 times faster on one thread, and runs batches on
|
|
5
|
+
several threads without the GIL.
|
|
6
|
+
|
|
7
|
+
## Wheels
|
|
8
|
+
|
|
9
|
+
The extension is built as an abi3 wheel: one wheel per platform works on every CPython from 3.10 on.
|
|
10
|
+
The workflow `.github/workflows/rust-wheels.yml` builds and tests wheels for Linux (x86_64 and aarch64,
|
|
11
|
+
manylinux2014; x86_64 musllinux), macOS (universal2: Intel and Apple silicon) and Windows (x64 and
|
|
12
|
+
arm64), and uploads them as workflow artifacts. Publishing to PyPI is not automated.
|
|
13
|
+
|
|
14
|
+
## Build and install
|
|
15
|
+
|
|
16
|
+
Needs a Rust toolchain and [maturin](https://www.maturin.rs/). From this directory:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
maturin build --release
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Other platforms from macOS (with rustup's toolchain first on `PATH`; Homebrew's `rust` has only the
|
|
23
|
+
host target):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
maturin build --release --target universal2-apple-darwin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install ziglang && maturin build --release --target x86_64-unknown-linux-gnu --zig --compatibility manylinux2014
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Cross-building aarch64 Linux with zig currently fails at link time (zig rejects rustc's
|
|
38
|
+
`--fix-cortex-a53-843419` flag), and Windows wheels need a Windows machine; CI builds both natively.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv pip install --python ../.venv/bin/python --reinstall target/wheels/fuzzy_search_rs-*.whl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or, with pip, `pip install target/wheels/fuzzy_search_rs-*.whl`.
|
|
45
|
+
|
|
46
|
+
## Use
|
|
47
|
+
|
|
48
|
+
Select the backend in the searcher config:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from fuzzy_search import FuzzyPhraseSearcher
|
|
52
|
+
|
|
53
|
+
searcher = FuzzyPhraseSearcher(phrase_model=phrases, config={'backend': 'auto'})
|
|
54
|
+
matches = searcher.find_matches(text) # one text
|
|
55
|
+
results = searcher.find_matches_batch(texts, workers=0) # many texts, one thread per CPU
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `'auto'` (default): Rust when installed and possible, otherwise Python.
|
|
59
|
+
- `'python'`: the Python implementation.
|
|
60
|
+
- `'rust'`: the Rust backend; raises `RuntimeError` if it is not installed or cannot reproduce the
|
|
61
|
+
searcher's settings.
|
|
62
|
+
|
|
63
|
+
With the Rust backend, `find_matches_batch(texts, workers=n)` uses `n` threads (0: one per CPU) instead
|
|
64
|
+
of worker processes, so no `if __name__ == '__main__':` guard is needed.
|
|
65
|
+
|
|
66
|
+
## What runs in Rust
|
|
67
|
+
|
|
68
|
+
`find_matches` runs exact matching in Python, then Rust does, per text:
|
|
69
|
+
|
|
70
|
+
1. the skipgram scan (`Searcher.find_skipgram_matches`),
|
|
71
|
+
2. candidate building (`get_skipmatch_candidates`, `CandidatePartial`),
|
|
72
|
+
3. word-boundary adjustment (`adjust_match_offsets`) and per-phrase overlap filtering
|
|
73
|
+
(`filter_overlapping_phrase_candidates`),
|
|
74
|
+
4. scoring (`PhraseMatch.add_scores`: character overlap, ngram overlap, `Levenshtein.ratio`).
|
|
75
|
+
|
|
76
|
+
Python then builds `PhraseMatch` objects only for candidates that pass the thresholds, and runs the
|
|
77
|
+
remaining steps (distractors, combining with exact matches, offset thresholds, overlap filtering).
|
|
78
|
+
|
|
79
|
+
The Rust code mirrors the Python code step by step, including quirks such as group ordering, the
|
|
80
|
+
`next_start_offset and ...` truthiness test and `Counter` entries that drop to zero. Offsets are Unicode
|
|
81
|
+
code points, as in Python slicing.
|
|
82
|
+
|
|
83
|
+
## Limits and differences
|
|
84
|
+
|
|
85
|
+
- `ngram_size` up to 6 (skipgrams are packed into 128-bit keys). Larger sizes fall back to Python
|
|
86
|
+
(`'auto'`) or raise (`'rust'`).
|
|
87
|
+
- Phrases with `max_end_offset` fall back to Python, which raises an `AttributeError` for them.
|
|
88
|
+
- `debug > 0` always runs the Python code, so its debug output is unchanged.
|
|
89
|
+
- With `ignorecase`, texts whose lowercase has a different length (such as `İ`) or that contain a capital
|
|
90
|
+
sigma `Σ` are scored in Python, because there slicing the lowercased text differs from lowercasing a slice.
|
|
91
|
+
- Where Python would raise `ZeroDivisionError` or `IndexError` on degenerate input (a phrase shorter
|
|
92
|
+
than `ngram_size`), the Rust backend raises `ValueError`.
|
|
93
|
+
|
|
94
|
+
## Verification
|
|
95
|
+
|
|
96
|
+
- `test/test_search_rust_backend.py` compares both backends over a grid of settings and texts, including
|
|
97
|
+
threaded batches, and checks backend selection and pickling.
|
|
98
|
+
- The regression fixture runs with either backend:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
.venv/bin/python -m pytest test/regression --regression-backend rust --regression-exact --regression-full
|
|
102
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["maturin>=1.5,<2"]
|
|
3
|
+
build-backend = "maturin"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fuzzy-search-rs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Rust backend for the skipgram scan and candidate building of fuzzy-search"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
|
|
11
|
+
[tool.maturin]
|
|
12
|
+
features = ["pyo3/extension-module"]
|