Merge lp:~robru/friends/simplify-contacts into lp:friends
- simplify-contacts
- Merge into trunk
Proposed by
Robert Bruce Park
Status: | Merged |
---|---|
Approved by: | Robert Bruce Park |
Approved revision: | 237 |
Merged at revision: | 227 |
Proposed branch: | lp:~robru/friends/simplify-contacts |
Merge into: | lp:friends |
Diff against target: |
443 lines (+120/-175) 8 files modified
friends/protocols/facebook.py (+13/-17) friends/protocols/linkedin.py (+7/-11) friends/protocols/twitter.py (+6/-11) friends/tests/test_facebook.py (+55/-63) friends/tests/test_identica.py (+4/-14) friends/tests/test_linkedin.py (+4/-28) friends/tests/test_twitter.py (+4/-14) friends/utils/base.py (+27/-17) |
To merge this branch: | bzr merge lp:~robru/friends/simplify-contacts |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Ken VanDine | Pending | ||
Review via email: mp+176338@code.launchpad.net |
Commit message
More explicit call signature for Base._push_to_eds.
Description of the change
So, re-reviewing the code after the last merge, I realized that I could take it even a step further in simplification ;-)
Now instead of passing an obscure dict as the argument to _push_to_eds, I've broken that down into explicit arguments, so that the call signature is significantly more scrutible. Note how the three protocols that call _push_to_eds now have to supply significantly less boilerplate, and it reads much more clearly.
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Approve
(continuous-integration)
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-07-20 02:21:49 +0000 |
3 | +++ friends/protocols/facebook.py 2013-07-23 08:22:30 +0000 |
4 | @@ -328,30 +328,26 @@ |
5 | |
6 | @feature |
7 | def contacts(self): |
8 | + access_token=self._get_access_token() |
9 | contacts = self._follow_pagination( |
10 | url=ME_URL + '/friends', |
11 | - params=dict(access_token=self._get_access_token(), limit=1000), |
12 | + params=dict(access_token=access_token, limit=1000), |
13 | limit=1000) |
14 | log.debug('Found {} contacts'.format(len(contacts))) |
15 | |
16 | for contact in contacts: |
17 | contact_id = contact.get('id') |
18 | - if contact_id is None or self._previously_stored_contact(contact_id): |
19 | - continue |
20 | - full_contact = Downloader( |
21 | - url=API_BASE.format(id=contact_id), |
22 | - params=dict(access_token=self._get_access_token())).get_json() |
23 | - self._push_to_eds({ |
24 | - 'facebook-id': contact_id, |
25 | - 'facebook-name': full_contact.get('name'), |
26 | - 'facebook-nick': full_contact.get('username'), |
27 | - 'X-URIS': full_contact.get('link'), |
28 | - 'X-GENDER': full_contact.get('gender'), |
29 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
30 | - 'jabber': '-{}@chat.facebook.com'.format(contact_id), |
31 | - 'remote-full-name': full_contact.get('name'), |
32 | - 'facebook-id': contact_id, |
33 | - }}) |
34 | + if not self._previously_stored_contact(contact_id): |
35 | + full_contact = Downloader( |
36 | + url=API_BASE.format(id=contact_id), |
37 | + params=dict(access_token=access_token)).get_json() |
38 | + self._push_to_eds( |
39 | + uid=contact_id, |
40 | + name=full_contact.get('name'), |
41 | + nick=full_contact.get('username'), |
42 | + link=full_contact.get('link'), |
43 | + gender=full_contact.get('gender'), |
44 | + jabber='-{}@chat.facebook.com'.format(contact_id)) |
45 | |
46 | return len(contacts) |
47 | |
48 | |
49 | === modified file 'friends/protocols/linkedin.py' |
50 | --- friends/protocols/linkedin.py 2013-07-20 02:21:49 +0000 |
51 | +++ friends/protocols/linkedin.py 2013-07-23 08:22:30 +0000 |
52 | @@ -113,17 +113,13 @@ |
53 | ).get_json().get('values', []) |
54 | |
55 | for connection in connections: |
56 | - connection_id = connection.get('id', 'private') |
57 | + uid = connection.get('id', 'private') |
58 | fullname = make_fullname(**connection) |
59 | - if connection_id != 'private' and not self._previously_stored_contact(connection_id): |
60 | - self._push_to_eds({ |
61 | - 'linkedin-id': connection_id, |
62 | - 'linkedin-name': fullname, |
63 | - 'X-URIS': connection.get( |
64 | - 'siteStandardProfileRequest', {}).get('url'), |
65 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
66 | - 'remote-full-name': fullname, |
67 | - 'linkedin-id': connection_id, |
68 | - }}) |
69 | + if uid != 'private' and not self._previously_stored_contact(uid): |
70 | + self._push_to_eds( |
71 | + uid=uid, |
72 | + name=fullname, |
73 | + link=connection.get( |
74 | + 'siteStandardProfileRequest', {}).get('url')) |
75 | |
76 | return len(connections) |
77 | |
78 | === modified file 'friends/protocols/twitter.py' |
79 | --- friends/protocols/twitter.py 2013-07-20 02:55:28 +0000 |
80 | +++ friends/protocols/twitter.py 2013-07-23 08:22:30 +0000 |
81 | @@ -364,17 +364,12 @@ |
82 | # https://dev.twitter.com/docs/api/1.1/get/users/show |
83 | full_contact = self._get_url(url=self._api_base.format( |
84 | endpoint='users/show') + '?user_id=' + contact_id) |
85 | - user_fullname = full_contact.get('name') |
86 | - user_nickname = full_contact.get('screen_name') |
87 | - self._push_to_eds({ |
88 | - '{}-id'.format(self._name): contact_id, |
89 | - '{}-name'.format(self._name): user_fullname, |
90 | - '{}-nick'.format(self._name): user_nickname, |
91 | - 'X-URIS': self._user_home.format(user_id=user_nickname), |
92 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
93 | - 'remote-full-name': user_fullname, |
94 | - '{}-id'.format(self._name): contact_id, |
95 | - }}) |
96 | + user_nickname = full_contact.get('screen_name', '') |
97 | + self._push_to_eds( |
98 | + uid=contact_id, |
99 | + name=full_contact.get('name'), |
100 | + nick=user_nickname, |
101 | + link=self._user_home.format(user_id=user_nickname)) |
102 | return len(contacts) |
103 | |
104 | |
105 | |
106 | === modified file 'friends/tests/test_facebook.py' |
107 | --- friends/tests/test_facebook.py 2013-07-20 03:23:41 +0000 |
108 | +++ friends/tests/test_facebook.py 2013-07-23 08:22:30 +0000 |
109 | @@ -26,7 +26,7 @@ |
110 | import unittest |
111 | import shutil |
112 | |
113 | -from gi.repository import GLib, EDataServer |
114 | +from gi.repository import GLib, EDataServer, EBookContacts |
115 | from pkg_resources import resource_filename |
116 | |
117 | from friends.protocols.facebook import Facebook |
118 | @@ -501,69 +501,61 @@ |
119 | params={'access_token': 'broken'})]) |
120 | self.assertEqual( |
121 | push.call_args_list, |
122 | - [mock.call({ |
123 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
124 | - 'jabber': '-contact1@chat.facebook.com', |
125 | - 'facebook-id': 'contact1', |
126 | - 'remote-full-name': 'Joe Blow'}, |
127 | - 'X-GENDER': 'male', |
128 | - 'facebook-name': 'Joe Blow', |
129 | - 'facebook-id': 'contact1', |
130 | - 'X-URIS': 'example.com', |
131 | - 'facebook-nick': 'jblow'}), |
132 | - mock.call({ |
133 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
134 | - 'jabber': '-contact2@chat.facebook.com', |
135 | - 'facebook-id': 'contact2', |
136 | - 'remote-full-name': 'Joe Blow'}, |
137 | - 'X-GENDER': 'male', |
138 | - 'facebook-name': 'Joe Blow', |
139 | - 'facebook-id': 'contact2', |
140 | - 'X-URIS': 'example.com', |
141 | - 'facebook-nick': 'jblow'})]) |
142 | + [mock.call(gender='male', jabber='-contact1@chat.facebook.com', |
143 | + nick='jblow', link='example.com', name='Joe Blow', |
144 | + uid='contact1'), |
145 | + mock.call(gender='male', jabber='-contact2@chat.facebook.com', |
146 | + nick='jblow', link='example.com', name='Joe Blow', |
147 | + uid='contact2')]) |
148 | |
149 | def test_create_contact(self, *mocks): |
150 | # Receive the users friends. |
151 | - bare_contact = { |
152 | - 'facebook-id': '555555555', |
153 | - 'facebook-name': 'Lucy Baron', |
154 | - 'facebook-nick': 'lucy.baron5', |
155 | - 'X-URIS': 'http:www.facebook.com/lucy.baron5', |
156 | - 'X-GENDER': 'female', |
157 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
158 | - 'jabber': '-555555555@chat.facebook.com', |
159 | - 'remote-full-name': 'Lucy Baron', |
160 | - 'facebook-id': '555555555', |
161 | - }} |
162 | - eds_contact = self.protocol._create_contact(bare_contact) |
163 | + eds_contact = self.protocol._create_contact( |
164 | + uid='555555555', |
165 | + name='Lucy Baron', |
166 | + nick='lucy.baron5', |
167 | + gender='female', |
168 | + link='http:www.facebook.com/lucy.baron5', |
169 | + jabber='-555555555@chat.facebook.com') |
170 | facebook_id_attr = eds_contact.get_attribute('facebook-id') |
171 | self.assertEqual(facebook_id_attr.get_value(), '555555555') |
172 | - facebook_name_attr = eds_contact.get_attribute('facebook-name') |
173 | - self.assertEqual(facebook_name_attr.get_value(), 'Lucy Baron') |
174 | web_service_addrs = eds_contact.get_attribute('X-FOLKS-WEB-SERVICES-IDS') |
175 | params= web_service_addrs.get_params() |
176 | - |
177 | - self.assertEqual(len(params), 3) |
178 | - |
179 | - test_jabber = False |
180 | - test_remote_name = False |
181 | - test_facebook_id = False |
182 | - |
183 | - for p in params: |
184 | - if p.get_name() == 'jabber': |
185 | - self.assertEqual(len(p.get_values()), 1) |
186 | - self.assertEqual(p.get_values()[0], '-555555555@chat.facebook.com') |
187 | - test_jabber = True |
188 | - if p.get_name() == 'remote-full-name': |
189 | - self.assertEqual(len(p.get_values()), 1) |
190 | - self.assertEqual(p.get_values()[0], 'Lucy Baron') |
191 | - test_remote_name = True |
192 | - if p.get_name() == 'facebook-id': |
193 | - self.assertEqual(len(p.get_values()), 1) |
194 | - self.assertEqual(p.get_values()[0], '555555555') |
195 | - test_facebook_id = True |
196 | - # Finally test to ensure all key value pairs were tested |
197 | - self.assertTrue(test_jabber and test_remote_name and test_facebook_id) |
198 | + self.assertEqual(len(params), 5) |
199 | + |
200 | + # Can't compare the vcard string directly because it is sorted randomly... |
201 | + vcard = eds_contact.to_string( |
202 | + EBookContacts.VCardFormat(1)).replace('\r\n ', '') |
203 | + self.assertIn( |
204 | + 'social-networking-attributes.X-URIS:http:www.facebook.com/lucy.baron5', |
205 | + vcard) |
206 | + self.assertIn( |
207 | + 'social-networking-attributes.X-GENDER:female', |
208 | + vcard) |
209 | + self.assertIn( |
210 | + 'social-networking-attributes.facebook-id:555555555', |
211 | + vcard) |
212 | + self.assertIn( |
213 | + 'FN:Lucy Baron', |
214 | + vcard) |
215 | + self.assertIn( |
216 | + 'NICKNAME:lucy.baron5', |
217 | + vcard) |
218 | + self.assertIn( |
219 | + 'social-networking-attributes.X-FOLKS-WEB-SERVICES-IDS;', |
220 | + vcard) |
221 | + self.assertIn( |
222 | + 'remote-full-name="Lucy Baron"', |
223 | + vcard) |
224 | + self.assertIn( |
225 | + 'facebook-id=555555555', |
226 | + vcard) |
227 | + self.assertIn( |
228 | + 'jabber="-555555555@chat.facebook.com"', |
229 | + vcard) |
230 | + self.assertIn( |
231 | + 'facebook-nick="lucy.baron5"', |
232 | + vcard) |
233 | |
234 | @mock.patch('friends.utils.base.Base._prepare_eds_connections', |
235 | return_value=True) |
236 | @@ -571,21 +563,21 @@ |
237 | return_value=EDSBookClientMock()) |
238 | def test_successfull_push_to_eds(self, *mocks): |
239 | bare_contact = {'name': 'Lucy Baron', |
240 | - 'id': '555555555', |
241 | - 'username': 'lucy.baron5', |
242 | + 'uid': '555555555', |
243 | + 'nick': 'lucy.baron5', |
244 | 'link': 'http:www.facebook.com/lucy.baron5'} |
245 | self.protocol._address_book = 'test-address-book' |
246 | client = self.protocol._book_client = mock.Mock() |
247 | client.add_contact_sync.return_value = True |
248 | # Implicitely fail test if the following raises any exceptions |
249 | - self.protocol._push_to_eds(bare_contact) |
250 | + self.protocol._push_to_eds(**bare_contact) |
251 | |
252 | @mock.patch('friends.utils.base.Base._prepare_eds_connections', |
253 | return_value=None) |
254 | def test_unsuccessfull_push_to_eds(self, *mocks): |
255 | bare_contact = {'name': 'Lucy Baron', |
256 | - 'id': '555555555', |
257 | - 'username': 'lucy.baron5', |
258 | + 'uid': '555555555', |
259 | + 'nick': 'lucy.baron5', |
260 | 'link': 'http:www.facebook.com/lucy.baron5'} |
261 | self.protocol._address_book = 'test-address-book' |
262 | client = self.protocol._book_client = mock.Mock() |
263 | @@ -593,7 +585,7 @@ |
264 | self.assertRaises( |
265 | ContactsError, |
266 | self.protocol._push_to_eds, |
267 | - bare_contact, |
268 | + **bare_contact |
269 | ) |
270 | |
271 | @mock.patch('gi.repository.EBook.BookClient.connect_sync', |
272 | |
273 | === modified file 'friends/tests/test_identica.py' |
274 | --- friends/tests/test_identica.py 2013-07-20 03:23:41 +0000 |
275 | +++ friends/tests/test_identica.py 2013-07-23 08:22:30 +0000 |
276 | @@ -263,17 +263,7 @@ |
277 | [mock.call('1'), mock.call('2')]) |
278 | self.assertEqual( |
279 | push.call_args_list, |
280 | - [mock.call({'identica-id': '1', |
281 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
282 | - 'identica-id': '1', |
283 | - 'remote-full-name': 'Bob'}, |
284 | - 'X-URIS': 'https://identi.ca/bobby', |
285 | - 'identica-name': 'Bob', |
286 | - 'identica-nick': 'bobby'}), |
287 | - mock.call({'identica-id': '2', |
288 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
289 | - 'identica-id': '2', |
290 | - 'remote-full-name': 'Bob'}, |
291 | - 'X-URIS': 'https://identi.ca/bobby', |
292 | - 'identica-name': 'Bob', |
293 | - 'identica-nick': 'bobby'})]) |
294 | + [mock.call(link='https://identi.ca/bobby', nick='bobby', |
295 | + uid='1', name='Bob'), |
296 | + mock.call(link='https://identi.ca/bobby', nick='bobby', |
297 | + uid='2', name='Bob')]) |
298 | |
299 | === modified file 'friends/tests/test_linkedin.py' |
300 | --- friends/tests/test_linkedin.py 2013-07-20 01:38:59 +0000 |
301 | +++ friends/tests/test_linkedin.py 2013-07-23 08:22:30 +0000 |
302 | @@ -122,31 +122,7 @@ |
303 | self.assertEqual(self.protocol.contacts(), 4) |
304 | self.assertEqual( |
305 | push.mock_calls, |
306 | - [mock.call( |
307 | - {'X-URIS': 'https://www.linkedin.com', |
308 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
309 | - 'linkedin-id': 'IFDI', |
310 | - 'remote-full-name': 'H A'}, |
311 | - 'linkedin-name': 'H A', |
312 | - 'linkedin-id': 'IFDI'}), |
313 | - mock.call( |
314 | - {'X-URIS': 'https://www.linkedin.com', |
315 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
316 | - 'linkedin-id': 'AefF', |
317 | - 'remote-full-name': 'C A'}, |
318 | - 'linkedin-name': 'C A', |
319 | - 'linkedin-id': 'AefF'}), |
320 | - mock.call( |
321 | - {'X-URIS': 'https://www.linkedin.com', |
322 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
323 | - 'linkedin-id': 'DFdV', |
324 | - 'remote-full-name': 'R A'}, |
325 | - 'linkedin-name': 'R A', |
326 | - 'linkedin-id': 'DFdV'}), |
327 | - mock.call( |
328 | - {'X-URIS': 'https://www.linkedin.com', |
329 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
330 | - 'linkedin-id': 'xkBU', |
331 | - 'remote-full-name': 'A Z'}, |
332 | - 'linkedin-name': 'A Z', |
333 | - 'linkedin-id': 'xkBU'})]) |
334 | + [mock.call(link='https://www.linkedin.com', name='H A', uid='IFDI'), |
335 | + mock.call(link='https://www.linkedin.com', name='C A', uid='AefF'), |
336 | + mock.call(link='https://www.linkedin.com', name='R A', uid='DFdV'), |
337 | + mock.call(link='https://www.linkedin.com', name='A Z', uid='xkBU')]) |
338 | |
339 | === modified file 'friends/tests/test_twitter.py' |
340 | --- friends/tests/test_twitter.py 2013-07-20 03:23:41 +0000 |
341 | +++ friends/tests/test_twitter.py 2013-07-23 08:22:30 +0000 |
342 | @@ -714,17 +714,7 @@ |
343 | [mock.call('1'), mock.call('2')]) |
344 | self.assertEqual( |
345 | push.call_args_list, |
346 | - [mock.call({'twitter-id': '1', |
347 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
348 | - 'twitter-id': '1', |
349 | - 'remote-full-name': 'Bob'}, |
350 | - 'X-URIS': 'https://twitter.com/bobby', |
351 | - 'twitter-name': 'Bob', |
352 | - 'twitter-nick': 'bobby'}), |
353 | - mock.call({'twitter-id': '2', |
354 | - 'X-FOLKS-WEB-SERVICES-IDS': { |
355 | - 'twitter-id': '2', |
356 | - 'remote-full-name': 'Bob'}, |
357 | - 'X-URIS': 'https://twitter.com/bobby', |
358 | - 'twitter-name': 'Bob', |
359 | - 'twitter-nick': 'bobby'})]) |
360 | + [mock.call(link='https://twitter.com/bobby', uid='1', |
361 | + name='Bob', nick='bobby'), |
362 | + mock.call(link='https://twitter.com/bobby', uid='2', |
363 | + name='Bob', nick='bobby')]) |
364 | |
365 | === modified file 'friends/utils/base.py' |
366 | --- friends/utils/base.py 2013-07-20 02:21:49 +0000 |
367 | +++ friends/utils/base.py 2013-07-23 08:22:30 +0000 |
368 | @@ -568,48 +568,58 @@ |
369 | if self._eds_source is not None: |
370 | self._book_client = EBook.BookClient.connect_sync(self._eds_source, None) |
371 | |
372 | - def _push_to_eds(self, contact): |
373 | + def _push_to_eds(self, **contact_details): |
374 | self._prepare_eds_connections() |
375 | - contact = self._create_contact(contact) |
376 | + contact = self._create_contact(**contact_details) |
377 | if not self._book_client.add_contact_sync(contact, None): |
378 | raise ContactsError('Failed to save contact {!r}'.format(contact)) |
379 | |
380 | def _previously_stored_contact(self, search_term): |
381 | self._prepare_eds_connections() |
382 | query = EBookContacts.BookQuery.vcard_field_test( |
383 | - '{}-id'.format(self._name), EBookContacts.BookQueryTest.IS, search_term) |
384 | + self._name + '-id', EBookContacts.BookQueryTest.IS, search_term) |
385 | success, result = self._book_client.get_contacts_sync(query.to_string(), None) |
386 | if not success: |
387 | raise ContactsError( |
388 | 'Id field is missing in {} address book.'.format(self._Name)) |
389 | return len(result) > 0 |
390 | |
391 | - def _create_contact(self, info): |
392 | + def _create_contact(self, uid, name, nick=None, link=None, gender=None, **folks): |
393 | """Build a VCard based on a dict representation of a contact.""" |
394 | contact = EBookContacts.Contact.new() |
395 | |
396 | - for key, value in info.items(): |
397 | + folks.update({ |
398 | + 'remote-full-name': name, |
399 | + self._name + '-id': uid, |
400 | + self._name + '-name': name, |
401 | + self._name + '-nick': nick, |
402 | + }) |
403 | + |
404 | + def add_attr(key, value=None, **params): |
405 | attr = EBookContacts.VCardAttribute.new( |
406 | 'social-networking-attributes', key) |
407 | - if hasattr(value, 'items'): |
408 | - for subkey, subval in value.items(): |
409 | + if value is not None: |
410 | + attr.add_value(value) |
411 | + elif params: |
412 | + for subkey, subval in params.items(): |
413 | if subval is not None: |
414 | param = EBookContacts.VCardAttributeParam.new(subkey) |
415 | param.add_value(subval) |
416 | attr.add_param(param); |
417 | - elif value is not None: |
418 | - attr.add_value(value) |
419 | else: |
420 | - continue |
421 | + return |
422 | contact.add_attribute(attr) |
423 | |
424 | - properties = (('full-name', '{}-name'), |
425 | - ('nickname', '{}-nick')) |
426 | - for prop, key in properties: |
427 | - value = info.get(key.format(self._name)) |
428 | - if value is not None: |
429 | - contact.set_property(prop, value) |
430 | - log.debug('New contact got {}: {}'.format(prop, value)) |
431 | + add_attr(self._name + '-id', uid) |
432 | + add_attr('X-GENDER', gender) |
433 | + add_attr('X-URIS', link) |
434 | + add_attr('X-FOLKS-WEB-SERVICES-IDS', **folks) |
435 | + |
436 | + contact.set_property('full-name', name) |
437 | + if nick: |
438 | + contact.set_property('nickname', nick) |
439 | + # X-TREME debugging! |
440 | + # print(contact.to_string(EBookContacts.VCardFormat(1))) |
441 | return contact |
442 | |
443 | @feature |
PASSED: Continuous integration, rev:237 jenkins. qa.ubuntu. com/job/ friends- ci/51/ jenkins. qa.ubuntu. com/job/ friends- saucy-amd64- ci/8
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ friends- ci/51/rebuild
http://