Metadata-Version: 2.4
Name: fuzzy-aho-corasick-rs
Version: 0.1.4
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Linguistic
Requires-Dist: maturin>=1.7,<2.0 ; extra == 'dev'
Requires-Dist: pytest>=8.0 ; extra == 'dev'
Provides-Extra: dev
Summary: Python bindings for the Rust fuzzy-aho-corasick crate.
Keywords: aho-corasick,fuzzy,string-matching,rust,pyo3
Home-Page: https://github.com/vineel7871/fuzzy-aho-corasick-rs
Author: Vineel K
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/vineel7871/fuzzy-aho-corasick-rs
Project-URL: Issues, https://github.com/vineel7871/fuzzy-aho-corasick-rs/issues
Project-URL: Repository, https://github.com/vineel7871/fuzzy-aho-corasick-rs

# fuzzy-aho-corasick

Python bindings for the Rust [`fuzzy-aho-corasick`](https://crates.io/crates/fuzzy-aho-corasick) crate using PyO3 and maturin.

The wrapper currently exposes the upstream crate's main global builder options and a Python-side `Pattern` type for per-pattern configuration:

- Build a fuzzy matcher from a list of patterns
- Configure per-pattern weights
- Configure per-pattern custom unique IDs
- Configure per-pattern fuzzy limits
- Configure global edit limits
- Configure global penalty weights
- Run non-overlapping and overlapping fuzzy search, split, and text segmentation helpers
- Build a fuzzy replacer from `(pattern, replacement)` pairs
- Ship `py.typed` metadata and `.pyi` stubs for editor type hints

## Python API

```python
from fuzzy_aho_corasick import FuzzyMatcher, FuzzyReplacer, Pattern

matcher = FuzzyMatcher(
    [
        Pattern("hello", edits=1, weight=1.5, custom_unique_id=7),
        Pattern("world", substitutions=1),
    ],
    case_insensitive=True,
)

matches = matcher.search("H3llo W0rld!", threshold=0.7)
for match in matches:
    print(match.pattern, match.text, match.similarity, match.pattern_custom_unique_id)

overlap_matcher = FuzzyMatcher(["saddam", "ddamhu"], edits=1)
overlapping = overlap_matcher.search_overlapping("saddamddamhu", threshold=0.5)
assert len(overlapping) > len(overlap_matcher.search("saddamddamhu", threshold=0.5))

split_matcher = FuzzyMatcher(
    ["FOO", "BAR"],
    edits=1,
    case_insensitive=True,
)

assert matcher.search("H3llo W0rld!", threshold=0.7)[0].pattern == "hello"
assert split_matcher.split("xxFo0yyBAARzz", threshold=0.8) == ["xx", "yy", "zz"]
assert matcher.pattern_specs[0].custom_unique_id == 7

replacer = FuzzyReplacer(
    [
        (Pattern("foo", substitutions=1), "bar"),
        (Pattern("baz", custom_unique_id=2), "qux"),
    ],
    case_insensitive=True,
)

assert replacer.replace("fo0 and BAZ!", threshold=0.7) == "bar and qux!"

matcher.save("matcher.json")
loaded = FuzzyMatcher.load("matcher.json")
assert loaded.search("H3llo W0rld!", threshold=0.7)[0].pattern == "hello"
```

## Search Modes

`FuzzyMatcher.search()` keeps the existing default behavior and returns non-overlapping matches unless you ask for a different strategy.

```python
matches = matcher.search("H3llo W0rld!", threshold=0.7)
```

When you want overlapping matches, use the dedicated method instead of passing a string strategy:

```python
overlapping = matcher.search_overlapping("saddamddamhu", threshold=0.5)
```

Overlap mode returns the full sorted upstream search result set. That means you can see additional nearby fuzzy candidates for the same pattern when they pass the threshold, not just one match per pattern.

For advanced control, `search()` still accepts these typed strategies:

- `"search"`: sorted matches with overlaps preserved
- `"non_overlapping"`: sorted matches with overlaps removed
- `"non_overlapping_unique"`: non-overlapping matches with one result per pattern or custom unique ID

The package now ships `.pyi` stubs together with `py.typed`, so editors such as Pylance can show method signatures, accepted strategy values, and return types.

## Saving And Loading

`FuzzyMatcher` can persist its configuration to JSON with `save()` / `load()` or `to_json()` / `from_json()`.

```python
from fuzzy_aho_corasick import FuzzyMatcher, Pattern

matcher = FuzzyMatcher(
    [Pattern("invoice", edits=1), Pattern("receipt", custom_unique_id=9)],
    case_insensitive=True,
    edits=2,
)

matcher.save("matcher.json")
loaded = FuzzyMatcher.load("matcher.json")
```

The saved form captures the matcher configuration and recreates the Rust engine when loaded. The upstream Rust crate does not currently expose a stable serialized form for the built automaton itself.

## Local development

1. Install Rust using `rustup`.
2. Install maturin: `py -m pip install --upgrade maturin`.
3. Build and install the extension into your active environment:

   ```powershell
   maturin develop
   ```

4. Run tests:

   ```powershell
   pytest
   ```

## Build distributions

Build wheels and an sdist locally:

```powershell
maturin build --release
maturin sdist
```

Artifacts will land under `target/wheels/` or the `--out` directory if you set one.

## Publish to PyPI

### Manual publish

```powershell
maturin publish --release
```

### GitHub Actions publish

This repository includes a workflow at `.github/workflows/release.yml` that:

- builds wheels on Linux, macOS, and Windows
- builds a source distribution
- uploads them to PyPI on tag pushes like `v0.1.0`
- skips files that were already uploaded if the same workflow is rerun for the same tag

Recommended setup:

1. Create the project on PyPI.
2. Enable trusted publishing for your GitHub repository on PyPI.
3. Replace the placeholder GitHub URLs in `pyproject.toml`.
4. Push a version tag such as `v0.1.0`.

PyPI does not allow reusing the same filename for a different upload. If `0.1.0` has already been published, make the next real release a new version such as `0.1.1`. The workflow only skips already-existing files so reruns do not fail after a partial publish.

## Notes

- The Python wrapper is intentionally small and focused on the upstream crate's core operations.
- Before publishing, update the author and project URLs in `pyproject.toml`.

