Skip to content

Commit a5454d5

Browse files
committed
Static liblzma for hopefully portable Py wheels, bump py to rc2, readme
1 parent 60c8cb9 commit a5454d5

4 files changed

Lines changed: 82 additions & 82 deletions

File tree

deacon-py/python/README.md

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,88 @@
1-
# Deacon Python bindings
1+
# Deacon for Python
22

3-
Enables index reuse between filtering sessions.
3+
Python bindings for [Deacon](https://github.com/bede/deacon), enabling fast multithreaded DNA sequence filtering for e.g. host pangenome depletion using Python code. These bindings load an index once, allowing subsequent filtering runs with low latency. Deacon's complete functionality is currently only available using the [Rust/CLI version of Deacon](https://github.com/bede/deacon).
4+
5+
## Installation
6+
7+
```bash
8+
uv install deacon
9+
```
10+
11+
## Quickstart
412

513
```python
614
from deacon import Index
715

816
index = Index("panhuman-1.k31w15.idx")
9-
stats = [
10-
index.filter(fq, deplete=True, rename=True,
11-
output=fq.replace(".fastq.gz", ".clean.fastq.gz"))
12-
for fq in input_fastqs
13-
]
14-
print(stats[0]["seqs_in"], stats[0]["seqs_out"])
17+
stats = index.filter(
18+
fastq_path,
19+
deplete=True,
20+
rename=True,
21+
output=fastq_path.replace(".fastq.gz", ".clean.fastq.gz")
22+
)
23+
print(stats["seqs_in"], stats["seqs_out"])
24+
```
25+
26+
## `Index()`
27+
28+
Load a minimizer index or probabilistic filter from disk. The resulting object may be reused across many `filter` calls.
29+
30+
## `Index.fetch()`
31+
32+
Download a prebuilt index, then load and return it (a static method, so `Index.fetch(...)` returns an `Index`). `output` is the local path to save to; when omitted it defaults to `"{name}.k{k}w{w}.idx"` in the working directory. The index is downloaded on every call — there is no local cache, so an existing file at that path is overwritten.
33+
34+
```python
35+
index = Index.fetch("panhuman-1", k=31, w=15, output=None)
1536
```
1637

17-
`filter` accepts single or paired input (`fastq2=`, `output2=`), auto-detects `.gz`/`.zst`/
18-
`.xz` output compression from the extension, and returns a `dict` of summary statistics.
19-
`from deacon import filter` also exposes `filter(index, ...)` as a free function.
38+
## `Index.info()`
39+
40+
`Index.info(index_path)` Returns a `dict` of index metadata:
41+
42+
| Key | Meaning |
43+
| --- | --- |
44+
| `k` | *k*-mer length |
45+
| `w` | minimizer window size |
46+
| `format` | `exact-u64`, `exact-u128`, or `bff` (binary fuse filter) |
47+
| `count` | number of keys in index (fingerprint slot count for `bff`) |
48+
49+
## `Index.filter()`
50+
51+
Filter a FASTA/FASTQ file or file pair against the index and return a `dict` of summary statistics. Auto-detects `.gz`/`.zst`/`.xz` compression on both input and output based on file extension. The Python GIL is released while filtering meaning calls benefit from multithreading. Refer to the [main Deacon readme](https://github.com/bede/deacon) for more detailed usage examples.
52+
53+
```python
54+
def filter(
55+
fastq, # input path (FASTA/FASTQ, optionally .gz/.zst/.xz)
56+
fastq2=None, # second mate for paired reads
57+
deplete=False, # False = search (keep matches); True = deplete (remove matches)
58+
rename=False, # replace read names with sequential integers
59+
rename_random=False, # replace read names with random strings
60+
output=None, # output path; None writes to stdout
61+
output2=None, # second output path for paired reads
62+
abs_threshold=2, # min absolute minimizer hits to call a match
63+
rel_threshold=0.01, # min proportion of minimizers hitting to call a match
64+
prefix_length=0, # only use the first N bp of each read (0 = whole read)
65+
output_fasta=False, # emit FASTA instead of FASTQ
66+
threads=8, # worker threads for filtering
67+
compression_level=2, # output compression level
68+
compression_threads=0, # threads for output compression (0 = auto)
69+
debug=False, # verbose per-read debug output
70+
quiet=True, # suppress progress/log output on stderr
71+
) -> dict
72+
```
73+
74+
**Modes.** With `deplete=False` (the default, *search* mode) reads that match the index are kept; with `deplete=True` reads that match are removed (host depletion). A read is a match only when it clears **both** thresholds: at least `abs_threshold` minimizer hits **and** at least `rel_threshold` of its minimizers hitting the index.
75+
76+
**Output.** When `output` is `None` the filtered records are written to stdout. To count without keeping the filtered sequences, pass `output="/dev/null"`. Statistics are returned regardless.
77+
78+
**Return value.** A `dict` including the run configuration (`version`, `index`, `input`/`input2`, `output`/`output2`, `k`, `w`, `abs_threshold`, `rel_threshold`, `prefix_length`, `deplete`, `rename`, `rename_random`) and the results:
79+
80+
| Key | Meaning |
81+
| --- | --- |
82+
| `seqs_in`, `seqs_out`, `seqs_removed` | sequence counts |
83+
| `seqs_out_proportion`, `seqs_removed_proportion` | sequence proportions |
84+
| `bp_in`, `bp_out`, `bp_removed` | base-pair counts |
85+
| `bp_out_proportion`, `bp_removed_proportion` | base-pair proportions |
86+
| `time` | wall-clock seconds |
87+
| `seqs_per_second`, `bp_per_second` | throughput (filtering only) |
88+
| `seqs_per_second_total`, `bp_per_second_total` | throughput (including load/IO) |
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from ._deacon import Index, __version__, filter
1+
from ._deacon import Index, __version__
22

3-
__all__ = ["Index", "filter", "__version__"]
3+
__all__ = ["Index", "__version__"]

deacon-py/python/deacon/_deacon.pyi

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,4 @@ class Index:
1818
Index metadata: k, w, format and minimizer/key count.
1919
"""
2020

21-
def filter(index: Index, fastq: str, fastq2: str |None = None, deplete: bool = False, rename: bool = False, rename_random: bool = False, output: str |None = None, output2: str |None = None, abs_threshold: int = 2, rel_threshold: float = 0.01, prefix_length: int = 0, output_fasta: bool = False, threads: int = 8, compression_level: int = 2, compression_threads: int = 0, debug: bool = False, quiet: bool = True) -> Any:
22-
"""
23-
Module-level wrapper so `from deacon import filter` works: `filter(index, ...)`.
24-
"""
25-
2621
def __getattr__(name: str) -> Incomplete: ...

deacon-py/src/lib.rs

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -132,77 +132,13 @@ impl Index {
132132
}
133133
}
134134

135-
/// Module-level wrapper so `from deacon import filter` works: `filter(index, ...)`.
136-
#[pyfunction]
137-
#[pyo3(signature = (
138-
index,
139-
fastq,
140-
fastq2=None,
141-
deplete=false,
142-
rename=false,
143-
rename_random=false,
144-
output=None,
145-
output2=None,
146-
abs_threshold=2,
147-
rel_threshold=0.01,
148-
prefix_length=0,
149-
output_fasta=false,
150-
threads=8,
151-
compression_level=2,
152-
compression_threads=0,
153-
debug=false,
154-
quiet=true,
155-
))]
156-
fn filter(
157-
py: Python<'_>,
158-
index: &Index,
159-
fastq: String,
160-
fastq2: Option<String>,
161-
deplete: bool,
162-
rename: bool,
163-
rename_random: bool,
164-
output: Option<String>,
165-
output2: Option<String>,
166-
abs_threshold: usize,
167-
rel_threshold: f64,
168-
prefix_length: usize,
169-
output_fasta: bool,
170-
threads: u16,
171-
compression_level: u8,
172-
compression_threads: u16,
173-
debug: bool,
174-
quiet: bool,
175-
) -> PyResult<Py<PyAny>> {
176-
index.filter(
177-
py,
178-
fastq,
179-
fastq2,
180-
deplete,
181-
rename,
182-
rename_random,
183-
output,
184-
output2,
185-
abs_threshold,
186-
rel_threshold,
187-
prefix_length,
188-
output_fasta,
189-
threads,
190-
compression_level,
191-
compression_threads,
192-
debug,
193-
quiet,
194-
)
195-
}
196-
197135
// Declarative module form so pyo3 introspection can link members (see scripts/gen-py-stubs.sh).
198136
#[pymodule]
199137
mod _deacon {
200138
use pyo3::prelude::*;
201139

202140
#[pymodule_export]
203141
use super::Index;
204-
#[pymodule_export]
205-
use super::filter;
206142

207143
#[pymodule_init]
208144
fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {

0 commit comments

Comments
 (0)