pythonize-0.20.0/.cargo_vcs_info.json0000644000000001360000000000100131440ustar { "git": { "sha1": "8eb657c4bf543a54104c1d5edab4df23547ad2de" }, "path_in_vcs": "" }pythonize-0.20.0/.github/FUNDING.yml000064400000000000000000000000241046102023000151050ustar 00000000000000github: davidhewitt pythonize-0.20.0/.github/dependabot.yml000064400000000000000000000011331046102023000161220ustar 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.20.0/.github/workflows/ci.yml000064400000000000000000000046331046102023000164550ustar 00000000000000name: CI on: push: branches: - main pull_request: env: CARGO_TERM_COLOR: always jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - 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@v2 - 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.platform.os }} runs-on: ${{ matrix.platform.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"] platform: [ { os: "macOS-latest", rust-target: "x86_64-apple-darwin" }, { os: "ubuntu-latest", rust-target: "x86_64-unknown-linux-gnu" }, { os: "windows-latest", rust-target: "x86_64-pc-windows-msvc" }, ] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.platform.rust-target }} - name: Build without default features run: cargo test --no-default-features --verbose --target ${{ matrix.platform.rust-target }} env: RUST_BACKTRACE: 1 coverage: needs: [fmt] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/cache@v2 with: path: | ~/.cargo/registry ~/.cargo/git target key: coverage-cargo-${{ hashFiles('**/Cargo.toml') }} 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@v2 with: file: codecov.json pythonize-0.20.0/.gitignore000064400000000000000000000000311046102023000137160ustar 00000000000000/target Cargo.lock .idea pythonize-0.20.0/CHANGELOG.md000064400000000000000000000016641046102023000135540ustar 00000000000000## 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.20.0/Cargo.toml0000644000000024740000000000100111510ustar # 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 = "2018" name = "pythonize" version = "0.20.0" 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.20.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.20.0" 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" pythonize-0.20.0/Cargo.toml.orig000064400000000000000000000014151046102023000146240ustar 00000000000000[package] name = "pythonize" version = "0.20.0" authors = ["David Hewitt <1939362+davidhewitt@users.noreply.github.com>"] edition = "2018" 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.20.0", default-features = false } [dev-dependencies] serde = { version = "1.0", default-features = false, features = ["derive"] } pyo3 = { version = "0.20.0", default-features = false, features = ["auto-initialize", "macros"] } serde_json = "1.0" maplit = "1.0.2" pythonize-0.20.0/LICENSE000064400000000000000000000020711046102023000127410ustar 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.20.0/README.md000064400000000000000000000024511046102023000132150ustar 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.20.0/src/de.rs000064400000000000000000000476001046102023000134700ustar 00000000000000use pyo3::types::*; use serde::de::{self, IntoDeserializer}; use serde::Deserialize; use crate::error::{PythonizeError, Result}; /// Attempt to convert a Python object to an instance of `T` pub fn depythonize<'de, T>(obj: &'de PyAny) -> Result where T: Deserialize<'de>, { let mut depythonizer = Depythonizer::from_object(obj); T::deserialize(&mut depythonizer) } pub struct Depythonizer<'de> { input: &'de PyAny, } impl<'de> Depythonizer<'de> { pub fn from_object(input: &'de PyAny) -> Self { Depythonizer { input } } fn sequence_access(&self, expected_len: Option) -> Result> { let seq: &PySequence = 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, 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, 'de> de::Deserializer<'de> for &'a mut Depythonizer<'de> { type Error = PythonizeError; fn deserialize_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { let obj = self.input; if obj.is_none() { self.deserialize_unit(visitor) } else if obj.is_instance_of::() { self.deserialize_bool(visitor) } else if obj.is_instance_of::() || obj.is_instance_of::() { self.deserialize_bytes(visitor) } else if obj.is_instance_of::() { self.deserialize_map(visitor) } else if obj.is_instance_of::() { self.deserialize_f64(visitor) } else if obj.is_instance_of::() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.is_instance_of::() { self.deserialize_i64(visitor) } else if obj.is_instance_of::() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.is_instance_of::() { self.deserialize_i64(visitor) } else if obj.is_instance_of::() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.is_instance_of::() { self.deserialize_str(visitor) } else if obj.is_instance_of::() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.is_instance_of::() { self.deserialize_str(visitor) } else if obj.downcast::().is_ok() { self.deserialize_tuple(obj.len()?, visitor) } else if obj.downcast::().is_ok() { self.deserialize_map(visitor) } else { Err(PythonizeError::unsupported_type( obj.get_type().name().unwrap_or(""), )) } } fn deserialize_bool(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_bool(self.input.is_true()?) } fn deserialize_char(self, visitor: V) -> Result where V: de::Visitor<'de>, { let s = self.input.downcast::()?.to_str()?; 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: &PyString = self.input.downcast()?; visitor.visit_str(s.to_str()?) } 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 obj = self.input; let b: &PyBytes = obj.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 item.is_instance_of::() { // Get the enum variant from the dict key let d: &PyDict = item.downcast().unwrap(); if d.len() != 1 { return Err(PythonizeError::invalid_length_enum()); } let variant: &PyString = d .keys() .get_item(0)? .downcast() .map_err(|_| PythonizeError::dict_key_not_string())?; let value = d.get_item(variant)?.unwrap(); let mut de = Depythonizer::from_object(value); visitor.visit_enum(PyEnumAccess::new(&mut de, variant)) } else if item.is_instance_of::() { let s: &PyString = self.input.downcast()?; visitor.visit_enum(s.to_str()?.into_deserializer()) } else { Err(PythonizeError::invalid_enum_type()) } } fn deserialize_identifier(self, visitor: V) -> Result where V: de::Visitor<'de>, { let s: &PyString = self .input .downcast() .map_err(|_| PythonizeError::dict_key_not_string())?; visitor.visit_str(s.to_str()?) } fn deserialize_ignored_any(self, visitor: V) -> Result where V: de::Visitor<'de>, { visitor.visit_unit() } } struct PySequenceAccess<'a> { seq: &'a PySequence, index: usize, len: usize, } impl<'a> PySequenceAccess<'a> { fn new(seq: &'a PySequence, len: usize) -> Self { Self { seq, index: 0, len } } } impl<'de> de::SeqAccess<'de> for PySequenceAccess<'de> { 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(self.seq.get_item(self.index)?); self.index += 1; seed.deserialize(&mut item_de).map(Some) } else { Ok(None) } } } struct PyMappingAccess<'de> { keys: &'de PySequence, values: &'de PySequence, key_idx: usize, val_idx: usize, len: usize, } impl<'de> PyMappingAccess<'de> { fn new(map: &'de 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> de::MapAccess<'de> for PyMappingAccess<'de> { 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(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(self.values.get_item(self.val_idx)?); self.val_idx += 1; seed.deserialize(&mut item_de) } } struct PyEnumAccess<'a, 'de> { de: &'a mut Depythonizer<'de>, variant: &'de PyString, } impl<'a, 'de> PyEnumAccess<'a, 'de> { fn new(de: &'a mut Depythonizer<'de>, variant: &'de PyString) -> Self { Self { de, variant } } } impl<'a, 'de> de::EnumAccess<'de> for PyEnumAccess<'a, 'de> { type Error = PythonizeError; type Variant = Self; fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> where V: de::DeserializeSeed<'de>, { let de: de::value::StrDeserializer<'_, PythonizeError> = self.variant.to_str()?.into_deserializer(); let val = seed.deserialize(de)?; Ok((val, self)) } } impl<'a, 'de> de::VariantAccess<'de> for PyEnumAccess<'a, 'de> { 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(py); py.run(&format!("obj = {}", code), None, Some(locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); let actual: T = depythonize(obj).unwrap(); assert_eq!(&actual, expected); let actual_json: JsonValue = depythonize(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, } let expected = Struct { foo: "Foo".to_string(), bar: 8usize, baz: 45.23, }; let expected_json = json!({ "foo": "Foo", "bar": 8, "baz": 45.23 }); let code = "{'foo': 'Foo', 'bar': 8, 'baz': 45.23}"; 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(py); py.run(&format!("obj = {}", code), None, Some(locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); assert!(matches!( *depythonize::(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(py); py.run(&format!("obj = {}", code), None, Some(locals)) .unwrap(); let obj = locals.get_item("obj").unwrap().unwrap(); assert!(matches!( *depythonize::(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.20.0/src/error.rs000064400000000000000000000122671046102023000142320ustar 00000000000000use pyo3::exceptions::*; 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())), } } } /// 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.20.0/src/lib.rs000064400000000000000000000023621046102023000136420ustar 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; pub use crate::de::depythonize; pub use crate::error::{PythonizeError, Result}; pub use crate::ser::{ pythonize, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes, }; pythonize-0.20.0/src/ser.rs000064400000000000000000000347011046102023000136670ustar 00000000000000use std::marker::PhantomData; use pyo3::types::{PyDict, PyList, PyMapping, PySequence, PyTuple}; use pyo3::{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<&PyMapping>; } /// Trait for types which can represent a Python sequence pub trait PythonizeListType: Sized { /// Constructor fn create_sequence( py: Python, elements: impl IntoIterator, ) -> PyResult<&PySequence> 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<&PyMapping> { Ok(PyDict::new(py).as_mapping()) } } impl PythonizeListType for PyList { fn create_sequence( py: Python, elements: impl IntoIterator, ) -> PyResult<&PySequence> where T: ToPyObject, U: ExactSizeIterator, { Ok(PyList::new(py, elements).as_sequence()) } } 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, { pythonize_custom::(py, value) } /// 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::

{ py, _types: PhantomData, }) } #[derive(Clone, Copy)] pub struct Pythonizer<'py, P> { py: Python<'py>, _types: PhantomData

, } #[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: &'py PyMapping, _types: PhantomData

, } #[doc(hidden)] pub struct PythonMapSerializer<'py, P: PythonizeTypes> { py: Python<'py>, map: &'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(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(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(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(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::types::PyDict; use pyo3::{PyResult, Python}; use serde::{Deserialize, 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(py); locals.set_item("obj", obj)?; py.run( "import json; result = json.dumps(obj, separators=(',', ':'))", None, Some(locals), )?; let result = locals.get_item("result")?.unwrap().extract::<&str>()?; assert_eq!(result, expected); assert_eq!(serde_json::to_string(&src).unwrap(), expected); Ok(()) }) .unwrap(); } #[test] fn test_empty_struct() { #[derive(Serialize, Deserialize)] struct Empty; test_ser(Empty, "null"); } #[test] fn test_struct() { #[derive(Serialize, Deserialize)] 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, Deserialize)] 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, Deserialize)] enum E { Empty, } test_ser(E::Empty, r#""Empty""#); } #[test] fn test_enum_tuple_variant() { #[derive(Serialize, Deserialize)] 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, Deserialize)] enum E { NewType(String), } test_ser(E::NewType("foo".to_string()), r#"{"NewType":"foo"}"#); } #[test] fn test_enum_struct_variant() { #[derive(Serialize, Deserialize)] enum E { Struct { foo: String, bar: usize }, } test_ser( E::Struct { foo: "foo".to_string(), bar: 5, }, r#"{"Struct":{"foo":"foo","bar":5}}"#, ); } } pythonize-0.20.0/tests/test_custom_types.rs000064400000000000000000000066171046102023000172530ustar 00000000000000use std::collections::HashMap; use pyo3::{ exceptions::{PyIndexError, PyKeyError}, prelude::*, types::{PyDict, PyList, PyMapping, PySequence}, }; use pythonize::{ depythonize, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes, }; 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<&PySequence> where T: ToPyObject, U: ExactSizeIterator, { let sequence = Py::new( py, CustomList { items: elements .into_iter() .map(|item| item.to_object(py)) .collect(), }, )? .into_ref(py); Ok(unsafe { PySequence::try_from_unchecked(sequence.as_ref()) }) } } 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_ref(py); assert!(serialized.is_instance_of::()); let deserialized: Value = depythonize(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<&PyMapping> { let mapping = Py::new( py, CustomDict { items: HashMap::new(), }, )? .into_ref(py); Ok(unsafe { PyMapping::try_from_unchecked(mapping.as_ref()) }) } } 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_ref(py); assert!(serialized.is_instance_of::()); let deserialized: Value = depythonize(serialized).unwrap(); assert_eq!(deserialized, json!({ "hello": 1, "world": 2 })); }) }