1 # python-simplemediawiki - Extremely low-level wrapper to the MediaWiki API
2 # Copyright (C) 2010 Red Hat, Inc.
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)
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
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/>.
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.
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.
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': {...}}}
38 from StringIO import StringIO
42 __author__ = 'Ian Weller <ian@ianweller.org>'
44 DEFAULT_UA = ('python-simplemediawiki/%s '
45 '+https://github.com/ianweller/python-simplemediawiki') \
51 Class to represent a MediaWiki installation with an enabled API.
53 api_url: URL to api.php (usually similar to http://example.com/w/api.php)
57 _psuedo_namespaces = None
59 def __init__(self, api_url, cookie_file=None, user_agent=DEFAULT_UA):
60 self._api_url = api_url
62 self._cj = cookielib.MozillaCookieJar(cookie_file)
69 self._cj = cookielib.CookieJar()
70 self._opener = urllib2.build_opener(
71 urllib2.HTTPCookieProcessor(self._cj)
73 self._opener.addheaders = [('User-agent', user_agent)]
75 def _fetch_http(self, url, params):
77 Standard HTTP request handler for this class with gzip and cookie
80 request = urllib2.Request(url, urllib.urlencode(params))
81 request.add_header('Accept-encoding', 'gzip')
82 response = self._opener.open(request)
83 if isinstance(self._cj, cookielib.MozillaCookieJar):
85 if response.headers.get('Content-Encoding') == 'gzip':
86 compressed = StringIO(response.read())
87 gzipper = gzip.GzipFile(fileobj=compressed)
90 data = response.read()
93 def call(self, params):
95 Make a call to the wiki. Returns a dictionary that represents the JSON
98 params['format'] = 'json'
99 return json.loads(self._fetch_http(self._api_url, params))
101 def normalize_api_url(self):
103 This function checks the given URL for a correct API endpoint and
104 returns that URL, while also helpfully setting this object's API URL to
105 it. If it can't magically conjure an API endpoint, it returns False.
107 def tester(self, api_url):
109 Attempts to fetch general information about the MediaWiki instance
110 in order to test whether the given URL will return JSON.
112 data = self._fetch_http(api_url, {'action': 'query',
115 data_json = json.loads(data)
116 return (data, data_json)
120 data, data_json = tester(self, self._api_url)
124 # if there's an index.php in the URL, we might find the API
125 if 'index.php' in self._api_url:
126 test_api_url = self._api_url.split('index.php')[0] + 'api.php'
128 test_data, test_data_json = tester(self, test_api_url)
129 print (test_data, test_data_json)
131 self._api_url = test_api_url
136 def login(self, user, passwd, token=None):
138 Convenience function for logging into the wiki. It should never be
139 necessary to provide a token argument; it is part of the login process
140 since MediaWiki 1.15.3 (see MediaWiki bug 23076).
142 data = {'action': 'login',
144 'lgpassword': passwd}
146 data['lgtoken'] = token
147 result = self.call(data)
148 if result['login']['result'] == 'Success':
149 self._high_limits = None
151 elif result['login']['result'] == 'NeedToken' and not token:
152 return self.login(user, passwd, result['login']['token'])
158 Conveinence function for logging out of the wiki.
160 data = {'action': 'logout'}
162 self._high_limits = None
165 def limits(self, low, high):
167 Convenience function for determining appropriate limits in the API. If
168 the logged in user has the "apihighlimits" right, it will return the
169 high argument; otherwise it will return the low argument.
171 if self._high_limits == None:
172 result = self.call({'action': 'query',
175 self._high_limits = 'apihighlimits' in \
176 result['query']['userinfo']['rights']
177 if self._high_limits:
182 def namespaces(self, psuedo=True):
184 Fetches a list of namespaces for this wiki.
186 if self._namespaces == None:
187 result = self.call({'action': 'query',
189 'siprop': 'namespaces'})
190 self._namespaces = {}
191 self._psuedo_namespaces = {}
192 for nsid in result['query']['namespaces']:
194 self._namespaces[int(nsid)] = \
195 result['query']['namespaces'][nsid]['*']
197 self._psuedo_namespaces[int(nsid)] = \
198 result['query']['namespaces'][nsid]['*']
201 retval.update(self._namespaces)
202 retval.update(self._psuedo_namespaces)
205 return self._namespaces
208 def parse_date(date):
210 Converts dates provided by the MediaWiki API into datetime.datetime
213 return iso8601.parse_date(date)