]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/api.py
Make search work again
[z_archive/twitter.git] / twitter / api.py
index 1e249d5d1f3e12bc4b8076220dff30459181da53..b7ba19857e3939586b14eb5b85f521f7f20507ac 100644 (file)
@@ -1,10 +1,9 @@
-
 import urllib2
 
 from exceptions import Exception
 
 from twitter.twitter_globals import POST_ACTIONS
 import urllib2
 
 from exceptions import Exception
 
 from twitter.twitter_globals import POST_ACTIONS
-from twitter.auth import UserPassAuth, NoAuth
+from twitter.auth import NoAuth
 
 def _py26OrGreater():
     import sys
 
 def _py26OrGreater():
     import sys
@@ -27,29 +26,78 @@ class TwitterHTTPError(TwitterError):
     Exception thrown by the Twitter object when there is an
     HTTP error interacting with twitter.com.
     """
     Exception thrown by the Twitter object when there is an
     HTTP error interacting with twitter.com.
     """
-    def __init__(self, e, uri, format, encoded_args):
+    def __init__(self, e, uri, format, uriparts):
       self.e = e
       self.uri = uri
       self.format = format
       self.e = e
       self.uri = uri
       self.format = format
-      self.encoded_args = encoded_args
+      self.uriparts = uriparts
 
     def __str__(self):
         return (
             "Twitter sent status %i for URL: %s.%s using parameters: "
             "(%s)\ndetails: %s" %(
 
     def __str__(self):
         return (
             "Twitter sent status %i for URL: %s.%s using parameters: "
             "(%s)\ndetails: %s" %(
-                self.e.code, self.uri, self.format, self.encoded_args, 
+                self.e.code, self.uri, self.format, self.uriparts,
                 self.e.fp.read()))
 
                 self.e.fp.read()))
 
+class TwitterResponse(object):
+    """
+    Response from a twitter request. Behaves like a list or a string
+    (depending on requested format) but it has a few other interesting
+    attributes.
+
+    `headers` gives you access to the response headers as an
+    httplib.HTTPHeaders instance. You can do
+    `response.headers.getheader('h')` to retrieve a header.
+    """
+    def __init__(self, headers):
+        self.headers = headers
+
+    @property
+    def rate_limit_remaining(self):
+        """
+        Remaining requests in the current rate-limit.
+        """
+        return int(self.headers.getheader('X-RateLimit-Remaining'))
+
+    @property
+    def rate_limit_reset(self):
+        """
+        Time in UTC epoch seconds when the rate limit will reset.
+        """
+        return int(self.headers.getheader('X-RateLimit-Reset'))
+
+
+# Multiple inheritance makes my inner Java nerd cry. Why can't I just
+# add arbitrary attributes to list or str objects?! Guido, we need to
+# talk.
+class TwitterJsonListResponse(TwitterResponse, list):
+    __doc__ = """Twitter JSON Response
+    """ + TwitterResponse.__doc__
+    def __init__(self, lst, headers):
+        TwitterResponse.__init__(self, headers)
+        list.__init__(self, lst)
+class TwitterJsonDictResponse(TwitterResponse, dict):
+    __doc__ = """Twitter JSON Response
+    """ + TwitterResponse.__doc__
+    def __init__(self, d, headers):
+        TwitterResponse.__init__(self, headers)
+        dict.__init__(self, d)
+
+class TwitterXmlResponse(TwitterResponse, str):
+    __doc__ = """Twitter XML Response
+    """ + TwitterResponse.__doc__
+
+
 class TwitterCall(object):
     def __init__(
         self, auth, format, domain, uri="", agent=None,
 class TwitterCall(object):
     def __init__(
         self, auth, format, domain, uri="", agent=None,
-        encoded_args=None, secure=True):
+        uriparts=None, secure=True):
         self.auth = auth
         self.format = format
         self.domain = domain
         self.uri = uri
         self.agent = agent
         self.auth = auth
         self.format = format
         self.domain = domain
         self.uri = uri
         self.agent = agent
-        self.encoded_args = encoded_args
+        self.uriparts = uriparts
         self.secure = secure
 
     def __getattr__(self, k):
         self.secure = secure
 
     def __getattr__(self, k):
@@ -57,61 +105,69 @@ class TwitterCall(object):
             return object.__getattr__(self, k)
         except AttributeError:
             return TwitterCall(
             return object.__getattr__(self, k)
         except AttributeError:
             return TwitterCall(
-                self.auth, self.format, self.domain,
-                self.uri + "/" + k, self.agent, self.encoded_args, self.secure)
+                auth=self.auth, format=self.format, domain=self.domain,
+                agent=self.agent, uriparts=self.uriparts + (k,),
+                secure=self.secure)
 
     def __call__(self, **kwargs):
 
     def __call__(self, **kwargs):
-        uri = self.uri.strip("/")
+        # Build the uri.
+        uriparts = []
+        for uripart in self.uriparts:
+            # If this part matches a keyword argument, use the
+            # supplied value otherwise, just use the part.
+            uriparts.append(unicode(kwargs.pop(uripart, uripart)))
+        uri = u'/'.join(uriparts)
+
         method = "GET"
         for action in POST_ACTIONS:
         method = "GET"
         for action in POST_ACTIONS:
-            if self.uri.endswith(action):
+            if uri.endswith(action):
                 method = "POST"
                 method = "POST"
-                if (self.agent):
-                    kwargs["source"] = self.agent
                 break
 
                 break
 
+        # If an id kwarg is present and there is no id to fill in in
+        # the list of uriparts, assume the id goes at the end.
+        id = kwargs.pop('id', None)
+        if id:
+            uri += "/%s" %(id)
+
         secure_str = ''
         if self.secure:
             secure_str = 's'
         dot = ""
         secure_str = ''
         if self.secure:
             secure_str = 's'
         dot = ""
-        if self.format != '':
+        if self.format:
             dot = "."
         uriBase = "http%s://%s/%s%s%s" %(
                     secure_str, self.domain, uri, dot, self.format)
 
             dot = "."
         uriBase = "http%s://%s/%s%s%s" %(
                     secure_str, self.domain, uri, dot, self.format)
 
-        if (not self.encoded_args):
-            if kwargs.has_key('id'):
-                uri += "/%s" %(kwargs['id'])
-
-            self.encoded_args = self.auth.encode_params(uriBase, method, kwargs)
-
-        argStr = ""
-        argData = None
-        if (method == "GET"):
-            if self.encoded_args:
-                argStr = "?%s" %(self.encoded_args)
-        else:
-            argData = self.encoded_args
-
         headers = {}
         headers = {}
-        if (self.agent):
-            headers["X-Twitter-Client"] = self.agent
-        if self.auth is not None:
+        if self.auth:
             headers.update(self.auth.generate_headers())
             headers.update(self.auth.generate_headers())
+            arg_data = self.auth.encode_params(uriBase, method, kwargs)
+            if method == 'GET':
+                uriBase += '?' + arg_data
+                body = None
+            else:
+                body = arg_data
+
+        req = urllib2.Request(uriBase, body, headers)
 
 
-        req = urllib2.Request(uriBase+argStr, argData, headers)
-        
         try:
             handle = urllib2.urlopen(req)
             if "json" == self.format:
         try:
             handle = urllib2.urlopen(req)
             if "json" == self.format:
-                return json.loads(handle.read())
+                res = json.loads(handle.read())
+                response_cls = (
+                    TwitterJsonListResponse if type(res) is list
+                    else TwitterJsonDictResponse)
+                return response_cls(res, handle.headers)
             else:
             else:
-                return handle.read()
+                r = TwitterXmlResponse(handle.read())
+                r.headers = handle.headers
+                return r
         except urllib2.HTTPError, e:
             if (e.code == 304):
                 return []
             else:
         except urllib2.HTTPError, e:
             if (e.code == 304):
                 return []
             else:
-                raise TwitterHTTPError(e, uri, self.format, self.encoded_args)
+                raise TwitterHTTPError(e, uri, self.format, arg_data)
 
 class Twitter(TwitterCall):
     """
 
 class Twitter(TwitterCall):
     """
@@ -122,12 +178,13 @@ class Twitter(TwitterCall):
 
     The Twitter API is documented here:
 
 
     The Twitter API is documented here:
 
-      http://apiwiki.twitter.com/
-      http://groups.google.com/group/twitter-development-talk/web/api-documentation
+      http://dev.twitter.com/doc
+
 
     Examples::
 
 
     Examples::
 
-      twitter = Twitter("hello@foo.com", "password123")
+      twitter = Twitter(
+          auth=OAuth(token, token_key, con_secret, con_secret_key)))
 
       # Get the public timeline
       twitter.statuses.public_timeline()
 
       # Get the public timeline
       twitter.statuses.public_timeline()
@@ -143,6 +200,10 @@ class Twitter(TwitterCall):
           user="billybob",
           text="I think yer swell!")
 
           user="billybob",
           text="I think yer swell!")
 
+      # Get the members of a particular list of a particular friend
+      twitter.user.listname.members(user="billybob", listname="billysbuds")
+
+
     Searching Twitter::
 
       twitter_search = Twitter(domain="search.twitter.com")
     Searching Twitter::
 
       twitter_search = Twitter(domain="search.twitter.com")
@@ -182,17 +243,19 @@ class Twitter(TwitterCall):
 
     """
     def __init__(
 
     """
     def __init__(
-        self, email=None, password=None, format="json",
-        domain="api.twitter.com", agent=None, secure=True, auth=None,
+        self, format="json",
+        domain="twitter.com", secure=True, auth=None,
         api_version=''):
         """
         Create a new twitter API connector.
 
         Pass an `auth` parameter to use the credentials of a specific
         user. Generally you'll want to pass an `OAuth`
         api_version=''):
         """
         Create a new twitter API connector.
 
         Pass an `auth` parameter to use the credentials of a specific
         user. Generally you'll want to pass an `OAuth`
-        instance. Alternately you can pass `email` and `password`
-        parameters but this authentication mode will be deactive by
-        Twitter in the future and is not recommended.
+        instance::
+
+            twitter = Twitter(auth=OAuth(
+                    token, token_secret, consumer_key, consumer_secret))
+
 
         `domain` lets you change the domain you are connecting. By
         default it's twitter.com but `search.twitter.com` may be
 
         `domain` lets you change the domain you are connecting. By
         default it's twitter.com but `search.twitter.com` may be
@@ -210,26 +273,21 @@ class Twitter(TwitterCall):
         nothing, but if you set it to '1' your URI will start with
         '1/'.
         """
         nothing, but if you set it to '1' your URI will start with
         '1/'.
         """
-        
-        if email is not None or password is not None:
-            if auth:
-                raise ValueError(
-                    "Can't specify 'email'/'password' and 'auth' params"
-                    " simultaneously.")
-            auth = UserPassAuth(email, password)
-
         if not auth:
             auth = NoAuth()
 
         if (format not in ("json", "xml", "")):
             raise ValueError("Unknown data format '%s'" %(format))
 
         if not auth:
             auth = NoAuth()
 
         if (format not in ("json", "xml", "")):
             raise ValueError("Unknown data format '%s'" %(format))
 
-        uri = ""
+        uriparts = ()
         if api_version:
         if api_version:
-            uri = str(api_version)
+            uriparts += (str(api_version),)
 
         TwitterCall.__init__(
 
         TwitterCall.__init__(
-            self, auth, format, domain, uri, agent, 
-            secure=secure)
+            self, auth=auth, format=format, domain=domain,
+            secure=secure, uriparts=uriparts)
+
 
 
-__all__ = ["Twitter", "TwitterError", "TwitterHTTPError"]
+__all__ = ["Twitter", "TwitterError", "TwitterHTTPError",
+           "TwitterJsonListResponse", "TwitterJsonDictResponse",
+           "TwitterXmlResponse"]