util: provide an `empty_series` helper

This will help make empty series with the right dtype,
name, and especially index type.
3 files changed, 45 insertions(+), 16 deletions(-)

M test/test_tsio.py
M tshistory/tsio.py
M tshistory/util.py
M test/test_tsio.py +5 -4
@@ 10,6 10,7 @@ from tshistory.snapshot import Snapshot
 from tshistory.util import (
     _set_cache,
     diff,
+    empty_series,
     pack_history,
     threadpool,
     unpack_history

          
@@ 441,7 442,7 @@ def test_update_with_nothing(engine, tsh
     diff = tsh.update(engine, series, 'ts-up-nothing', 'babar')
     assert len(diff) == 3
 
-    diff = tsh.update(engine, pd.Series(dtype='float64'), 'ts-up-nothing', 'babar')
+    diff = tsh.update(engine, empty_series(False), 'ts-up-nothing', 'babar')
     assert len(diff) == 0
 
     diff = tsh.update(engine, series, 'ts-up-nothing', 'babar')

          
@@ 1684,7 1685,7 @@ 2018-01-03 00:00:00+00:00    3.0
 
 
 def test_null_serie(engine, tsh):
-    ts = pd.Series(dtype='float64')
+    ts = empty_series(False)
 
     tsh.update(engine, ts, 'null', 'Babar')
 

          
@@ 1763,7 1764,7 @@ def test_insert_errors(engine, tsh):
                    index=pd.date_range(start=utcdt(2018, 1, 1),
                                        freq='D', periods=3))
 
-    with pytest.raises(TypeError):
+    with pytest.raises(AssertionError):
         tsh.update(engine, 42, 'error', 'Babar')
 
     with pytest.raises(AssertionError):

          
@@ 1865,7 1866,7 @@ 2019-01-03 00:00:00+00:00  2020-01-02 00
     assert ival.right == pd.Timestamp('2020-01-02 00:00:00+0000', tz='UTC')
 
     d = tsh.replace(
-        engine, pd.Series(dtype='float64'), 'replaceme', 'Arthur',
+        engine, empty_series(True), 'replaceme', 'Arthur',
         insertion_date=utcdt(2019, 1, 3)
     )
     assert len(d) == 0

          
M tshistory/tsio.py +27 -12
@@ 15,6 15,7 @@ from tshistory.util import (
     closed_overlaps,
     compatible_date,
     diff,
+    empty_series,
     num2float,
     patch,
     pruned_history,

          
@@ 68,13 69,15 @@ class timeseries:
         author: str free-form author name
         metadata: optional dict for changeset metadata
         """
-        if not len(updatets):
-            return pd.Series(dtype=updatets.dtype)
-
         updatets = self._guard_insert(
             updatets, name, author, metadata,
             insertion_date
         )
+        if not len(updatets):
+            return empty_series(
+                updatets.index.tz is not None,
+                dtype=updatets.dtype
+            )
 
         assert ('<M8[ns]' == updatets.index.dtype or
                 'datetime' in str(updatets.index.dtype) and not

          
@@ 104,14 107,17 @@ class timeseries:
         author: str free-form author name
         metadata: optional dict for changeset metadata
         """
+        # nans have no replacement semantics -> drop them
         newts = newts.dropna()
-        if not len(newts):
-            return pd.Series(dtype=newts.dtype)
-
         newts = self._guard_insert(
             newts, name, author, metadata,
             insertion_date
         )
+        if not len(newts):
+            return empty_series(
+                newts.index.tz is not None,
+                dtype=newts.dtype
+            )
 
         assert ('<M8[ns]' == newts.index.dtype or
                 'datetime' in str(newts.index.dtype) and not

          
@@ 200,7 206,11 @@ class timeseries:
 
         if current is None:
             meta = self.metadata(cn, name)
-            return pd.Series(name=name, dtype=meta['value_type'])
+            return empty_series(
+                meta['tzaware'],
+                dtype=meta['value_type'],
+                name=name
+            )
 
         if not _keep_nans:
             current = current.dropna()

          
@@ 354,7 364,8 @@ class timeseries:
             _keep_nans=True
         )
         if not len(base):
-            return pd.Series(name=name, dtype='float64')
+            meta = self.metadata(cn, name)
+            return empty_series(meta['tzaware'], name=name)
 
         # prepare the needed revision dates
         fromidate = base.index.min() - delta

          
@@ 607,7 618,11 @@ class timeseries:
         if not len(series_diff):
             L.info('no difference in %s by %s (for ts of size %s)',
                    name, author, len(newts))
-            return pd.Series(dtype=newts.dtype)
+            meta = self.metadata(cn, name)
+            return empty_series(
+                meta['tzaware'],
+                name=name
+            )
 
         # compute series start/end stamps
         tsstart, tsend = start_end(newts)

          
@@ 862,7 877,7 @@ class historycache:
             to_value_date=None):
 
         if not len(self.hist):
-            return pd.Series(name=self.name, dtype='float64')
+            return empty_series(self.tzaware, name=self.name)
 
         if revision_date is None:
             return list(self.hist.values())[-1].dropna()

          
@@ 873,7 888,7 @@ class historycache:
                 from_value_date:to_value_date
             ].dropna()
 
-        return pd.Series(name=self.name, dtype='float64')
+        return empty_series(self.tzaware, name=self.name)
 
     def staircase(self, delta,
                   from_value_date=None,

          
@@ 899,7 914,7 @@ class historycache:
             if ts is not None and len(ts):
                 chunks.append(ts)
 
-        ts = pd.Series(dtype='float64')
+        ts = empty_series(self.tzaware, name=self.name)
         if chunks:
             ts = pd.concat(chunks)
         ts.name = self.name

          
M tshistory/util.py +13 -0
@@ 34,6 34,18 @@ def logme(name, level=logging.DEBUG):
     return logger
 
 
+def empty_series(tzaware, dtype='float64', name=None):
+    return pd.Series(
+        [],
+        index=pd.DatetimeIndex(
+            [],
+            tz='UTC' if tzaware else None
+        ),
+        dtype=dtype,
+        name=name
+    )
+
+
 @contextmanager
 def tempdir(suffix='', prefix='tmp'):
     tmp = tempfile.mkdtemp(suffix=suffix, prefix=prefix)

          
@@ 423,6 435,7 @@ def fromjson(jsonb, tsname, tzaware=Fals
 
 def _fromjson(jsonb, tsname):
     if jsonb == '{}':
+        # NOTE: weak (but json ...)
         return pd.Series(name=tsname)
 
     result = pd.read_json(jsonb, typ='series', dtype=False)