Revision: 4914 Author: russblau Date: 2008-01-18 20:55:39 +0000 (Fri, 18 Jan 2008)
Log Message: ----------- First cut at a testable, documented api.py module. Only partially implemented.
Modified Paths: -------------- branches/rewrite/pywikibot/data/__init__.py branches/rewrite/pywikibot/data/api.py branches/rewrite/pywikibot/data/http.py branches/rewrite/pywikibot/tests/dummy.py
Added Paths: ----------- branches/rewrite/pywikibot/__init__.py branches/rewrite/pywikibot/tests/__init__.py branches/rewrite/pywikibot/tests/api_tests.py
Added: branches/rewrite/pywikibot/__init__.py ===================================================================
Modified: branches/rewrite/pywikibot/data/__init__.py =================================================================== --- branches/rewrite/pywikibot/data/__init__.py 2008-01-18 20:12:16 UTC (rev 4913) +++ branches/rewrite/pywikibot/data/__init__.py 2008-01-18 20:55:39 UTC (rev 4914) @@ -10,5 +10,4 @@ __version__ = '$Id: $'
#Import the classes exposed by this module: -from http import HTTP -from api import API +# TODO
Modified: branches/rewrite/pywikibot/data/api.py =================================================================== --- branches/rewrite/pywikibot/data/api.py 2008-01-18 20:12:16 UTC (rev 4913) +++ branches/rewrite/pywikibot/data/api.py 2008-01-18 20:55:39 UTC (rev 4914) @@ -10,47 +10,117 @@ __version__ = '$Id: $'
+from UserDict import DictMixin import urllib import http import simplejson as json import warnings
+ class APIError(Exception): """The wiki site returned an error message.""" - def __init__(self, errordict): + def __init__(self, code, info, **kwargs): """Save error dict returned by MW API.""" - self.errors = errordict - + self.code = code + self.info = info + self.other = kwargs + def __repr__(self): + return 'APIError("%(code)s", "%(info)s", %(other)s)' % self.__dict__ def __str__(self): - return "%(code)s: %(info)s" % self.errors + return "%(code)s: %(info)s" % self.__dict__
-class API: +class Request(DictMixin): + """A request to a Site's api.php interface.
- def __init__(self, site): - self.site = site + Attributes of this object get passed as commands to api.php, and can be + get or set using the dict interface. All attributes must be strings + (unicode). Attributes supplied without values are passed to the API as + keys. + + @param site: The Site to which the request will be submitted. If not + supplied, uses the user's configured default Site. + @param format: (optional) Defaults to "json"
- def request(self, **params): - if not params.has_key('format'): #Most probably, we want the JSON format - params['format'] = 'json' - return http.HTTP(None).POST('/w/api.php',params) #TODO: Use site's HTTP object instead + Example:
- def query(self, **params): - if not params.has_key('action'): - params['action'] = 'query' - return self.request(**params) + >>> r = Request(site=mysite, action="query", meta="userinfo") + >>> # This is equivalent to + >>> # http://%5Bpath%5D/api.php?action=query&meta=userinfo&format=json + >>> # change a parameter + >>> r['meta'] = "userinfo|siteinfo" + >>> # add a new parameter + >>> r['siprop'] = "namespaces" + >>> r.params + {'action': 'query', 'meta': 'userinfo|siteinfo', 'siprop': 'namespaces', + 'format': 'json'} + >>> data = r.submit() + >>> type(data) + <type 'dict'> + + """ + def __init__(self, *args, **kwargs): + if "site" in kwargs: + self.site = kwargs["site"] + del kwargs["site"] + # else use defaultSite() ... when written + self.params = {} + if not "format" in kwargs: + self.params["format"] = "json" + self.update(*args, **kwargs)
- def query_response(self, **params): - """Submit a query and parse the response, returning a dict or None.""" - if params.has_key('format') and params['format'] != 'json': - raise TypeError("Query format '%s' cannot be parsed." % params['format']) + # implement dict interface + def __getitem__(self, key): + return self.params[key] + + def __setitem__(self, key, value): + self.params[key] = value + + def __delitem__(self, key): + del self.params[key] + + def keys(self): + return self.params.keys() + + def __contains__(self, key): + return self.params.__contains__(key) + + def __iter__(self): + return self.params.__iter__() + + def iteritems(self): + return self.params.iteritems() + + def update(self, *args, **kwargs): + """Update the request parameters""" + self.params.update(kwargs) + for arg in args: + if arg not in self.params: + self.params[arg] = "" + + def submit(self): + """Submit a query and parse the response. + + @return: The data retrieved from api.php (a dict) + + """ + if self.params['format'] != 'json': + raise TypeError("Query format '%s' cannot be parsed." + % self.params['format']) + uri = self.site.script_path() + "api.php" + params = urllib.urlencode(self.params) while True: - httpcode, rawdata = self.query(**params) - if httpcode != 200: - raise APIError( - {'code': httpcode, - 'info': "HTTP error code received.", - 'data': rawdata}) + # TODO wait on errors + # TODO catch http errors + if self.params.get("action", "") in ("login",): + rawdata = http.request(self.site, uri, method="POST", + headers={'Content-Type': + 'application/x-www-form-urlencoded'}, + body=params) + return rawdata + else: + uri = uri + "?" + params + rawdata = http.request(self.site, uri) if rawdata.startswith(u"unknown_action"): e = {'code': data[:14], 'info': data[16:]} raise APIError(e) @@ -63,27 +133,31 @@ warnings.warn( "Non-JSON response received from server %s; the server may be down." % self.site) + print rawdata continue + if not result: + return {} if type(result) is dict: - if result.has_key("error"): + if "error" in result: + if "code" in result["error"]: + code = result["error"]["code"] + del result["error"]["code"] + else: + code = "Unknown" + if "info" in result["error"]: + info = result["error"]["info"] + del result["error"]["info"] + else: + info = None # raise error - raise APIError(result['error']) - if result.has_key("query"): - return result - raise APIError( - {'code': "Unknown API error", - 'info': "Response received with no 'query' key.", - 'data': result}) - if type(result) is list: - if result == []: - return None - raise APIError( - {'code': "Unknown API error", - 'info': "Query returned a list instead of a dict.", - 'data': result}) - raise APIError( - {'code': "Unknown API error", - 'info': "Unable to process query response of type %s." - % type(result), - 'data': result}) + raise APIError(code, info, **result["error"]) + return result + raise APIError("Unknown", + "Unable to process query response of type %s." + % type(result), + {'data': result}) + +if __name__ == "__main__": + from pywikibot.tests.dummy import TestSite as Site + mysite = Site("en.wikipedia.org")
Modified: branches/rewrite/pywikibot/data/http.py =================================================================== --- branches/rewrite/pywikibot/data/http.py 2008-01-18 20:12:16 UTC (rev 4913) +++ branches/rewrite/pywikibot/data/http.py 2008-01-18 20:55:39 UTC (rev 4914) @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- """ Basic HTTP access interface.
@@ -58,16 +58,17 @@
def request(site, uri, *args, **kwargs): """ @param site The Site to connect to - All other parameters are the same as `Http.request`, but the uri is relative - Returns: The recieved data. + All other parameters are the same as L{httplib2.Http.request}, but + the uri is relative + @return The received data (a unicode string). """ - baseuri = site #.baseuri(), etc + baseuri = "%s://%s/" % (site.protocol(), site.hostname()) uri = urlparse.urljoin(baseuri, uri) - + request = threadedhttp.HttpRequest(uri, *args, **kwargs) http_queue.put(request) request.lock.acquire()
#do some error correcting stuff
- return request.data[1] \ No newline at end of file + return request.data[1]
Added: branches/rewrite/pywikibot/tests/__init__.py ===================================================================
Added: branches/rewrite/pywikibot/tests/api_tests.py =================================================================== --- branches/rewrite/pywikibot/tests/api_tests.py (rev 0) +++ branches/rewrite/pywikibot/tests/api_tests.py 2008-01-18 20:55:39 UTC (rev 4914) @@ -0,0 +1,26 @@ +import unittest +import pywikibot.data.api as api + +from pywikibot.tests.dummy import TestSite as Site +mysite = Site('en.wikipedia.org') + + +class TestApiFunctions(unittest.TestCase): + + def testObjectCreation(self): + """Test that api.Request() creates an object with desired attributes""" + req = api.Request(mysite, "foo", bar="test") + self.assert_(req) + self.assertEqual(req.site, mysite) + self.assert_("foo" in req.params) + self.assertEqual(req["format"], "json") + self.assertEqual(req["bar"], "test") + # test item assignment + req["one"] = "1" + self.assertEqual(req.params['one'], "1") + +if __name__ == '__main__': + try: + unittest.main() + except SystemExit: + pass
Modified: branches/rewrite/pywikibot/tests/dummy.py =================================================================== --- branches/rewrite/pywikibot/tests/dummy.py 2008-01-18 20:12:16 UTC (rev 4913) +++ branches/rewrite/pywikibot/tests/dummy.py 2008-01-18 20:55:39 UTC (rev 4914) @@ -11,13 +11,16 @@
class TestSite(object): """Mimic a Site object.""" - def __init__(self, hostname, protocol="http"): + def __init__(self, hostname, protocol="http", path="w/"): self._hostname = hostname self._protocol = protocol + self._path = path def protocol(self): return self._protocol def hostname(self): return self._hostname + def script_path(self): + return self._path def cookies(self, sysop=False): if hasattr(self, "_cookies"): return self._cookies