Commit 93ca76e5 authored by inactivist's avatar inactivist
Browse files

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.
parent b852f8e3
......@@ -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):
......
......@@ -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):
......
......@@ -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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment