laurus-python
Python bindings for the Laurus search engine. Provides lexical search, vector search, and hybrid search from Python via a native Rust extension built with PyO3 and Maturin.
Features
- Lexical Search -- Full-text search powered by an inverted index with BM25 scoring
- Vector Search -- Approximate nearest neighbor (ANN) search using Flat, HNSW, or IVF indexes
- Hybrid Search -- Combine lexical and vector results with fusion algorithms (RRF, WeightedSum)
- Rich Query DSL -- Term, Phrase, Fuzzy, Wildcard, NumericRange, Geo, Boolean, Span queries
- Text Analysis -- Tokenizers, filters, stemmers, and synonym expansion
- Flexible Storage -- In-memory (ephemeral) or file-based (persistent) indexes
- Pythonic API -- Clean, intuitive Python classes with full type information
Installation
pip install laurus
To build from source (requires Rust toolchain):
pip install maturin
maturin develop
Quick Start
import laurus
# Create an in-memory index
index = laurus.Index()
# Index documents
index.put_document("doc1", {"title": "Introduction to Rust", "body": "Systems programming language."})
index.put_document("doc2", {"title": "Python for Data Science", "body": "Data analysis with Python."})
index.commit()
# Search with a DSL string
results = index.search("title:rust", limit=5)
for r in results:
print(f"[{r.id}] score={r.score:.4f} {r.document['title']}")
# Search with a query object
results = index.search(laurus.TermQuery("body", "python"), limit=5)
Index Types
In-memory (ephemeral)
index = laurus.Index()
File-based (persistent)
schema = laurus.Schema()
schema.add_text_field("title")
schema.add_text_field("body")
schema.add_hnsw_field("embedding", dimension=384)
index = laurus.Index(path="./myindex", schema=schema)
This writes ./myindex/schema.toml and ./myindex/store/ -- the same
layout laurus-cli create index --schema uses, so the directory can be
opened by either. Reopening it later only needs the path (schema must be
omitted, since it's loaded from the persisted schema.toml):
index = laurus.Index(path="./myindex")
A file-based index holds an exclusive lock on its directory for as long as
the Index object is alive, so a second laurus.Index(path=...) on the
same path fails while the first is still open. Call index.close() when
you are done with an index -- especially before reopening the same path --
rather than relying on CPython's garbage collector, whose destruction
timing is not guaranteed for every reference pattern (e.g. reference
cycles). close() is idempotent; every other method raises after it has
been called.
index.close()
Reloading an index
reload() picks up changes committed by another process without paying the
cost of constructing a brand-new Index -- in particular, it reuses the
already-loaded embedding model(s) when the schema's embedding configuration
hasn't changed, instead of reloading them from scratch:
changed = index.reload() # True if the commit generation actually advanced
reload() works whether the index is currently open or already
close()d -- it reopens the same directory either way, so you can hold
onto one Index object across a full reload cycle instead of constructing a
new one and swapping references. It requires a file-backed index (path was
given at construction); calling it on an in-memory index raises ValueError.
index.commit_generation() returns the same O(1) counter as
stats()["commit_generation"], without stats()'s cost of scanning vector
fields. It only reflects commits made through this Index object,
though -- it's a snapshot held in memory, not re-read from disk on every
call -- so it confirms whether reload() (or your own commit()) actually
advanced the state, but it cannot by itself tell you whether another
process has committed something new; only reload() picks that up.
To cheaply check for another process's changes without paying reload()'s
cost, use the module-level laurus.peek_commit_generation(path). Unlike
index.commit_generation(), it isn't tied to any Index object -- it reads
commit_generation.json straight off disk, with no Engine construction at
all, so it works even before you've opened an Index for that path in this
process:
before = laurus.peek_commit_generation(path)
# ... later ...
if laurus.peek_commit_generation(path) != before:
index.reload()
Raises ValueError if path isn't a laurus index directory (no persisted
schema).
Durability / WAL
A persistent index writes every change to a write-ahead log (WAL). By default
the WAL is fsync-ed on every record, so each write is fully durable. Opt into
group commit to batch fsync for higher write throughput (a crash can lose up
to the last unsynced batch, like SQLite's synchronous = NORMAL):
import laurus
policy = laurus.WalSyncPolicy.group(max_records=4096, max_interval_ms=1000)
index = laurus.Index(path="./myindex", schema=schema, wal_sync_policy=policy)
index.put_document("doc1", {"title": "Hello"})
index.flush_wal() # force a durable barrier on demand
index.commit() # also flushes the WAL
Omit wal_sync_policy (or pass laurus.WalSyncPolicy.per_record()) to keep
the default per-record durability.
Query Types
| Query class | Description |
|---|---|
TermQuery(field, term) |
Exact term match |
PhraseQuery(field, [terms]) |
Ordered phrase match |
FuzzyQuery(field, term, max_edits) |
Approximate term match |
WildcardQuery(field, pattern) |
Wildcard pattern match (*, ?) |
NumericRangeQuery(field, min, max) |
Numeric range (int or float) |
GeoDistanceQuery.within_radius(field, lat, lon, distance_m) |
Geo-distance radius search |
GeoBoundingBoxQuery.within_bounding_box(field, min_lat, min_lon, max_lat, max_lon) |
Geo bounding-box search |
Geo3dDistanceQuery.within_sphere(field, x, y, z, distance_m) |
3D ECEF sphere search |
Geo3dBoundingBoxQuery.within_box(field, min_x, min_y, min_z, max_x, max_y, max_z) |
3D ECEF AABB search |
Geo3dNearestQuery.k_nearest(field, x, y, z, k) |
3D ECEF k-nearest neighbours |
BooleanQuery(must, should, must_not) |
Compound boolean logic |
SpanNearQuery(field, [terms], slop) |
Proximity / ordered span match |
VectorQuery(field, vector) |
Pre-computed vector similarity |
VectorTextQuery(field, text) |
Text-to-vector similarity (requires embedder) |
Hybrid Search
request = laurus.SearchRequest(
lexical_query=laurus.TermQuery("body", "rust"),
vector_query=laurus.VectorQuery("embedding", query_vec),
fusion=laurus.RRF(k=60.0),
limit=10,
)
results = index.search(request)
Fusion algorithms
| Class | Description |
|---|---|
RRF(k=60.0) |
Reciprocal Rank Fusion (rank-based, default for hybrid) |
WeightedSum(lexical_weight=0.5, vector_weight=0.5) |
Score-normalised weighted sum |
Text Analysis
syn_dict = laurus.SynonymDictionary()
syn_dict.add_synonym_group(["ml", "machine learning"])
tokenizer = laurus.WhitespaceTokenizer()
filt = laurus.SynonymGraphFilter(syn_dict, keep_original=True, boost=0.8)
tokens = tokenizer.tokenize("ml tutorial")
tokens = filt.apply(tokens)
for tok in tokens:
print(tok.text, tok.position, tok.boost)
Examples
Usage examples are in the examples/ directory:
| Example | Description |
|---|---|
| quickstart.py | Basic indexing and full-text search |
| lexical_search.py | All query types (Term, Phrase, Boolean, Fuzzy, Wildcard, Range, Geo, Span) |
| vector_search.py | Semantic similarity search with embeddings |
| hybrid_search.py | Combining lexical and vector search with fusion |
| synonym_graph_filter.py | Synonym expansion in the analysis pipeline |
| search_with_openai.py | Cloud-based embeddings via OpenAI |
| multimodal_search.py | Text-to-image and image-to-image search |
Documentation
License
This project is licensed under the MIT License - see the LICENSE file for details.
Metadata
Release files for laurus 0.13.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| laurus-0.13.2.tar.gz | 1.5 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| laurus-0.13.2-cp310-abi3-win_arm64.whl | CPython 3.10 | abi3 | Windows ARM64 | Details |
| laurus-0.13.2-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| laurus-0.13.2-cp310-abi3-manylinux_2_28_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| laurus-0.13.2-cp310-abi3-manylinux_2_28_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| laurus-0.13.2-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| laurus-0.13.2-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 60.0 MB
Release files / laurus-0.13.2.tar.gz
| Download URL | laurus-0.13.2.tar.gz |
|---|---|
| Size | 1.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e45834f6015ca064b57789a4d4c40616ab5c4b8e3429fe2b3db4f0716a228417
|
|
BLAKE2b-256 checksum How to use checksums |
a1cdaca3ac27b1173a4873cf5f5a9a70de1fdc5c2311b7fba39014c9ed4c3c52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-win_arm64.whl
| Download URL | laurus-0.13.2-cp310-abi3-win_arm64.whl |
|---|---|
| Size | 9.2 MB |
| Tags | CPython 3.10 Windows ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
1cc8a385b6977fa80d4e02dfa1e12577ef8a14552f642a76c12d69fab5c3b7e7
|
|
BLAKE2b-256 checksum How to use checksums |
6d8b3025b07c5d92b274b297881523de3936ea6cfe57c48c6d9466e759a30afe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-win_amd64.whl
| Download URL | laurus-0.13.2-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 9.7 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
54630b0a4207fd419fd2d89b0ce774382ddd6c2a86b9ba3017d32e6a0e7b287f
|
|
BLAKE2b-256 checksum How to use checksums |
3917fc744c68554e2f2677a59e50376e92d89dc70e3c1353f6aed539beaab300
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-manylinux_2_28_x86_64.whl
| Download URL | laurus-0.13.2-cp310-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 10.3 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
c97e59ed3b2dec25a6247d76e5b57831802d545424666ac89632a537fd7ab5da
|
|
BLAKE2b-256 checksum How to use checksums |
4deaf19b2c663623705bd27f96a54ed0f2f07cac050dd420444c00900baea72a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-manylinux_2_28_aarch64.whl
| Download URL | laurus-0.13.2-cp310-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 9.8 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
b0f60cc77c619d74365f19191d49b599a280dcf656e5057b4d6b72496e891b6a
|
|
BLAKE2b-256 checksum How to use checksums |
2632d81097296db3846c58ebb68df28f668330090cfabc6a6c77e6fd96a63cba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | laurus-0.13.2-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 9.4 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
ca36f153b54efd67418794be05dd4c5fa8bc2451ed0c413506da9683d3f10c5e
|
|
BLAKE2b-256 checksum How to use checksums |
7a6a10601bde98ca6a0fa8ab8475821c9c750f6c69bf3734d4ea14042670ed74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / laurus-0.13.2-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | laurus-0.13.2-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 10.0 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
315da5e867095b7f8edfb65169c547e2ba2313f4d59caee445132a19f977db14
|
|
BLAKE2b-256 checksum How to use checksums |
ae1d657b5ca9762d3b9f74155e33016123223a6820c0f5fe699555e82c00dad3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|