Place model support and geo API mods
authorinactivist <inactivist@gmail.com>
Tue, 13 Nov 2012 07:15:08 +0000 (23:15 -0800)
committerinactivist <inactivist@gmail.com>
Tue, 13 Nov 2012 07:15:08 +0000 (23:15 -0800)
api.py:

* Adding  API.geo_similar_places (geo/similar_places.json).
* Updating geo_search, geo_id, nearby_places, and reverse_geocode to return Place model instances as appropriate, rather than raw query result JSON data.  The geo APIs parse the 'result' element into a single Place or a list of Place instances.

models.py:

* Status: Now uses Place model if place attribute present in tweet/status body.
* Adding BoundingBox, Place model classes.

NOTE: Breaking Changes

* Clients referencing Status.place or existing geo  functions (see list above) must be reviewed and updated to use proper attribute access.  The geo methods now return Place model instances rather than the raw JSON query data.

tests.py
tweepy/api.py
tweepy/models.py

index e1271fd8f850580618853ce84fce52f26685d83f..04baf887e47bc2cb99822b631f967371a9e65c83 100644 (file)
--- a/tests.py
+++ b/tests.py
@@ -282,9 +282,19 @@ class TweepyAPITests(unittest.TestCase):
         self.api.trends_weekly()
 
     def testgeoapis(self):
-        self.api.geo_id(id='c3f37afa9efcf94b') # Austin, TX, USA
-        self.api.nearby_places(lat=30.267370168467806, long=-97.74261474609375) # Austin, TX, USA
-        self.api.reverse_geocode(lat=30.267370168467806, long=-97.74261474609375) # Austin, TX, USA
+        def place_name_in_list(place_name, place_list):
+            """Return True if a given place_name is in place_list."""
+            return any([x.full_name.lower() == place_name.lower() for x in place_list])
+
+        twitter_hq = self.api.geo_similar_places(lat=37, long= -122, name='Twitter HQ')
+        # Assumes that twitter_hq is first Place returned...
+        self.assertEqual(twitter_hq[0].id, '3bdf30ed8b201f31')
+        # Test various API functions using Austin, TX, USA
+        self.assertEqual(self.api.geo_id(id='c3f37afa9efcf94b').full_name, 'Austin, TX')
+        self.assertTrue(place_name_in_list('Austin, TX',
+            self.api.nearby_places(lat=30.267370168467806, long= -97.74261474609375))) # Austin, TX, USA
+        self.assertTrue(place_name_in_list('Austin, TX',
+            self.api.reverse_geocode(lat=30.267370168467806, long= -97.74261474609375))) # Austin, TX, USA
 
 class TweepyCursorTests(unittest.TestCase):
 
index 09bebf9f80b650ac70d5063400c1b61681ddc94e..e1f89ae0ca1177490c82df12788e485cf4ada842 100644 (file)
@@ -702,7 +702,7 @@ class API(object):
     """ geo/reverse_geocode """
     reverse_geocode = bind_api(
         path = '/geo/reverse_geocode.json',
-        payload_type = 'json',
+        payload_type = 'place', payload_list = True,
         allowed_param = ['lat', 'long', 'accuracy', 'granularity', 'max_results']
     )
 
@@ -710,24 +710,31 @@ class API(object):
     # listed as deprecated on twitter's API documents
     nearby_places = bind_api(
         path = '/geo/nearby_places.json',
-        payload_type = 'json',
+        payload_type = 'place', payload_list = True,
         allowed_param = ['lat', 'long', 'ip', 'accuracy', 'granularity', 'max_results']
     )
 
     """ geo/id """
     geo_id = bind_api(
         path = '/geo/id/{id}.json',
-        payload_type = 'json',
+        payload_type = 'place',
         allowed_param = ['id']
     )
 
     """ geo/search """
     geo_search = bind_api(
         path = '/geo/search.json',
-        payload_type = 'json',
+        payload_type = 'place', payload_list = True,
         allowed_param = ['lat', 'long', 'query', 'ip', 'granularity', 'accuracy', 'max_results', 'contained_within']
     )
 
+    """ geo/similar_places """
+    geo_similar_places = bind_api(
+        path = '/geo/similar_places.json',
+        payload_type = 'place', payload_list = True,
+        allowed_param = ['lat', 'long', 'name', 'contained_within']
+    )
+
     """ Internal use only """
     @staticmethod
     def _pack_image(filename, max_size):
index bef08fbf07964ee8a4f779bfda4c45860e824c55..f5838dd7c4fa2462f76cf2ead52590b45dd5e4a1 100644 (file)
@@ -62,6 +62,8 @@ class Status(Model):
                     setattr(status, 'source_url', None)
             elif k == 'retweeted_status':
                 setattr(status, k, Status.parse(api, v))
+            elif k == 'place':
+                setattr(status, k, Place.parse(api, v))
             else:
                 setattr(status, k, v)
         return status
@@ -321,6 +323,65 @@ class IDModel(Model):
             return json['ids']
 
 
+class BoundingBox(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        result = cls(api)
+        for k, v in json.items():
+            setattr(result, k, v)
+        return result
+
+    def origin(self):
+        """
+        Return longitude, latitude of northwest corner of bounding box, as
+        tuple.  This assumes that bounding box is always a rectangle, which
+        appears to be the case at present.
+        """
+        return tuple(self.coordinates[0][0])
+
+    def corner(self):
+        """
+        Return longitude, latitude of southeast corner of bounding box, as
+        tuple.  This assumes that bounding box is always a rectangle, which
+        appears to be the case at present.
+        """
+        return tuple(self.coordinates[0][1])
+
+
+class Place(Model):
+
+    @classmethod
+    def parse(cls, api, json):
+        place = cls(api)
+        for k, v in json.items():
+            if k == 'bounding_box':
+                # bounding_box value may be null (None.)
+                # Example: "United States" (id=96683cc9126741d1)
+                if v is not None:
+                    t = BoundingBox.parse(api, v)
+                else:
+                    t = v
+                setattr(place, k, t)
+            elif k == 'contained_within':
+                # contained_within is a list of Places.
+                setattr(place, k, Place.parse_list(api, v))
+            else:
+                setattr(place, k, v)
+        return place
+
+    @classmethod
+    def parse_list(cls, api, json_list):
+        if isinstance(json_list, list):
+            item_list = json_list
+        else:
+            item_list = json_list['result']['places']
+
+        results = ResultSet()
+        for obj in item_list:
+            results.append(cls.parse(api, obj))
+        return results
+
 class ModelFactory(object):
     """
     Used by parsers for creating instances
@@ -340,4 +401,6 @@ class ModelFactory(object):
 
     json = JSONModel
     ids = IDModel
+    place = Place
+    bounding_box = BoundingBox