2 from collections import namedtuple
4 from ...util import Heap
5 from ...types import Timestamp
7 from .event import Event, unpack_events
10 logger = logging.getLogger("mw.lib.sessions.cache")
12 Session = namedtuple("Session", ["user", "events"])
14 Represents a user session (a cluster over events for a user). This class
15 behaves like :class:`collections.namedtuple`. Note that the datatypes of
16 `events`, is not specified since those types will depend on the revision data
17 provided during revert detection.
21 A hashable user identifier : `hashable`
23 A list of event data : list( `mixed` )
29 A cache of recent user session. Since sessions expire once activities stop
30 for at least `cutoff` seconds, this class manages a cache of *active*
35 Maximum amount of time in seconds between session events
38 >>> from mw.lib import sessions
40 >>> cache = sessions.Cache(cutoff=3600)
42 >>> list(cache.process("Willy on wheels", 100000, {'rev_id': 1}))
44 >>> list(cache.process("Walter", 100001, {'rev_id': 2}))
46 >>> list(cache.process("Willy on wheels", 100001, {'rev_id': 3}))
48 >>> list(cache.process("Walter", 100035, {'rev_id': 4}))
50 >>> list(cache.process("Willy on wheels", 103602, {'rev_id': 5}))
51 [Session(user='Willy on wheels', events=[{'rev_id': 1}, {'rev_id': 3}])]
52 >>> list(cache.get_active_sessions())
53 [Session(user='Walter', events=[{'rev_id': 2}, {'rev_id': 4}]), Session(user='Willy on wheels', events=[{'rev_id': 5}])]
58 def __init__(self, cutoff=defaults.CUTOFF):
59 self.cutoff = int(cutoff)
61 self.recently_active = Heap()
62 self.active_users = {}
64 def process(self, user, timestamp, data=None):
66 Processes a user event.
70 A hashable value to identify a user (`int` or `str` are OK)
71 timestamp : :class:`mw.Timestamp`
72 The timestamp of the event
77 A generator of :class:`~mw.lib.sessions.Session` expired after
78 processing the user event.
80 event = Event(user, Timestamp(timestamp), data)
82 for user, events in self._clear_expired(event.timestamp):
83 yield Session(user, unpack_events(events))
86 if event.user in self.active_users:
87 events = self.active_users[event.user]
90 self.active_users[event.user] = events
91 self.recently_active.push((event.timestamp, events))
95 def get_active_sessions(self):
97 Retrieves the active, unexpired sessions.
100 A generator of :class:`~mw.lib.sessions.Session`
103 for last_timestamp, events in self.recently_active:
104 yield Session(events[-1].user, unpack_events(events))
106 def _clear_expired(self, timestamp):
109 while (len(self.recently_active) > 0 and
110 timestamp - self.recently_active.peek()[0] >= self.cutoff):
112 _, events = self.recently_active.pop()
114 if timestamp - events[-1].timestamp >= self.cutoff:
115 del self.active_users[events[-1].user]
116 yield events[-1].user, events
118 self.recently_active.push((events[-1].timestamp, events))
121 return "%s(%s)".format(self.__class__.__name__, repr(self.cutoff))