implement Serialize, Deserialize for Py<T>
This commit is contained in:
parent
b22ceb94dc
commit
abb5829e9c
|
@ -91,22 +91,22 @@ jobs:
|
|||
run: cargo build --no-default-features --verbose --target ${{ matrix.platform.rust-target }}
|
||||
|
||||
- name: Build (all additive features)
|
||||
run: cargo build --no-default-features --features "macros num-bigint num-complex hashbrown" --verbose --target ${{ matrix.platform.rust-target }}
|
||||
run: cargo build --no-default-features --features "macros num-bigint num-complex hashbrown serde" --verbose --target ${{ matrix.platform.rust-target }}
|
||||
|
||||
# Run tests (except on PyPy, because no embedding API).
|
||||
- if: matrix.python-version != 'pypy-3.6'
|
||||
name: Test
|
||||
run: cargo test --no-default-features --features "macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
|
||||
run: cargo test --no-default-features --features "macros num-bigint num-complex hashbrown serde" --target ${{ matrix.platform.rust-target }}
|
||||
|
||||
# Run tests again, but in abi3 mode
|
||||
- if: matrix.python-version != 'pypy-3.6'
|
||||
name: Test (abi3)
|
||||
run: cargo test --no-default-features --features "abi3 macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
|
||||
run: cargo test --no-default-features --features "abi3 macros num-bigint num-complex hashbrown serde" --target ${{ matrix.platform.rust-target }}
|
||||
|
||||
# Run tests again, for abi3-py36 (the minimal Python version)
|
||||
- if: (matrix.python-version != 'pypy-3.6') && (matrix.python-version != '3.6')
|
||||
name: Test (abi3-py36)
|
||||
run: cargo test --no-default-features --features "abi3-py36 macros num-bigint num-complex hashbrown" --target ${{ matrix.platform.rust-target }}
|
||||
run: cargo test --no-default-features --features "abi3-py36 macros num-bigint num-complex hashbrown serde" --target ${{ matrix.platform.rust-target }}
|
||||
|
||||
- name: Test proc-macro code
|
||||
run: cargo test --manifest-path=pyo3-macros-backend/Cargo.toml --target ${{ matrix.platform.rust-target }}
|
||||
|
@ -143,7 +143,7 @@ jobs:
|
|||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --features "num-bigint num-complex hashbrown" --no-fail-fast
|
||||
args: --features "num-bigint num-complex hashbrown serde" --no-fail-fast
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests"
|
||||
|
|
|
@ -5,6 +5,10 @@ PyO3 versions, please see the [migration guide](https://pyo3.rs/master/migration
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Add `serde` feature to support `Serialize/Deserialize` for `Py<T>`. [#1366](https://github.com/PyO3/pyo3/pull/1366)
|
||||
|
||||
## [0.13.1] - 2021-01-10
|
||||
### Added
|
||||
- Add support for `#[pyclass(dict)]` and `#[pyclass(weakref)]` with the `abi3` feature on Python 3.9 and up. [#1342](https://github.com/PyO3/pyo3/pull/1342)
|
||||
|
|
|
@ -27,6 +27,7 @@ paste = { version = "1.0.3", optional = true }
|
|||
pyo3-macros = { path = "pyo3-macros", version = "=0.13.1", optional = true }
|
||||
unindent = { version = "0.1.4", optional = true }
|
||||
hashbrown = { version = "0.9", optional = true }
|
||||
serde = {version = "1.0", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
assert_approx_eq = "1.1.0"
|
||||
|
@ -35,6 +36,7 @@ rustversion = "1.0"
|
|||
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
|
||||
# features needed to run the PyO3 test suite
|
||||
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
|
||||
serde_json = "1.0.61"
|
||||
|
||||
[features]
|
||||
default = ["macros", "auto-initialize"]
|
||||
|
|
4
Makefile
4
Makefile
|
@ -12,8 +12,8 @@ fmt:
|
|||
|
||||
clippy:
|
||||
@touch src/lib.rs # Touching file to ensure that cargo clippy will re-check the project
|
||||
cargo clippy --features="num-bigint num-complex hashbrown" --tests -- -Dwarnings
|
||||
cargo clippy --features="abi3 num-bigint num-complex hashbrown" --tests -- -Dwarnings
|
||||
cargo clippy --features="num-bigint num-complex hashbrown serde" --tests -- -Dwarnings
|
||||
cargo clippy --features="abi3 num-bigint num-complex hashbrown serde" --tests -- -Dwarnings
|
||||
for example in examples/*; do cargo clippy --manifest-path $$example/Cargo.toml -- -Dwarnings || exit 1; done
|
||||
|
||||
lint: fmt clippy
|
||||
|
|
|
@ -62,3 +62,24 @@ These macros require a number of dependencies which may not be needed by users w
|
|||
The `nightly` feature needs the nightly Rust compiler. This allows PyO3 to use Rust's unstable specialization feature to apply the following optimizations:
|
||||
- `FromPyObject` for `Vec` and `[T;N]` can perform a `memcpy` when the object supports the Python buffer protocol.
|
||||
- `ToBorrowedObject` can skip a reference count increase when the provided object is a Python native type.
|
||||
|
||||
### `serde`
|
||||
|
||||
The `serde` feature enables (de)serialization of Py<T> objects via [serde](https://serde.rs/).
|
||||
This allows to use [`#[derive(Serialize, Deserialize)`](https://serde.rs/derive.html) on structs that hold references to `#[pyclass]` instances
|
||||
|
||||
```rust
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Permission {
|
||||
name: String
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct User {
|
||||
username: String,
|
||||
permissions: Vec<Py<Permission>>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -207,6 +207,9 @@ mod python;
|
|||
pub mod type_object;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
/// The proc macros, which are also part of the prelude.
|
||||
#[cfg(feature = "macros")]
|
||||
pub mod proc_macro {
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
use crate::type_object::PyBorrowFlagLayout;
|
||||
use crate::{Py, PyClass, PyClassInitializer, PyTypeInfo, Python};
|
||||
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
impl<T> Serialize for Py<T>
|
||||
where
|
||||
T: Serialize + PyClass,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Python::with_gil(|py| {
|
||||
self.try_borrow(py)
|
||||
.map_err(|e| ser::Error::custom(e.to_string()))?
|
||||
.serialize(serializer)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T> Deserialize<'de> for Py<T>
|
||||
where
|
||||
T: Into<PyClassInitializer<T>> + PyClass + Deserialize<'de>,
|
||||
<T as PyTypeInfo>::BaseLayout: PyBorrowFlagLayout<<T as PyTypeInfo>::BaseType>,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Py<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let deserialized = T::deserialize(deserializer)?;
|
||||
|
||||
Python::with_gil(|py| {
|
||||
Py::new(py, deserialized).map_err(|e| de::Error::custom(e.to_string()))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
#[cfg(feature = "serde")]
|
||||
mod test_serde {
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Group {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct User {
|
||||
username: String,
|
||||
group: Option<Py<Group>>,
|
||||
friends: Vec<Py<User>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize() {
|
||||
let friend1 = User {
|
||||
username: "friend 1".into(),
|
||||
group: None,
|
||||
friends: vec![],
|
||||
};
|
||||
let friend2 = User {
|
||||
username: "friend 2".into(),
|
||||
..friend1.clone()
|
||||
};
|
||||
|
||||
let user = Python::with_gil(|py| {
|
||||
let py_friend1 = Py::new(py, friend1).expect("failed to create friend 1");
|
||||
let py_friend2 = Py::new(py, friend2).expect("failed to create friend 2");
|
||||
|
||||
let friends = vec![py_friend1, py_friend2];
|
||||
let py_group = Py::new(
|
||||
py,
|
||||
Group {
|
||||
name: "group name".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
User {
|
||||
username: "danya".into(),
|
||||
group: Some(py_group),
|
||||
friends,
|
||||
}
|
||||
});
|
||||
|
||||
let serialized = serde_json::to_string(&user).expect("failed to serialize");
|
||||
assert_eq!(
|
||||
serialized,
|
||||
r#"{"username":"danya","group":{"name":"group name"},"friends":[{"username":"friend 1","group":null,"friends":[]},{"username":"friend 2","group":null,"friends":[]}]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize() {
|
||||
let serialized = r#"{"username": "danya", "friends":
|
||||
[{"username": "friend", "group": {"name": "danya's friends"}, "friends": []}]}"#;
|
||||
let user: User = serde_json::from_str(serialized).expect("failed to deserialize");
|
||||
|
||||
assert_eq!(user.username, "danya");
|
||||
assert_eq!(user.group, None);
|
||||
assert_eq!(user.friends.len(), 1usize);
|
||||
let friend = user.friends.get(0).unwrap();
|
||||
|
||||
Python::with_gil(|py| {
|
||||
assert_eq!(friend.borrow(py).username, "friend");
|
||||
assert_eq!(
|
||||
friend.borrow(py).group.as_ref().unwrap().borrow(py).name,
|
||||
"danya's friends"
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue