]> code.communitydata.science - rises_declines_wikia_code.git/blob - mediawiki_dump_tools/Mediawiki-Utilities/mw/api/collections/recent_changes.py
Initial commit
[rises_declines_wikia_code.git] / mediawiki_dump_tools / Mediawiki-Utilities / mw / api / collections / recent_changes.py
1 import logging
2 import re
3
4 from ...util import none_or
5 from ..errors import MalformedResponse
6 from .collection import Collection
7
8 logger = logging.getLogger("mw.api.collections.recent_changes")
9
10
11 class RecentChanges(Collection):
12     """
13     Recent changes (revisions, page creations, registrations, moves, etc.)
14     """
15
16     RCCONTINUE = re.compile(r"([0-9]{4}-[0-9]{2}-[0-9]{2}T" +
17                             r"[0-9]{2}:[0-9]{2}:[0-9]{2}Z|" +
18                             r"[0-9]{14})" +
19                             r"\|[0-9]+")
20
21     PROPERTIES = {'user', 'userid', 'comment', 'timestamp', 'title',
22                   'ids', 'sizes', 'redirect', 'flags', 'loginfo',
23                   'tags', 'sha1'}
24
25     SHOW = {'minor', '!minor', 'bot', '!bot', 'anon', '!anon',
26             'redirect', '!redirect', 'patrolled', '!patrolled'}
27     
28     TYPES = {'edit', 'external', 'new', 'log'}
29     
30     DIRECTIONS = {'newer', 'older'}
31
32     MAX_CHANGES = 50
33
34     def _check_rccontinue(self, rccontinue):
35         if rccontinue is None:
36             return None
37         elif self.RCCONTINUE.match(rccontinue):
38             return rccontinue
39         else:
40             raise TypeError(
41                 "rccontinue {0} is not formatted correctly ".format(rccontinue) +
42                 "'%Y-%m-%dT%H:%M:%SZ|<last_rcid>'"
43             )
44
45     def query(self, *args, limit=None, **kwargs):
46         """
47         Enumerate recent changes.
48         See `<https://www.mediawiki.org/wiki/API:Recentchanges>`_
49
50         :Parameters:
51             start : :class:`mw.Timestamp`
52                 The timestamp to start enumerating from
53             end : :class:`mw.Timestamp`
54                 The timestamp to end enumerating
55             direction :
56                 "newer" or "older"
57             namespace : int
58                 Filter log entries to only this namespace(s)
59             user : str
60                 Only list changes by this user
61             excludeuser : str
62                 Don't list changes by this user
63             tag : str
64                 Only list changes tagged with this tag
65             properties : set(str)
66                 Include additional pieces of information
67
68                 * user           - Adds the user responsible for the edit and tags if they are an IP
69                 * userid         - Adds the user id responsible for the edit
70                 * comment        - Adds the comment for the edit
71                 * parsedcomment  - Adds the parsed comment for the edit
72                 * flags          - Adds flags for the edit
73                 * timestamp      - Adds timestamp of the edit
74                 * title          - Adds the page title of the edit
75                 * ids            - Adds the page ID, recent changes ID and the new and old revision ID
76                 * sizes          - Adds the new and old page length in bytes
77                 * redirect       - Tags edit if page is a redirect
78                 * patrolled      - Tags patrollable edits as being patrolled or unpatrolled
79                 * loginfo        - Adds log information (logid, logtype, etc) to log entries
80                 * tags           - Lists tags for the entry
81                 * sha1           - Adds the content checksum for entries associated with a revision
82
83             token : set(str)
84                 Which tokens to obtain for each change
85
86                 * patrol
87
88             show : set(str)
89                 Show only items that meet this criteria. For example, to see
90                 only minor edits done by logged-in users, set
91                 show={'minor', '!anon'}.
92
93                 * minor
94                 * !minor
95                 * bot
96                 * !bot
97                 * anon
98                 * !anon
99                 * redirect
100                 * !redirect
101                 * patrolled
102                 * !patrolled
103                 * unpatrolled
104             limit : int
105                 How many total changes to return
106             type : set(str)
107                 Which types of changes to show
108
109                 * edit
110                 * external
111                 * new
112                 * log
113
114             toponly : bool
115                 Only list changes which are the latest revision
116             rccontinue : str
117                 Use this to continue loading results from where you last left off
118         """
119         limit = none_or(limit, int)
120
121         changes_yielded = 0
122         done = False
123         while not done:
124
125             if limit is None:
126                 kwargs['limit'] = self.MAX_CHANGES
127             else:
128                 kwargs['limit'] = min(limit - changes_yielded, self.MAX_CHANGES)
129
130             rc_docs, rccontinue = self._query(*args, **kwargs)
131
132             for doc in rc_docs:
133                 yield doc
134                 changes_yielded += 1
135
136                 if limit is not None and changes_yielded >= limit:
137                     done = True
138                     break
139
140             if rccontinue is not None and len(rc_docs) > 0:
141
142                 kwargs['rccontinue'] = rccontinue
143             else:
144                 done = True
145
146     def _query(self, start=None, end=None, direction=None, namespace=None,
147                user=None, excludeuser=None, tag=None, properties=None,
148                token=None, show=None, limit=None, type=None,
149                toponly=None, rccontinue=None):
150
151         params = {
152             'action': "query",
153             'list': "recentchanges"
154         }
155
156         params['rcstart'] = none_or(start, str)
157         params['rcend'] = none_or(end, str)
158
159         assert direction in {None} | self.DIRECTIONS, \
160             "Direction must be one of {0}".format(self.DIRECTIONS)
161
162         params['rcdir'] = direction
163         params['rcnamespace'] = none_or(namespace, int)
164         params['rcuser'] = none_or(user, str)
165         params['rcexcludeuser'] = none_or(excludeuser, str)
166         params['rctag'] = none_or(tag, str)
167         params['rcprop'] = self._items(properties, levels=self.PROPERTIES)
168         params['rctoken'] = none_or(tag, str)
169         params['rcshow'] = self._items(show, levels=self.SHOW)
170         params['rclimit'] = none_or(limit, int)
171         params['rctype'] = self._items(type, self.TYPES)
172         params['rctoponly'] = none_or(toponly, bool)
173         params['rccontinue'] = self._check_rccontinue(rccontinue)
174
175         doc = self.session.get(params)
176
177         try:
178             rc_docs = doc['query']['recentchanges']
179
180             if 'query-continue' in doc:
181                 rccontinue = \
182                         doc['query-continue']['recentchanges']['rccontinue']
183             elif len(rc_docs) > 0:
184                 rccontinue = "|".join([rc_docs[-1]['timestamp'],
185                                        str(rc_docs[-1]['rcid'] + 1)])
186             else:
187                 pass  # Leave it be
188
189         except KeyError as e:
190             raise MalformedResponse(str(e), doc)
191
192         return rc_docs, rccontinue

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