Skip to content

Commit 79174a0

Browse files
committed
serializing (pickling in Python) of cif.Document and cif.Block (#258)
1 parent 6f1d54f commit 79174a0

File tree

4 files changed

+50
-13
lines changed

4 files changed

+50
-13
lines changed

include/gemmi/cifdoc.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ struct Item {
522522
Block frame;
523523
};
524524

525+
Item() : type(ItemType::Erased) {}
525526
explicit Item(LoopArg)
526527
: type{ItemType::Loop}, loop{} {}
527528
explicit Item(std::string&& t)

include/gemmi/serialize.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define GEMMI_SERIALIZE_HPP_
99

1010
#include "model.hpp"
11+
#include "cifdoc.hpp"
1112

1213
#define SERIALIZE(Struct, ...) \
1314
template <typename Archive> \
@@ -144,6 +145,38 @@ SERIALIZE(Structure, o.name, o.cell, o.spacegroup_hm, o.models,
144145
o.input_format, o.has_d_fraction, o.ter_status,
145146
o.has_origx, o.origx, o.info, o.shortened_ccd_codes,
146147
o.raw_remarks, o.resolution)
148+
149+
150+
namespace cif {
151+
152+
SERIALIZE(Loop, o.tags, o.values)
153+
SERIALIZE(Block, o.name, o.items)
154+
SERIALIZE(Document, o.source, o.blocks)
155+
156+
template <typename Archive>
157+
void serialize(Archive& archive, Item& o) {
158+
archive(o.type, o.line_number);
159+
switch (o.type) {
160+
case ItemType::Pair:
161+
case ItemType::Comment: new(&o.pair) cif::Pair; archive(o.pair); break;
162+
case ItemType::Loop: new(&o.loop) cif::Loop; archive(o.loop); break;
163+
case ItemType::Frame: new(&o.frame) cif::Block; archive(o.frame); break;
164+
case ItemType::Erased: break;
165+
}
166+
}
167+
template <typename Archive>
168+
void serialize(Archive& archive, const Item& o) {
169+
archive(o.type, o.line_number);
170+
switch (o.type) {
171+
case ItemType::Pair:
172+
case ItemType::Comment: archive(o.pair); break;
173+
case ItemType::Loop: archive(o.loop); break;
174+
case ItemType::Frame: archive(o.frame); break;
175+
case ItemType::Erased: break;
176+
}
177+
}
178+
179+
} // namespace cif
147180
} // namespace gemmi
148181

149182
#undef SERIALIZE

python/cif.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "gemmi/read_cif.hpp" // for read_cif_gz
1010

1111
#include "common.h"
12+
#include "serial.h" // for getstate, setstate
1213
#include "make_iterator.h"
1314
#include <nanobind/stl/string.h>
1415
#include <nanobind/stl/vector.h>
@@ -168,6 +169,8 @@ void add_cif(nb::module_& cif) {
168169
return os.str();
169170
}, nb::arg("mmjson")=false, nb::arg("lowercase_names")=true,
170171
"Returns JSON representation in a string.")
172+
.def("__getstate__", &getstate<Document>)
173+
.def("__setstate__", &setstate<Document>)
171174
.def("__repr__", [](const Document &d) {
172175
std::string s = "<gemmi.cif.Document with ";
173176
s += std::to_string(d.blocks.size());
@@ -297,6 +300,8 @@ void add_cif(nb::module_& cif) {
297300
write_cif_block_to_stream(os, self, opt);
298301
return os.str();
299302
}, nb::arg("options")=WriteOptions(), "Returns a string in CIF format.")
303+
.def("__getstate__", &getstate<Block>)
304+
.def("__setstate__", &setstate<Block>)
300305
.def("__repr__", [](const Block &self) {
301306
return gemmi::cat("<gemmi.cif.Block ", self.name, '>');
302307
});

tests/test_cif.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import gc
44
import os
5+
import pickle
56
import unittest
67
from gemmi import cif
78

89
class TestDoc(unittest.TestCase):
9-
def test_slice(self):
10+
def test_slicing_and_pickling(self):
1011
doc = cif.read_string("""
1112
data_a
1213
_one 1 _two 2 _three 3
@@ -15,23 +16,16 @@ def test_slice(self):
1516
data_c
1617
_two 2 _four 4 _six 6
1718
""")
19+
self.assertTrue('a' in doc)
20+
self.assertFalse('d' in doc)
1821
self.assertEqual([b.name for b in doc[:1]], ['a'])
1922
self.assertEqual([b.name for b in doc[1:]], ['b', 'c'])
2023
self.assertEqual([b.name for b in doc[:]], ['a', 'b', 'c'])
2124
self.assertEqual([b.name for b in doc[1:-1]], ['b'])
2225
self.assertEqual([b.name for b in doc[1:1]], [])
2326

24-
def test_contains(self):
25-
doc = cif.read_string("""
26-
data_a
27-
_one 1 _two 2 _three 3
28-
data_b
29-
_four 4
30-
data_c
31-
_two 2 _four 4 _six 6
32-
""")
33-
self.assertEqual('a' in doc, True)
34-
self.assertEqual('d' in doc, False)
27+
unpickled = pickle.loads(pickle.dumps(doc))
28+
self.assertEqual(unpickled.as_string(), doc.as_string())
3529

3630
class TestBlock(unittest.TestCase):
3731
def test_find(self):
@@ -358,7 +352,11 @@ def test_case_sensitivity(self):
358352
_One 1 _thrEE 3
359353
_NonLoop_a alpha
360354
loop_ _lbBb _ln B 1 D 2"""
361-
self.assertEqual(block.as_string().split(), expected.split())
355+
block_str = block.as_string()
356+
self.assertEqual(block_str.split(), expected.split())
357+
358+
unpickled = pickle.loads(pickle.dumps(block))
359+
self.assertEqual(unpickled.as_string(), block_str)
362360

363361
class TestQuote(unittest.TestCase):
364362
def test_quote(self):

0 commit comments

Comments
 (0)