models.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. # Tweepy
  2. # Copyright 2009-2010 Joshua Roesslein
  3. # See LICENSE for details.
  4. from __future__ import absolute_import, print_function
  5. from tweepy.utils import parse_datetime, parse_html_value, parse_a_href
  6. class ResultSet(list):
  7. """A list like object that holds results from a Twitter API query."""
  8. def __init__(self, max_id=None, since_id=None):
  9. super(ResultSet, self).__init__()
  10. self._max_id = max_id
  11. self._since_id = since_id
  12. @property
  13. def max_id(self):
  14. if self._max_id:
  15. return self._max_id
  16. ids = self.ids()
  17. # Max_id is always set to the *smallest* id, minus one, in the set
  18. return (min(ids) - 1) if ids else None
  19. @property
  20. def since_id(self):
  21. if self._since_id:
  22. return self._since_id
  23. ids = self.ids()
  24. # Since_id is always set to the *greatest* id in the set
  25. return max(ids) if ids else None
  26. def ids(self):
  27. return [item.id for item in self if hasattr(item, 'id')]
  28. class Model(object):
  29. def __init__(self, api=None):
  30. self._api = api
  31. def __getstate__(self):
  32. # pickle
  33. pickle = dict(self.__dict__)
  34. try:
  35. del pickle['_api'] # do not pickle the API reference
  36. except KeyError:
  37. pass
  38. return pickle
  39. @classmethod
  40. def parse(cls, api, json):
  41. """Parse a JSON object into a model instance."""
  42. raise NotImplementedError
  43. @classmethod
  44. def parse_list(cls, api, json_list):
  45. """
  46. Parse a list of JSON objects into
  47. a result set of model instances.
  48. """
  49. results = ResultSet()
  50. for obj in json_list:
  51. if obj:
  52. results.append(cls.parse(api, obj))
  53. return results
  54. def __repr__(self):
  55. state = ['%s=%s' % (k, repr(v)) for (k, v) in vars(self).items()]
  56. return '%s(%s)' % (self.__class__.__name__, ', '.join(state))
  57. class Status(Model):
  58. @classmethod
  59. def parse(cls, api, json):
  60. status = cls(api)
  61. setattr(status, '_json', json)
  62. for k, v in json.items():
  63. if k == 'user':
  64. user_model = getattr(api.parser.model_factory, 'user') if api else User
  65. user = user_model.parse(api, v)
  66. setattr(status, 'author', user)
  67. setattr(status, 'user', user) # DEPRECIATED
  68. elif k == 'created_at':
  69. setattr(status, k, parse_datetime(v))
  70. elif k == 'source':
  71. if '<' in v:
  72. setattr(status, k, parse_html_value(v))
  73. setattr(status, 'source_url', parse_a_href(v))
  74. else:
  75. setattr(status, k, v)
  76. setattr(status, 'source_url', None)
  77. elif k == 'retweeted_status':
  78. setattr(status, k, Status.parse(api, v))
  79. elif k == 'place':
  80. if v is not None:
  81. setattr(status, k, Place.parse(api, v))
  82. else:
  83. setattr(status, k, None)
  84. else:
  85. setattr(status, k, v)
  86. return status
  87. def destroy(self):
  88. return self._api.destroy_status(self.id)
  89. def retweet(self):
  90. return self._api.retweet(self.id)
  91. def retweets(self):
  92. return self._api.retweets(self.id)
  93. def favorite(self):
  94. return self._api.create_favorite(self.id)
  95. def __eq__(self, other):
  96. if isinstance(other, Status):
  97. return self.id == other.id
  98. return NotImplemented
  99. def __ne__(self, other):
  100. result = self == other
  101. if result is NotImplemented:
  102. return result
  103. return not result
  104. class User(Model):
  105. @classmethod
  106. def parse(cls, api, json):
  107. user = cls(api)
  108. setattr(user, '_json', json)
  109. for k, v in json.items():
  110. if k == 'created_at':
  111. setattr(user, k, parse_datetime(v))
  112. elif k == 'status':
  113. setattr(user, k, Status.parse(api, v))
  114. elif k == 'following':
  115. # twitter sets this to null if it is false
  116. if v is True:
  117. setattr(user, k, True)
  118. else:
  119. setattr(user, k, False)
  120. else:
  121. setattr(user, k, v)
  122. return user
  123. @classmethod
  124. def parse_list(cls, api, json_list):
  125. if isinstance(json_list, list):
  126. item_list = json_list
  127. else:
  128. item_list = json_list['users']
  129. results = ResultSet()
  130. for obj in item_list:
  131. results.append(cls.parse(api, obj))
  132. return results
  133. def timeline(self, **kargs):
  134. return self._api.user_timeline(user_id=self.id, **kargs)
  135. def friends(self, **kargs):
  136. return self._api.friends(user_id=self.id, **kargs)
  137. def followers(self, **kargs):
  138. return self._api.followers(user_id=self.id, **kargs)
  139. def follow(self):
  140. self._api.create_friendship(user_id=self.id)
  141. self.following = True
  142. def unfollow(self):
  143. self._api.destroy_friendship(user_id=self.id)
  144. self.following = False
  145. def lists_memberships(self, *args, **kargs):
  146. return self._api.lists_memberships(user=self.screen_name,
  147. *args,
  148. **kargs)
  149. def lists_subscriptions(self, *args, **kargs):
  150. return self._api.lists_subscriptions(user=self.screen_name,
  151. *args,
  152. **kargs)
  153. def lists(self, *args, **kargs):
  154. return self._api.lists_all(user=self.screen_name,
  155. *args,
  156. **kargs)
  157. def followers_ids(self, *args, **kargs):
  158. return self._api.followers_ids(user_id=self.id,
  159. *args,
  160. **kargs)
  161. class DirectMessage(Model):
  162. @classmethod
  163. def parse(cls, api, json):
  164. dm = cls(api)
  165. for k, v in json.items():
  166. if k == 'sender' or k == 'recipient':
  167. setattr(dm, k, User.parse(api, v))
  168. elif k == 'created_at':
  169. setattr(dm, k, parse_datetime(v))
  170. else:
  171. setattr(dm, k, v)
  172. return dm
  173. def destroy(self):
  174. return self._api.destroy_direct_message(self.id)
  175. class Friendship(Model):
  176. @classmethod
  177. def parse(cls, api, json):
  178. relationship = json['relationship']
  179. # parse source
  180. source = cls(api)
  181. for k, v in relationship['source'].items():
  182. setattr(source, k, v)
  183. # parse target
  184. target = cls(api)
  185. for k, v in relationship['target'].items():
  186. setattr(target, k, v)
  187. return source, target
  188. class Category(Model):
  189. @classmethod
  190. def parse(cls, api, json):
  191. category = cls(api)
  192. for k, v in json.items():
  193. setattr(category, k, v)
  194. return category
  195. class SavedSearch(Model):
  196. @classmethod
  197. def parse(cls, api, json):
  198. ss = cls(api)
  199. for k, v in json.items():
  200. if k == 'created_at':
  201. setattr(ss, k, parse_datetime(v))
  202. else:
  203. setattr(ss, k, v)
  204. return ss
  205. def destroy(self):
  206. return self._api.destroy_saved_search(self.id)
  207. class SearchResults(ResultSet):
  208. @classmethod
  209. def parse(cls, api, json):
  210. metadata = json['search_metadata']
  211. results = SearchResults()
  212. results.refresh_url = metadata.get('refresh_url')
  213. results.completed_in = metadata.get('completed_in')
  214. results.query = metadata.get('query')
  215. results.count = metadata.get('count')
  216. results.next_results = metadata.get('next_results')
  217. status_model = getattr(api.parser.model_factory, 'status') if api else Status
  218. for status in json['statuses']:
  219. results.append(status_model.parse(api, status))
  220. return results
  221. class List(Model):
  222. @classmethod
  223. def parse(cls, api, json):
  224. lst = List(api)
  225. for k, v in json.items():
  226. if k == 'user':
  227. setattr(lst, k, User.parse(api, v))
  228. elif k == 'created_at':
  229. setattr(lst, k, parse_datetime(v))
  230. else:
  231. setattr(lst, k, v)
  232. return lst
  233. @classmethod
  234. def parse_list(cls, api, json_list, result_set=None):
  235. results = ResultSet()
  236. if isinstance(json_list, dict):
  237. json_list = json_list['lists']
  238. for obj in json_list:
  239. results.append(cls.parse(api, obj))
  240. return results
  241. def update(self, **kargs):
  242. return self._api.update_list(self.slug, **kargs)
  243. def destroy(self):
  244. return self._api.destroy_list(self.slug)
  245. def timeline(self, **kargs):
  246. return self._api.list_timeline(self.user.screen_name,
  247. self.slug,
  248. **kargs)
  249. def add_member(self, id):
  250. return self._api.add_list_member(self.slug, id)
  251. def remove_member(self, id):
  252. return self._api.remove_list_member(self.slug, id)
  253. def members(self, **kargs):
  254. return self._api.list_members(self.user.screen_name,
  255. self.slug,
  256. **kargs)
  257. def is_member(self, id):
  258. return self._api.is_list_member(self.user.screen_name,
  259. self.slug,
  260. id)
  261. def subscribe(self):
  262. return self._api.subscribe_list(self.user.screen_name, self.slug)
  263. def unsubscribe(self):
  264. return self._api.unsubscribe_list(self.user.screen_name, self.slug)
  265. def subscribers(self, **kargs):
  266. return self._api.list_subscribers(self.user.screen_name,
  267. self.slug,
  268. **kargs)
  269. def is_subscribed(self, id):
  270. return self._api.is_subscribed_list(self.user.screen_name,
  271. self.slug,
  272. id)
  273. class Relation(Model):
  274. @classmethod
  275. def parse(cls, api, json):
  276. result = cls(api)
  277. for k, v in json.items():
  278. if k == 'value' and json['kind'] in ['Tweet', 'LookedupStatus']:
  279. setattr(result, k, Status.parse(api, v))
  280. elif k == 'results':
  281. setattr(result, k, Relation.parse_list(api, v))
  282. else:
  283. setattr(result, k, v)
  284. return result
  285. class Relationship(Model):
  286. @classmethod
  287. def parse(cls, api, json):
  288. result = cls(api)
  289. for k, v in json.items():
  290. if k == 'connections':
  291. setattr(result, 'is_following', 'following' in v)
  292. setattr(result, 'is_followed_by', 'followed_by' in v)
  293. else:
  294. setattr(result, k, v)
  295. return result
  296. class JSONModel(Model):
  297. @classmethod
  298. def parse(cls, api, json):
  299. return json
  300. class IDModel(Model):
  301. @classmethod
  302. def parse(cls, api, json):
  303. if isinstance(json, list):
  304. return json
  305. else:
  306. return json['ids']
  307. class BoundingBox(Model):
  308. @classmethod
  309. def parse(cls, api, json):
  310. result = cls(api)
  311. if json is not None:
  312. for k, v in json.items():
  313. setattr(result, k, v)
  314. return result
  315. def origin(self):
  316. """
  317. Return longitude, latitude of southwest (bottom, left) corner of
  318. bounding box, as a tuple.
  319. This assumes that bounding box is always a rectangle, which
  320. appears to be the case at present.
  321. """
  322. return tuple(self.coordinates[0][0])
  323. def corner(self):
  324. """
  325. Return longitude, latitude of northeast (top, right) corner of
  326. bounding box, as a tuple.
  327. This assumes that bounding box is always a rectangle, which
  328. appears to be the case at present.
  329. """
  330. return tuple(self.coordinates[0][2])
  331. class Place(Model):
  332. @classmethod
  333. def parse(cls, api, json):
  334. place = cls(api)
  335. for k, v in json.items():
  336. if k == 'bounding_box':
  337. # bounding_box value may be null (None.)
  338. # Example: "United States" (id=96683cc9126741d1)
  339. if v is not None:
  340. t = BoundingBox.parse(api, v)
  341. else:
  342. t = v
  343. setattr(place, k, t)
  344. elif k == 'contained_within':
  345. # contained_within is a list of Places.
  346. setattr(place, k, Place.parse_list(api, v))
  347. else:
  348. setattr(place, k, v)
  349. return place
  350. @classmethod
  351. def parse_list(cls, api, json_list):
  352. if isinstance(json_list, list):
  353. item_list = json_list
  354. else:
  355. item_list = json_list['result']['places']
  356. results = ResultSet()
  357. for obj in item_list:
  358. results.append(cls.parse(api, obj))
  359. return results
  360. class Media(Model):
  361. @classmethod
  362. def parse(cls, api, json):
  363. media = cls(api)
  364. for k, v in json.items():
  365. setattr(media, k, v)
  366. return media
  367. class ModelFactory(object):
  368. """
  369. Used by parsers for creating instances
  370. of models. You may subclass this factory
  371. to add your own extended models.
  372. """
  373. status = Status
  374. user = User
  375. direct_message = DirectMessage
  376. friendship = Friendship
  377. saved_search = SavedSearch
  378. search_results = SearchResults
  379. category = Category
  380. list = List
  381. relation = Relation
  382. relationship = Relationship
  383. media = Media
  384. json = JSONModel
  385. ids = IDModel
  386. place = Place
  387. bounding_box = BoundingBox