[tor-commits] [sbws/master] chg: timestamps: Add module to manage datetime sequences

juga at torproject.org juga at torproject.org
Tue Apr 14 13:54:37 UTC 2020


commit 003337ee7d241e18637ad5d67099d06cb0b4e9e8
Author: juga0 <juga at riseup.net>
Date:   Sat Mar 14 18:27:44 2020 +0000

    chg: timestamps: Add module to manage datetime sequences
---
 sbws/util/timestamps.py            | 89 ++++++++++++++++++++++++++++++++++++++
 tests/unit/util/test_timestamps.py | 44 +++++++++++++++++++
 2 files changed, 133 insertions(+)

diff --git a/sbws/util/timestamps.py b/sbws/util/timestamps.py
new file mode 100644
index 0000000..393b30b
--- /dev/null
+++ b/sbws/util/timestamps.py
@@ -0,0 +1,89 @@
+"""Util classes to manipulate sequences of datetime timestamps.
+
+Optionally update also a state file.
+
+"""
+# Workarounds to store datetimes for objects because they are not compossed
+# by other objects nor stored in a database with a creation datetime.
+import collections
+from datetime import datetime, timedelta
+import logging
+
+from sbws.util.timestamp import is_old
+
+log = logging.getLogger(__name__)
+
+
+class DateTimeSeq(collections.deque):
+    """Store and manage a datetime sequence and optionally a state file."""
+
+    def __init__(self, iterable=[], maxlen=None, state=None, state_key=None):
+        self._maxlen = maxlen
+        self._items = collections.deque(iterable, maxlen)
+        self._state = state
+        self._state_key = state_key
+
+    def _remove_old(self):
+        self._items = collections.deque(
+            filter(lambda x: not is_old(x), self._items), maxlen=self._maxlen
+        )
+
+    def update(self, dt=None):
+        self._remove_old()
+        self._items.append(dt or datetime.utcnow().replace(microsecond=0))
+        if self._state is not None and self._state_key:
+            self._state[self._state_key] = list(self._items)
+        return list(self._items)
+
+    def last(self):
+        if len(self._items) > 0:
+            return self._items[-1]
+        return datetime.utcnow().replace(microsecond=0) - timedelta(hour=1)
+
+    def list(self):
+        return list(self._items)
+
+    def __len__(self):
+        return len(self._items)
+
+
+class DateTimeIntSeq(collections.deque):
+    """
+    Store and manage a sequence of lists composed of a datetime and an int.
+
+    Optionally store and manage an state file.
+    """
+
+    def __init__(self, iterable=[], maxlen=None, state=None, state_key=None):
+        self._maxlen = maxlen
+        self._items = collections.deque(iterable, maxlen)
+        self._state = state
+        self._state_key = state_key
+
+    def _remove_old(self):
+        self._items = collections.deque(
+            filter(lambda x: not is_old(x[0]), self._items),
+            maxlen=self._maxlen,
+        )
+
+    def update(self, dt=None, number=0):
+        self._remove_old()
+        # Because json serializes tuples to lists, use list instead of tuple
+        # to facilitate comparisons.
+        self._items.append(
+            [dt or datetime.utcnow().replace(microsecond=0), number]
+        )
+        if self._state is not None and self._state_key:
+            self._state[self._state_key] = list(self._items)
+        return list(self._items)
+
+    def last(self):
+        if len(self._items) > 0:
+            return self._items[-1]
+        return datetime.utcnow().replace(microsecond=0) - timedelta(hour=1)
+
+    def list(self):
+        return list(self._items)
+
+    def __len__(self):
+        return sum(map(lambda x: x[1], self._items))
diff --git a/tests/unit/util/test_timestamps.py b/tests/unit/util/test_timestamps.py
new file mode 100644
index 0000000..aef0501
--- /dev/null
+++ b/tests/unit/util/test_timestamps.py
@@ -0,0 +1,44 @@
+"""timestamps.py unit tests."""
+
+from datetime import datetime, timedelta
+
+from sbws.util.state import State
+from sbws.util.timestamps import (
+    DateTimeSeq,
+    DateTimeIntSeq,
+)
+
+
+def test_update_datetime_seq(conf):
+    now = datetime.utcnow().replace(microsecond=0)
+    state = State(conf["paths"]["state_fpath"])
+    # Create a list of 6 datetimes that started 6 days in the past.
+    dts = [now - timedelta(days=x) for x in range(6, 0, -1)]
+    dt_seq = DateTimeSeq(
+        dts, state=state, state_key="recent_measurement_attempt"
+    )
+    new_dts = dt_seq.update()
+    # The updated list will not contain the 2 first (left) datetimes and it
+    # will have one last timestamp (right).
+    assert new_dts[:-1] == dts[2:]
+    assert 5 == state.count("recent_measurement_attempt")
+    assert 5 == len(dt_seq)
+
+
+def test_update_datetime_int_seq(conf):
+    now = datetime.utcnow().replace(microsecond=0)
+    state = State(conf["paths"]["state_fpath"])
+    # Create a list of 6 datetimes that started 6 days in the past.
+    dts = [[now - timedelta(days=x), 2] for x in range(6, 0, -1)]
+    dt_seq = DateTimeIntSeq(
+        dts, state=state, state_key="recent_measurement_attempt"
+    )
+    new_dts = dt_seq.update()
+    # The updated list will not contain the 2 first (left) tuples and it
+    # will have one last tuple (right).
+    # The last tuple have 0 as the integer, instead of 2, so the count will be
+    # 2 * 4 = 8
+    assert new_dts[:-1] == dts[2:]
+    assert 8 == state.count("recent_measurement_attempt")
+    # And `len` should return the same.
+    assert 8 == len(dt_seq)





More information about the tor-commits mailing list