├── .github └── workflows │ ├── docs.yml │ ├── release.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src ├── de.rs ├── error.rs ├── lib.rs ├── pylit.rs └── ser.rs └── tests ├── check_revertible.rs └── to_json_to_pyobject.rs /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | rust: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup Rust 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | profile: minimal 31 | toolchain: stable 32 | 33 | - name: Generate rustdoc 34 | run: cargo doc --all --no-deps 35 | 36 | - name: Upload html 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: rustdoc 40 | path: ./target/doc 41 | retention-days: 30 42 | 43 | # See https://github.com/actions/deploy-pages#usage 44 | deploy: 45 | needs: [rust] 46 | environment: 47 | name: "Document" 48 | url: ${{ steps.deployment.outputs.page_url }}/serde_pyobject 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Download rustdoc 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: rustdoc 55 | path: . 56 | 57 | - name: Configure GitHub Pages 58 | uses: actions/configure-pages@v3 59 | 60 | - name: Upload artifact 61 | uses: actions/upload-pages-artifact@v3 62 | with: 63 | # Upload entire repository 64 | path: '.' 65 | 66 | - name: Deploy to GitHub Pages 67 | id: deployment 68 | uses: actions/deploy-pages@v4 69 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: cargo publish 17 | run: | 18 | cargo publish 19 | env: 20 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 21 | 22 | next_version: 23 | runs-on: ubuntu-latest 24 | needs: publish 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Install cargo-edit 30 | run: cargo install cargo-edit 31 | 32 | - name: Bump version 33 | run: | 34 | cargo set-version --bump patch 35 | echo "NEW_VERSION=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')" >> $GITHUB_ENV 36 | 37 | - name: Create Pull Request 38 | uses: peter-evans/create-pull-request@v6 39 | with: 40 | title: "Start developing ${{ env.NEW_VERSION }}" 41 | branch: "rust-version-update/${{ env.NEW_VERSION }}" 42 | base: "main" 43 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | doc-check: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Rust 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | 23 | - name: Cache dependencies 24 | uses: Swatinem/rust-cache@v2 25 | 26 | - name: Check warnings in documents 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: doc 30 | args: --no-deps 31 | env: 32 | RUSTDOCFLAGS: -D warnings 33 | 34 | fmt: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v4 39 | 40 | - name: Setup Rust 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | profile: minimal 44 | toolchain: stable 45 | components: rustfmt 46 | 47 | - name: Run cargo fmt 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: fmt 51 | args: --all -- --check 52 | 53 | clippy: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v4 58 | 59 | - name: Setup Rust 60 | uses: actions-rs/toolchain@v1 61 | with: 62 | profile: minimal 63 | toolchain: stable 64 | components: clippy 65 | 66 | - name: Cache dependencies 67 | uses: Swatinem/rust-cache@v2 68 | 69 | - name: Check with clippy 70 | uses: actions-rs/clippy-check@v1 71 | with: 72 | args: --all-features -- -D warnings 73 | token: ${{ secrets.GITHUB_TOKEN }} 74 | 75 | test: 76 | runs-on: ubuntu-latest 77 | steps: 78 | - name: Checkout 79 | uses: actions/checkout@v4 80 | 81 | - name: Setup Rust 82 | uses: actions-rs/toolchain@v1 83 | with: 84 | profile: minimal 85 | toolchain: stable 86 | components: clippy 87 | 88 | - name: Cache dependencies 89 | uses: Swatinem/rust-cache@v2 90 | 91 | - name: Run tests 92 | uses: actions-rs/cargo@v1 93 | with: 94 | command: test 95 | 96 | - name: Run tests with abi3 feature 97 | uses: actions-rs/cargo@v1 98 | with: 99 | command: test 100 | args: --features abi3-py38 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | 4 | # Python 5 | .venv/ 6 | Untitled*.ipynb 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde-pyobject" 3 | version = "0.6.3" 4 | edition = "2021" 5 | 6 | description = "PyO3's PyAny as a serde data format" 7 | documentation = "https://docs.rs/serde-pyobject/" 8 | repository = "https://github.com/Jij-Inc/serde-pyobject" 9 | keywords = ["serde", "pyo3", "python", "ffi"] 10 | license = "MIT OR Apache-2.0" 11 | 12 | [dependencies] 13 | pyo3 = ">=0.23.0, <0.25.0" 14 | serde = "1.0.190" 15 | 16 | [dev-dependencies] 17 | maplit = "1.0.2" 18 | pyo3 = { version = ">=0.23.0, <0.25.0", features = ["auto-initialize"] } 19 | serde = { version = "1.0.190", features = ["derive"] } 20 | serde_json = "1.0.108" 21 | 22 | [features] 23 | abi3-py38 = ["pyo3/abi3-py38"] 24 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jij Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serde-pyobject 2 | 3 | 4 | [![crate](https://img.shields.io/crates/v/serde-pyobject.svg)](https://crates.io/crates/serde-pyobject) 5 | [![docs.rs](https://docs.rs/serde-pyobject/badge.svg)](https://docs.rs/serde-pyobject) 6 | 7 | PyO3's PyAny as a serde data format 8 | 9 | ## Usage 10 | 11 | ### Serialize `T: Serialize` into `&'py PyAny`: 12 | 13 | ```rust 14 | use serde::Serialize; 15 | use pyo3::{Python, Bound, types::{PyAny, PyAnyMethods, PyDict}}; 16 | use serde_pyobject::{to_pyobject, pydict}; 17 | 18 | #[derive(Serialize)] 19 | struct A { 20 | a: u32, 21 | b: String, 22 | } 23 | 24 | Python::with_gil(|py| { 25 | let a = A { a: 1, b: "test".to_string() }; 26 | let obj: Bound = to_pyobject(py, &a).unwrap(); 27 | assert!(obj.eq(pydict! { py, "a" => 1, "b" => "test" }.unwrap()).unwrap()); 28 | }); 29 | ``` 30 | 31 | ### Deserialize `&'py PyAny` into `T: Deserialize<'de>`: 32 | 33 | ```rust 34 | use serde::Deserialize; 35 | use pyo3::{Python, Bound, types::{PyAny, PyAnyMethods, PyDict}}; 36 | use serde_pyobject::{from_pyobject, pydict}; 37 | 38 | #[derive(Debug, PartialEq, Deserialize)] 39 | struct A { 40 | a: u32, 41 | b: String, 42 | } 43 | 44 | Python::with_gil(|py| { 45 | let a: Bound = pydict! { py, 46 | "a" => 1, 47 | "b" => "test" 48 | } 49 | .unwrap(); 50 | let a: A = from_pyobject(a).unwrap(); 51 | assert_eq!(a, A { a: 1, b: "test".to_string() }); 52 | }); 53 | ``` 54 | 55 | ## Mapping between Python and [serde data model] 56 | 57 | [serde data model]: https://serde.rs/data-model.html 58 | 59 | | [serde data model] | PyO3 type | Rust | Python | 60 | |------------------|-----------|------------|---------------| 61 | | `i8`, `i16`, `i32`, `i64`, `isize`,
`u8`, `u16`, `u32`, `u64`, `usize` | `PyLong` | `123` | `123` | 62 | | `f32`, `f64` | `PyFloat` | `1.0` | `1.0` | 63 | | `bool` | `PyBool` | `true` | `true` | 64 | | `char`, `string` | `PyString` | `'a'`, `"test"` | `"a"`, `"test"` | 65 | | option | `PyAny` [^1] | `None`, `Some(1)` | `None`, `1` | 66 | | unit | `PyTuple` | `()` | `()` | 67 | | unit struct | `PyTuple` | `struct Unit` | `()` | 68 | | unit variant | `PyDict` | `E::A` in `enum E { A, B }` | `"A"` | 69 | | newtype struct | `PyDict` | `A(32)` of `struct A(u8)` | `32` | 70 | | newtype variant | `PyDict` | `E::N(41)` of `enum E { N(u8) }` | `{ "N": 41 }` | 71 | | seq | `PyList` | `vec![1, 2, 3]` | `[1, 2, 3]` | 72 | | tuple | `PyTuple` | `(1, "test")` | `(1, "test")` | 73 | | tuple struct | `PyDict` | `T(1, "a")` of `struct T(u32, String)` | `(1, "a")` | 74 | | tuple variant | `PyDict` | `E::S(1, 2)` of `enum E { S(u8, u8) }` | `{ "S": (1, 2) }` | 75 | | map | `PyDict` | `hashmap!{ "a".to_string() => 1, "b".to_string() => 2 }` | `{ "a": 1, "b": 2 }` | 76 | | struct | `PyDict` | `A { a: 1, b: "test" }` of `struct A { a: u32, b: String }` | `{ "a": 1, "b": "test"}` | 77 | | struct variant | `PyDict` | `E::S { r: 1, g: 2, b: 3 }` of `enum E { S { r: u8, g: u8, b: u8 } }` | `{ "S": { "r": 1, "g": 2, "b": 3 } }` | 78 | 79 | [^1]: `Some(value)` is serialized as `value` 80 | 81 | ## License 82 | 83 | © 2023 Jij Inc. 84 | 85 | This project is licensed under either of 86 | 87 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) 88 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or ) 89 | 90 | at your option. 91 | -------------------------------------------------------------------------------- /src/de.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use pyo3::{types::*, Bound}; 3 | use serde::{ 4 | de::{self, value::StrDeserializer, MapAccess, SeqAccess, Visitor}, 5 | forward_to_deserialize_any, Deserialize, Deserializer, 6 | }; 7 | 8 | /// Deserialize a Python object into Rust type `T: Deserialize`. 9 | /// 10 | /// # Examples 11 | /// 12 | /// ## primitive 13 | /// 14 | /// ``` 15 | /// use pyo3::{Python, Py, PyAny, IntoPy}; 16 | /// use serde_pyobject::from_pyobject; 17 | /// 18 | /// Python::with_gil(|py| { 19 | /// // integer 20 | /// let any: Py = 42.into_py(py); 21 | /// let i: i32 = from_pyobject(any.into_bound(py)).unwrap(); 22 | /// assert_eq!(i, 42); 23 | /// 24 | /// // float 25 | /// let any: Py = (0.1).into_py(py); 26 | /// let x: f32 = from_pyobject(any.into_bound(py)).unwrap(); 27 | /// assert_eq!(x, 0.1); 28 | /// 29 | /// // bool 30 | /// let any: Py = true.into_py(py); 31 | /// let x: bool = from_pyobject(any.into_bound(py)).unwrap(); 32 | /// assert_eq!(x, true); 33 | /// }); 34 | /// ``` 35 | /// 36 | /// ## option 37 | /// 38 | /// ``` 39 | /// use pyo3::{Python, Py, PyAny, IntoPy}; 40 | /// use serde_pyobject::from_pyobject; 41 | /// 42 | /// Python::with_gil(|py| { 43 | /// let none = py.None(); 44 | /// let option: Option = from_pyobject(none.into_bound(py)).unwrap(); 45 | /// assert_eq!(option, None); 46 | /// 47 | /// let py_int: Py = 42.into_py(py); 48 | /// let i: Option = from_pyobject(py_int.into_bound(py)).unwrap(); 49 | /// assert_eq!(i, Some(42)); 50 | /// }) 51 | /// ``` 52 | /// 53 | /// ## unit 54 | /// 55 | /// ``` 56 | /// use pyo3::{Python, types::PyTuple}; 57 | /// use serde_pyobject::from_pyobject; 58 | /// 59 | /// Python::with_gil(|py| { 60 | /// let py_unit = PyTuple::empty(py); 61 | /// let unit: () = from_pyobject(py_unit).unwrap(); 62 | /// assert_eq!(unit, ()); 63 | /// }) 64 | /// ``` 65 | /// 66 | /// ## unit_struct 67 | /// 68 | /// ``` 69 | /// use serde::Deserialize; 70 | /// use pyo3::{Python, types::PyTuple}; 71 | /// use serde_pyobject::from_pyobject; 72 | /// 73 | /// #[derive(Debug, PartialEq, Deserialize)] 74 | /// struct UnitStruct; 75 | /// 76 | /// Python::with_gil(|py| { 77 | /// let py_unit = PyTuple::empty(py); 78 | /// let unit: UnitStruct = from_pyobject(py_unit).unwrap(); 79 | /// assert_eq!(unit, UnitStruct); 80 | /// }) 81 | /// ``` 82 | /// 83 | /// ## unit variant 84 | /// 85 | /// ``` 86 | /// use serde::Deserialize; 87 | /// use pyo3::{Python, types::PyString}; 88 | /// use serde_pyobject::from_pyobject; 89 | /// 90 | /// #[derive(Debug, PartialEq, Deserialize)] 91 | /// enum E { 92 | /// A, 93 | /// B, 94 | /// } 95 | /// 96 | /// Python::with_gil(|py| { 97 | /// let any = PyString::new_bound(py, "A"); 98 | /// let out: E = from_pyobject(any).unwrap(); 99 | /// assert_eq!(out, E::A); 100 | /// }) 101 | /// ``` 102 | /// 103 | /// ## newtype struct 104 | /// 105 | /// ``` 106 | /// use serde::Deserialize; 107 | /// use pyo3::{Python, Bound, PyAny, IntoPy}; 108 | /// use serde_pyobject::from_pyobject; 109 | /// 110 | /// #[derive(Debug, PartialEq, Deserialize)] 111 | /// struct NewTypeStruct(u8); 112 | /// 113 | /// Python::with_gil(|py| { 114 | /// let any: Bound = 1_u32.into_py(py).into_bound(py); 115 | /// let obj: NewTypeStruct = from_pyobject(any).unwrap(); 116 | /// assert_eq!(obj, NewTypeStruct(1)); 117 | /// }); 118 | /// ``` 119 | /// 120 | /// ## newtype variant 121 | /// 122 | /// ``` 123 | /// use serde::Deserialize; 124 | /// use pyo3::Python; 125 | /// use serde_pyobject::{from_pyobject, pydict}; 126 | /// 127 | /// #[derive(Debug, PartialEq, Deserialize)] 128 | /// enum NewTypeVariant { 129 | /// N(u8), 130 | /// } 131 | /// 132 | /// Python::with_gil(|py| { 133 | /// let dict = pydict! { py, "N" => 41 }.unwrap(); 134 | /// let obj: NewTypeVariant = from_pyobject(dict).unwrap(); 135 | /// assert_eq!(obj, NewTypeVariant::N(41)); 136 | /// }); 137 | /// ``` 138 | /// 139 | /// ## seq 140 | /// 141 | /// ``` 142 | /// use pyo3::Python; 143 | /// use serde_pyobject::{from_pyobject, pylist}; 144 | /// 145 | /// Python::with_gil(|py| { 146 | /// let list = pylist![py; 1, 2, 3].unwrap(); 147 | /// let seq: Vec = from_pyobject(list).unwrap(); 148 | /// assert_eq!(seq, vec![1, 2, 3]); 149 | /// }); 150 | /// ``` 151 | /// 152 | /// ## tuple 153 | /// 154 | /// ``` 155 | /// use pyo3::{Python, types::PyTuple}; 156 | /// use serde_pyobject::from_pyobject; 157 | /// 158 | /// Python::with_gil(|py| { 159 | /// let tuple = PyTuple::new_bound(py, &[1, 2, 3]); 160 | /// let tuple: (i32, i32, i32) = from_pyobject(tuple).unwrap(); 161 | /// assert_eq!(tuple, (1, 2, 3)); 162 | /// }); 163 | /// ``` 164 | /// 165 | /// ## tuple struct 166 | /// 167 | /// ``` 168 | /// use serde::Deserialize; 169 | /// use pyo3::{Python, IntoPy, types::PyTuple}; 170 | /// use serde_pyobject::from_pyobject; 171 | /// 172 | /// #[derive(Debug, PartialEq, Deserialize)] 173 | /// struct T(u8, String); 174 | /// 175 | /// Python::with_gil(|py| { 176 | /// let tuple = PyTuple::new_bound(py, &[1_u32.into_py(py), "test".into_py(py)]); 177 | /// let obj: T = from_pyobject(tuple).unwrap(); 178 | /// assert_eq!(obj, T(1, "test".to_string())); 179 | /// }); 180 | /// ``` 181 | /// 182 | /// ## tuple variant 183 | /// 184 | /// ``` 185 | /// use serde::Deserialize; 186 | /// use pyo3::Python; 187 | /// use serde_pyobject::{from_pyobject, pydict}; 188 | /// 189 | /// #[derive(Debug, PartialEq, Deserialize)] 190 | /// enum TupleVariant { 191 | /// T(u8, u8), 192 | /// } 193 | /// 194 | /// Python::with_gil(|py| { 195 | /// let dict = pydict! { py, "T" => (1, 2) }.unwrap(); 196 | /// let obj: TupleVariant = from_pyobject(dict).unwrap(); 197 | /// assert_eq!(obj, TupleVariant::T(1, 2)); 198 | /// }); 199 | /// ``` 200 | /// 201 | /// ## map 202 | /// 203 | /// ``` 204 | /// use pyo3::Python; 205 | /// use serde_pyobject::{from_pyobject, pydict}; 206 | /// use std::collections::BTreeMap; 207 | /// 208 | /// Python::with_gil(|py| { 209 | /// let dict = pydict! { py, 210 | /// "a" => "hom", 211 | /// "b" => "test" 212 | /// } 213 | /// .unwrap(); 214 | /// let map: BTreeMap = from_pyobject(dict).unwrap(); 215 | /// assert_eq!(map.get("a"), Some(&"hom".to_string())); 216 | /// assert_eq!(map.get("b"), Some(&"test".to_string())); 217 | /// }); 218 | /// ``` 219 | /// 220 | /// ## struct 221 | /// 222 | /// ``` 223 | /// use serde::Deserialize; 224 | /// use pyo3::Python; 225 | /// use serde_pyobject::{from_pyobject, pydict}; 226 | /// 227 | /// #[derive(Debug, PartialEq, Deserialize)] 228 | /// struct A { 229 | /// a: i32, 230 | /// b: String, 231 | /// } 232 | /// 233 | /// Python::with_gil(|py| { 234 | /// let dict = pydict! { 235 | /// "a" => 1, 236 | /// "b" => "test" 237 | /// } 238 | /// .unwrap(); 239 | /// let a: A = from_pyobject(dict.into_bound(py)).unwrap(); 240 | /// assert_eq!( 241 | /// a, 242 | /// A { 243 | /// a: 1, 244 | /// b: "test".to_string() 245 | /// } 246 | /// ); 247 | /// }); 248 | /// 249 | /// Python::with_gil(|py| { 250 | /// let dict = pydict! { 251 | /// "A" => pydict! { 252 | /// "a" => 1, 253 | /// "b" => "test" 254 | /// } 255 | /// .unwrap() 256 | /// } 257 | /// .unwrap(); 258 | /// let a: A = from_pyobject(dict.into_bound(py)).unwrap(); 259 | /// assert_eq!( 260 | /// a, 261 | /// A { 262 | /// a: 1, 263 | /// b: "test".to_string() 264 | /// } 265 | /// ); 266 | /// }); 267 | /// ``` 268 | /// 269 | /// ## struct variant 270 | /// 271 | /// ``` 272 | /// use serde::Deserialize; 273 | /// use pyo3::Python; 274 | /// use serde_pyobject::{from_pyobject, pydict}; 275 | /// 276 | /// #[derive(Debug, PartialEq, Deserialize)] 277 | /// enum StructVariant { 278 | /// S { r: u8, g: u8, b: u8 }, 279 | /// } 280 | /// 281 | /// Python::with_gil(|py| { 282 | /// let dict = pydict! { 283 | /// py, 284 | /// "S" => pydict! { 285 | /// "r" => 1, 286 | /// "g" => 2, 287 | /// "b" => 3 288 | /// }.unwrap() 289 | /// } 290 | /// .unwrap(); 291 | /// let obj: StructVariant = from_pyobject(dict).unwrap(); 292 | /// assert_eq!(obj, StructVariant::S { r: 1, g: 2, b: 3 }); 293 | /// }); 294 | /// ``` 295 | pub fn from_pyobject<'py, 'de, T: Deserialize<'de>, Any>(any: Bound<'py, Any>) -> Result { 296 | let any = any.into_any(); 297 | T::deserialize(PyAnyDeserializer(any)) 298 | } 299 | 300 | struct PyAnyDeserializer<'py>(Bound<'py, PyAny>); 301 | 302 | impl<'de> de::Deserializer<'de> for PyAnyDeserializer<'_> { 303 | type Error = Error; 304 | 305 | fn deserialize_any(self, visitor: V) -> Result 306 | where 307 | V: Visitor<'de>, 308 | { 309 | if self.0.is_instance_of::() { 310 | return visitor.visit_map(MapDeserializer::new(self.0.downcast()?)); 311 | } 312 | if self.0.is_instance_of::() { 313 | return visitor.visit_seq(SeqDeserializer::from_list(self.0.downcast()?)); 314 | } 315 | if self.0.is_instance_of::() { 316 | return visitor.visit_seq(SeqDeserializer::from_tuple(self.0.downcast()?)); 317 | } 318 | if self.0.is_instance_of::() { 319 | return visitor.visit_str(&self.0.extract::()?); 320 | } 321 | if self.0.is_instance_of::() { 322 | // must be match before PyLong 323 | return visitor.visit_bool(self.0.extract()?); 324 | } 325 | if self.0.is_instance_of::() { 326 | return visitor.visit_i64(self.0.extract()?); 327 | } 328 | if self.0.is_instance_of::() { 329 | return visitor.visit_f64(self.0.extract()?); 330 | } 331 | if self.0.hasattr("__dict__")? { 332 | return visitor.visit_map(MapDeserializer::new( 333 | self.0.getattr("__dict__")?.downcast()?, 334 | )); 335 | } 336 | if self.0.hasattr("__slots__")? { 337 | // __slots__ and __dict__ are mutually exclusive, see 338 | // https://docs.python.org/3/reference/datamodel.html#slots 339 | return visitor.visit_map(MapDeserializer::from_slots(&self.0)?); 340 | } 341 | if self.0.is_none() { 342 | return visitor.visit_none(); 343 | } 344 | 345 | unreachable!("Unsupported type: {}", self.0.get_type()); 346 | } 347 | 348 | fn deserialize_struct>( 349 | self, 350 | name: &'static str, 351 | _fields: &'static [&'static str], 352 | visitor: V, 353 | ) -> Result { 354 | // Nested dict `{ "A": { "a": 1, "b": 2 } }` is deserialized as `A { a: 1, b: 2 }` 355 | if self.0.is_instance_of::() { 356 | let dict: &Bound = self.0.downcast()?; 357 | if let Some(inner) = dict.get_item(name)? { 358 | if let Ok(inner) = inner.downcast() { 359 | return visitor.visit_map(MapDeserializer::new(inner)); 360 | } 361 | } 362 | } 363 | // Default to `any` case 364 | self.deserialize_any(visitor) 365 | } 366 | 367 | fn deserialize_newtype_struct>( 368 | self, 369 | _name: &'static str, 370 | visitor: V, 371 | ) -> Result { 372 | visitor.visit_seq(SeqDeserializer { 373 | seq_reversed: vec![self.0], 374 | }) 375 | } 376 | 377 | fn deserialize_option>(self, visitor: V) -> Result { 378 | if self.0.is_none() { 379 | visitor.visit_none() 380 | } else { 381 | visitor.visit_some(self) 382 | } 383 | } 384 | 385 | fn deserialize_unit>(self, visitor: V) -> Result { 386 | if self.0.is(&PyTuple::empty(self.0.py())) { 387 | visitor.visit_unit() 388 | } else { 389 | self.deserialize_any(visitor) 390 | } 391 | } 392 | 393 | fn deserialize_unit_struct>( 394 | self, 395 | _name: &'static str, 396 | visitor: V, 397 | ) -> Result { 398 | if self.0.is(&PyTuple::empty(self.0.py())) { 399 | visitor.visit_unit() 400 | } else { 401 | self.deserialize_any(visitor) 402 | } 403 | } 404 | 405 | fn deserialize_enum>( 406 | self, 407 | _name: &'static str, 408 | _variants: &'static [&'static str], 409 | visitor: V, 410 | ) -> Result { 411 | if self.0.is_instance_of::() { 412 | let variant: String = self.0.extract()?; 413 | let py = self.0.py(); 414 | let none = py.None().into_bound(py); 415 | return visitor.visit_enum(EnumDeserializer { 416 | variant: &variant, 417 | inner: none, 418 | }); 419 | } 420 | if self.0.is_instance_of::() { 421 | let dict: &Bound = self.0.downcast()?; 422 | if dict.len() == 1 { 423 | let key = dict.keys().get_item(0).unwrap(); 424 | let value = dict.values().get_item(0).unwrap(); 425 | if key.is_instance_of::() { 426 | let variant: String = key.extract()?; 427 | return visitor.visit_enum(EnumDeserializer { 428 | variant: &variant, 429 | inner: value, 430 | }); 431 | } 432 | } 433 | } 434 | self.deserialize_any(visitor) 435 | } 436 | 437 | fn deserialize_tuple_struct>( 438 | self, 439 | name: &'static str, 440 | _len: usize, 441 | visitor: V, 442 | ) -> Result { 443 | if self.0.is_instance_of::() { 444 | let dict: &Bound = self.0.downcast()?; 445 | if let Some(value) = dict.get_item(name)? { 446 | if value.is_instance_of::() { 447 | let tuple: &Bound = value.downcast()?; 448 | return visitor.visit_seq(SeqDeserializer::from_tuple(tuple)); 449 | } 450 | } 451 | } 452 | self.deserialize_any(visitor) 453 | } 454 | 455 | forward_to_deserialize_any! { 456 | bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string 457 | bytes byte_buf seq tuple 458 | map identifier ignored_any 459 | } 460 | } 461 | 462 | struct SeqDeserializer<'py> { 463 | seq_reversed: Vec>, 464 | } 465 | 466 | impl<'py> SeqDeserializer<'py> { 467 | fn from_list(list: &Bound<'py, PyList>) -> Self { 468 | let mut seq_reversed = Vec::new(); 469 | for item in list.iter().rev() { 470 | seq_reversed.push(item); 471 | } 472 | Self { seq_reversed } 473 | } 474 | 475 | fn from_tuple(tuple: &Bound<'py, PyTuple>) -> Self { 476 | let mut seq_reversed = Vec::new(); 477 | for item in tuple.iter().rev() { 478 | seq_reversed.push(item); 479 | } 480 | Self { seq_reversed } 481 | } 482 | } 483 | 484 | impl<'de> SeqAccess<'de> for SeqDeserializer<'_> { 485 | type Error = Error; 486 | fn next_element_seed(&mut self, seed: T) -> Result> 487 | where 488 | T: de::DeserializeSeed<'de>, 489 | { 490 | self.seq_reversed.pop().map_or(Ok(None), |value| { 491 | let value = seed.deserialize(PyAnyDeserializer(value))?; 492 | Ok(Some(value)) 493 | }) 494 | } 495 | } 496 | 497 | struct MapDeserializer<'py> { 498 | keys: Vec>, 499 | values: Vec>, 500 | } 501 | 502 | impl<'py> MapDeserializer<'py> { 503 | fn new(dict: &Bound<'py, PyDict>) -> Self { 504 | let mut keys = Vec::new(); 505 | let mut values = Vec::new(); 506 | for (key, value) in dict.iter() { 507 | keys.push(key); 508 | values.push(value); 509 | } 510 | Self { keys, values } 511 | } 512 | 513 | fn from_slots(obj: &Bound<'py, PyAny>) -> Result { 514 | let mut keys = vec![]; 515 | let mut values = vec![]; 516 | for key in obj.getattr("__slots__")?.try_iter()? { 517 | let key = key?; 518 | keys.push(key.clone()); 519 | let v = obj.getattr(key.str()?)?; 520 | values.push(v); 521 | } 522 | Ok(Self { keys, values }) 523 | } 524 | } 525 | 526 | impl<'de> MapAccess<'de> for MapDeserializer<'_> { 527 | type Error = Error; 528 | 529 | fn next_key_seed(&mut self, seed: K) -> Result> 530 | where 531 | K: de::DeserializeSeed<'de>, 532 | { 533 | if let Some(key) = self.keys.pop() { 534 | let key = seed.deserialize(PyAnyDeserializer(key))?; 535 | Ok(Some(key)) 536 | } else { 537 | Ok(None) 538 | } 539 | } 540 | 541 | fn next_value_seed(&mut self, seed: V) -> Result 542 | where 543 | V: de::DeserializeSeed<'de>, 544 | { 545 | if let Some(value) = self.values.pop() { 546 | let value = seed.deserialize(PyAnyDeserializer(value))?; 547 | Ok(value) 548 | } else { 549 | unreachable!() 550 | } 551 | } 552 | } 553 | 554 | // this lifetime is technically no longer 'py 555 | struct EnumDeserializer<'py> { 556 | variant: &'py str, 557 | inner: Bound<'py, PyAny>, 558 | } 559 | 560 | impl<'de> de::EnumAccess<'de> for EnumDeserializer<'_> { 561 | type Error = Error; 562 | type Variant = Self; 563 | 564 | fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant)> 565 | where 566 | V: de::DeserializeSeed<'de>, 567 | { 568 | Ok(( 569 | seed.deserialize(StrDeserializer::::new(self.variant))?, 570 | self, 571 | )) 572 | } 573 | } 574 | 575 | impl<'de> de::VariantAccess<'de> for EnumDeserializer<'_> { 576 | type Error = Error; 577 | 578 | fn unit_variant(self) -> Result<()> { 579 | Ok(()) 580 | } 581 | 582 | fn newtype_variant_seed(self, seed: T) -> Result 583 | where 584 | T: de::DeserializeSeed<'de>, 585 | { 586 | seed.deserialize(PyAnyDeserializer(self.inner)) 587 | } 588 | 589 | fn tuple_variant(self, _len: usize, visitor: V) -> Result 590 | where 591 | V: Visitor<'de>, 592 | { 593 | PyAnyDeserializer(self.inner).deserialize_seq(visitor) 594 | } 595 | 596 | fn struct_variant(self, _fields: &'static [&'static str], visitor: V) -> Result 597 | where 598 | V: Visitor<'de>, 599 | { 600 | PyAnyDeserializer(self.inner).deserialize_map(visitor) 601 | } 602 | } 603 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use pyo3::{exceptions::PyRuntimeError, DowncastError, PyErr}; 2 | use serde::{de, ser}; 3 | use std::fmt::{self, Display}; 4 | 5 | /// New-type wrapper of `PyErr` to implement `serde::ser::Error`. 6 | #[derive(Debug)] 7 | pub struct Error(pub PyErr); 8 | 9 | impl From for Error { 10 | fn from(err: PyErr) -> Self { 11 | Error(err) 12 | } 13 | } 14 | 15 | impl From> for Error { 16 | fn from(err: DowncastError) -> Self { 17 | let err: PyErr = err.into(); 18 | Error(err) 19 | } 20 | } 21 | 22 | impl From for PyErr { 23 | fn from(err: Error) -> Self { 24 | err.0 25 | } 26 | } 27 | 28 | impl ser::Error for Error { 29 | fn custom(msg: T) -> Self { 30 | Error(PyRuntimeError::new_err(msg.to_string())) 31 | } 32 | } 33 | 34 | impl de::Error for Error { 35 | fn custom(msg: T) -> Self { 36 | Error(PyRuntimeError::new_err(msg.to_string())) 37 | } 38 | } 39 | 40 | impl Display for Error { 41 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 42 | self.0.fmt(formatter) 43 | } 44 | } 45 | 46 | impl std::error::Error for Error {} 47 | 48 | pub type Result = ::std::result::Result; 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! PyO3's PyAny as a serde data format 2 | //! 3 | //! This crate provides a mapping from [serde data model](https://serde.rs/data-model.html) 4 | //! to Python objects. 5 | //! 6 | 7 | mod de; 8 | mod error; 9 | mod pylit; 10 | mod ser; 11 | 12 | /// Re-export of `pyo3` crate. 13 | pub use pyo3; 14 | 15 | pub use de::from_pyobject; 16 | pub use error::Error; 17 | pub use ser::to_pyobject; 18 | 19 | #[cfg_attr(doc, doc = include_str!("../README.md"))] 20 | mod readme {} 21 | -------------------------------------------------------------------------------- /src/pylit.rs: -------------------------------------------------------------------------------- 1 | /// Create [`pyo3::types::PyDict`] from a list of key-value pairs. 2 | /// 3 | /// Examples 4 | /// --------- 5 | /// 6 | /// - When you have GIL marker `py`, you can pass it and get a Bound pointer `PyResult>`: 7 | /// 8 | /// ``` 9 | /// use pyo3::{Python, Bound, types::{PyDict, PyDictMethods, PyAnyMethods}}; 10 | /// use serde_pyobject::pydict; 11 | /// 12 | /// Python::with_gil(|py| { 13 | /// let dict: Bound = pydict! { 14 | /// py, 15 | /// "foo" => 42, 16 | /// "bar" => "baz" 17 | /// } 18 | /// .unwrap(); 19 | /// 20 | /// assert_eq!( 21 | /// dict.get_item("foo") 22 | /// .unwrap() 23 | /// .unwrap() 24 | /// .extract::() 25 | /// .unwrap(), 26 | /// 42 27 | /// ); 28 | /// assert_eq!( 29 | /// dict.get_item("bar") 30 | /// .unwrap() 31 | /// .unwrap() 32 | /// .extract::() 33 | /// .unwrap(), 34 | /// "baz", 35 | /// ); 36 | /// }) 37 | /// ``` 38 | /// 39 | /// - When you don't have GIL marker, you get a `PyResult>`: 40 | /// 41 | /// ``` 42 | /// use pyo3::{Python, Py, types::{PyDict, PyDictMethods, PyAnyMethods}}; 43 | /// use serde_pyobject::pydict; 44 | /// 45 | /// let dict: Py = pydict! { 46 | /// "foo" => 42, 47 | /// "bar" => "baz" 48 | /// } 49 | /// .unwrap(); 50 | /// 51 | /// Python::with_gil(|py| { 52 | /// let dict = dict.into_bound(py); 53 | /// assert_eq!( 54 | /// dict.get_item("foo") 55 | /// .unwrap() 56 | /// .unwrap() 57 | /// .extract::() 58 | /// .unwrap(), 59 | /// 42 60 | /// ); 61 | /// assert_eq!( 62 | /// dict.get_item("bar") 63 | /// .unwrap() 64 | /// .unwrap() 65 | /// .extract::() 66 | /// .unwrap(), 67 | /// "baz", 68 | /// ); 69 | /// }) 70 | /// ``` 71 | /// 72 | #[macro_export] 73 | macro_rules! pydict { 74 | ($py:expr, $($key:expr => $value:expr),*) => { 75 | (|| -> $crate::pyo3::PyResult<$crate::pyo3::Bound<$crate::pyo3::types::PyDict>> { 76 | use $crate::pyo3::types::PyDictMethods; 77 | let dict = $crate::pyo3::types::PyDict::new_bound($py); 78 | $(dict.set_item($key, $value)?;)* 79 | Ok(dict) 80 | })() 81 | }; 82 | ($($key:expr => $value:expr),*) => { 83 | $crate::pyo3::Python::with_gil(|py| -> $crate::pyo3::PyResult<$crate::pyo3::Py<$crate::pyo3::types::PyDict>> { 84 | let dict = pydict!(py, $($key => $value),*)?; 85 | Ok(dict.into()) 86 | }) 87 | }; 88 | } 89 | 90 | /// Create [`pyo3::types::PyList`] from a list of values. 91 | /// 92 | /// Examples 93 | /// -------- 94 | /// 95 | /// - When you have GIL marker `py`, you can pass it and get a reference `PyResult<&PyList>`: 96 | /// 97 | /// ``` 98 | /// use pyo3::{Python, types::{PyList, PyListMethods, PyAnyMethods}}; 99 | /// use serde_pyobject::pylist; 100 | /// 101 | /// Python::with_gil(|py| { 102 | /// let list = pylist![py; 1, "two"].unwrap(); 103 | /// assert_eq!(list.len(), 2); 104 | /// assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 1); 105 | /// assert_eq!(list.get_item(1).unwrap().extract::().unwrap(), "two"); 106 | /// }) 107 | /// ``` 108 | /// 109 | /// - When you don't have GIL marker, you get a `PyResult>`: 110 | /// 111 | /// ``` 112 | /// use pyo3::{Python, Py, types::{PyList, PyListMethods, PyAnyMethods}}; 113 | /// use serde_pyobject::pylist; 114 | /// 115 | /// let list: Py = pylist![1, "two"].unwrap(); 116 | /// 117 | /// Python::with_gil(|py| { 118 | /// let list = list.into_bound(py); 119 | /// assert_eq!(list.len(), 2); 120 | /// assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 1); 121 | /// assert_eq!(list.get_item(1).unwrap().extract::().unwrap(), "two"); 122 | /// }); 123 | /// ``` 124 | /// 125 | #[macro_export] 126 | macro_rules! pylist { 127 | ($py:expr; $($value:expr),*) => { 128 | (|| -> $crate::pyo3::PyResult<$crate::pyo3::Bound<$crate::pyo3::types::PyList>> { 129 | use $crate::pyo3::types::PyListMethods; 130 | let list = $crate::pyo3::types::PyList::empty_bound($py); 131 | $(list.append($value)?;)* 132 | Ok(list) 133 | })() 134 | }; 135 | ($($value:expr),*) => { 136 | $crate::pyo3::Python::with_gil(|py| -> $crate::pyo3::PyResult<$crate::pyo3::Py<$crate::pyo3::types::PyList>> { 137 | let list = pylist!(py; $($value),*)?; 138 | Ok(list.into()) 139 | }) 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /src/ser.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use pyo3::{prelude::*, types::*, IntoPyObjectExt}; 3 | use serde::{ser, Serialize}; 4 | 5 | /// Serialize `T: Serialize` into a [`pyo3::PyAny`] value. 6 | /// 7 | /// # Examples 8 | /// 9 | /// ## string 10 | /// 11 | /// ``` 12 | /// use pyo3::{Python, types::{PyString, PyAnyMethods}}; 13 | /// use serde_pyobject::to_pyobject; 14 | /// 15 | /// Python::with_gil(|py| { 16 | /// // char 17 | /// let obj = to_pyobject(py, &'a').unwrap(); 18 | /// assert!(obj.is_exact_instance_of::()); 19 | /// // &str 20 | /// let obj = to_pyobject(py, "test").unwrap(); 21 | /// assert!(obj.is_exact_instance_of::()); 22 | /// }); 23 | /// ``` 24 | /// 25 | /// ## integer 26 | /// 27 | /// ``` 28 | /// use pyo3::{Python, types::{PyLong, PyAnyMethods}}; 29 | /// use serde_pyobject::to_pyobject; 30 | /// 31 | /// Python::with_gil(|py| { 32 | /// let obj = to_pyobject(py, &1_u16).unwrap(); 33 | /// assert!(obj.is_exact_instance_of::()); 34 | /// 35 | /// let obj = to_pyobject(py, &1_i64).unwrap(); 36 | /// assert!(obj.is_exact_instance_of::()); 37 | /// 38 | /// let obj = to_pyobject(py, &1_i64).unwrap(); 39 | /// assert!(obj.is_exact_instance_of::()); 40 | /// }); 41 | /// ``` 42 | /// 43 | /// ## float 44 | /// 45 | /// ``` 46 | /// use pyo3::{Python, types::{PyFloat, PyAnyMethods}}; 47 | /// use serde_pyobject::to_pyobject; 48 | /// 49 | /// Python::with_gil(|py| { 50 | /// let obj = to_pyobject(py, &3.1_f32).unwrap(); 51 | /// assert!(obj.is_exact_instance_of::()); 52 | /// 53 | /// let obj = to_pyobject(py, &-3.1_f64).unwrap(); 54 | /// assert!(obj.is_exact_instance_of::()); 55 | /// }); 56 | /// ``` 57 | /// 58 | /// ## option 59 | /// 60 | /// Rust `None` is serialized to Python `None`, and `Some(value)` is serialized as `value` is serialized 61 | /// 62 | /// ``` 63 | /// use pyo3::{Python, types::{PyLong, PyAnyMethods}}; 64 | /// use serde_pyobject::to_pyobject; 65 | /// 66 | /// Python::with_gil(|py| { 67 | /// let obj = to_pyobject(py, &Option::::None).unwrap(); 68 | /// assert!(obj.is(&py.None())); 69 | /// 70 | /// let obj = to_pyobject(py, &Some(1_i64)).unwrap(); 71 | /// assert!(obj.is_exact_instance_of::()); 72 | /// }); 73 | /// ``` 74 | /// 75 | /// ## unit 76 | /// 77 | /// Rust's `()` is serialized to Python's `()` 78 | /// 79 | /// ``` 80 | /// use pyo3::{Python, types::{PyTuple, PyAnyMethods}}; 81 | /// use serde_pyobject::to_pyobject; 82 | /// 83 | /// Python::with_gil(|py| { 84 | /// let obj = to_pyobject(py, &()).unwrap(); 85 | /// assert!(obj.is(&PyTuple::empty_bound(py))); 86 | /// }); 87 | /// ``` 88 | /// 89 | /// ## unit_struct 90 | /// 91 | /// `Unit` is serialized as an empty tuple `()` 92 | /// 93 | /// ``` 94 | /// use serde::Serialize; 95 | /// use pyo3::{Python, types::{PyTuple, PyAnyMethods}}; 96 | /// use serde_pyobject::{to_pyobject, pydict}; 97 | /// 98 | /// #[derive(Serialize)] 99 | /// struct UnitStruct; 100 | /// 101 | /// Python::with_gil(|py| { 102 | /// let obj = to_pyobject(py, &UnitStruct {}).unwrap(); 103 | /// assert!(obj.eq(PyTuple::empty_bound(py)).unwrap()); 104 | /// }); 105 | /// ``` 106 | /// 107 | /// ## unit_variant 108 | /// 109 | /// ``` 110 | /// use serde::Serialize; 111 | /// use pyo3::{Python, types::{PyTuple, PyAnyMethods}}; 112 | /// use serde_pyobject::{to_pyobject, pydict}; 113 | /// 114 | /// #[derive(Serialize)] 115 | /// enum UnitVariant { 116 | /// A, 117 | /// B, 118 | /// } 119 | /// 120 | /// Python::with_gil(|py| { 121 | /// let obj = to_pyobject(py, &UnitVariant::A).unwrap(); 122 | /// assert!(obj.eq("A").unwrap()); 123 | /// let obj = to_pyobject(py, &UnitVariant::B).unwrap(); 124 | /// assert!(obj.eq("B").unwrap()); 125 | /// }); 126 | /// ``` 127 | /// 128 | /// ## newtype_struct 129 | /// 130 | /// ``` 131 | /// use serde::Serialize; 132 | /// use pyo3::{Python, types::{PyLong, PyAnyMethods}}; 133 | /// use serde_pyobject::to_pyobject; 134 | /// 135 | /// #[derive(Serialize)] 136 | /// struct NewtypeStruct(u8); 137 | /// 138 | /// Python::with_gil(|py| { 139 | /// let obj = to_pyobject(py, &NewtypeStruct(10)).unwrap(); 140 | /// assert!(obj.is_exact_instance_of::()); 141 | /// assert!(obj.eq(10).unwrap()); 142 | /// }); 143 | /// ``` 144 | /// 145 | /// ## newtype_variant 146 | /// 147 | /// ``` 148 | /// use serde::Serialize; 149 | /// use pyo3::{Python, types::PyAnyMethods}; 150 | /// use serde_pyobject::{to_pyobject, pydict}; 151 | /// 152 | /// #[derive(Serialize)] 153 | /// enum NewtypeVariant { 154 | /// N(u8), 155 | /// } 156 | /// 157 | /// Python::with_gil(|py| { 158 | /// let obj = to_pyobject(py, &NewtypeVariant::N(3)).unwrap(); 159 | /// assert!(obj.eq(pydict! { "N" => 3 }.unwrap()).unwrap()); 160 | /// }); 161 | /// ``` 162 | /// 163 | /// ## seq 164 | /// 165 | /// ``` 166 | /// use pyo3::{Python, types::PyAnyMethods}; 167 | /// use serde_pyobject::{to_pyobject, pylist}; 168 | /// 169 | /// Python::with_gil(|py| { 170 | /// let obj = to_pyobject(py, &vec![1, 2, 3]).unwrap(); 171 | /// assert!(obj.eq(pylist![py; 1, 2, 3].unwrap()).unwrap()); 172 | /// }); 173 | /// ``` 174 | /// 175 | /// ## tuple 176 | /// 177 | /// ``` 178 | /// use pyo3::{IntoPy, Python, types::{PyTuple, PyAnyMethods}}; 179 | /// use serde_pyobject::to_pyobject; 180 | /// 181 | /// Python::with_gil(|py| { 182 | /// let obj = to_pyobject(py, &(3, "test")).unwrap(); 183 | /// assert!(obj.eq(PyTuple::new_bound(py, [3.into_py(py), "test".into_py(py)])).unwrap()); 184 | /// }); 185 | /// ``` 186 | /// 187 | /// ## tuple struct 188 | /// 189 | /// ``` 190 | /// use pyo3::{Python, types::{PyTuple, PyAnyMethods}}; 191 | /// use serde::Serialize; 192 | /// use serde_pyobject::to_pyobject; 193 | /// 194 | /// #[derive(Serialize)] 195 | /// struct TupleStruct(u8, u8, u8); 196 | /// 197 | /// Python::with_gil(|py| { 198 | /// let obj = to_pyobject(py, &TupleStruct(1, 2, 3)).unwrap(); 199 | /// assert!(obj.eq(PyTuple::new_bound(py, [1, 2, 3])).unwrap()); 200 | /// }); 201 | /// ``` 202 | /// 203 | /// ## tuple variant 204 | /// 205 | /// ``` 206 | /// use pyo3::{Python, types::PyAnyMethods}; 207 | /// use serde::Serialize; 208 | /// use serde_pyobject::{to_pyobject, pydict}; 209 | /// 210 | /// #[derive(Serialize)] 211 | /// enum TupleVariant { 212 | /// T(u8, u8), 213 | /// } 214 | /// 215 | /// Python::with_gil(|py| { 216 | /// let obj = to_pyobject(py, &TupleVariant::T(1, 2)).unwrap(); 217 | /// assert!(obj.eq(pydict!{ "T" => (1, 2) }.unwrap()).unwrap()); 218 | /// }); 219 | /// ``` 220 | /// 221 | /// ## map 222 | /// 223 | /// ``` 224 | /// use pyo3::{Python, types::PyAnyMethods}; 225 | /// use serde_pyobject::{to_pyobject, pydict}; 226 | /// use maplit::hashmap; 227 | /// 228 | /// Python::with_gil(|py| { 229 | /// let obj = to_pyobject(py, &hashmap! { 230 | /// "a".to_owned() => 1_u8, 231 | /// "b".to_owned() => 2, 232 | /// "c".to_owned() => 3 233 | /// }).unwrap(); 234 | /// assert!(obj.eq(pydict! { 235 | /// "a" => 1, 236 | /// "b" => 2, 237 | /// "c" => 3 238 | /// }.unwrap()).unwrap()); 239 | /// }); 240 | /// ``` 241 | /// 242 | /// ## struct 243 | /// 244 | /// ``` 245 | /// use serde::Serialize; 246 | /// use pyo3::{IntoPy, Python, types::{PyTuple, PyAnyMethods}}; 247 | /// use serde_pyobject::{to_pyobject, pydict}; 248 | /// 249 | /// #[derive(Serialize)] 250 | /// struct Struct { 251 | /// a: i32, 252 | /// b: String, 253 | /// } 254 | /// 255 | /// Python::with_gil(|py| { 256 | /// let obj = to_pyobject(py, &Struct { a: 32, b: "test".to_string() }).unwrap(); 257 | /// assert!(obj.eq(pydict!{ "a" => 32, "b" => "test" }.unwrap()).unwrap()); 258 | /// }); 259 | /// ``` 260 | /// 261 | /// ## struct variant 262 | /// 263 | /// ``` 264 | /// use serde::Serialize; 265 | /// use pyo3::{Python, types::PyAnyMethods}; 266 | /// use serde_pyobject::{to_pyobject, pydict}; 267 | /// 268 | /// #[derive(Serialize)] 269 | /// enum StructVariant { 270 | /// S { r: u8, g: u8, b: u8 }, 271 | /// } 272 | /// 273 | /// Python::with_gil(|py| { 274 | /// let obj = to_pyobject(py, &StructVariant::S { r: 1, g: 2, b: 3 }).unwrap(); 275 | /// assert!( 276 | /// obj.eq( 277 | /// pydict! { 278 | /// "S" => pydict! { 279 | /// "r" => 1, 280 | /// "g" => 2, 281 | /// "b" => 3 282 | /// }.unwrap() 283 | /// }.unwrap() 284 | /// ).unwrap() 285 | /// ); 286 | /// }); 287 | /// ``` 288 | pub fn to_pyobject<'py, T>(py: Python<'py>, value: &T) -> Result> 289 | where 290 | T: Serialize + ?Sized, 291 | { 292 | let serializer = PyAnySerializer { py }; 293 | value.serialize(serializer) 294 | } 295 | 296 | pub struct PyAnySerializer<'py> { 297 | py: Python<'py>, 298 | } 299 | 300 | macro_rules! serialize_impl { 301 | ($f:ident, $t:ty) => { 302 | fn $f(self, v: $t) -> Result { 303 | Ok(v.into_bound_py_any(self.py)?) 304 | } 305 | }; 306 | } 307 | 308 | impl<'py> ser::Serializer for PyAnySerializer<'py> { 309 | type Ok = Bound<'py, PyAny>; 310 | 311 | type Error = Error; 312 | 313 | type SerializeSeq = Seq<'py>; 314 | type SerializeTuple = Seq<'py>; 315 | type SerializeTupleStruct = TupleStruct<'py>; 316 | type SerializeTupleVariant = TupleVariant<'py>; 317 | type SerializeMap = Map<'py>; 318 | type SerializeStruct = Struct<'py>; 319 | type SerializeStructVariant = StructVariant<'py>; 320 | 321 | serialize_impl!(serialize_bool, bool); 322 | serialize_impl!(serialize_i8, i8); 323 | serialize_impl!(serialize_i16, i16); 324 | serialize_impl!(serialize_i32, i32); 325 | serialize_impl!(serialize_i64, i64); 326 | serialize_impl!(serialize_u8, u8); 327 | serialize_impl!(serialize_u16, u16); 328 | serialize_impl!(serialize_u32, u32); 329 | serialize_impl!(serialize_u64, u64); 330 | serialize_impl!(serialize_f32, f32); 331 | serialize_impl!(serialize_f64, f64); 332 | serialize_impl!(serialize_char, char); 333 | serialize_impl!(serialize_str, &str); 334 | serialize_impl!(serialize_bytes, &[u8]); 335 | 336 | fn serialize_none(self) -> Result { 337 | Ok(self.py.None().into_bound(self.py)) 338 | } 339 | 340 | fn serialize_some(self, value: &T) -> Result 341 | where 342 | T: ?Sized + Serialize, 343 | { 344 | value.serialize(self) 345 | } 346 | 347 | fn serialize_unit(self) -> Result { 348 | Ok(PyTuple::empty(self.py).into_any()) 349 | } 350 | 351 | fn serialize_unit_struct(self, _name: &'static str) -> Result { 352 | Ok(PyTuple::empty(self.py).into_any()) 353 | } 354 | 355 | fn serialize_unit_variant( 356 | self, 357 | _name: &'static str, 358 | _index: u32, 359 | variant: &'static str, 360 | ) -> Result { 361 | Ok(PyString::new(self.py, variant).into_any()) 362 | } 363 | 364 | fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result 365 | where 366 | T: ?Sized + Serialize, 367 | { 368 | value.serialize(self) 369 | } 370 | 371 | fn serialize_newtype_variant( 372 | self, 373 | _name: &'static str, 374 | _variant_index: u32, 375 | variant: &'static str, 376 | value: &T, 377 | ) -> Result 378 | where 379 | T: ?Sized + Serialize, 380 | { 381 | let dict = PyDict::new(self.py).into_any(); 382 | dict.set_item(variant, value.serialize(self)?)?; 383 | Ok(dict) 384 | } 385 | 386 | fn serialize_seq(self, _len: Option) -> Result { 387 | Ok(Seq { 388 | py: self.py, 389 | seq: Vec::new(), 390 | }) 391 | } 392 | 393 | fn serialize_tuple(self, _len: usize) -> Result { 394 | Ok(Seq { 395 | py: self.py, 396 | seq: Vec::new(), 397 | }) 398 | } 399 | 400 | fn serialize_tuple_struct( 401 | self, 402 | _name: &'static str, 403 | _len: usize, 404 | ) -> Result { 405 | Ok(TupleStruct { 406 | py: self.py, 407 | fields: Vec::new(), 408 | }) 409 | } 410 | 411 | fn serialize_tuple_variant( 412 | self, 413 | _name: &'static str, 414 | _variant_index: u32, 415 | variant: &'static str, 416 | _len: usize, 417 | ) -> Result { 418 | Ok(TupleVariant { 419 | py: self.py, 420 | variant, 421 | fields: Vec::new(), 422 | }) 423 | } 424 | 425 | fn serialize_map(self, _len: Option) -> Result { 426 | Ok(Map { 427 | py: self.py, 428 | map: PyDict::new(self.py), 429 | key: None, 430 | }) 431 | } 432 | 433 | fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { 434 | Ok(Struct { 435 | py: self.py, 436 | fields: PyDict::new(self.py), 437 | }) 438 | } 439 | 440 | fn serialize_struct_variant( 441 | self, 442 | _name: &'static str, 443 | _variant_index: u32, 444 | variant: &'static str, 445 | _len: usize, 446 | ) -> Result { 447 | Ok(StructVariant { 448 | py: self.py, 449 | variant, 450 | fields: PyDict::new(self.py), 451 | }) 452 | } 453 | } 454 | 455 | pub struct Seq<'py> { 456 | py: Python<'py>, 457 | seq: Vec>, 458 | } 459 | 460 | impl<'py> ser::SerializeSeq for Seq<'py> { 461 | type Ok = Bound<'py, PyAny>; 462 | type Error = Error; 463 | 464 | fn serialize_element(&mut self, value: &T) -> Result<()> 465 | where 466 | T: ?Sized + Serialize, 467 | { 468 | self.seq 469 | .push(value.serialize(PyAnySerializer { py: self.py })?); 470 | Ok(()) 471 | } 472 | 473 | fn end(self) -> Result { 474 | Ok(PyList::new(self.py, self.seq)?.into_any()) 475 | } 476 | } 477 | 478 | impl<'py> ser::SerializeTuple for Seq<'py> { 479 | type Ok = Bound<'py, PyAny>; 480 | type Error = Error; 481 | 482 | fn serialize_element(&mut self, value: &T) -> Result<()> 483 | where 484 | T: ?Sized + Serialize, 485 | { 486 | self.seq 487 | .push(value.serialize(PyAnySerializer { py: self.py })?); 488 | Ok(()) 489 | } 490 | 491 | fn end(self) -> Result { 492 | Ok(PyTuple::new(self.py, self.seq)?.into_any()) 493 | } 494 | } 495 | 496 | pub struct TupleStruct<'py> { 497 | py: Python<'py>, 498 | fields: Vec>, 499 | } 500 | 501 | impl<'py> ser::SerializeTupleStruct for TupleStruct<'py> { 502 | type Ok = Bound<'py, PyAny>; 503 | type Error = Error; 504 | 505 | fn serialize_field(&mut self, value: &T) -> Result<()> 506 | where 507 | T: ?Sized + Serialize, 508 | { 509 | self.fields 510 | .push(value.serialize(PyAnySerializer { py: self.py })?); 511 | Ok(()) 512 | } 513 | 514 | fn end(self) -> Result { 515 | Ok(PyTuple::new(self.py, self.fields)?.into_any()) 516 | } 517 | } 518 | 519 | pub struct TupleVariant<'py> { 520 | py: Python<'py>, 521 | variant: &'static str, 522 | fields: Vec>, 523 | } 524 | 525 | impl<'py> ser::SerializeTupleVariant for TupleVariant<'py> { 526 | type Ok = Bound<'py, PyAny>; 527 | type Error = Error; 528 | 529 | fn serialize_field(&mut self, value: &T) -> Result<()> 530 | where 531 | T: ?Sized + Serialize, 532 | { 533 | self.fields 534 | .push(value.serialize(PyAnySerializer { py: self.py })?); 535 | Ok(()) 536 | } 537 | 538 | fn end(self) -> Result { 539 | let dict = PyDict::new(self.py); 540 | dict.set_item(self.variant, PyTuple::new(self.py, self.fields)?)?; 541 | Ok(dict.into_any()) 542 | } 543 | } 544 | 545 | pub struct Map<'py> { 546 | py: Python<'py>, 547 | map: Bound<'py, PyDict>, 548 | key: Option>, 549 | } 550 | 551 | impl<'py> ser::SerializeMap for Map<'py> { 552 | type Ok = Bound<'py, PyAny>; 553 | type Error = Error; 554 | 555 | fn serialize_key(&mut self, key: &T) -> Result<()> 556 | where 557 | T: ?Sized + Serialize, 558 | { 559 | self.key = Some(key.serialize(PyAnySerializer { py: self.py })?); 560 | Ok(()) 561 | } 562 | 563 | fn serialize_value(&mut self, value: &T) -> Result<()> 564 | where 565 | T: ?Sized + Serialize, 566 | { 567 | let key = self 568 | .key 569 | .take() 570 | .expect("Invalid Serialize implementation. Key is missing."); 571 | self.map 572 | .set_item(key, value.serialize(PyAnySerializer { py: self.py })?)?; 573 | Ok(()) 574 | } 575 | 576 | fn end(self) -> Result { 577 | Ok(self.map.into_any()) 578 | } 579 | } 580 | 581 | pub struct Struct<'py> { 582 | py: Python<'py>, 583 | fields: Bound<'py, PyDict>, 584 | } 585 | 586 | impl<'py> ser::SerializeStruct for Struct<'py> { 587 | type Ok = Bound<'py, PyAny>; 588 | type Error = Error; 589 | 590 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> 591 | where 592 | T: ?Sized + Serialize, 593 | { 594 | self.fields 595 | .set_item(key, value.serialize(PyAnySerializer { py: self.py })?)?; 596 | Ok(()) 597 | } 598 | 599 | fn end(self) -> Result { 600 | Ok(self.fields.into_any()) 601 | } 602 | } 603 | 604 | pub struct StructVariant<'py> { 605 | py: Python<'py>, 606 | variant: &'static str, 607 | fields: Bound<'py, PyDict>, 608 | } 609 | 610 | impl<'py> ser::SerializeStructVariant for StructVariant<'py> { 611 | type Ok = Bound<'py, PyAny>; 612 | type Error = Error; 613 | 614 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> 615 | where 616 | T: ?Sized + Serialize, 617 | { 618 | self.fields 619 | .set_item(key, value.serialize(PyAnySerializer { py: self.py })?)?; 620 | Ok(()) 621 | } 622 | 623 | fn end(self) -> Result { 624 | let dict = PyDict::new(self.py); 625 | dict.set_item(self.variant, self.fields)?; 626 | Ok(dict.into_any()) 627 | } 628 | } 629 | -------------------------------------------------------------------------------- /tests/check_revertible.rs: -------------------------------------------------------------------------------- 1 | use maplit::hashmap; 2 | use pyo3::{ffi::c_str, prelude::*}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_pyobject::{from_pyobject, to_pyobject}; 5 | 6 | fn check_revertible<'de, T: Serialize + Deserialize<'de> + PartialEq + std::fmt::Debug>(obj: T) { 7 | Python::with_gil(|py| { 8 | let any = to_pyobject(py, &obj).unwrap(); 9 | let reverted = from_pyobject(any).unwrap(); 10 | assert_eq!(obj, reverted); 11 | }) 12 | } 13 | 14 | #[test] 15 | fn primitive() { 16 | check_revertible(1_u8); 17 | check_revertible(-4_i32); 18 | check_revertible(-3.1); 19 | check_revertible(true); 20 | check_revertible("test".to_string()); 21 | } 22 | 23 | #[test] 24 | fn option() { 25 | check_revertible(Some(10_u8)); 26 | check_revertible(None::); 27 | } 28 | 29 | #[test] 30 | fn unit() { 31 | check_revertible(()); 32 | } 33 | 34 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 35 | struct UnitStruct; 36 | 37 | #[test] 38 | fn unit_struct() { 39 | check_revertible(UnitStruct); 40 | } 41 | 42 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 43 | enum UnitVariant { 44 | A, 45 | B, 46 | } 47 | 48 | #[test] 49 | fn unit_variant() { 50 | check_revertible(UnitVariant::A); 51 | check_revertible(UnitVariant::B); 52 | } 53 | 54 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 55 | struct NewtypeStruct(u8); 56 | 57 | #[test] 58 | fn newtype_struct() { 59 | check_revertible(NewtypeStruct(10)); 60 | } 61 | 62 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 63 | enum NewtypeVariant { 64 | N(u8), 65 | } 66 | 67 | #[test] 68 | fn newtype_variant() { 69 | check_revertible(NewtypeVariant::N(10)); 70 | } 71 | 72 | #[test] 73 | fn seq() { 74 | check_revertible(vec![1_u8, 2, 3]); 75 | } 76 | 77 | #[test] 78 | fn tuple() { 79 | check_revertible((1, "test".to_string())); 80 | } 81 | 82 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 83 | struct TupleStruct(u8, u8, u8); 84 | 85 | #[test] 86 | fn tuple_struct() { 87 | check_revertible(TupleStruct(1, 2, 3)); 88 | } 89 | 90 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 91 | enum TupleVariant { 92 | T(u8, u8), 93 | } 94 | 95 | #[test] 96 | fn tuple_variant() { 97 | check_revertible(TupleVariant::T(1, 2)); 98 | } 99 | 100 | #[test] 101 | fn map() { 102 | check_revertible(hashmap! { 103 | "a".to_owned() => 1_u8, 104 | "b".to_owned() => 2, 105 | "c".to_owned() => 3, 106 | }); 107 | } 108 | 109 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 110 | struct A { 111 | a: i32, 112 | b: String, 113 | } 114 | 115 | #[test] 116 | fn struct_() { 117 | check_revertible(A { 118 | a: 10, 119 | b: "hello".to_owned(), 120 | }); 121 | } 122 | 123 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 124 | enum StructVariant { 125 | S { r: u8, g: u8, b: u8 }, 126 | } 127 | 128 | #[test] 129 | fn struct_variant() { 130 | check_revertible(StructVariant::S { 131 | r: 10, 132 | g: 20, 133 | b: 30, 134 | }); 135 | } 136 | 137 | #[test] 138 | fn check_python_object() { 139 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 140 | struct MyClass { 141 | name: String, 142 | age: i32, 143 | } 144 | 145 | Python::with_gil(|py| { 146 | // Create an instance of Python object 147 | py.run( 148 | c_str!( 149 | r#" 150 | class MyClass: 151 | def __init__(self, name: str, age: int): 152 | self.name = name 153 | self.age = age 154 | "# 155 | ), 156 | None, 157 | None, 158 | ) 159 | .unwrap(); 160 | // Create an instance of MyClass 161 | let my_python_class = py 162 | .eval( 163 | c_str!( 164 | r#" 165 | MyClass("John", 30) 166 | "# 167 | ), 168 | None, 169 | None, 170 | ) 171 | .unwrap(); 172 | 173 | let my_rust_class = MyClass { 174 | name: "John".to_string(), 175 | age: 30, 176 | }; 177 | let any: Bound<'_, PyAny> = to_pyobject(py, &my_rust_class).unwrap(); 178 | let rust_version: MyClass = from_pyobject(my_python_class).unwrap(); 179 | let python_version: MyClass = from_pyobject(any).unwrap(); 180 | assert_eq!(rust_version, python_version); 181 | }) 182 | } 183 | -------------------------------------------------------------------------------- /tests/to_json_to_pyobject.rs: -------------------------------------------------------------------------------- 1 | use maplit::*; 2 | use pyo3::prelude::*; 3 | use serde::Serialize; 4 | 5 | fn to_json_to_pyobject(py: Python<'_>, obj: T) -> PyResult> { 6 | let json = serde_json::to_string(&obj).unwrap(); 7 | let obj = py.import("json")?.getattr("loads")?.call1((json,))?; 8 | Ok(obj) 9 | } 10 | 11 | fn test(obj: impl Serialize) { 12 | Python::with_gil(|py| { 13 | let direct = serde_pyobject::to_pyobject(py, &obj).unwrap(); 14 | let by_json = to_json_to_pyobject(py, obj).unwrap(); 15 | assert!(dbg!(direct).eq(dbg!(by_json)).unwrap()); 16 | }) 17 | } 18 | 19 | #[test] 20 | fn primitive() { 21 | test(1_u8); 22 | test(-4_i32); 23 | test(-3.1); 24 | test(true); 25 | } 26 | 27 | #[test] 28 | fn string() { 29 | test("test"); 30 | test("test".to_string()); 31 | } 32 | 33 | #[test] 34 | fn option() { 35 | test(Some(10_u8)); 36 | test(None::); 37 | } 38 | 39 | // skip unit 40 | // 41 | // Input: `()` 42 | // Output: 43 | // - to_pyobject = `()` 44 | // - to_json_to_pyobject = `None` 45 | 46 | // skip unit_struct 47 | // 48 | // #[derive(Serialize)] 49 | // struct UnitStruct; 50 | // 51 | // Input: `UnitStruct` 52 | // - to_pyobject = `()` 53 | // - to_json_to_pyobject = `None` 54 | 55 | #[derive(Serialize)] 56 | enum UnitVariant { 57 | A, 58 | B, 59 | } 60 | 61 | #[test] 62 | fn unit_variant() { 63 | test(UnitVariant::A); 64 | test(UnitVariant::B); 65 | } 66 | 67 | #[derive(Serialize)] 68 | struct NewtypeStruct(u8); 69 | 70 | #[test] 71 | fn newtype_struct() { 72 | test(NewtypeStruct(10)); 73 | } 74 | 75 | #[derive(Serialize)] 76 | enum NewtypeVariant { 77 | N(u8), 78 | } 79 | 80 | #[test] 81 | fn newtype_variant() { 82 | test(NewtypeVariant::N(10)); 83 | } 84 | 85 | #[test] 86 | fn seq() { 87 | test(vec![1_u8, 2, 3]); 88 | } 89 | 90 | // Skip tuple 91 | // 92 | // Input: `(1, "test")` 93 | // Output: 94 | // - to_pyobject = `(1, "test")` 95 | // - to_json_to_pyobject = `[1, "test"]` 96 | 97 | // Skip tuple_struct 98 | // 99 | // #[derive(Serialize)] 100 | // struct TupleStruct(u8, u8, u8); 101 | // 102 | // Input: `TupleStruct(1, 2, 3)` 103 | // Output: 104 | // - to_pyobject = `(1, 2, 3)` 105 | // - to_json_to_pyobject = `[1, 2, 3]` 106 | 107 | // Skip tuple_variant 108 | // 109 | // #[derive(Serialize)] 110 | // enum TupleVariant { 111 | // T(u8, u8), 112 | // } 113 | // 114 | // Input: `TupleVariant::T(1, 2)` 115 | // Output: 116 | // - to_pyobject = {'T': (1, 2)} 117 | // - to_json_to_pyobject = {'T': [1, 2]} 118 | 119 | #[test] 120 | fn map() { 121 | test(hashmap! { 122 | "a".to_owned() => 1_u8, 123 | "b".to_owned() => 2, 124 | "c".to_owned() => 3, 125 | }); 126 | } 127 | 128 | #[derive(Serialize)] 129 | struct A { 130 | a: i32, 131 | b: String, 132 | } 133 | 134 | #[test] 135 | fn struct_() { 136 | test(A { 137 | a: 10, 138 | b: "hello".to_owned(), 139 | }); 140 | } 141 | 142 | #[derive(Serialize)] 143 | enum StructVariant { 144 | S { r: u8, g: u8, b: u8 }, 145 | } 146 | 147 | #[test] 148 | fn struct_variant() { 149 | test(StructVariant::S { 150 | r: 10, 151 | g: 20, 152 | b: 30, 153 | }); 154 | } 155 | --------------------------------------------------------------------------------