diff --git a/Cargo.lock b/Cargo.lock index 9e45471..56590df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,10 +217,10 @@ version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.89", ] [[package]] @@ -378,7 +378,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.89", ] [[package]] @@ -434,6 +434,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -478,9 +484,9 @@ checksum = "4e6ba961c14e98151cd6416dd3685efe786a94c38bc1a535c06ceff0a1600813" [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instant" @@ -559,16 +565,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -665,9 +661,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.19.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437213adf41bbccf4aeae535fbfcdad0f6fed241e1ae182ebe97fa1f3ce19389" +checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" dependencies = [ "libc", "ndarray", @@ -699,29 +695,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets 0.48.0", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -740,6 +713,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -748,9 +727,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.65" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92de25114670a878b1261c79c9f8f729fb97e95bac93f6312f583c60dd6a1dfe" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -769,15 +748,16 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.19.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -786,9 +766,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", "target-lexicon", @@ -796,9 +776,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" dependencies = [ "libc", "pyo3-build-config", @@ -806,32 +786,34 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" dependencies = [ + "heck 0.5.0", "proc-macro2", + "pyo3-build-config", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -881,15 +863,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags", -] - [[package]] name = "ring" version = "0.17.3" @@ -988,12 +961,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "sct" version = "0.7.1" @@ -1044,7 +1011,7 @@ checksum = "6ea3780fe4396cc8077e44623443a923e400f4a6015249de6d38ba064bed79d8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.89", ] [[package]] @@ -1087,9 +1054,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -1098,9 +1065,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.13" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" @@ -1110,7 +1077,7 @@ checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall", "rustix", "windows-sys 0.45.0", ] @@ -1132,7 +1099,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.89", ] [[package]] @@ -1152,12 +1119,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "num_cpus", "pin-project-lite", ] @@ -1211,9 +1177,9 @@ dependencies = [ [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "untrusted" diff --git a/pybigtools/Cargo.toml b/pybigtools/Cargo.toml index 44a749d..fd613a9 100644 --- a/pybigtools/Cargo.toml +++ b/pybigtools/Cargo.toml @@ -10,11 +10,11 @@ crate-type = ["cdylib"] [dependencies] bigtools = { version = "0.5.5-dev", path = "../bigtools", default_features = false, features = ["read", "write"] } -url = "2.4.0" -tokio = { version = "1.34.0", features = ["rt", "rt-multi-thread"] } +url = "2.5.0" +tokio = { version = "1.41.0", features = ["rt", "rt-multi-thread"] } futures = { version = "0.3.1", features = ["thread-pool"] } -numpy = "0.19" -pyo3 = { version = "0.19.1", features = ["extension-module"] } +numpy = "0.22" +pyo3 = { version = "0.22", features = ["extension-module"] } [features] default = ["remote"] diff --git a/pybigtools/src/file_like.rs b/pybigtools/src/file_like.rs index 77a2c75..7b9eeb6 100644 --- a/pybigtools/src/file_like.rs +++ b/pybigtools/src/file_like.rs @@ -1,41 +1,47 @@ -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::{ + io::{self, Read, Seek, SeekFrom, Write}, + sync::Arc, +}; use pyo3::{ - exceptions::PyTypeError, types::PyBytes, IntoPy, PyErr, PyObject, PyResult, Python, ToPyObject, + exceptions::PyTypeError, + types::{PyAnyMethods, PyBytes, PyBytesMethods}, + Bound, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, }; /// Represents a file-like object in python. This simply wraps the Rust io /// traits, calling into python io methods. #[derive(Clone)] pub struct PyFileLikeObject { - pub(crate) inner: PyObject, + pub(crate) inner: Arc, } impl PyFileLikeObject { /// Creates a `PyFileLikeObject`, validating that it conforms to some /// required set of methods (one or more of `read`, `write`, or `seek`). /// Will return a `TypeError` if object does not have the required methods. - pub fn new(object: PyObject, read: bool, write: bool, seek: bool) -> PyResult { - Python::with_gil(|py| { - if read && object.getattr(py, "read").is_err() { - return Err(PyErr::new::( - "Object does not have a .read() method.", - )); - } - - if seek && object.getattr(py, "seek").is_err() { - return Err(PyErr::new::( - "Object does not have a .seek() method.", - )); - } - - if write && object.getattr(py, "write").is_err() { - return Err(PyErr::new::( - "Object does not have a .write() method.", - )); - } - - Ok(PyFileLikeObject { inner: object }) + pub fn new(object: &Bound<'_, PyAny>, read: bool, write: bool, seek: bool) -> PyResult { + if read && object.getattr("read").is_err() { + return Err(PyErr::new::( + "Object does not have a .read() method.", + )); + } + + if seek && object.getattr("seek").is_err() { + return Err(PyErr::new::( + "Object does not have a .seek() method.", + )); + } + + if write && object.getattr("write").is_err() { + return Err(PyErr::new::( + "Object does not have a .write() method.", + )); + } + + let object = object.clone().unbind(); + Ok(PyFileLikeObject { + inner: Arc::new(object), }) } } @@ -44,7 +50,7 @@ impl PyFileLikeObject { fn to_io_error(py: Python<'_>, e: PyErr) -> io::Error { let pyobj: PyObject = e.into_py(py); - match pyobj.call_method(py, "__str__", (), None) { + match pyobj.call_method_bound(py, "__str__", (), None) { Ok(repr) => match repr.extract::(py) { Ok(s) => io::Error::new(io::ErrorKind::Other, s), Err(_e) => io::Error::new(io::ErrorKind::Other, "An unknown error has occurred"), @@ -58,9 +64,11 @@ impl Read for PyFileLikeObject { Python::with_gil(|py| { let res = self .inner - .call_method(py, "read", (buf.len(),), None) + .call_method_bound(py, "read", (buf.len(),), None) .map_err(|e| to_io_error(py, e))?; - let pybytes: &PyBytes = res.downcast(py).map_err(|e| to_io_error(py, e.into()))?; + let pybytes: &Bound<'_, PyBytes> = res + .downcast_bound(py) + .map_err(|e| to_io_error(py, e.into()))?; let bytes = pybytes.as_bytes(); buf.write_all(bytes)?; Ok(bytes.len()) @@ -71,11 +79,11 @@ impl Read for PyFileLikeObject { impl Write for PyFileLikeObject { fn write(&mut self, buf: &[u8]) -> Result { Python::with_gil(|py| { - let arg = PyBytes::new(py, buf).to_object(py); + let arg = PyBytes::new_bound(py, buf).to_object(py); let number_bytes_written = self .inner - .call_method(py, "write", (arg,), None) + .call_method_bound(py, "write", (arg,), None) .map_err(|e| to_io_error(py, e))?; if number_bytes_written.is_none(py) { @@ -91,7 +99,7 @@ impl Write for PyFileLikeObject { fn flush(&mut self) -> Result<(), io::Error> { Python::with_gil(|py| { self.inner - .call_method(py, "flush", (), None) + .call_method_bound(py, "flush", (), None) .map_err(|e| to_io_error(py, e))?; Ok(()) @@ -110,7 +118,7 @@ impl Seek for PyFileLikeObject { let new_position = self .inner - .call_method(py, "seek", (offset, whence), None) + .call_method_bound(py, "seek", (offset, whence), None) .map_err(|e| to_io_error(py, e))?; new_position.extract(py).map_err(|e| to_io_error(py, e)) diff --git a/pybigtools/src/lib.rs b/pybigtools/src/lib.rs index 932f75c..3c15ac3 100644 --- a/pybigtools/src/lib.rs +++ b/pybigtools/src/lib.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use std::fs::File; use std::io::{self, BufReader}; -use std::ops::IndexMut; +use std::ops::{Deref, IndexMut}; use std::path::Path; use bigtools::bed::autosql::parse::parse_autosql; @@ -23,7 +23,7 @@ use bigtools::{ use bigtools::utils::reopen::Reopen; use file_like::PyFileLikeObject; use numpy::ndarray::ArrayViewMut; -use numpy::PyArray1; +use numpy::{PyArray1, PyArrayMethods}; use pyo3::exceptions::{self, PyKeyError, PyTypeError}; use pyo3::types::{ IntoPyDict, PyAny, PyDict, PyFloat, PyInt, PyIterator, PyList, PyString, PyTuple, @@ -207,10 +207,12 @@ fn intervals_to_array( }; let arr = match (bins, arr) { (_, Some(arr)) => arr, - (Some(bins), None) => PyArray1::from_vec(py, vec![missing; bins]).to_object(py), - (None, None) => PyArray1::from_vec(py, vec![missing; (end - start) as usize]).to_object(py), + (Some(bins), None) => PyArray1::from_vec_bound(py, vec![missing; bins]).to_object(py), + (None, None) => { + PyArray1::from_vec_bound(py, vec![missing; (end - start) as usize]).to_object(py) + } }; - let v: &PyArray1 = arr.downcast::>(py).map_err(|_| { + let v: &Bound<'_, PyArray1> = arr.downcast_bound::>(py).map_err(|_| { PyErr::new::( "`arr` option must be a one-dimensional numpy array, if passed.", ) @@ -218,11 +220,11 @@ fn intervals_to_array( let (intervals_start, intervals_end) = (start.max(0) as u32, end.min(length) as u32); let bin_size = match bins { Some(bins) => { - if v.len() != bins { + if v.len()? != bins { return Err(PyErr::new::(format!( "`arr` does not have the expected size (expected `{}`, found `{}`), if passed.", bins, - v.len(), + v.len()?, ))); } @@ -269,11 +271,11 @@ fn intervals_to_array( (end - start) as f64 / bins as f64 } _ => { - if v.len() != (end - start) as usize { + if v.len()? != (end - start) as usize { return Err(PyErr::new::(format!( "`arr` does not have the expected size (expected `{}`, found `{}`), if passed.", (end - start) as usize, - v.len(), + v.len()?, ))); } @@ -343,10 +345,12 @@ fn entries_to_array( }; let arr = match (bins, arr) { (_, Some(arr)) => arr, - (Some(bins), None) => PyArray1::from_vec(py, vec![missing; bins]).to_object(py), - (None, None) => PyArray1::from_vec(py, vec![missing; (end - start) as usize]).to_object(py), + (Some(bins), None) => PyArray1::from_vec_bound(py, vec![missing; bins]).to_object(py), + (None, None) => { + PyArray1::from_vec_bound(py, vec![missing; (end - start) as usize]).to_object(py) + } }; - let v: &PyArray1 = arr.downcast::>(py).map_err(|_| { + let v: &Bound<'_, PyArray1> = arr.downcast_bound::>(py).map_err(|_| { PyErr::new::( "`arr` option must be a one-dimensional numpy array, if passed.", ) @@ -354,11 +358,11 @@ fn entries_to_array( let (intervals_start, intervals_end) = (start.max(0) as u32, end.min(length) as u32); let bin_size = match bins { Some(bins) => { - if v.len() != bins { + if v.len()? != bins { return Err(PyErr::new::(format!( "`arr` does not have the expected size (expected `{}`, found `{}`), if passed.", bins, - v.len(), + v.len()?, ))); } @@ -405,11 +409,11 @@ fn entries_to_array( (end - start) as f64 / bins as f64 } _ => { - if v.len() != (end - start) as usize { + if v.len()? != (end - start) as usize { return Err(PyErr::new::(format!( "`arr` does not have the expected size (expected `{}`, found `{}`), if passed.", (end - start) as usize, - v.len(), + v.len()?, ))); } @@ -1095,7 +1099,7 @@ impl BBIRead { ("max", summary.max_val.to_object(py)), ("std", f64::sqrt(var).to_object(py)), ] - .into_py_dict(py) + .into_py_dict_bound(py) .to_object(py); let info = [ ("version", info.header.version.to_object(py)), @@ -1108,7 +1112,7 @@ impl BBIRead { ("chromCount", info.chrom_info.len().to_object(py)), ("summary", summary), ] - .into_py_dict(py) + .into_py_dict_bound(py) .to_object(py); Ok(info) } @@ -1192,7 +1196,7 @@ impl BBIRead { } let declaration = declarations.pop(); match declaration { - None => PyDict::new(py).to_object(py), + None => PyDict::new_bound(py).to_object(py), Some(d) => { let fields = d .fields @@ -1205,7 +1209,7 @@ impl BBIRead { ("comment", d.comment.to_object(py)), ("fields", fields), ] - .into_py_dict(py) + .into_py_dict_bound(py) .to_object(py) } } @@ -1245,6 +1249,7 @@ impl BBIRead { /// -------- /// zoom_records : Get the zoom records of a given range on a chromosome. /// values : Get the values of a given range on a chromosome. + #[pyo3(signature = (chrom, start=None, end=None))] fn records( &mut self, py: Python<'_>, @@ -1348,6 +1353,7 @@ impl BBIRead { /// zooms : Get a list of available zoom levels. /// records : Get the records of a given range on a chromosome. /// values : Get the values of a given range on a chromosome. + #[pyo3(signature = (reduction_level, chrom, start=None, end=None))] fn zoom_records( &mut self, reduction_level: u32, @@ -1542,6 +1548,7 @@ impl BBIRead { /// ------- /// int or Dict[str, int] or None: /// Chromosome length or a dictionary of chromosome lengths. + #[pyo3(signature = (chrom=None))] fn chroms(&mut self, py: Python, chrom: Option) -> PyResult { fn get_chrom_obj( b: &B, @@ -1567,7 +1574,7 @@ impl BBIRead { .chroms() .into_iter() .map(|c| (c.name.clone(), c.length)) - .into_py_dict(py) + .into_py_dict_bound(py) .into(); Ok(chrom_dict) } @@ -1641,10 +1648,11 @@ impl BBIRead { /// tuples of the form ``({name}, {average})``. /// Importantly, if the statistics value is itself a tuple, then that /// tuple will be **nested** as the second value of the outer tuple. + #[pyo3(signature = (bed, names=None, stats=None))] fn average_over_bed( &mut self, py: Python, - bed: PyObject, + bed: Bound<'_, PyAny>, names: Option, stats: Option, ) -> PyResult { @@ -1677,7 +1685,7 @@ impl BBIRead { }; let stats = { match stats { - Some(stats) => match stats.downcast::(py) { + Some(stats) => match stats.downcast_bound::(py) { Ok(stat) => { let stat = stat.to_str()?; if stat.eq_ignore_ascii_case("all") { @@ -1692,7 +1700,7 @@ impl BBIRead { Some(vec![stat]) } } - Err(_) => match stats.downcast::(py) { + Err(_) => match stats.downcast_bound::(py) { Ok(stats) => { let mut ret_stats = Vec::with_capacity(stats.len()); for stat in stats.into_iter() { @@ -1720,13 +1728,13 @@ impl BBIRead { None => Some(vec![BigWigAverageOverBedStatistics::Mean]), } }; - let bed = if let Ok(bed) = bed.downcast::(py) { - bed + let bed = if let Ok(bed) = bed.downcast::() { + bed.clone() } else { - let path_class = py.import("pathlib")?.getattr("Path")?; + let path_class = py.import_bound("pathlib")?.getattr("Path")?; // If pathlib.Path, convert to string and try to open - if bed.as_ref(py).is_instance(path_class)? { - bed.as_ref(py).str()? + if bed.is_instance(&path_class)? { + bed.str()? } else { return Err(PyErr::new::(format!( "Unknown argument for `path`. Not a string or Path object.", @@ -1735,7 +1743,7 @@ impl BBIRead { }; let bedin = BufReader::new(File::open(bed.to_str()?)?); - let module = PyModule::import(py, "pybigtools")?; + let module = PyModule::import_bound(py, "pybigtools")?; let summary_statistics = module.getattr("SummaryStatistics")?.to_object(py); let res = match &mut self.bbi { BBIReadRaw::Closed => return Err(BBIFileClosed::new_err("File is closed.")), @@ -1808,8 +1816,12 @@ impl BBIRead { BBIReadRaw::Closed | BBIReadRaw::BigWigFile(_) | BBIReadRaw::BigBedFile(_) => {} #[cfg(feature = "remote")] BBIReadRaw::BigWigRemote(_) | BBIReadRaw::BigBedRemote(_) => {} - BBIReadRaw::BigWigFileLike(b) => visit.call(&b.inner_read().inner_read().inner)?, - BBIReadRaw::BigBedFileLike(b) => visit.call(&b.inner_read().inner_read().inner)?, + BBIReadRaw::BigWigFileLike(b) => { + visit.call(b.inner_read().inner_read().inner.deref())? + } + BBIReadRaw::BigBedFileLike(b) => { + visit.call(b.inner_read().inner_read().inner.deref())? + } } Ok(()) } @@ -1844,7 +1856,7 @@ impl ZoomIntervalIterator { ("sum", v.summary.sum.to_object(slf.py())), ("sum_squares", v.summary.sum_squares.to_object(slf.py())), ] - .into_py_dict(slf.py()) + .into_py_dict_bound(slf.py()) .to_object(slf.py()); (v.start, v.end, summary) }) @@ -1900,7 +1912,7 @@ impl BigBedEntriesIterator { .chain(next.rest.split_whitespace().map(|o| o.to_object(py))) .collect(); Ok(Some( - PyTuple::new::(py, elements.into_iter()).to_object(py), + PyTuple::new_bound::(py, elements.into_iter()).to_object(py), )) } } @@ -1931,7 +1943,7 @@ impl BigWigWrite { /// ----- /// The underlying file will be closed automatically when the function /// completes, and no other operations will be able to be performed. - fn write(&mut self, py: Python, chroms: &PyDict, vals: Py) -> PyResult<()> { + fn write(&mut self, py: Python, chroms: &Bound<'_, PyDict>, vals: Py) -> PyResult<()> { let runtime = runtime::Builder::new_multi_thread() .worker_threads( std::thread::available_parallelism() @@ -1948,7 +1960,7 @@ impl BigWigWrite { let length: u32 = val.downcast::()?.to_object(py).extract(py).unwrap(); Ok((chrom, length)) }) - .collect::, pyo3::PyDowncastError>>()?; + .collect::, pyo3::PyErr>>()?; let bigwig = self .bigwig @@ -1970,15 +1982,15 @@ impl BigWigWrite { fn next(&mut self) -> Option { // We have to reacquire the gil for each iteration Python::with_gil(|py| { - let mut iter: &PyIterator = match self.inner.downcast(py) { - Ok(o) => o, + let mut iter: Bound<'_, PyIterator> = match self.inner.downcast_bound(py) { + Ok(o) => o.clone(), Err(_) => { return Some(Err(IterError(format!( "Passed value for `val` is not iterable." )))) } }; - let next: Result<(String, Value), pyo3::PyDowncastError> = match iter.next()? { + let next: Result<(String, Value), pyo3::PyErr> = match iter.next()? { Err(e) => { e.print(py); return Some(Err(IterError(format!( @@ -2032,8 +2044,8 @@ impl BigWigWrite { } py.allow_threads(|| { let iter = Python::with_gil(|py| { - let inner_obj: PyObject = vals.into_py(py); - match PyIterator::from_object(py, &inner_obj) { + let inner_obj = vals.bind(py); + match PyIterator::from_bound_object(inner_obj) { Ok(iter) => Ok(iter.to_object(py)), Err(_) => Err(PyTypeError::new_err( "Passed value for `val` is not iterable.", @@ -2090,7 +2102,7 @@ impl BigBedWrite { /// ----- /// The underlying file will be closed automatically when the function /// completes, and no other operations will be able to be performed. - fn write(&mut self, py: Python, chroms: &PyDict, vals: Py) -> PyResult<()> { + fn write(&mut self, py: Python, chroms: &Bound<'_, PyDict>, vals: Py) -> PyResult<()> { let runtime = runtime::Builder::new_multi_thread() .worker_threads( std::thread::available_parallelism() @@ -2107,7 +2119,7 @@ impl BigBedWrite { let length: u32 = val.downcast::()?.to_object(py).extract(py).unwrap(); Ok((chrom, length)) }) - .collect::, pyo3::PyDowncastError>>()?; + .collect::, pyo3::PyErr>>()?; let bigbed = self .bigbed @@ -2129,59 +2141,58 @@ impl BigBedWrite { fn next(&mut self) -> Option { // We have to reacquire the gil for each iteration Python::with_gil(|py| { - let mut iter: &PyIterator = match self.inner.downcast(py) { - Ok(o) => o, + let mut iter: Bound<'_, PyIterator> = match self.inner.downcast_bound(py) { + Ok(o) => o.clone(), Err(_) => { return Some(Err(IterError(format!( "Passed value for `val` is not iterable." )))) } }; - let next: Result<(String, BedEntry), pyo3::PyDowncastError> = - match iter.next()? { - Err(e) => { - e.print(py); - return Some(Err(IterError(format!( - "An error occurred while iterating." - )))); - } - Ok(n) => { - // TODO: try block or separate function - (|| { - let tuple = n.downcast::()?; - assert!(tuple.len() == 4); - let chrom: String = tuple - .get_item(0) - .unwrap() - .downcast::()? - .to_str() - .unwrap() - .to_owned(); - let start: u32 = tuple - .get_item(1) - .unwrap() - .downcast::()? - .to_object(py) - .extract(py) - .unwrap(); - let end: u32 = tuple - .get_item(2) - .unwrap() - .downcast::()? - .to_object(py) - .extract(py) - .unwrap(); - let rest: String = tuple - .get_item(3) - .unwrap() - .downcast::()? - .to_str() - .unwrap() - .to_owned(); - Ok((chrom, BedEntry { start, end, rest })) - })() - } - }; + let next: Result<(String, BedEntry), pyo3::PyErr> = match iter.next()? { + Err(e) => { + e.print(py); + return Some(Err(IterError(format!( + "An error occurred while iterating." + )))); + } + Ok(n) => { + // TODO: try block or separate function + (|| { + let tuple = n.downcast::()?; + assert!(tuple.len() == 4); + let chrom: String = tuple + .get_item(0) + .unwrap() + .downcast::()? + .to_str() + .unwrap() + .to_owned(); + let start: u32 = tuple + .get_item(1) + .unwrap() + .downcast::()? + .to_object(py) + .extract(py) + .unwrap(); + let end: u32 = tuple + .get_item(2) + .unwrap() + .downcast::()? + .to_object(py) + .extract(py) + .unwrap(); + let rest: String = tuple + .get_item(3) + .unwrap() + .downcast::()? + .to_str() + .unwrap() + .to_owned(); + Ok((chrom, BedEntry { start, end, rest })) + })() + } + }; let ret = match next { Err(_) => Err(IterError("Invalid iterator value. Must a tuple of type (String, int, int, String)".to_string())), Ok(n) => Ok(n), @@ -2192,8 +2203,8 @@ impl BigBedWrite { } py.allow_threads(|| { let iter = Python::with_gil(|py| { - let inner_obj: PyObject = vals.into_py(py); - match PyIterator::from_object(py, &inner_obj) { + let inner_obj = vals.bind(py); + match PyIterator::from_bound_object(inner_obj) { Ok(iter) => Ok(iter.to_object(py)), Err(_) => Err(PyTypeError::new_err( "Passed value for `val` is not iterable.", @@ -2305,13 +2316,12 @@ impl BigWigAverageOverBedEntriesIterator { if ret.len() == 1 { ret[0].to_object(slf.py()) } else { - PyTuple::new(slf.py(), ret).to_object(slf.py()) + PyTuple::new_bound(slf.py(), ret).to_object(slf.py()) } } None => { - let summary_statistics = slf.summary_statistics.as_ref(slf.py()); - //let summary_statistics = module.getattr("SummaryStatistics").unwrap(); - let val = summary_statistics.call( + let val = slf.summary_statistics.call_bound( + slf.py(), (v.size, v.bases, v.sum, v.mean0, v.mean, v.min, v.max), None, )?; @@ -2350,7 +2360,12 @@ impl BigWigAverageOverBedEntriesIterator { /// If passing a file-like object, concurrent reading of different intervals /// is not supported and may result in incorrect behavior. #[pyfunction] -fn open(py: Python, path_url_or_file_like: PyObject, mode: Option) -> PyResult { +#[pyo3(signature = (path_url_or_file_like, mode=None))] +fn open( + py: Python, + path_url_or_file_like: &Bound<'_, PyAny>, + mode: Option, +) -> PyResult { let iswrite = match &mode { Some(mode) if mode == "w" => true, Some(mode) if mode == "r" => false, @@ -2364,15 +2379,15 @@ fn open(py: Python, path_url_or_file_like: PyObject, mode: Option) -> Py }; // If string, might be path or url like - if let Ok(string_ref) = path_url_or_file_like.downcast::(py) { + if let Ok(string_ref) = path_url_or_file_like.downcast::() { return open_path_or_url(py, string_ref.to_str().unwrap().to_owned(), iswrite); } // If pathlib.Path, convert to string and try to open - let path_class = py.import("pathlib")?.getattr("Path")?; - if path_url_or_file_like.as_ref(py).is_instance(path_class)? { - let path_str = path_url_or_file_like.as_ref(py).str()?.to_str()?; - return open_path_or_url(py, path_str.to_owned(), iswrite); + let path_class = py.import_bound("pathlib")?.getattr("Path")?; + if path_url_or_file_like.is_instance(&path_class)? { + let path_string = path_url_or_file_like.str()?.to_string(); + return open_path_or_url(py, path_string, iswrite); } if iswrite { @@ -2386,7 +2401,7 @@ fn open(py: Python, path_url_or_file_like: PyObject, mode: Option) -> Py "Unknown argument for `path_url_or_file_like`. Not a file path string or url, and not a file-like object.", ))), }; - let read = match GenericBBIRead::open(file_like.clone()) { + let read = match GenericBBIRead::open(file_like) { Ok(GenericBBIRead::BigWig(bigwig)) => BBIRead { bbi: BBIReadRaw::BigWigFileLike(bigwig.cached()), } @@ -2533,7 +2548,7 @@ fn open_path_or_url( /// Read and write Big Binary Indexed (BBI) file types: BigWig and BigBed. #[pymodule] -fn pybigtools(py: Python, m: &PyModule) -> PyResult<()> { +fn pybigtools(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; m.add_wrapped(wrap_pyfunction!(open))?; @@ -2544,10 +2559,10 @@ fn pybigtools(py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - m.add("BBIFileClosed", m.py().get_type::())?; - m.add("BBIReadError", m.py().get_type::())?; + m.add("BBIFileClosed", m.py().get_type_bound::())?; + m.add("BBIReadError", m.py().get_type_bound::())?; - let collections = PyModule::import(py, "collections")?; + let collections = PyModule::import_bound(py, "collections")?; let namedtuple = collections.getattr("namedtuple")?; let summary_statistics = namedtuple.call(