Merge lp:~robru/friends/model-cleanup into lp:friends

Proposed by Robert Bruce Park
Status: Merged
Approved by: Ken VanDine
Approved revision: 178
Merged at revision: 175
Proposed branch: lp:~robru/friends/model-cleanup
Merge into: lp:friends
Diff against target: 316 lines (+118/-22)
7 files modified
friends/protocols/facebook.py (+10/-3)
friends/protocols/twitter.py (+10/-7)
friends/tests/test_facebook.py (+14/-3)
friends/tests/test_identica.py (+1/-1)
friends/tests/test_protocols.py (+49/-0)
friends/tests/test_twitter.py (+9/-1)
friends/utils/base.py (+25/-7)
To merge this branch: bzr merge lp:~robru/friends/model-cleanup
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Ken VanDine Approve
Review via email: mp+156704@code.launchpad.net

Commit message

Thread replies properly, and fix up likes/unlikes to modify model data upon success.

Description of the change

Alright Ken, just like you asked, replies are threading and likes/unlikes are updating the model contents as you'd expect. Not sure how we overlooked this originally ;-)

Anyway, I did the model alteration stuff with a fairly simple API in the Base class, rather than having to import the Model into the protocol classes, which is a bit hacky.

As usual, excellent test coverage is included.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:178
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~robru/friends/model-cleanup/+merge/156704/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/friends-ci/16/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-raring-amd64-ci/16

Click here to trigger a rebuild:
http://s-jenkins:8080/job/friends-ci/16/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks good and does fix the issue. To test it I connected debug_slave.py to row-changed and called the like/unlike APIs. I consistently saw the value of likes get incremented/decremented along with the success callback, very quickly. However the value of liked gets changed separately, and a good 20 to 40 seconds later, which seems weird. The value does get changed though, just not seen on the model quickly.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

That is quite strange. I wonder if that might actually be a dbus synchronization issue rather than anything specific to the code I wrote. Because the way I wrote it, it changes the one value right after the other, although not in an atomic way.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'friends/protocols/facebook.py'
2--- friends/protocols/facebook.py 2013-03-16 00:54:45 +0000
3+++ friends/protocols/facebook.py 2013-04-02 21:50:28 +0000
4@@ -226,6 +226,8 @@
5 This includes messages, statuses, wall posts, events, etc.
6 """
7 self._like(obj_id, 'POST')
8+ self._inc_cell(obj_id, 'likes')
9+ self._set_cell(obj_id, 'liked', True)
10 return obj_id
11
12 @feature
13@@ -235,9 +237,11 @@
14 This includes messages, statuses, wall posts, events, etc.
15 """
16 self._like(obj_id, 'DELETE')
17+ self._dec_cell(obj_id, 'likes')
18+ self._set_cell(obj_id, 'liked', False)
19 return obj_id
20
21- def _send(self, obj_id, message, endpoint):
22+ def _send(self, obj_id, message, endpoint, stream='messages'):
23 url = API_BASE.format(id=obj_id) + endpoint
24 token = self._get_access_token()
25
26@@ -251,7 +255,9 @@
27
28 url = API_BASE.format(id=new_id)
29 entry = Downloader(url, params=dict(access_token=token)).get_json()
30- return self._publish_entry(entry)
31+ return self._publish_entry(
32+ stream=stream,
33+ entry=entry)
34
35 @feature
36 def send(self, message, obj_id='me'):
37@@ -270,7 +276,8 @@
38 obj_id can be the id of any Facebook object that supports being
39 commented on, which will generally be Posts.
40 """
41- return self._send(obj_id, message, '/comments')
42+ return self._send(obj_id, message, '/comments',
43+ stream='reply_to/{}'.format(obj_id))
44
45 @feature
46 def delete(self, obj_id):
47
48=== modified file 'friends/protocols/twitter.py'
49--- friends/protocols/twitter.py 2013-03-20 01:24:15 +0000
50+++ friends/protocols/twitter.py 2013-04-02 21:50:28 +0000
51@@ -248,9 +248,10 @@
52 def send_thread(self, message_id, message):
53 """Send a reply message to message_id.
54
55- Note that you have to @mention the message_id owner's screen name in
56- order for Twitter to actually accept this as a reply. Otherwise it
57- will just be an ordinary tweet.
58+ This method takes care to prepend the @mention to the start of
59+ your tweet if you forgot it. Without this, Twitter will just
60+ consider it a regular message, and it won't be part of any
61+ conversation.
62 """
63 try:
64 sender = '@{}'.format(self._fetch_cell(message_id, 'sender_nick'))
65@@ -261,7 +262,8 @@
66 url = self._api_base.format(endpoint='statuses/update')
67 tweet = self._get_url(url, dict(in_reply_to_status_id=message_id,
68 status=message))
69- return self._publish_tweet(tweet)
70+ return self._publish_tweet(
71+ tweet, stream='reply_to/{}'.format(message_id))
72
73 # https://dev.twitter.com/docs/api/1.1/post/statuses/destroy/%3Aid
74 @feature
75@@ -303,10 +305,9 @@
76 """Announce to the world your undying love for a tweet."""
77 url = self._api_base.format(endpoint='favorites/create')
78 self._get_url(url, dict(id=message_id))
79+ self._inc_cell(message_id, 'likes')
80+ self._set_cell(message_id, 'liked', True)
81 return message_id
82- # I don't think we need to publish this tweet because presumably the
83- # user has clicked the 'favorite' button on the message that's already
84- # in the stream.
85
86 # https://dev.twitter.com/docs/api/1.1/post/favorites/destroy
87 @feature
88@@ -314,6 +315,8 @@
89 """Renounce your undying love for a tweet."""
90 url = self._api_base.format(endpoint='favorites/destroy')
91 self._get_url(url, dict(id=message_id))
92+ self._dec_cell(message_id, 'likes')
93+ self._set_cell(message_id, 'liked', False)
94 return message_id
95
96 # https://dev.twitter.com/docs/api/1.1/get/search/tweets
97
98=== modified file 'friends/tests/test_facebook.py'
99--- friends/tests/test_facebook.py 2013-03-16 00:54:45 +0000
100+++ friends/tests/test_facebook.py 2013-04-02 21:50:28 +0000
101@@ -269,7 +269,8 @@
102 'http://facebook.com/post_id')
103
104 token.assert_called_once_with()
105- publish.assert_called_with({'id': 'post_id'})
106+ publish.assert_called_with(entry={'id': 'post_id'},
107+ stream='messages')
108 self.assertEqual(
109 dload.mock_calls,
110 [mock.call(),
111@@ -298,7 +299,8 @@
112 'http://facebook.com/new_post_id')
113
114 token.assert_called_once_with()
115- publish.assert_called_with({'id': 'post_id'})
116+ publish.assert_called_with(entry={'id': 'post_id'},
117+ stream='messages')
118 self.assertEqual(
119 dload.mock_calls,
120 [mock.call(),
121@@ -327,7 +329,8 @@
122 'http://facebook.com/private_message_id')
123
124 token.assert_called_once_with()
125- publish.assert_called_with({'id': 'comment_id'})
126+ publish.assert_called_with(entry={'id': 'comment_id'},
127+ stream='reply_to/post_id')
128 self.assertEqual(
129 dload.mock_calls,
130 [mock.call(),
131@@ -427,9 +430,13 @@
132 dload().get_json.return_value = True
133 token = self.protocol._get_access_token = mock.Mock(
134 return_value='face')
135+ inc_cell = self.protocol._inc_cell = mock.Mock()
136+ set_cell = self.protocol._set_cell = mock.Mock()
137
138 self.assertEqual(self.protocol.like('post_id'), 'post_id')
139
140+ inc_cell.assert_called_once_with('post_id', 'likes')
141+ set_cell.assert_called_once_with('post_id', 'liked', True)
142 token.assert_called_once_with()
143 dload.assert_called_with(
144 'https://graph.facebook.com/post_id/likes',
145@@ -441,9 +448,13 @@
146 dload.get_json.return_value = True
147 token = self.protocol._get_access_token = mock.Mock(
148 return_value='face')
149+ dec_cell = self.protocol._dec_cell = mock.Mock()
150+ set_cell = self.protocol._set_cell = mock.Mock()
151
152 self.assertEqual(self.protocol.unlike('post_id'), 'post_id')
153
154+ dec_cell.assert_called_once_with('post_id', 'likes')
155+ set_cell.assert_called_once_with('post_id', 'liked', False)
156 token.assert_called_once_with()
157 dload.assert_called_once_with(
158 'https://graph.facebook.com/post_id/likes',
159
160=== modified file 'friends/tests/test_identica.py'
161--- friends/tests/test_identica.py 2013-03-14 19:14:03 +0000
162+++ friends/tests/test_identica.py 2013-04-02 21:50:28 +0000
163@@ -150,7 +150,7 @@
164 '1234',
165 'Why yes, I would love to respond to your tweet @pumpichank!')
166
167- publish.assert_called_with('tweet')
168+ publish.assert_called_with('tweet', stream='reply_to/1234')
169 get_url.assert_called_with(
170 'http://identi.ca/api/statuses/update.json',
171 dict(status=
172
173=== modified file 'friends/tests/test_protocols.py'
174--- friends/tests/test_protocols.py 2013-03-19 00:43:13 +0000
175+++ friends/tests/test_protocols.py 2013-04-02 21:50:28 +0000
176@@ -370,6 +370,55 @@
177 self.assertEqual(TestModel.get_row(1)[COLUMN_INDICES['sender']],
178 'tedtholomew')
179
180+ @mock.patch('friends.utils.base.Model', TestModel)
181+ @mock.patch('friends.utils.base._seen_ids', {})
182+ def test_inc_cell(self):
183+ base = Base(FakeAccount())
184+ self.assertEqual(0, TestModel.get_n_rows())
185+ self.assertTrue(base._publish(message_id='1234', likes=10, liked=True))
186+ base._inc_cell('1234', 'likes')
187+ row = TestModel.get_row(0)
188+ self.assertEqual(
189+ list(row),
190+ ['base', 88, '1234', '', '', '', '', False, '', '', '', '', 11,
191+ True, '', '', '', '', '', '', '', 0.0, 0.0])
192+
193+ @mock.patch('friends.utils.base.Model', TestModel)
194+ @mock.patch('friends.utils.base._seen_ids', {})
195+ def test_dec_cell(self):
196+ base = Base(FakeAccount())
197+ self.assertEqual(0, TestModel.get_n_rows())
198+ self.assertTrue(base._publish(message_id='1234', likes=10, liked=True))
199+ base._dec_cell('1234', 'likes')
200+ row = TestModel.get_row(0)
201+ self.assertEqual(
202+ list(row),
203+ ['base', 88, '1234', '', '', '', '', False, '', '', '', '', 9,
204+ True, '', '', '', '', '', '', '', 0.0, 0.0])
205+
206+ @mock.patch('friends.utils.base.Model', TestModel)
207+ @mock.patch('friends.utils.base._seen_ids', {})
208+ def test_set_cell(self):
209+ base = Base(FakeAccount())
210+ self.assertEqual(0, TestModel.get_n_rows())
211+ self.assertTrue(base._publish(message_id='1234', likes=10, liked=True))
212+ base._set_cell('1234', 'likes', 500)
213+ base._set_cell('1234', 'liked', False)
214+ base._set_cell('1234', 'message_id', '5678')
215+ row = TestModel.get_row(0)
216+ self.assertEqual(
217+ list(row),
218+ ['base', 88, '5678', '', '', '', '', False, '', '', '', '', 500,
219+ False, '', '', '', '', '', '', '', 0.0, 0.0])
220+
221+ @mock.patch('friends.utils.base.Model', TestModel)
222+ @mock.patch('friends.utils.base._seen_ids', {})
223+ def test_fetch_cell(self):
224+ base = Base(FakeAccount())
225+ self.assertEqual(0, TestModel.get_n_rows())
226+ self.assertTrue(base._publish(message_id='1234', likes=10, liked=True))
227+ self.assertEqual(base._fetch_cell('1234', 'likes'), 10)
228+
229 def test_basic_login(self):
230 # Try to log in twice. The second login attempt returns False because
231 # it's already logged in.
232
233=== modified file 'friends/tests/test_twitter.py'
234--- friends/tests/test_twitter.py 2013-03-19 04:17:58 +0000
235+++ friends/tests/test_twitter.py 2013-04-02 21:50:28 +0000
236@@ -383,7 +383,7 @@
237 'Why yes, I would love to respond to your tweet @pumpichank!'),
238 'tweet permalink')
239
240- publish.assert_called_with('tweet')
241+ publish.assert_called_with('tweet', stream='reply_to/1234')
242 get_url.assert_called_with(
243 'https://api.twitter.com/1.1/statuses/update.json',
244 dict(status='Why yes, I would love to respond to your '
245@@ -471,18 +471,26 @@
246
247 def test_like(self):
248 get_url = self.protocol._get_url = mock.Mock()
249+ inc_cell = self.protocol._inc_cell = mock.Mock()
250+ set_cell = self.protocol._set_cell = mock.Mock()
251
252 self.assertEqual(self.protocol.like('1234'), '1234')
253
254+ inc_cell.assert_called_once_with('1234', 'likes')
255+ set_cell.assert_called_once_with('1234', 'liked', True)
256 get_url.assert_called_with(
257 'https://api.twitter.com/1.1/favorites/create.json',
258 dict(id='1234'))
259
260 def test_unlike(self):
261 get_url = self.protocol._get_url = mock.Mock()
262+ dec_cell = self.protocol._dec_cell = mock.Mock()
263+ set_cell = self.protocol._set_cell = mock.Mock()
264
265 self.assertEqual(self.protocol.unlike('1234'), '1234')
266
267+ dec_cell.assert_called_once_with('1234', 'likes')
268+ set_cell.assert_called_once_with('1234', 'liked', False)
269 get_url.assert_called_with(
270 'https://api.twitter.com/1.1/favorites/destroy.json',
271 dict(id='1234'))
272
273=== modified file 'friends/utils/base.py'
274--- friends/utils/base.py 2013-03-20 01:19:39 +0000
275+++ friends/utils/base.py 2013-04-02 21:50:28 +0000
276@@ -503,15 +503,33 @@
277 message = None
278 raise FriendsError(message or str(error))
279
280+ def _calculate_row_cell(self, message_id, column_name):
281+ """Find x,y coords in the model based on message_id and column_name."""
282+ row_id = _seen_ids.get(message_id)
283+ col_idx = COLUMN_INDICES.get(column_name)
284+ if None in (row_id, col_idx):
285+ raise FriendsError('Cell could not be found.')
286+ return row_id, col_idx
287+
288 def _fetch_cell(self, message_id, column_name):
289 """Find a column value associated with a specific message_id."""
290- row_id = _seen_ids.get(message_id)
291- col_idx = COLUMN_INDICES.get(column_name)
292- if None not in (row_id, col_idx):
293- row = Model.get_row(row_id)
294- return row[col_idx]
295- else:
296- raise FriendsError('Value could not be found.')
297+ row_id, col_idx = self._calculate_row_cell(message_id, column_name)
298+ return Model.get_row(row_id)[col_idx]
299+
300+ def _set_cell(self, message_id, column_name, value):
301+ """Set a column value associated with a specific message_id."""
302+ row_id, col_idx = self._calculate_row_cell(message_id, column_name)
303+ Model.get_row(row_id)[col_idx] = value
304+
305+ def _inc_cell(self, message_id, column_name):
306+ """Increment a column value associated with a specific message_id."""
307+ row_id, col_idx = self._calculate_row_cell(message_id, column_name)
308+ Model.get_row(row_id)[col_idx] += 1
309+
310+ def _dec_cell(self, message_id, column_name):
311+ """Decrement a column value associated with a specific message_id."""
312+ row_id, col_idx = self._calculate_row_cell(message_id, column_name)
313+ Model.get_row(row_id)[col_idx] -= 1
314
315 def _new_book_client(self, source):
316 client = EBook.BookClient.new(source)

Subscribers

People subscribed via source and target branches