1/*
2    tests/test_pickling.cpp -- pickle support
3
4    Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
5
6    All rights reserved. Use of this source code is governed by a
7    BSD-style license that can be found in the LICENSE file.
8*/
9
10#include "pybind11_tests.h"
11
12TEST_SUBMODULE(pickling, m) {
13    // test_roundtrip
14    class Pickleable {
15    public:
16        Pickleable(const std::string &value) : m_value(value) { }
17        const std::string &value() const { return m_value; }
18
19        void setExtra1(int extra1) { m_extra1 = extra1; }
20        void setExtra2(int extra2) { m_extra2 = extra2; }
21        int extra1() const { return m_extra1; }
22        int extra2() const { return m_extra2; }
23    private:
24        std::string m_value;
25        int m_extra1 = 0;
26        int m_extra2 = 0;
27    };
28
29    class PickleableNew : public Pickleable {
30    public:
31        using Pickleable::Pickleable;
32    };
33
34    py::class_<Pickleable>(m, "Pickleable")
35        .def(py::init<std::string>())
36        .def("value", &Pickleable::value)
37        .def("extra1", &Pickleable::extra1)
38        .def("extra2", &Pickleable::extra2)
39        .def("setExtra1", &Pickleable::setExtra1)
40        .def("setExtra2", &Pickleable::setExtra2)
41        // For details on the methods below, refer to
42        // http://docs.python.org/3/library/pickle.html#pickling-class-instances
43        .def("__getstate__", [](const Pickleable &p) {
44            /* Return a tuple that fully encodes the state of the object */
45            return py::make_tuple(p.value(), p.extra1(), p.extra2());
46        })
47        .def("__setstate__", [](Pickleable &p, py::tuple t) {
48            if (t.size() != 3)
49                throw std::runtime_error("Invalid state!");
50            /* Invoke the constructor (need to use in-place version) */
51            new (&p) Pickleable(t[0].cast<std::string>());
52
53            /* Assign any additional state */
54            p.setExtra1(t[1].cast<int>());
55            p.setExtra2(t[2].cast<int>());
56        });
57
58    py::class_<PickleableNew, Pickleable>(m, "PickleableNew")
59        .def(py::init<std::string>())
60        .def(py::pickle(
61            [](const PickleableNew &p) {
62                return py::make_tuple(p.value(), p.extra1(), p.extra2());
63            },
64            [](py::tuple t) {
65                if (t.size() != 3)
66                    throw std::runtime_error("Invalid state!");
67                auto p = PickleableNew(t[0].cast<std::string>());
68
69                p.setExtra1(t[1].cast<int>());
70                p.setExtra2(t[2].cast<int>());
71                return p;
72            }
73        ));
74
75#if !defined(PYPY_VERSION)
76    // test_roundtrip_with_dict
77    class PickleableWithDict {
78    public:
79        PickleableWithDict(const std::string &value) : value(value) { }
80
81        std::string value;
82        int extra;
83    };
84
85    class PickleableWithDictNew : public PickleableWithDict {
86    public:
87        using PickleableWithDict::PickleableWithDict;
88    };
89
90    py::class_<PickleableWithDict>(m, "PickleableWithDict", py::dynamic_attr())
91        .def(py::init<std::string>())
92        .def_readwrite("value", &PickleableWithDict::value)
93        .def_readwrite("extra", &PickleableWithDict::extra)
94        .def("__getstate__", [](py::object self) {
95            /* Also include __dict__ in state */
96            return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__"));
97        })
98        .def("__setstate__", [](py::object self, py::tuple t) {
99            if (t.size() != 3)
100                throw std::runtime_error("Invalid state!");
101            /* Cast and construct */
102            auto& p = self.cast<PickleableWithDict&>();
103            new (&p) PickleableWithDict(t[0].cast<std::string>());
104
105            /* Assign C++ state */
106            p.extra = t[1].cast<int>();
107
108            /* Assign Python state */
109            self.attr("__dict__") = t[2];
110        });
111
112    py::class_<PickleableWithDictNew, PickleableWithDict>(m, "PickleableWithDictNew")
113        .def(py::init<std::string>())
114        .def(py::pickle(
115            [](py::object self) {
116                return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__"));
117            },
118            [](const py::tuple &t) {
119                if (t.size() != 3)
120                    throw std::runtime_error("Invalid state!");
121
122                auto cpp_state = PickleableWithDictNew(t[0].cast<std::string>());
123                cpp_state.extra = t[1].cast<int>();
124
125                auto py_state = t[2].cast<py::dict>();
126                return std::make_pair(cpp_state, py_state);
127            }
128        ));
129#endif
130}
131