Merge lp:~robru/friends/simplify-contacts into lp:friends

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
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 :

PASSED: Continuous integration, rev:237
http://jenkins.qa.ubuntu.com/job/friends-ci/51/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/friends-saucy-amd64-ci/8

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

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

Subscribers

People subscribed via source and target branches