diff --git a/Cargo.lock b/Cargo.lock index 878387e..18dfbbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "indoc", @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" dependencies = [ "once_cell", "target-lexicon", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" dependencies = [ "libc", "pyo3-build-config", @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index da561b9..ebe85fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] toml = {version = "0.5.9", features = ["preserve_order"]} serde = "1.0.126" -pyo3 = "0.20.0" +pyo3 = "0.21.2" ahash = "0.8.1" nohash-hasher = "0.2.0" diff --git a/src/datetime.rs b/src/datetime.rs index 2c1369d..0524b6d 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,52 +1,44 @@ -extern crate pyo3; - -use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo}; + use toml::value::{Datetime as TomlDatetime, Offset as TomlOffset}; pub fn parse(py: Python, datetime: &TomlDatetime) -> PyResult { - let py_dt: PyObject = match &datetime.date { - Some(date) => match &datetime.time { - Some(t) => { - let py_tz: PyObject; - let tzinfo = match &datetime.offset { - Some(offset) => { - let tz_info = match offset { - TomlOffset::Z => TzInfo::new(0, 0), - TomlOffset::Custom { hours, minutes } => TzInfo::new(*hours, *minutes), - }; - py_tz = Py::new(py, tz_info)?.to_object(py); - Some(py_tz.extract(py)?) - } - None => None, - }; + match (&datetime.date, &datetime.time) { + (Some(date), Some(t)) => { + let tz_info: Option> = match &datetime.offset { + Some(offset) => { + let tz_info = match offset { + TomlOffset::Z => TzInfo::new(0, 0), + TomlOffset::Custom { hours, minutes } => TzInfo::new(*hours, *minutes), + }; + Some(Bound::new(py, tz_info)?.into_any().downcast_into()?) + } + None => None, + }; - PyDateTime::new( - py, - date.year as i32, - date.month, - date.day, - t.hour, - t.minute, - t.second, - t.nanosecond / 1000, - tzinfo, - )? - .to_object(py) - } - None => PyDate::new(py, date.year as i32, date.month, date.day)?.to_object(py), - }, - None => match &datetime.time { - Some(t) => PyTime::new(py, t.hour, t.minute, t.second, t.nanosecond / 1000, None)?.to_object(py), - None => { - // AFAIK this can't actually happen - let msg = "either time or date (or both) are required)".to_string(); - return Err(PyErr::new::(PyValueError::new_err(msg))); - } - }, - }; - Ok(py_dt) + PyDateTime::new_bound( + py, + date.year as i32, + date.month, + date.day, + t.hour, + t.minute, + t.second, + t.nanosecond / 1000, + tz_info.as_ref(), + ) + .map(|dt| dt.to_object(py)) + } + (Some(date), None) => PyDate::new_bound(py, date.year as i32, date.month, date.day).map(|d| d.to_object(py)), + (None, Some(t)) => { + PyTime::new_bound(py, t.hour, t.minute, t.second, t.nanosecond / 1000, None).map(|t| t.to_object(py)) + } + (None, None) => { + // AFAIK this can't actually happen + unreachable!("either time or date (or both) are required") + } + } } #[pyclass(module = "rtoml._rtoml", extends = PyTzInfo)] @@ -66,15 +58,15 @@ impl TzInfo { (self.hours as i32) * 3600 + (self.minutes as i32) * 60 } - fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> { - PyDelta::new(py, 0, self.seconds(), 0, true) + fn utcoffset<'py>(&self, dt: &Bound<'py, PyDateTime>) -> PyResult> { + PyDelta::new_bound(dt.py(), 0, self.seconds(), 0, true) } - fn tzname(&self, _dt: &PyDateTime) -> String { + fn tzname(&self, _dt: &Bound<'_, PyAny>) -> String { self.__str__() } - fn dst(&self, _dt: &PyDateTime) -> Option<&PyDelta> { + fn dst(&self, _dt: &Bound<'_, PyAny>) -> Option<&PyDelta> { None } diff --git a/src/de.rs b/src/de.rs index ab152c8..c38074d 100644 --- a/src/de.rs +++ b/src/de.rs @@ -119,7 +119,7 @@ impl<'de, 'py> Visitor<'de> for PyDeserializer<'py> { let mut key_set = NoHashSet::::with_hasher(BuildHasherDefault::default()); key_set.insert(hash_builder.hash_one(&first_key)); - let dict = PyDict::new(self.py); + let dict = PyDict::new_bound(self.py); dict.set_item(first_key, first_value).map_err(de::Error::custom)?; while let Some((key, value)) = @@ -134,7 +134,7 @@ impl<'de, 'py> Visitor<'de> for PyDeserializer<'py> { Ok(dict.into_py(self.py)) } - None => Ok(PyDict::new(self.py).into_py(self.py)), + None => Ok(PyDict::new_bound(self.py).into_py(self.py)), } } } diff --git a/src/lib.rs b/src/lib.rs index 7412f7f..8c50a3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::{create_exception, wrap_pyfunction}; -use serde::de::DeserializeSeed; +use serde::de::DeserializeSeed; use toml::{to_string as to_toml_string, to_string_pretty as to_toml_string_pretty, Deserializer}; use crate::ser::SerializePyObject; @@ -24,7 +24,7 @@ fn deserialize(py: Python, toml_data: String, none_value: Option<&str>) -> PyRes } #[pyfunction] -fn serialize(py: Python, obj: &PyAny, none_value: Option<&str>) -> PyResult { +fn serialize(py: Python, obj: Bound<'_, PyAny>, none_value: Option<&str>) -> PyResult { let s = SerializePyObject::new(py, obj, none_value); match to_toml_string(&s) { Ok(s) => Ok(s), @@ -33,7 +33,7 @@ fn serialize(py: Python, obj: &PyAny, none_value: Option<&str>) -> PyResult) -> PyResult { +fn serialize_pretty(py: Python, obj: Bound<'_, PyAny>, none_value: Option<&str>) -> PyResult { let s = SerializePyObject::new(py, obj, none_value); match to_toml_string_pretty(&s) { Ok(s) => Ok(s), @@ -52,9 +52,9 @@ pub fn get_version() -> String { } #[pymodule] -fn _rtoml(py: Python, m: &PyModule) -> PyResult<()> { - m.add("TomlParsingError", py.get_type::())?; - m.add("TomlSerializationError", py.get_type::())?; +fn _rtoml(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add("TomlParsingError", py.get_type_bound::())?; + m.add("TomlSerializationError", py.get_type_bound::())?; let version = get_version(); m.add("__version__", version.clone())?; // keep VERSION for compatibility diff --git a/src/py_type.rs b/src/py_type.rs index 97ce441..768f473 100644 --- a/src/py_type.rs +++ b/src/py_type.rs @@ -1,6 +1,9 @@ -use pyo3::once_cell::GILOnceCell; use pyo3::prelude::*; -use pyo3::types::{PyByteArray, PyBytes, PyDate, PyDateTime, PyDict, PyList, PyString, PyTime, PyTuple}; +use pyo3::sync::GILOnceCell; +use pyo3::types::{ + PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDict, PyFloat, PyInt, PyList, PyNone, PyString, PyTime, PyTuple, +}; +use pyo3::PyTypeInfo; #[derive(Clone)] #[cfg_attr(debug_assertions, derive(Debug))] @@ -30,26 +33,24 @@ static TYPE_LOOKUP: GILOnceCell = GILOnceCell::new(); impl PyTypeLookup { fn new(py: Python) -> Self { Self { - none: py.None().as_ref(py).get_type_ptr() as usize, + none: PyNone::type_object_raw(py) as usize, // numeric types - int: 0i32.into_py(py).as_ref(py).get_type_ptr() as usize, - bool: true.into_py(py).as_ref(py).get_type_ptr() as usize, - float: 0f32.into_py(py).as_ref(py).get_type_ptr() as usize, + int: PyInt::type_object_raw(py) as usize, + bool: PyBool::type_object_raw(py) as usize, + float: PyFloat::type_object_raw(py) as usize, // string types - string: PyString::new(py, "s").get_type_ptr() as usize, - bytes: PyBytes::new(py, b"s").get_type_ptr() as usize, - bytearray: PyByteArray::new(py, b"s").get_type_ptr() as usize, + string: PyString::type_object_raw(py) as usize, + bytes: PyBytes::type_object_raw(py) as usize, + bytearray: PyByteArray::type_object_raw(py) as usize, // sequence types - list: PyList::empty(py).get_type_ptr() as usize, - tuple: PyTuple::empty(py).get_type_ptr() as usize, + list: PyList::type_object_raw(py) as usize, + tuple: PyTuple::type_object_raw(py) as usize, // mapping types - dict: PyDict::new(py).get_type_ptr() as usize, + dict: PyDict::type_object_raw(py) as usize, // datetime types - datetime: PyDateTime::new(py, 2000, 1, 1, 0, 0, 0, 0, None) - .unwrap() - .get_type_ptr() as usize, - date: PyDate::new(py, 2000, 1, 1).unwrap().get_type_ptr() as usize, - time: PyTime::new(py, 0, 0, 0, 0, None).unwrap().get_type_ptr() as usize, + datetime: PyDateTime::type_object_raw(py) as usize, + date: PyDate::type_object_raw(py) as usize, + time: PyTime::type_object_raw(py) as usize, } } diff --git a/src/ser.rs b/src/ser.rs index 0519877..eedef90 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -9,30 +9,40 @@ use toml::value::Datetime; use crate::py_type::PyTypeLookup; pub struct SerializePyObject<'py> { - obj: &'py PyAny, - py: Python<'py>, + obj: Bound<'py, PyAny>, none_value: Option<&'py str>, ob_type_lookup: &'py PyTypeLookup, } impl<'py> SerializePyObject<'py> { - pub fn new(py: Python<'py>, obj: &'py PyAny, none_value: Option<&'py str>) -> Self { + pub fn new(py: Python<'py>, obj: Bound<'py, PyAny>, none_value: Option<&'py str>) -> Self { Self { obj, - py, none_value, ob_type_lookup: PyTypeLookup::cached(py), } } - fn with_obj(&self, obj: &'py PyAny) -> Self { + fn with_obj(&self, obj: Bound<'py, PyAny>) -> Self { Self { obj, - py: self.py, none_value: self.none_value, ob_type_lookup: self.ob_type_lookup, } } + + fn ser_dict( + &self, + map: &mut S::SerializeMap, + dict_items: Vec<(Bound<'_, PyAny>, Bound<'_, PyAny>)>, + ) -> Result<(), S::Error> { + for (k, v) in dict_items { + let key = table_key(&k, self.none_value)?; + let value = self.with_obj(v); + map.serialize_entry(key, &value)?; + } + Ok(()) + } } macro_rules! serde_err { @@ -68,16 +78,16 @@ impl<'py> Serialize for SerializePyObject<'py> { } else if ob_type == lookup.float { serialize!(f64) } else if ob_type == lookup.string { - let py_str: &PyString = self.obj.downcast().map_err(map_py_err)?; + let py_str: &Bound<'_, PyString> = self.obj.downcast().map_err(map_py_err)?; let s = py_str.to_str().map_err(map_py_err)?; serializer.serialize_str(s) } else if ob_type == lookup.dict { - let py_dict: &PyDict = self.obj.downcast().map_err(map_py_err)?; + let py_dict: &Bound<'_, PyDict> = self.obj.downcast().map_err(map_py_err)?; let len = py_dict.len(); - let mut simple_items: Vec<(&PyAny, &PyAny)> = Vec::with_capacity(len); - let mut array_items: Vec<(&PyAny, &PyAny)> = Vec::with_capacity(len); - let mut dict_items: Vec<(&PyAny, &PyAny)> = Vec::with_capacity(len); + let mut simple_items: Vec<(Bound<'_, PyAny>, Bound<'_, PyAny>)> = Vec::with_capacity(len); + let mut array_items: Vec<(Bound<'_, PyAny>, Bound<'_, PyAny>)> = Vec::with_capacity(len); + let mut dict_items: Vec<(Bound<'_, PyAny>, Bound<'_, PyAny>)> = Vec::with_capacity(len); for (k, v) in py_dict { let v_ob_type = v.get_type_ptr() as usize; @@ -92,24 +102,12 @@ impl<'py> Serialize for SerializePyObject<'py> { } } let mut map = serializer.serialize_map(Some(len))?; - for (k, v) in simple_items { - let key = table_key(k, self.none_value)?; - let value = self.with_obj(v); - map.serialize_entry(key, &value)?; - } - for (k, v) in array_items { - let key = table_key(k, self.none_value)?; - let value = self.with_obj(v); - map.serialize_entry(key, &value)?; - } - for (k, v) in dict_items { - let key = table_key(k, self.none_value)?; - let value = self.with_obj(v); - map.serialize_entry(key, &value)?; - } + self.ser_dict::(&mut map, simple_items)?; + self.ser_dict::(&mut map, array_items)?; + self.ser_dict::(&mut map, dict_items)?; map.end() } else if ob_type == lookup.list { - let py_list: &PyList = self.obj.downcast().map_err(map_py_err)?; + let py_list: &Bound<'_, PyList> = self.obj.downcast().map_err(map_py_err)?; let mut seq = serializer.serialize_seq(Some(py_list.len()))?; for element in py_list { if self.none_value.is_some() || !element.is_none() { @@ -118,7 +116,7 @@ impl<'py> Serialize for SerializePyObject<'py> { } seq.end() } else if ob_type == lookup.tuple { - let py_tuple: &PyTuple = self.obj.downcast().map_err(map_py_err)?; + let py_tuple: &Bound<'_, PyTuple> = self.obj.downcast().map_err(map_py_err)?; let mut seq = serializer.serialize_seq(Some(py_tuple.len()))?; for element in py_tuple { if self.none_value.is_some() || !element.is_none() { @@ -127,23 +125,26 @@ impl<'py> Serialize for SerializePyObject<'py> { } seq.end() } else if ob_type == lookup.datetime { - let py_dt: &PyDateTime = self.obj.downcast().map_err(map_py_err)?; - let dt_str = py_dt.str().map_err(map_py_err)?.to_str().map_err(map_py_err)?; + let py_dt: &Bound<'_, PyDateTime> = self.obj.downcast().map_err(map_py_err)?; + let dt_pystr = py_dt.str().map_err(map_py_err)?; + let dt_str = dt_pystr.to_str().map_err(map_py_err)?; let iso_str = dt_str.replacen("+00:00", "Z", 1); match Datetime::from_str(&iso_str) { Ok(dt) => dt.serialize(serializer), Err(e) => serde_err!("unable to convert datetime string to TOML datetime object {:?}", e), } } else if ob_type == lookup.date { - let py_date: &PyDate = self.obj.downcast().map_err(map_py_err)?; - let date_str = py_date.str().map_err(map_py_err)?.to_str().map_err(map_py_err)?; + let py_date: &Bound<'_, PyDate> = self.obj.downcast().map_err(map_py_err)?; + let date_pystr = py_date.str().map_err(map_py_err)?; + let date_str = date_pystr.to_str().map_err(map_py_err)?; match Datetime::from_str(date_str) { Ok(dt) => dt.serialize(serializer), Err(e) => serde_err!("unable to convert date string to TOML date object {:?}", e), } } else if ob_type == lookup.time { - let py_time: &PyTime = self.obj.downcast().map_err(map_py_err)?; - let time_str = py_time.str().map_err(map_py_err)?.to_str().map_err(map_py_err)?; + let py_time: &Bound<'_, PyTime> = self.obj.downcast().map_err(map_py_err)?; + let time_pystr = py_time.str().map_err(map_py_err)?; + let time_str = time_pystr.to_str().map_err(map_py_err)?; match Datetime::from_str(time_str) { Ok(dt) => dt.serialize(serializer), Err(e) => serde_err!("unable to convert time string to TOML time object {:?}", e), @@ -151,7 +152,7 @@ impl<'py> Serialize for SerializePyObject<'py> { } else if ob_type == lookup.bytes || ob_type == lookup.bytearray { serialize!(&[u8]) } else { - serde_err!("{} is not serializable to TOML", any_repr(self.obj)) + serde_err!("{} is not serializable to TOML", any_repr(&self.obj)) } } } @@ -160,7 +161,7 @@ fn map_py_err(err: I) -> O { O::custom(err.to_string()) } -fn table_key<'py, E: SerError>(key: &'py PyAny, none_value: Option<&'py str>) -> Result<&'py str, E> { +fn table_key<'py, E: SerError>(key: &'py Bound<'py, PyAny>, none_value: Option<&'py str>) -> Result<&'py str, E> { if let Ok(py_string) = key.downcast::() { py_string.to_str().map_err(map_py_err) } else if key.is_none() { @@ -173,8 +174,9 @@ fn table_key<'py, E: SerError>(key: &'py PyAny, none_value: Option<&'py str>) -> } } -fn any_repr(obj: &PyAny) -> String { - let name = obj.get_type().name().unwrap_or("unknown"); +fn any_repr(obj: &Bound<'_, PyAny>) -> String { + let typ = obj.get_type(); + let name = typ.name().unwrap_or_else(|_| "unknown".into()); match obj.repr() { Ok(repr) => format!("{repr} ({name})"), Err(_) => name.to_string(),