]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/api.py
Remove api_version when domain is not api.twitter.com.
[z_archive/twitter.git] / twitter / api.py
index 608b08f9670cc52d16e2760cddd358076cccd428..4520e354bc48fd564666b77e1f580363abe75fef 100644 (file)
@@ -1,20 +1,21 @@
-
-import urllib2
-
-from exceptions import Exception
+try:
+    import urllib.request as urllib_request
+    import urllib.error as urllib_error
+except ImportError:
+    import urllib2 as urllib_request
+    import urllib2 as urllib_error
 
 from twitter.twitter_globals import POST_ACTIONS
 
 from twitter.twitter_globals import POST_ACTIONS
-from twitter.auth import UserPassAuth, NoAuth
-
-def _py26OrGreater():
-    import sys
-    return sys.hexversion > 0x20600f0
+from twitter.auth import NoAuth
 
 
-if _py26OrGreater():
+try:
     import json
     import json
-else:
+except ImportError:
     import simplejson as json
 
     import simplejson as json
 
+class _DEFAULT(object):
+    pass
+
 class TwitterError(Exception):
     """
     Base Exception thrown by the Twitter object when there is a
 class TwitterError(Exception):
     """
     Base Exception thrown by the Twitter object when there is a
@@ -27,91 +28,144 @@ 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):
-      self.e = e
-      self.uri = uri
-      self.format = format
-      self.encoded_args = encoded_args
+    def __init__(self, e, uri, format, uriparts):
+        self.e = e
+        self.uri = uri
+        self.format = format
+        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):
 class TwitterCall(object):
+
     def __init__(
     def __init__(
-        self, auth, format, domain, uri="", agent=None,
-        encoded_args=None, secure=True):
+        self, auth, format, domain, callable_cls, uri="",
+        uriparts=None, secure=True):
         self.auth = auth
         self.format = format
         self.domain = domain
         self.auth = auth
         self.format = format
         self.domain = domain
+        self.callable_cls = callable_cls
         self.uri = uri
         self.uri = uri
-        self.agent = agent
-        self.encoded_args = encoded_args
+        self.uriparts = uriparts
         self.secure = secure
 
     def __getattr__(self, k):
         try:
             return object.__getattr__(self, k)
         except AttributeError:
         self.secure = secure
 
     def __getattr__(self, k):
         try:
             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)
+            return self.callable_cls(
+                auth=self.auth, format=self.format, domain=self.domain,
+                callable_cls=self.callable_cls, 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)
+        return self._handle_response(req, uri, arg_data)
 
 
-        req = urllib2.Request(uriBase+argStr, argData, headers)
-        
+    def _handle_response(self, req, uri, arg_data):
         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 +176,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 +198,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,9 +241,9 @@ class Twitter(TwitterCall):
 
     """
     def __init__(
 
     """
     def __init__(
-        self, email=None, password=None, format="json",
-        domain="twitter.com", agent=None, secure=True, auth=None,
-        api_version=''):
+        self, format="json",
+        domain="api.twitter.com", secure=True, auth=None,
+        api_version=_DEFAULT):
         """
         Create a new twitter API connector.
 
         """
         Create a new twitter API connector.
 
@@ -198,49 +255,36 @@ 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
         `domain` lets you change the domain you are connecting. By
-        default it's twitter.com but `search.twitter.com` may be
+        default it's `api.twitter.com` but `search.twitter.com` may be
         useful too.
 
         If `secure` is False you will connect with HTTP instead of
         HTTPS.
 
         useful too.
 
         If `secure` is False you will connect with HTTP instead of
         HTTPS.
 
-        The value of `agent` is sent in the `X-Twitter-Client`
-        header. This is deprecated. Instead Twitter determines the
-        application using the OAuth Client Key and Client Key Secret
-        parameters.
-
         `api_version` is used to set the base uri. By default it's
         `api_version` is used to set the base uri. By default it's
-        nothing, but if you set it to '1' your URI will start with
-        '1/'.
+        '1'. If you are using "search.twitter.com" set this to None.
         """
         """
-        
-        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 = ""
+        if api_version is _DEFAULT:
+            if domain == 'api.twitter.com':
+                api_version = '1'
+            else:
+                api_version = None
+
+        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,
+            callable_cls=TwitterCall,
+            secure=secure, uriparts=uriparts)
+
 
 
-__all__ = ["Twitter", "TwitterError", "TwitterHTTPError"]
+__all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]