]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/api.py
Merge branch 'master' into python3
[z_archive/twitter.git] / twitter / api.py
index 608b08f9670cc52d16e2760cddd358076cccd428..f675822f03e2500f978568e10eb88e3d2c2d885a 100644 (file)
@@ -1,10 +1,7 @@
-
-import urllib2
-
-from exceptions import Exception
+import urllib.request, urllib.error, urllib.parse
 
 from twitter.twitter_globals import POST_ACTIONS
 
 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 +24,75 @@ 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'))
+
+
+def wrap_response(response, headers):
+    response_typ = type(response)
+    if response_typ is bool:
+        # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
+        response_typ = int
+
+    class WrappedTwitterResponse(response_typ, TwitterResponse):
+        __doc__ = TwitterResponse.__doc__
+
+        def __init__(self, response):
+            if response_typ is not int:
+                response_typ.__init__(self, response)
+            TwitterResponse.__init__(self, headers)
+
+    return WrappedTwitterResponse(response)
+
+
+
 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 +100,65 @@ 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(str(kwargs.pop(uripart, uripart)))
+        uri = '/'.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.encode('utf8')
+
+        req = urllib.request.Request(uriBase, body, headers)
 
 
-        req = urllib2.Request(uriBase+argStr, argData, headers)
-        
         try:
         try:
-            handle = urllib2.urlopen(req)
+            handle = urllib.request.urlopen(req)
             if "json" == self.format:
             if "json" == self.format:
-                return json.loads(handle.read())
+                res = json.loads(handle.read().decode('utf8'))
+                return wrap_response(res, handle.headers)
             else:
             else:
-                return handle.read()
-        except urllib2.HTTPError, e:
+                return wrap_response(
+                    handle.read().decode('utf8'), handle.headers)
+        except urllib.error.HTTPError as e:
             if (e.code == 304):
                 return []
             else:
             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,8 +169,8 @@ 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::
 
@@ -144,6 +191,9 @@ 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::
 
 
     Searching Twitter::
 
@@ -184,8 +234,8 @@ class Twitter(TwitterCall):
 
     """
     def __init__(
 
     """
     def __init__(
-        self, email=None, password=None, format="json",
-        domain="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.
         api_version=''):
         """
         Create a new twitter API connector.
@@ -198,13 +248,6 @@ class Twitter(TwitterCall):
                     token, token_secret, consumer_key, consumer_secret))
 
 
                     token, token_secret, consumer_key, consumer_secret))
 
 
-        Alternately you can pass `email` and `password` parameters but
-        this authentication mode will be deactive by Twitter very soon
-        and is not recommended::
-
-            twitter = Twitter(email="blah@blah.com", password="foobar")
-
-
         `domain` lets you change the domain you are connecting. By
         default it's twitter.com but `search.twitter.com` may be
         useful too.
         `domain` lets you change the domain you are connecting. By
         default it's twitter.com but `search.twitter.com` may be
         useful too.
@@ -221,26 +264,19 @@ 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", "TwitterResponse"]