util: provide a pair of coding/decoding helpers for snapshot serialization
2 files changed, 63 insertions(+), 0 deletions(-)

M test/test_util.py
M tshistory/util.py
M test/test_util.py +27 -0
@@ 8,6 8,8 @@ from tshistory.util import (
     bisect_search,
     diff,
     fromjson,
+    make_snapshot_record,
+    unpack_snapshot_record,
     nary_pack,
     nary_unpack,
     pack_history,

          
@@ 27,6 29,31 @@ from tshistory.testutil import (
 )
 
 
+def test_make_snapshot_record():
+    rec = make_snapshot_record(
+        1,
+        utcdt(2020, 1, 1),
+        utcdt(2020, 1, 2),
+        0,
+        True,
+        42,
+        155
+    )
+    assert len(rec) == 28
+    assert isinstance(rec, bytearray)
+
+    rid, start, end, parent, packed, bstart, offset = unpack_snapshot_record(
+        bytes(rec)
+    )
+    assert rid == 2
+    assert start == utcdt(2020, 1, 1)
+    assert end == utcdt(2020, 1, 2)
+    assert parent == 0
+    assert packed
+    assert bstart == 42
+    assert offset == 155
+
+
 def test_patch():
     s1 = pd.Series(
         [1., 2., 3., 4.],

          
M tshistory/util.py +36 -0
@@ 13,6 13,7 @@ from functools import (
     partial,
     reduce
 )
+from datetime import datetime
 from contextlib import contextmanager
 from pathlib import Path
 from warnings import warn

          
@@ 386,6 387,8 @@ def unpack_history(bytestring):
     return metadata, hist
 
 
+# json serialisation
+
 def num2float(pdobj):
     # get a Series or a Dataframe column
     if str(pdobj.dtype).startswith('int'):

          
@@ 421,6 424,39 @@ def _fromjson(jsonb, tsname):
     return result
 
 
+# file binary serialisation
+
+def make_snapshot_record(lastid, start, end, parent, packed, bstart, offset):
+    # everything consumes 4 octets
+    buffer = bytearray(28)
+    newid = struct.pack_into('!I', buffer, 0, lastid + 1)
+    start = struct.pack_into('!f', buffer, 4, start.timestamp())
+    end = struct.pack_into('!f', buffer, 8, end.timestamp())
+    parent = struct.pack_into('!I', buffer, 12, parent)
+    packed = struct.pack_into('!?', buffer, 16, packed)
+    bstart = struct.pack_into('!I', buffer, 20, bstart)
+    offset = struct.pack_into('!h', buffer, 24, offset)
+    return buffer
+
+
+def unpack_snapshot_record(bytestr):
+    buffer = array('B', bytestr)
+    rid = struct.unpack_from('!I', buffer, 0)[0]
+    start = datetime.fromtimestamp(
+        struct.unpack_from('!f', buffer, 4)[0],
+        tz=pytz.utc
+    )
+    end = datetime.fromtimestamp(
+        struct.unpack_from('!f', buffer, 8)[0],
+        tz=pytz.utc
+    )
+    parent = struct.unpack_from('!I', buffer, 12)[0]
+    packed = struct.unpack_from('!?', buffer, 16)[0]
+    bstart = struct.unpack_from('!i', buffer, 20)[0]
+    offset = struct.unpack_from('!h', buffer, 24)[0]
+    return rid, start, end, parent, packed, bstart, offset
+
+
 # diff/patch utilities
 
 def _populate(index, values, outindex, outvalues):