pythonize-0.21.1/.cargo_vcs_info.json0000644000000001360000000000100131460ustar { "git": { "sha1": "c3a45879ce0455099191c4de26bd62d2743cd7d2" }, "path_in_vcs": "" }pythonize-0.21.1/.github/FUNDING.yml000064400000000000000000000000241046102023000151070ustar 00000000000000github: davidhewitt pythonize-0.21.1/.github/dependabot.yml000064400000000000000000000011331046102023000161240ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" pythonize-0.21.1/.github/workflows/ci.yml000064400000000000000000000045201046102023000164520ustar 00000000000000name: CI on: push: branches: - main pull_request: env: CARGO_TERM_COLOR: always jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check rust formatting (rustfmt) run: cargo fmt --all -- --check clippy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --all build: needs: [fmt] # don't wait for clippy as fails rarely and takes longer name: python${{ matrix.python-version }} ${{ matrix.os }} rust-${{ matrix.rust}} runs-on: ${{ matrix.os }} strategy: fail-fast: false # If one platform fails, allow the rest to keep testing. matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] os: [ "macos-latest", "ubuntu-latest", "windows-latest", ] rust: [stable] include: - python-version: "3.12" os: "ubuntu-latest" rust: "1.56" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 continue-on-error: true - name: Test run: cargo test --verbose - name: Test (abi3) run: cargo test --verbose --features pyo3/abi3-py37 env: RUST_BACKTRACE: 1 coverage: needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 continue-on-error: true - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - uses: dtolnay/rust-toolchain@stable with: components: llvm-tools-preview - run: | cargo llvm-cov clean cargo llvm-cov --codecov --output-path codecov.json - uses: codecov/codecov-action@v4 with: file: codecov.json token: ${{ secrets.CODECOV_TOKEN }} pythonize-0.21.1/.gitignore000064400000000000000000000000311046102023000137200ustar 00000000000000/target Cargo.lock .idea pythonize-0.21.1/CHANGELOG.md000064400000000000000000000022351046102023000135510ustar 00000000000000## 0.21.1 - 2024-04-02 - Fix compile error when using PyO3 `abi3` feature targeting a minimum version below 3.10 ## 0.21.0 - 2024-04-01 - Bump edition to 2021 - Bump MSRV to 1.56 - Update to PyO3 0.21 - Export `PythonizeDefault` ## 0.20.0 - 2023-10-15 - Update to PyO3 0.20 ## 0.19.0 - 2023-06-11 - Update to PyO3 0.19 ## 0.18.0 - 2023-01-22 - Add LICENSE file to the crate - Update to PyO3 0.18 ## 0.17.0 - 2022-08-24 - Update to PyO3 0.17 ## 0.16.0 - 2022-03-06 - Update to PyO3 0.16 ## 0.15.0 - 2021-11-12 - Update to PyO3 0.15 - Add `pythonize_custom` for customizing the Python types to serialize to. - Add support for `depythonize` to handle arbitrary Python sequence and mapping types. ## 0.14.0 - 2021-07-05 - Update to PyO3 0.14 ## 0.13.0 - 2020-12-28 - Update to PyO3 0.13 ## 0.12.1 - 2020-12-08 - Require `std` feature of `serde`. - Reduce memory consumption when deserializing sequences. - Fix deserializing untagged struct enum variants. - Fix deserializing sequences from Python tuples. ## 0.12.0 - 2020-11-22 - Change release versioning to match `pyo3` major/minor version. - Implement `depythonizer` ## 0.1.0 - 2020-08-12 - Initial release pythonize-0.21.1/Cargo.toml0000644000000026150000000000100111500ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.56" name = "pythonize" version = "0.21.1" authors = ["David Hewitt <1939362+davidhewitt@users.noreply.github.com>"] description = "Serde Serializer & Deserializer from Rust <--> Python, backed by PyO3." homepage = "https://github.com/davidhewitt/pythonize" documentation = "https://docs.rs/crate/pythonize/" readme = "README.md" license = "MIT" repository = "https://github.com/davidhewitt/pythonize" [dependencies.pyo3] version = "0.21.0" default-features = false [dependencies.serde] version = "1.0" features = ["std"] default-features = false [dev-dependencies.maplit] version = "1.0.2" [dev-dependencies.pyo3] version = "0.21.1" features = [ "auto-initialize", "macros", ] default-features = false [dev-dependencies.serde] version = "1.0" features = ["derive"] default-features = false [dev-dependencies.serde_json] version = "1.0" [dev-dependencies.serde_path_to_error] version = "0.1.15" pythonize-0.21.1/Cargo.toml.orig000064400000000000000000000015021046102023000146230ustar 00000000000000[package] name = "pythonize" version = "0.21.1" authors = ["David Hewitt <1939362+davidhewitt@users.noreply.github.com>"] edition = "2021" rust-version = "1.56" license = "MIT" description = "Serde Serializer & Deserializer from Rust <--> Python, backed by PyO3." homepage = "https://github.com/davidhewitt/pythonize" repository = "https://github.com/davidhewitt/pythonize" documentation = "https://docs.rs/crate/pythonize/" [dependencies] serde = { version = "1.0", default-features = false, features = ["std"] } pyo3 = { version = "0.21.0", default-features = false } [dev-dependencies] serde = { version = "1.0", default-features = false, features = ["derive"] } pyo3 = { version = "0.21.1", default-features = false, features = ["auto-initialize", "macros"] } serde_json = "1.0" maplit = "1.0.2" serde_path_to_error = "0.1.15" pythonize-0.21.1/LICENSE000064400000000000000000000020711046102023000127430ustar 00000000000000Copyright (c) 2022-present David Hewitt and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pythonize-0.21.1/README.md000064400000000000000000000024511046102023000132170ustar 00000000000000# Pythonize This is an experimental serializer for Rust's serde ecosystem, which can convert Rust objects to Python values and back. At the moment the Python structures it produces should be _very_ similar to those which are produced by `serde_json`; i.e. calling Python's `json.loads()` on a value encoded by `serde_json` should produce an identical structure to that which is produced directly by `pythonize`. ## Usage This crate converts Rust types which implement the [Serde] serialization traits into Python objects using the [PyO3] library. Pythonize has two public APIs: `pythonize` and `depythonize`. [Serde]: https://github.com/serde-rs/serde [PyO3]: https://github.com/PyO3/pyo3 # Examples ```rust use serde::{Serialize, Deserialize}; use pyo3::Python; use pythonize::{depythonize, pythonize}; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Sample { foo: String, bar: Option } let gil = Python::acquire_gil(); let py = gil.python(); let sample = Sample { foo: "Foo".to_string(), bar: None }; // Rust -> Python let obj = pythonize(py, &sample).unwrap(); assert_eq!("{'foo': 'Foo', 'bar': None}", &format!("{}", obj.as_ref(py).repr().unwrap())); // Python -> Rust let new_sample: Sample = depythonize(obj.as_ref(py)).unwrap(); assert_eq!(new_sample, sample); ``` pythonize-0.21.1/src/de.rs000064400000000000000000000515441046102023000134740ustar 00000000000000use pyo3::{types::*, Bound, PyNativeType}; use serde::de::{self, IntoDeserializer}; use serde::Deserialize; use crate::error::{PythonizeError, Result}; /// Attempt to convert a Python object to an instance of `T` #[deprecated( since = "0.21.0", note = "will be replaced by `depythonize_bound` in a future release" )] pub fn depythonize<'de, T>(obj: &'de PyAny) -> Result where T: Deserialize<'de>, { let mut depythonizer = Depythonizer::from_object_bound(obj.as_borrowed().to_owned()); T::deserialize(&mut depythonizer) } /// Attempt to convert a Python object to an instance of `T` pub fn depythonize_bound<'py, T>(obj: Bound<'py, PyAny>) -> Result where T: for<'a> Deserialize<'a>, { let mut depythonizer = Depythonizer::from_object_bound(obj); T::deserialize(&mut depythonizer) } /// A structure that deserializes Python objects into Rust values pub struct Depythonizer<'py> { input: Bound<'py, PyAny>, } impl<'py> Depythonizer<'py> { /// Create a deserializer from a Python object #[deprecated( since = "0.21.0", note = "will be replaced by `Depythonizer::from_object_bound` in a future version" )] pub fn from_object(input: &'py PyAny) -> Self { Self::from_object_bound(input.as_borrowed().to_owned()) } /// Create a deserializer from a Python object pub fn from_object_bound(input: Bound<'py, PyAny>) -> Self { Depythonizer { input } } fn sequence_access(&self, expected_len: Option) -> Result> { let seq = self.input.downcast::()?; let len = self.input.len()?; match expected_len { Some(expected) if expected != len => { Err(PythonizeError::incorrect_sequence_length(expected, len)) } _ => Ok(PySequenceAccess::new(seq.clone(), len)), } } fn dict_access(&self) -> Result> { PyMappingAccess::new(self.input.downcast()?) } } macro_rules! deserialize_type { ($method:ident => $visit:ident) => { fn $method(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.$visit(self.input.extract()?) } }; } impl<'a, 'py, 'de> de::Deserializer<'de> for &'a mut Depythonizer<'py> { type Error = PythonizeError; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { let obj = &self.input; // First check for cases which are cheap to check due to pointer // comparison or bitflag checks if obj.is_none() { self.deserialize_unit(visitor) } else if obj.is_instance_of::() { self.deserialize_bool(visitor) } else if obj.is_instance_of::() { self.deserialize_i64(visitor) } else if obj.is_instance_of::() || obj.is_instance_of::() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.is_instance_of::() { self.deserialize_map(visitor) } else if obj.is_instance_of::() { self.deserialize_str(visitor) } // Continue with cases which are slower to check because they go // throuh `isinstance` machinery else if obj.is_instance_of::() || obj.is_instance_of::() { self.deserialize_bytes(visitor) } else if obj.is_instance_of::() { self.deserialize_f64(visitor) } else if obj.is_instance_of::() || obj.is_instance_of::() || obj.downcast::().is_ok() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.downcast::().is_ok() { self.deserialize_map(visitor) } else { Err(obj.get_type().qualname().map_or_else( |_| PythonizeError::unsupported_type("unknown"), PythonizeError::unsupported_type, )) } } fn deserialize_bool(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_bool(self.input.is_truthy()?) } fn deserialize_char(self, visitor: V) -> Result where V: de::Visitor<'de>, { let s = self.input.downcast::()?.to_cow()?; if s.len() != 1 { return Err(PythonizeError::invalid_length_char()); } visitor.visit_char(s.chars().next().unwrap()) } deserialize_type!(deserialize_i8 => visit_i8); deserialize_type!(deserialize_i16 => visit_i16); deserialize_type!(deserialize_i32 => visit_i32); deserialize_type!(deserialize_i64 => visit_i64); deserialize_type!(deserialize_u8 => visit_u8); deserialize_type!(deserialize_u16 => visit_u16); deserialize_type!(deserialize_u32 => visit_u32); deserialize_type!(deserialize_u64 => visit_u64); deserialize_type!(deserialize_f32 => visit_f32); deserialize_type!(deserialize_f64 => visit_f64); fn deserialize_str(self, visitor: V) -> Result where V: de::Visitor<'de>, { let s = self.input.downcast::()?; visitor.visit_str(&s.to_cow()?) } fn deserialize_string(self, visitor: V) -> Result where V: de::Visitor<'de>, { self.deserialize_str(visitor) } fn deserialize_bytes(self, visitor: V) -> Result where V: de::Visitor<'de>, { let b = self.input.downcast::()?; visitor.visit_bytes(b.as_bytes()) } fn deserialize_byte_buf(self, visitor: V) -> Result where V: de::Visitor<'de>, { self.deserialize_bytes(visitor) } fn deserialize_option(self, visitor: V) -> Result where V: de::Visitor<'de>, { if self.input.is_none() { visitor.visit_none() } else { visitor.visit_some(self) } } fn deserialize_unit(self, visitor: V) -> Result where V: de::Visitor<'de>, { if self.input.is_none() { visitor.visit_unit() } else { Err(PythonizeError::msg("expected None")) } } fn deserialize_unit_struct(self, _name: &'static str, visitor: V) -> Result where V: de::Visitor<'de>, { self.deserialize_unit(visitor) } fn deserialize_newtype_struct(self, _name: &'static str, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_newtype_struct(self) } fn deserialize_seq(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_seq(self.sequence_access(None)?) } fn deserialize_tuple(self, len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_seq(self.sequence_access(Some(len))?) } fn deserialize_tuple_struct( self, _name: &'static str, len: usize, visitor: V, ) -> Result where V: de::Visitor<'de>, { visitor.visit_seq(self.sequence_access(Some(len))?) } fn deserialize_map(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_map(self.dict_access()?) } fn deserialize_struct( self, _name: &'static str, _fields: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { self.deserialize_map(visitor) } fn deserialize_enum( self, _name: &'static str, _variants: &'static [&'static str], visitor: V, ) -> Result where V: de::Visitor<'de>, { let item = &self.input; if let Ok(d) = item.downcast::() { // Get the enum variant from the dict key if d.len() != 1 { return Err(PythonizeError::invalid_length_enum()); } let variant = d .keys() .get_item(0)? .downcast_into::() .map_err(|_| PythonizeError::dict_key_not_string())?; let value = d.get_item(&variant)?.unwrap(); let mut de = Depythonizer::from_object_bound(value); visitor.visit_enum(PyEnumAccess::new(&mut de, variant)) } else if let Ok(s) = item.downcast::() { visitor.visit_enum(s.to_cow()?.into_deserializer()) } else { Err(PythonizeError::invalid_enum_type()) } } fn deserialize_identifier(self, visitor: V) -> Result where V: de::Visitor<'de>, { let s = self .input .downcast::() .map_err(|_| PythonizeError::dict_key_not_string())?; visitor.visit_str(&s.to_cow()?) } fn deserialize_ignored_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_unit() } } struct PySequenceAccess<'py> { seq: Bound<'py, PySequence>, index: usize, len: usize, } impl<'py> PySequenceAccess<'py> { fn new(seq: Bound<'py, PySequence>, len: usize) -> Self { Self { seq, index: 0, len } } } impl<'de, 'py> de::SeqAccess<'de> for PySequenceAccess<'py> { type Error = PythonizeError; fn next_element_seed(&mut self, seed: T) -> Result> where T: de::DeserializeSeed<'de>, { if self.index < self.len { let mut item_de = Depythonizer::from_object_bound(self.seq.get_item(self.index)?); self.index += 1; seed.deserialize(&mut item_de).map(Some) } else { Ok(None) } } } struct PyMappingAccess<'py> { keys: Bound<'py, PySequence>, values: Bound<'py, PySequence>, key_idx: usize, val_idx: usize, len: usize, } impl<'py> PyMappingAccess<'py> { fn new(map: &Bound<'py, PyMapping>) -> Result { let keys = map.keys()?; let values = map.values()?; let len = map.len()?; Ok(Self { keys, values, key_idx: 0, val_idx: 0, len, }) } } impl<'de, 'py> de::MapAccess<'de> for PyMappingAccess<'py> { type Error = PythonizeError; fn next_key_seed(&mut self, seed: K) -> Result> where K: de::DeserializeSeed<'de>, { if self.key_idx < self.len { let mut item_de = Depythonizer::from_object_bound(self.keys.get_item(self.key_idx)?); self.key_idx += 1; seed.deserialize(&mut item_de).map(Some) } else { Ok(None) } } fn next_value_seed(&mut self, seed: V) -> Result where V: de::DeserializeSeed<'de>, { let mut item_de = Depythonizer::from_object_bound(self.values.get_item(self.val_idx)?); self.val_idx += 1; seed.deserialize(&mut item_de) } } struct PyEnumAccess<'a, 'py> { de: &'a mut Depythonizer<'py>, variant: Bound<'py, PyString>, } impl<'a, 'py> PyEnumAccess<'a, 'py> { fn new(de: &'a mut Depythonizer<'py>, variant: Bound<'py, PyString>) -> Self { Self { de, variant } } } impl<'a, 'py, 'de> de::EnumAccess<'de> for PyEnumAccess<'a, 'py> { type Error = PythonizeError; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { let cow = self.variant.to_cow()?; let de: de::value::StrDeserializer<'_, PythonizeError> = cow.as_ref().into_deserializer(); let val = seed.deserialize(de)?; Ok((val, self)) } } impl<'a, 'py, 'de> de::VariantAccess<'de> for PyEnumAccess<'a, 'py> { type Error = PythonizeError; fn unit_variant(self) -> Result<()> { Ok(()) } fn newtype_variant_seed(self, seed: T) -> Result where T: de::DeserializeSeed<'de>, { seed.deserialize(self.de) } fn tuple_variant(self, len: usize, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_seq(self.de.sequence_access(Some(len))?) } fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_map(self.de.dict_access()?) } } #[cfg(test)] mod test { use super::*; use crate::error::ErrorImpl; use maplit::hashmap; use pyo3::Python; use serde_json::{json, Value as JsonValue}; fn test_de(code: &str, expected: &T, expected_json: &JsonValue) where T: de::DeserializeOwned + PartialEq + std::fmt::Debug, { Python::with_gil(|py| { let locals = PyDict::new_bound(py); py.run_bound(&format!("obj = {}", code), None, Some(&locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); let actual: T = depythonize_bound(obj.clone()).unwrap(); assert_eq!(&actual, expected); let actual_json: JsonValue = depythonize_bound(obj).unwrap(); assert_eq!(&actual_json, expected_json); }); } #[test] fn test_empty_struct() { #[derive(Debug, Deserialize, PartialEq)] struct Empty; let expected = Empty; let expected_json = json!(null); let code = "None"; test_de(code, &expected, &expected_json); } #[test] fn test_struct() { #[derive(Debug, Deserialize, PartialEq)] struct Struct { foo: String, bar: usize, baz: f32, qux: bool, } let expected = Struct { foo: "Foo".to_string(), bar: 8usize, baz: 45.23, qux: true, }; let expected_json = json!({ "foo": "Foo", "bar": 8, "baz": 45.23, "qux": true }); let code = "{'foo': 'Foo', 'bar': 8, 'baz': 45.23, 'qux': True}"; test_de(code, &expected, &expected_json); } #[test] fn test_struct_missing_key() { #[derive(Debug, Deserialize, PartialEq)] struct Struct { foo: String, bar: usize, } let code = "{'foo': 'Foo'}"; Python::with_gil(|py| { let locals = PyDict::new_bound(py); py.run_bound(&format!("obj = {}", code), None, Some(&locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); assert!(matches!( *depythonize_bound::(obj).unwrap_err().inner, ErrorImpl::Message(msg) if msg == "missing field `bar`" )); }) } #[test] fn test_tuple_struct() { #[derive(Debug, Deserialize, PartialEq)] struct TupleStruct(String, f64); let expected = TupleStruct("cat".to_string(), -10.05); let expected_json = json!(["cat", -10.05]); let code = "('cat', -10.05)"; test_de(code, &expected, &expected_json); } #[test] fn test_tuple_too_long() { #[derive(Debug, Deserialize, PartialEq)] struct TupleStruct(String, f64); let code = "('cat', -10.05, 'foo')"; Python::with_gil(|py| { let locals = PyDict::new_bound(py); py.run_bound(&format!("obj = {}", code), None, Some(&locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); assert!(matches!( *depythonize_bound::(obj).unwrap_err().inner, ErrorImpl::IncorrectSequenceLength { expected, got } if expected == 2 && got == 3 )); }) } #[test] fn test_tuple_struct_from_pylist() { #[derive(Debug, Deserialize, PartialEq)] struct TupleStruct(String, f64); let expected = TupleStruct("cat".to_string(), -10.05); let expected_json = json!(["cat", -10.05]); let code = "['cat', -10.05]"; test_de(code, &expected, &expected_json); } #[test] fn test_tuple() { let expected = ("foo".to_string(), 5); let expected_json = json!(["foo", 5]); let code = "('foo', 5)"; test_de(code, &expected, &expected_json); } #[test] fn test_tuple_from_pylist() { let expected = ("foo".to_string(), 5); let expected_json = json!(["foo", 5]); let code = "['foo', 5]"; test_de(code, &expected, &expected_json); } #[test] fn test_vec() { let expected = vec![3, 2, 1]; let expected_json = json!([3, 2, 1]); let code = "[3, 2, 1]"; test_de(code, &expected, &expected_json); } #[test] fn test_vec_from_tuple() { let expected = vec![3, 2, 1]; let expected_json = json!([3, 2, 1]); let code = "(3, 2, 1)"; test_de(code, &expected, &expected_json); } #[test] fn test_hashmap() { let expected = hashmap! {"foo".to_string() => 4}; let expected_json = json!({"foo": 4 }); let code = "{'foo': 4}"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_variant() { #[derive(Debug, Deserialize, PartialEq)] enum Foo { Variant, } let expected = Foo::Variant; let expected_json = json!("Variant"); let code = "'Variant'"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_tuple_variant() { #[derive(Debug, Deserialize, PartialEq)] enum Foo { Tuple(i32, String), } let expected = Foo::Tuple(12, "cat".to_string()); let expected_json = json!({"Tuple": [12, "cat"]}); let code = "{'Tuple': [12, 'cat']}"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_newtype_variant() { #[derive(Debug, Deserialize, PartialEq)] enum Foo { NewType(String), } let expected = Foo::NewType("cat".to_string()); let expected_json = json!({"NewType": "cat" }); let code = "{'NewType': 'cat'}"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_struct_variant() { #[derive(Debug, Deserialize, PartialEq)] enum Foo { Struct { foo: String, bar: usize }, } let expected = Foo::Struct { foo: "cat".to_string(), bar: 25, }; let expected_json = json!({"Struct": {"foo": "cat", "bar": 25 }}); let code = "{'Struct': {'foo': 'cat', 'bar': 25}}"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_untagged_tuple_variant() { #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] enum Foo { Tuple(f32, char), } let expected = Foo::Tuple(12.0, 'c'); let expected_json = json!([12.0, 'c']); let code = "[12.0, 'c']"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_untagged_newtype_variant() { #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] enum Foo { NewType(String), } let expected = Foo::NewType("cat".to_string()); let expected_json = json!("cat"); let code = "'cat'"; test_de(code, &expected, &expected_json); } #[test] fn test_enum_untagged_struct_variant() { #[derive(Debug, Deserialize, PartialEq)] #[serde(untagged)] enum Foo { Struct { foo: Vec, bar: [u8; 4] }, } let expected = Foo::Struct { foo: vec!['a', 'b', 'c'], bar: [2, 5, 3, 1], }; let expected_json = json!({"foo": ["a", "b", "c"], "bar": [2, 5, 3, 1]}); let code = "{'foo': ['a', 'b', 'c'], 'bar': [2, 5, 3, 1]}"; test_de(code, &expected, &expected_json); } #[test] fn test_nested_type() { #[derive(Debug, Deserialize, PartialEq)] struct Foo { name: String, bar: Bar, } #[derive(Debug, Deserialize, PartialEq)] struct Bar { value: usize, variant: Baz, } #[derive(Debug, Deserialize, PartialEq)] enum Baz { Basic, Tuple(f32, u32), } let expected = Foo { name: "SomeFoo".to_string(), bar: Bar { value: 13, variant: Baz::Tuple(-1.5, 8), }, }; let expected_json = json!({"name": "SomeFoo", "bar": { "value": 13, "variant": { "Tuple": [-1.5, 8]}}}); let code = "{'name': 'SomeFoo', 'bar': {'value': 13, 'variant': {'Tuple': [-1.5, 8]}}}"; test_de(code, &expected, &expected_json); } } pythonize-0.21.1/src/error.rs000064400000000000000000000134451046102023000142330ustar 00000000000000use pyo3::{exceptions::*, DowncastError, DowncastIntoError}; use pyo3::{PyDowncastError, PyErr}; use serde::{de, ser}; use std::error; use std::fmt::{self, Debug, Display}; use std::result; /// Alias for `std::result::Result` with error type `PythonizeError` pub type Result = result::Result; /// Errors that can occur when serializing/deserializing Python objects pub struct PythonizeError { pub(crate) inner: Box, } impl PythonizeError { pub(crate) fn msg(text: T) -> Self where T: ToString, { Self { inner: Box::new(ErrorImpl::Message(text.to_string())), } } pub(crate) fn unsupported_type(t: T) -> Self where T: ToString, { Self { inner: Box::new(ErrorImpl::UnsupportedType(t.to_string())), } } pub(crate) fn dict_key_not_string() -> Self { Self { inner: Box::new(ErrorImpl::DictKeyNotString), } } pub(crate) fn incorrect_sequence_length(expected: usize, got: usize) -> Self { Self { inner: Box::new(ErrorImpl::IncorrectSequenceLength { expected, got }), } } pub(crate) fn invalid_enum_type() -> Self { Self { inner: Box::new(ErrorImpl::InvalidEnumType), } } pub(crate) fn invalid_length_enum() -> Self { Self { inner: Box::new(ErrorImpl::InvalidLengthEnum), } } pub(crate) fn invalid_length_char() -> Self { Self { inner: Box::new(ErrorImpl::InvalidLengthChar), } } } /// Error codes for problems that can occur when serializing/deserializing Python objects #[derive(Debug)] pub enum ErrorImpl { /// An error originating from the Python runtime PyErr(PyErr), /// Generic error message Message(String), /// A Python type not supported by the deserializer UnsupportedType(String), /// A `PyAny` object that failed to downcast to an expected Python type UnexpectedType(String), /// Dict keys should be strings to deserialize to struct fields DictKeyNotString, /// Sequence length did not match expected tuple or tuple struct length. IncorrectSequenceLength { expected: usize, got: usize }, /// Enum variants should either be dict (tagged) or str (variant) InvalidEnumType, /// Tagged enum variants should be a dict with exactly 1 key InvalidLengthEnum, /// Expected a `char`, but got a Python str that was not length 1 InvalidLengthChar, } impl error::Error for PythonizeError {} impl Display for PythonizeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.inner.as_ref() { ErrorImpl::PyErr(e) => Display::fmt(e, f), ErrorImpl::Message(s) => Display::fmt(s, f), ErrorImpl::UnsupportedType(s) => write!(f, "unsupported type {}", s), ErrorImpl::UnexpectedType(s) => write!(f, "unexpected type: {}", s), ErrorImpl::DictKeyNotString => f.write_str("dict keys must have type str"), ErrorImpl::IncorrectSequenceLength { expected, got } => { write!(f, "expected sequence of length {}, got {}", expected, got) } ErrorImpl::InvalidEnumType => f.write_str("expected either a str or dict for enum"), ErrorImpl::InvalidLengthEnum => { f.write_str("expected tagged enum dict to have exactly 1 key") } ErrorImpl::InvalidLengthChar => f.write_str("expected a str of length 1 for char"), } } } impl Debug for PythonizeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.inner.as_ref().fmt(f) } } impl ser::Error for PythonizeError { fn custom(msg: T) -> Self where T: Display, { Self { inner: Box::new(ErrorImpl::Message(msg.to_string())), } } } impl de::Error for PythonizeError { fn custom(msg: T) -> Self where T: Display, { Self { inner: Box::new(ErrorImpl::Message(msg.to_string())), } } } /// Convert an exception raised in Python to a `PythonizeError` impl From for PythonizeError { fn from(other: PyErr) -> Self { Self { inner: Box::new(ErrorImpl::PyErr(other)), } } } /// Handle errors that occur when attempting to use `PyAny::cast_as` impl<'a> From> for PythonizeError { fn from(other: PyDowncastError) -> Self { Self { inner: Box::new(ErrorImpl::UnexpectedType(other.to_string())), } } } /// Handle errors that occur when attempting to use `PyAny::cast_as` impl<'a, 'py> From> for PythonizeError { fn from(other: DowncastError<'a, 'py>) -> Self { Self { inner: Box::new(ErrorImpl::UnexpectedType(other.to_string())), } } } /// Handle errors that occur when attempting to use `PyAny::cast_as` impl<'py> From> for PythonizeError { fn from(other: DowncastIntoError<'py>) -> Self { Self { inner: Box::new(ErrorImpl::UnexpectedType(other.to_string())), } } } /// Convert a `PythonizeError` to a Python exception impl From for PyErr { fn from(other: PythonizeError) -> Self { match *other.inner { ErrorImpl::PyErr(e) => e, ErrorImpl::Message(e) => PyException::new_err(e), ErrorImpl::UnsupportedType(_) | ErrorImpl::UnexpectedType(_) | ErrorImpl::DictKeyNotString | ErrorImpl::InvalidEnumType => PyTypeError::new_err(other.to_string()), ErrorImpl::IncorrectSequenceLength { .. } | ErrorImpl::InvalidLengthEnum | ErrorImpl::InvalidLengthChar => PyValueError::new_err(other.to_string()), } } } pythonize-0.21.1/src/lib.rs000064400000000000000000000025371046102023000136500ustar 00000000000000//! This crate converts Rust types which implement the [Serde] serialization //! traits into Python objects using the [PyO3] library. //! //! Pythonize has two public APIs: `pythonize` and `depythonize`. //! //! [Serde]: https://github.com/serde-rs/serde //! [PyO3]: https://github.com/PyO3/pyo3 //! //! # Examples //! ``` //! use serde::{Serialize, Deserialize}; //! use pyo3::Python; //! use pythonize::{depythonize, pythonize}; //! //! #[derive(Debug, Serialize, Deserialize, PartialEq)] //! struct Sample { //! foo: String, //! bar: Option //! } //! //! Python::with_gil(|py| { //! let sample = Sample { //! foo: "Foo".to_string(), //! bar: None //! }; //! //! // Rust -> Python //! let obj = pythonize(py, &sample).unwrap(); //! //! assert_eq!("{'foo': 'Foo', 'bar': None}", &format!("{}", obj.as_ref(py).repr().unwrap())); //! //! // Python -> Rust //! let new_sample: Sample = depythonize(obj.as_ref(py)).unwrap(); //! //! assert_eq!(new_sample, sample); //! }); //! //! ``` mod de; mod error; mod ser; #[allow(deprecated)] pub use crate::de::depythonize; pub use crate::de::{depythonize_bound, Depythonizer}; pub use crate::error::{PythonizeError, Result}; pub use crate::ser::{ pythonize, pythonize_custom, PythonizeDefault, PythonizeDictType, PythonizeListType, PythonizeTypes, Pythonizer, }; pythonize-0.21.1/src/ser.rs000064400000000000000000000401151046102023000136650ustar 00000000000000use std::marker::PhantomData; use pyo3::types::{PyAnyMethods, PyDict, PyList, PyMapping, PySequence, PyTuple}; use pyo3::{Bound, IntoPy, PyObject, PyResult, Python, ToPyObject}; use serde::{ser, Serialize}; use crate::error::{PythonizeError, Result}; /// Trait for types which can represent a Python mapping pub trait PythonizeDictType { /// Constructor fn create_mapping(py: Python) -> PyResult>; } /// Trait for types which can represent a Python sequence pub trait PythonizeListType: Sized { /// Constructor fn create_sequence( py: Python, elements: impl IntoIterator, ) -> PyResult> where T: ToPyObject, U: ExactSizeIterator; } /// Custom types for serialization pub trait PythonizeTypes { /// Python map type (should be representable as python mapping) type Map: PythonizeDictType; /// Python sequence type (should be representable as python sequence) type List: PythonizeListType; } impl PythonizeDictType for PyDict { fn create_mapping(py: Python) -> PyResult> { Ok(PyDict::new_bound(py).into_any().downcast_into().unwrap()) } } impl PythonizeListType for PyList { fn create_sequence( py: Python, elements: impl IntoIterator, ) -> PyResult> where T: ToPyObject, U: ExactSizeIterator, { Ok(PyList::new_bound(py, elements) .into_any() .downcast_into() .unwrap()) } } pub struct PythonizeDefault; impl PythonizeTypes for PythonizeDefault { type Map = PyDict; type List = PyList; } /// Attempt to convert the given data into a Python object pub fn pythonize(py: Python, value: &T) -> Result where T: ?Sized + Serialize, { value.serialize(Pythonizer::new(py)) } /// Attempt to convert the given data into a Python object. /// Also uses custom mapping python class for serialization. pub fn pythonize_custom(py: Python, value: &T) -> Result where T: ?Sized + Serialize, P: PythonizeTypes, { value.serialize(Pythonizer::custom::

(py)) } /// A structure that serializes Rust values into Python objects #[derive(Clone, Copy)] pub struct Pythonizer<'py, P> { py: Python<'py>, _types: PhantomData

, } impl<'py, P> From> for Pythonizer<'py, P> { fn from(py: Python<'py>) -> Self { Self { py, _types: PhantomData, } } } impl<'py> Pythonizer<'py, PythonizeDefault> { /// Creates a serializer to convert data into a Python object using the default mapping class pub fn new(py: Python<'py>) -> Self { Self::from(py) } /// Creates a serializer to convert data into a Python object using a custom mapping class pub fn custom

(py: Python<'py>) -> Pythonizer<'py, P> { Pythonizer::from(py) } } #[doc(hidden)] pub struct PythonCollectionSerializer<'py, P> { items: Vec, py: Python<'py>, _types: PhantomData

, } #[doc(hidden)] pub struct PythonTupleVariantSerializer<'py, P> { variant: &'static str, inner: PythonCollectionSerializer<'py, P>, } #[doc(hidden)] pub struct PythonStructVariantSerializer<'py, P: PythonizeTypes> { variant: &'static str, inner: PythonDictSerializer<'py, P>, } #[doc(hidden)] pub struct PythonDictSerializer<'py, P: PythonizeTypes> { py: Python<'py>, dict: Bound<'py, PyMapping>, _types: PhantomData

, } #[doc(hidden)] pub struct PythonMapSerializer<'py, P: PythonizeTypes> { py: Python<'py>, map: Bound<'py, PyMapping>, key: Option, _types: PhantomData

, } impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { type Ok = PyObject; type Error = PythonizeError; type SerializeSeq = PythonCollectionSerializer<'py, P>; type SerializeTuple = PythonCollectionSerializer<'py, P>; type SerializeTupleStruct = PythonCollectionSerializer<'py, P>; type SerializeTupleVariant = PythonTupleVariantSerializer<'py, P>; type SerializeMap = PythonMapSerializer<'py, P>; type SerializeStruct = PythonDictSerializer<'py, P>; type SerializeStructVariant = PythonStructVariantSerializer<'py, P>; fn serialize_bool(self, v: bool) -> Result { Ok(v.into_py(self.py)) } fn serialize_i8(self, v: i8) -> Result { Ok(v.into_py(self.py)) } fn serialize_i16(self, v: i16) -> Result { Ok(v.into_py(self.py)) } fn serialize_i32(self, v: i32) -> Result { Ok(v.into_py(self.py)) } fn serialize_i64(self, v: i64) -> Result { Ok(v.into_py(self.py)) } fn serialize_u8(self, v: u8) -> Result { Ok(v.into_py(self.py)) } fn serialize_u16(self, v: u16) -> Result { Ok(v.into_py(self.py)) } fn serialize_u32(self, v: u32) -> Result { Ok(v.into_py(self.py)) } fn serialize_u64(self, v: u64) -> Result { Ok(v.into_py(self.py)) } fn serialize_f32(self, v: f32) -> Result { Ok(v.into_py(self.py)) } fn serialize_f64(self, v: f64) -> Result { Ok(v.into_py(self.py)) } fn serialize_char(self, v: char) -> Result { self.serialize_str(&v.to_string()) } fn serialize_str(self, v: &str) -> Result { Ok(v.into_py(self.py)) } fn serialize_bytes(self, v: &[u8]) -> Result { Ok(v.into_py(self.py)) } fn serialize_none(self) -> Result { Ok(self.py.None()) } fn serialize_some(self, value: &T) -> Result where T: ?Sized + Serialize, { value.serialize(self) } fn serialize_unit(self) -> Result { self.serialize_none() } fn serialize_unit_struct(self, _name: &'static str) -> Result { self.serialize_none() } fn serialize_unit_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, ) -> Result { self.serialize_str(variant) } fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result where T: ?Sized + Serialize, { value.serialize(self) } fn serialize_newtype_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, value: &T, ) -> Result where T: ?Sized + Serialize, { let d = PyDict::new_bound(self.py); d.set_item(variant, value.serialize(self)?)?; Ok(d.into()) } fn serialize_seq(self, len: Option) -> Result> { let items = match len { Some(len) => Vec::with_capacity(len), None => Vec::new(), }; Ok(PythonCollectionSerializer { items, py: self.py, _types: PhantomData, }) } fn serialize_tuple(self, len: usize) -> Result> { Ok(PythonCollectionSerializer { items: Vec::with_capacity(len), py: self.py, _types: PhantomData, }) } fn serialize_tuple_struct( self, _name: &'static str, len: usize, ) -> Result> { self.serialize_tuple(len) } fn serialize_tuple_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result> { let inner = self.serialize_tuple(len)?; Ok(PythonTupleVariantSerializer { variant, inner }) } fn serialize_map(self, _len: Option) -> Result> { Ok(PythonMapSerializer { map: P::Map::create_mapping(self.py)?, key: None, py: self.py, _types: PhantomData, }) } fn serialize_struct( self, _name: &'static str, _len: usize, ) -> Result> { Ok(PythonDictSerializer { dict: P::Map::create_mapping(self.py)?, py: self.py, _types: PhantomData, }) } fn serialize_struct_variant( self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize, ) -> Result> { Ok(PythonStructVariantSerializer { variant, inner: PythonDictSerializer { dict: P::Map::create_mapping(self.py)?, py: self.py, _types: PhantomData, }, }) } } impl<'py, P: PythonizeTypes> ser::SerializeSeq for PythonCollectionSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_element(&mut self, value: &T) -> Result<()> where T: ?Sized + Serialize, { self.items.push(pythonize_custom::(self.py, value)?); Ok(()) } fn end(self) -> Result { let instance = P::List::create_sequence(self.py, self.items)?; Ok(instance.to_object(self.py)) } } impl<'py, P: PythonizeTypes> ser::SerializeTuple for PythonCollectionSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_element(&mut self, value: &T) -> Result<()> where T: ?Sized + Serialize, { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { Ok(PyTuple::new_bound(self.py, self.items).into()) } } impl<'py, P: PythonizeTypes> ser::SerializeTupleStruct for PythonCollectionSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_field(&mut self, value: &T) -> Result<()> where T: ?Sized + Serialize, { ser::SerializeSeq::serialize_element(self, value) } fn end(self) -> Result { ser::SerializeTuple::end(self) } } impl<'py, P: PythonizeTypes> ser::SerializeTupleVariant for PythonTupleVariantSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_field(&mut self, value: &T) -> Result<()> where T: ?Sized + Serialize, { ser::SerializeSeq::serialize_element(&mut self.inner, value) } fn end(self) -> Result { let d = PyDict::new_bound(self.inner.py); d.set_item(self.variant, ser::SerializeTuple::end(self.inner)?)?; Ok(d.into()) } } impl<'py, P: PythonizeTypes> ser::SerializeMap for PythonMapSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_key(&mut self, key: &T) -> Result<()> where T: ?Sized + Serialize, { self.key = Some(pythonize_custom::(self.py, key)?); Ok(()) } fn serialize_value(&mut self, value: &T) -> Result<()> where T: ?Sized + Serialize, { self.map.set_item( self.key .take() .expect("serialize_value should always be called after serialize_key"), pythonize_custom::(self.py, value)?, )?; Ok(()) } fn end(self) -> Result { Ok(self.map.into()) } } impl<'py, P: PythonizeTypes> ser::SerializeStruct for PythonDictSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: ?Sized + Serialize, { Ok(self .dict .set_item(key, pythonize_custom::(self.py, value)?)?) } fn end(self) -> Result { Ok(self.dict.into()) } } impl<'py, P: PythonizeTypes> ser::SerializeStructVariant for PythonStructVariantSerializer<'py, P> { type Ok = PyObject; type Error = PythonizeError; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where T: ?Sized + Serialize, { self.inner .dict .set_item(key, pythonize_custom::(self.inner.py, value)?)?; Ok(()) } fn end(self) -> Result { let d = PyDict::new_bound(self.inner.py); d.set_item(self.variant, self.inner.dict)?; Ok(d.into()) } } #[cfg(test)] mod test { use super::pythonize; use maplit::hashmap; use pyo3::prelude::*; use pyo3::pybacked::PyBackedStr; use pyo3::types::PyDict; use serde::Serialize; fn test_ser(src: T, expected: &str) where T: Serialize, { Python::with_gil(|py| -> PyResult<()> { let obj = pythonize(py, &src)?; let locals = PyDict::new_bound(py); locals.set_item("obj", obj)?; py.run_bound( "import json; result = json.dumps(obj, separators=(',', ':'))", None, Some(&locals), )?; let result = locals.get_item("result")?.unwrap(); let result = result.extract::()?; assert_eq!(result, expected); assert_eq!(serde_json::to_string(&src).unwrap(), expected); Ok(()) }) .unwrap(); } #[test] fn test_empty_struct() { #[derive(Serialize)] struct Empty; test_ser(Empty, "null"); } #[test] fn test_struct() { #[derive(Serialize)] struct Struct { foo: String, bar: usize, } test_ser( Struct { foo: "foo".to_string(), bar: 5, }, r#"{"foo":"foo","bar":5}"#, ); } #[test] fn test_tuple_struct() { #[derive(Serialize)] struct TupleStruct(String, usize); test_ser(TupleStruct("foo".to_string(), 5), r#"["foo",5]"#); } #[test] fn test_tuple() { test_ser(("foo", 5), r#"["foo",5]"#); } #[test] fn test_vec() { test_ser(vec![1, 2, 3], r#"[1,2,3]"#); } #[test] fn test_map() { test_ser(hashmap! {"foo" => "foo"}, r#"{"foo":"foo"}"#); } #[test] fn test_enum_unit_variant() { #[derive(Serialize)] enum E { Empty, } test_ser(E::Empty, r#""Empty""#); } #[test] fn test_enum_tuple_variant() { #[derive(Serialize)] enum E { Tuple(i32, String), } test_ser(E::Tuple(5, "foo".to_string()), r#"{"Tuple":[5,"foo"]}"#); } #[test] fn test_enum_newtype_variant() { #[derive(Serialize)] enum E { NewType(String), } test_ser(E::NewType("foo".to_string()), r#"{"NewType":"foo"}"#); } #[test] fn test_enum_struct_variant() { #[derive(Serialize)] enum E { Struct { foo: String, bar: usize }, } test_ser( E::Struct { foo: "foo".to_string(), bar: 5, }, r#"{"Struct":{"foo":"foo","bar":5}}"#, ); } #[test] fn test_integers() { #[derive(Serialize)] struct Integers { a: i8, b: i16, c: i32, d: i64, e: u8, f: u16, g: u32, h: u64, } test_ser( Integers { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, }, r#"{"a":1,"b":2,"c":3,"d":4,"e":5,"f":6,"g":7,"h":8}"#, ) } #[test] fn test_bool() { test_ser(true, "true"); test_ser(false, "false"); } #[test] fn test_none() { #[derive(Serialize)] struct S; test_ser((), "null"); test_ser(S, "null"); } #[test] fn test_bytes() { test_ser(b"foo", "[102,111,111]"); } } pythonize-0.21.1/tests/test_custom_types.rs000064400000000000000000000077211046102023000172520ustar 00000000000000use std::collections::HashMap; use pyo3::{ exceptions::{PyIndexError, PyKeyError}, prelude::*, types::{PyDict, PyList, PyMapping, PySequence}, }; use pythonize::{ depythonize_bound, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes, Pythonizer, }; use serde::Serialize; use serde_json::{json, Value}; #[pyclass(sequence)] struct CustomList { items: Vec, } #[pymethods] impl CustomList { fn __len__(&self) -> usize { self.items.len() } fn __getitem__(&self, idx: isize) -> PyResult { self.items .get(idx as usize) .cloned() .ok_or_else(|| PyIndexError::new_err(idx)) } } impl PythonizeListType for CustomList { fn create_sequence( py: Python, elements: impl IntoIterator, ) -> PyResult> where T: ToPyObject, U: ExactSizeIterator, { let sequence = Bound::new( py, CustomList { items: elements .into_iter() .map(|item| item.to_object(py)) .collect(), }, )? .into_any(); Ok(unsafe { sequence.downcast_into_unchecked() }) } } struct PythonizeCustomList; impl PythonizeTypes for PythonizeCustomList { type Map = PyDict; type List = CustomList; } #[test] fn test_custom_list() { Python::with_gil(|py| { PySequence::register::(py).unwrap(); let serialized = pythonize_custom::(py, &json!([1, 2, 3])) .unwrap() .into_bound(py); assert!(serialized.is_instance_of::()); let deserialized: Value = depythonize_bound(serialized).unwrap(); assert_eq!(deserialized, json!([1, 2, 3])); }) } #[pyclass(mapping)] struct CustomDict { items: HashMap, } #[pymethods] impl CustomDict { fn __len__(&self) -> usize { self.items.len() } fn __getitem__(&self, key: String) -> PyResult { self.items .get(&key) .cloned() .ok_or_else(|| PyKeyError::new_err(key)) } fn __setitem__(&mut self, key: String, value: PyObject) { self.items.insert(key, value); } fn keys(&self) -> Vec<&String> { self.items.keys().collect() } fn values(&self) -> Vec { self.items.values().cloned().collect() } } impl PythonizeDictType for CustomDict { fn create_mapping(py: Python) -> PyResult> { let mapping = Bound::new( py, CustomDict { items: HashMap::new(), }, )? .into_any(); Ok(unsafe { mapping.downcast_into_unchecked() }) } } struct PythonizeCustomDict; impl PythonizeTypes for PythonizeCustomDict { type Map = CustomDict; type List = PyList; } #[test] fn test_custom_dict() { Python::with_gil(|py| { PyMapping::register::(py).unwrap(); let serialized = pythonize_custom::(py, &json!({ "hello": 1, "world": 2 })) .unwrap() .into_bound(py); assert!(serialized.is_instance_of::()); let deserialized: Value = depythonize_bound(serialized).unwrap(); assert_eq!(deserialized, json!({ "hello": 1, "world": 2 })); }) } #[test] fn test_pythonizer_can_be_created() { // https://github.com/davidhewitt/pythonize/pull/56 Python::with_gil(|py| { let sample = json!({ "hello": 1, "world": 2 }); assert!(sample .serialize(Pythonizer::new(py)) .unwrap() .bind(py) .is_instance_of::()); assert!(sample .serialize(Pythonizer::custom::(py)) .unwrap() .bind(py) .is_instance_of::()); }) } pythonize-0.21.1/tests/test_with_serde_path_to_err.rs000064400000000000000000000141271046102023000212350ustar 00000000000000use std::collections::BTreeMap; use pyo3::{ prelude::*, types::{PyDict, PyList}, Py, PyAny, Python, }; use pythonize::PythonizeTypes; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] struct Root { root_key: String, root_map: BTreeMap>, } impl PythonizeTypes for Root { type Map = PyDict; type List = PyList; } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] struct Nested { nested_key: T, } #[derive(Deserialize, Debug, PartialEq, Eq)] struct CannotSerialize {} impl Serialize for CannotSerialize { fn serialize(&self, _serializer: S) -> Result where S: serde::Serializer, { Err(serde::ser::Error::custom( "something went intentionally wrong", )) } } #[test] fn test_de_valid() { Python::with_gil(|py| { let pyroot = PyDict::new_bound(py); pyroot.set_item("root_key", "root_value").unwrap(); let nested = PyDict::new_bound(py); let nested_0 = PyDict::new_bound(py); nested_0.set_item("nested_key", "nested_value_0").unwrap(); nested.set_item("nested_0", nested_0).unwrap(); let nested_1 = PyDict::new_bound(py); nested_1.set_item("nested_key", "nested_value_1").unwrap(); nested.set_item("nested_1", nested_1).unwrap(); pyroot.set_item("root_map", nested).unwrap(); let de = &mut pythonize::Depythonizer::from_object_bound(pyroot.into_any()); let root: Root = serde_path_to_error::deserialize(de).unwrap(); assert_eq!( root, Root { root_key: String::from("root_value"), root_map: BTreeMap::from([ ( String::from("nested_0"), Nested { nested_key: String::from("nested_value_0") } ), ( String::from("nested_1"), Nested { nested_key: String::from("nested_value_1") } ) ]) } ); }) } #[test] fn test_de_invalid() { Python::with_gil(|py| { let pyroot = PyDict::new_bound(py); pyroot.set_item("root_key", "root_value").unwrap(); let nested = PyDict::new_bound(py); let nested_0 = PyDict::new_bound(py); nested_0.set_item("nested_key", "nested_value_0").unwrap(); nested.set_item("nested_0", nested_0).unwrap(); let nested_1 = PyDict::new_bound(py); nested_1.set_item("nested_key", 1).unwrap(); nested.set_item("nested_1", nested_1).unwrap(); pyroot.set_item("root_map", nested).unwrap(); let de = &mut pythonize::Depythonizer::from_object_bound(pyroot.into_any()); let err = serde_path_to_error::deserialize::<_, Root>(de).unwrap_err(); assert_eq!(err.path().to_string(), "root_map.nested_1.nested_key"); assert_eq!(err.to_string(), "root_map.nested_1.nested_key: unexpected type: 'int' object cannot be converted to 'PyString'"); }) } #[test] fn test_ser_valid() { Python::with_gil(|py| { let root = Root { root_key: String::from("root_value"), root_map: BTreeMap::from([ ( String::from("nested_0"), Nested { nested_key: String::from("nested_value_0"), }, ), ( String::from("nested_1"), Nested { nested_key: String::from("nested_value_1"), }, ), ]), }; let ser = pythonize::Pythonizer::>::from(py); let pyroot: Py = serde_path_to_error::serialize(&root, ser).unwrap(); let pyroot = pyroot.bind(py).downcast::().unwrap(); assert_eq!(pyroot.len(), 2); let root_value: String = pyroot .get_item("root_key") .unwrap() .unwrap() .extract() .unwrap(); assert_eq!(root_value, "root_value"); let root_map = pyroot .get_item("root_map") .unwrap() .unwrap() .downcast_into::() .unwrap(); assert_eq!(root_map.len(), 2); let nested_0 = root_map .get_item("nested_0") .unwrap() .unwrap() .downcast_into::() .unwrap(); assert_eq!(nested_0.len(), 1); let nested_key_0: String = nested_0 .get_item("nested_key") .unwrap() .unwrap() .extract() .unwrap(); assert_eq!(nested_key_0, "nested_value_0"); let nested_1 = root_map .get_item("nested_1") .unwrap() .unwrap() .downcast_into::() .unwrap(); assert_eq!(nested_1.len(), 1); let nested_key_1: String = nested_1 .get_item("nested_key") .unwrap() .unwrap() .extract() .unwrap(); assert_eq!(nested_key_1, "nested_value_1"); }); } #[test] fn test_ser_invalid() { Python::with_gil(|py| { let root = Root { root_key: String::from("root_value"), root_map: BTreeMap::from([ ( String::from("nested_0"), Nested { nested_key: CannotSerialize {}, }, ), ( String::from("nested_1"), Nested { nested_key: CannotSerialize {}, }, ), ]), }; let ser = pythonize::Pythonizer::>::from(py); let err = serde_path_to_error::serialize(&root, ser).unwrap_err(); assert_eq!(err.path().to_string(), "root_map.nested_0.nested_key"); }); }