]> code.communitydata.science - rises_declines_wikia_code.git/blob - mediawiki_dump_tools/Mediawiki-Utilities/mw/lib/sessions/cache.py
add copy of the GPL
[rises_declines_wikia_code.git] / mediawiki_dump_tools / Mediawiki-Utilities / mw / lib / sessions / cache.py
1 import logging
2 from collections import namedtuple
3
4 from ...util import Heap
5 from ...types import Timestamp
6 from . import defaults
7 from .event import Event, unpack_events
8
9
10 logger = logging.getLogger("mw.lib.sessions.cache")
11
12 Session = namedtuple("Session", ["user", "events"])
13 """
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.
18
19 :Members:
20     **user**
21         A hashable user identifier : `hashable`
22     **events**
23         A list of event data : list( `mixed` )
24 """
25
26
27 class Cache:
28     """
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*
31     sessions.
32
33     :Parameters:
34         cutoff : int
35             Maximum amount of time in seconds between session events
36
37     :Example:
38         >>> from mw.lib import sessions
39         >>>
40         >>> cache = sessions.Cache(cutoff=3600)
41         >>>
42         >>> list(cache.process("Willy on wheels", 100000, {'rev_id': 1}))
43         []
44         >>> list(cache.process("Walter", 100001, {'rev_id': 2}))
45         []
46         >>> list(cache.process("Willy on wheels", 100001, {'rev_id': 3}))
47         []
48         >>> list(cache.process("Walter", 100035, {'rev_id': 4}))
49         []
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}])]
54
55
56     """
57
58     def __init__(self, cutoff=defaults.CUTOFF):
59         self.cutoff = int(cutoff)
60
61         self.recently_active = Heap()
62         self.active_users = {}
63
64     def process(self, user, timestamp, data=None):
65         """
66         Processes a user event.
67
68         :Parameters:
69             user : `hashable`
70                 A hashable value to identify a user (`int` or `str` are OK)
71             timestamp : :class:`mw.Timestamp`
72                 The timestamp of the event
73             data : `mixed`
74                 Event meta data
75
76         :Returns:
77             A generator of :class:`~mw.lib.sessions.Session` expired after
78             processing the user event.
79         """
80         event = Event(user, Timestamp(timestamp), data)
81
82         for user, events in self._clear_expired(event.timestamp):
83             yield Session(user, unpack_events(events))
84
85         # Apply revision
86         if event.user in self.active_users:
87             events = self.active_users[event.user]
88         else:
89             events = []
90             self.active_users[event.user] = events
91             self.recently_active.push((event.timestamp, events))
92
93         events.append(event)
94
95     def get_active_sessions(self):
96         """
97         Retrieves the active, unexpired sessions.
98
99         :Returns:
100             A generator of :class:`~mw.lib.sessions.Session`
101
102         """
103         for last_timestamp, events in self.recently_active:
104             yield Session(events[-1].user, unpack_events(events))
105
106     def _clear_expired(self, timestamp):
107
108         # Cull old sessions
109         while (len(self.recently_active) > 0 and
110                timestamp - self.recently_active.peek()[0] >= self.cutoff):
111
112             _, events = self.recently_active.pop()
113
114             if timestamp - events[-1].timestamp >= self.cutoff:
115                 del self.active_users[events[-1].user]
116                 yield events[-1].user, events
117             else:
118                 self.recently_active.push((events[-1].timestamp, events))
119
120     def __repr__(self):
121         return "%s(%s)".format(self.__class__.__name__, repr(self.cutoff))

Community Data Science Collective || Want to submit a patch?