[Pywikipedia-l] SVN: [4914] branches/rewrite/pywikibot

russblau at svn.wikimedia.org russblau at svn.wikimedia.org
Fri Jan 18 20:55:39 UTC 2008


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://[path]/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





More information about the Pywikipedia-l mailing list