From 93ca76e5bc5a3a20c44948ab122542cccfddaf90 Mon Sep 17 00:00:00 2001 From: inactivist Date: Mon, 12 Nov 2012 23:15:08 -0800 Subject: [PATCH] Place model support and geo API mods 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 | 16 +++++++++--- tweepy/api.py | 15 +++++++++--- tweepy/models.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/tests.py b/tests.py index e1271fd..04baf88 100644 --- 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): diff --git a/tweepy/api.py b/tweepy/api.py index 09bebf9..e1f89ae 100644 --- a/tweepy/api.py +++ b/tweepy/api.py @@ -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): diff --git a/tweepy/models.py b/tweepy/models.py index bef08fb..f5838dd 100644 --- a/tweepy/models.py +++ b/tweepy/models.py @@ -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 -- 2.25.1