added PKG-INFO file from upstream tarball
[python-simplemediawiki.debian] / simplemediawiki.py
1 # python-simplemediawiki - Extremely low-level wrapper to the MediaWiki API
2 # Copyright (C) 2010 Red Hat, Inc.
3 #
4 # This library is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU Lesser General Public License as published by the Free
6 # Software Foundation; either version 2.1 of the License, or (at your option)
7 # any later version.
8 #
9 # This library is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
12 # details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 """
18 simplemediawiki is an extremely low-level wrapper to the MediaWiki API. It
19 automatically handles cookies and gzip compression so that you can make basic
20 calls to the API in the easiest way possible. It also provides a few functions
21 to make day-to-day API access easier.
22
23 To use this module, instantiate a MediaWiki object, passing it the URL of
24 api.php for the wiki you want to work with. Calls go through MediaWiki.call().
25 A generic login wrapper as well as functions to determine limits and get a list
26 of namespaces are provided for your convenience.
27
28 >>> from simplemediawiki import MediaWiki
29 >>> wiki = MediaWiki('http://en.wikipedia.org/w/api.php')
30 >>> wiki.call({'action': 'query', 'prop': 'revisions', 'titles': 'Main Page'})
31 {u'query': {u'pages': {...}}}
32 """
33
34 import cookielib
35 import gzip
36 from iso8601 import iso8601
37 import json
38 from StringIO import StringIO
39 import urllib
40 import urllib2
41
42
43 class MediaWiki():
44     """
45     Class to represent a MediaWiki installation with an enabled API.
46
47     api_url: URL to api.php (usually similar to http://example.com/w/api.php)
48     """
49     _high_limits = None
50     _namespaces = None
51     _psuedo_namespaces = None
52     _mediawiki_version = None
53
54     def __init__(self, api_url, cookie_file=None, user_agent=DEFAULT_UA):
55         self._api_url = api_url
56         if cookie_file:
57             self._cj = cookielib.MozillaCookieJar(cookie_file)
58             try:
59                 self._cj.load()
60             except IOError:
61                 self._cj.save()
62                 self._cj.load()
63         else:
64             self._cj = cookielib.CookieJar()
65         self._opener = urllib2.build_opener(
66                 urllib2.HTTPCookieProcessor(self._cj)
67         )
68         self._opener.addheaders = [('User-agent', user_agent)]
69
70     def _fetch_http(self, url, params):
71         request = urllib2.Request(url, urllib.urlencode(params))
72         request.add_header('Accept-encoding', 'gzip')
73         response = self._opener.open(request)
74         if isinstance(self._cj, cookielib.MozillaCookieJar):
75             self._cj.save()
76         if response.headers.get('Content-Encoding') == 'gzip':
77             compressed = StringIO(response.read())
78             gzipper = gzip.GzipFile(fileobj=compressed)
79             data = gzipper.read()
80         else:
81             data = response.read()
82         return data
83
84     def call(self, params):
85         """
86         Make a call to the wiki. Returns a dictionary that represents the JSON
87         returned by the API.
88         """
89         params['format'] = 'json'
90         return json.loads(self._fetch_http(self._api_url, params))
91
92     def normalize_api_url(self):
93         """
94         This function checks the given URL for a correct API endpoint and
95         returns that URL, while also helpfully setting this object's API URL to
96         it. If it can't magically conjure an API endpoint, it returns False.
97         """
98         data, data_json = self._normalize_api_url_tester(self._api_url)
99         if data_json:
100             return self._api_url
101         else:
102             # if there's an index.php in the URL, we might find the API
103             if 'index.php' in self._api_url:
104                 test_api_url = self._api_url.split('index.php')[0] + 'api.php'
105                 print test_api_url
106                 test_data, test_data_json = \
107                         self._normalize_api_url_tester(test_api_url)
108                 print (test_data, test_data_json)
109                 if test_data_json:
110                     self._api_url = test_api_url
111                     return self._api_url
112             return False
113
114     def _normalize_api_url_tester(self, api_url):
115         data = self._fetch_http(api_url, {'action': 'query',
116                                           'meta': 'siteinfo',
117                                           'siprop': 'general',
118                                           'format': 'json'})
119         try:
120             data_json = json.loads(data)
121             # may as well set the version
122             try:
123                 version_string = data_json['query']['general']['generator']
124                 self._mediawiki_version = version_string.split(' ', 1)[1]
125             except KeyError:
126                 pass
127             return (data, data_json)
128         except ValueError:
129             return (data, None)
130
131     def login(self, user, passwd, token=None):
132         """
133         Convenience function for logging into the wiki. It should never be
134         necessary to provide a token argument; it is part of the login process
135         since MediaWiki 1.15.3 (see MediaWiki bug 23076).
136         """
137         data = {'action': 'login',
138                 'lgname': user,
139                 'lgpassword': passwd}
140         if token:
141             data['lgtoken'] = token
142         result = self.call(data)
143         if result['login']['result'] == 'Success':
144             self._high_limits = None
145             return True
146         elif result['login']['result'] == 'NeedToken' and not token:
147             return self.login(user, passwd, result['login']['token'])
148         else:
149             return False
150
151     def logout(self):
152         """
153         Conveinence function for logging out of the wiki.
154         """
155         data = {'action': 'logout'}
156         self.call(data)
157         self._high_limits = None
158         return True
159
160     def limits(self, low, high):
161         """
162         Convenience function for determining appropriate limits in the API. If
163         the logged in user has the "apihighlimits" right, it will return the
164         high argument; otherwise it will return the low argument.
165         """
166         if self._high_limits == None:
167             result = self.call({'action': 'query',
168                                 'meta': 'userinfo',
169                                 'uiprop': 'rights'})
170             self._high_limits = 'apihighlimits' in \
171                     result['query']['userinfo']['rights']
172         if self._high_limits:
173             return high
174         else:
175             return low
176
177     def namespaces(self, psuedo=True):
178         """
179         Fetches a list of namespaces for this wiki.
180         """
181         if self._namespaces == None:
182             result = self.call({'action': 'query',
183                                 'meta': 'siteinfo',
184                                 'siprop': 'namespaces'})
185             self._namespaces = {}
186             self._psuedo_namespaces = {}
187             for nsid in result['query']['namespaces']:
188                 if int(nsid) >= 0:
189                     self._namespaces[int(nsid)] = \
190                             result['query']['namespaces'][nsid]['*']
191                 else:
192                     self._psuedo_namespaces[int(nsid)] = \
193                             result['query']['namespaces'][nsid]['*']
194         if psuedo:
195             retval = {}
196             retval.update(self._namespaces)
197             retval.update(self._psuedo_namespaces)
198             return retval
199         else:
200             return self._namespaces
201
202     @staticmethod
203     def parse_date(date):
204         """
205         Converts dates provided by the MediaWiki API into datetime.datetime
206         objects.
207         """
208         return iso8601.parse_date(date)
209
210
211 __author__ = 'Ian Weller <ian@ianweller.org>'
212 __version__ = '1.0.2'
213 DEFAULT_UA = 'python-simplemediawiki/%s ' + \
214         '+https://github.com/ianweller/python-simplemediawiki' \
215         % __version__

Benjamin Mako Hill || Want to submit a patch?