Merge lp:~robru/gwibber/accounts-fixup into lp:~barry/gwibber/py3
- accounts-fixup
- Merge into py3
Status: | Merged |
---|---|
Merged at revision: | 1438 |
Proposed branch: | lp:~robru/gwibber/accounts-fixup |
Merge into: | lp:~barry/gwibber/py3 |
Diff against target: |
630 lines (+122/-104) 13 files modified
gwibber/gwibber/protocols/flickr.py (+9/-12) gwibber/gwibber/protocols/foursquare.py (+8/-8) gwibber/gwibber/service/dispatcher.py (+2/-3) gwibber/gwibber/testing/helpers.py (+11/-3) gwibber/gwibber/testing/mocks.py (+1/-1) gwibber/gwibber/tests/test_account.py (+21/-28) gwibber/gwibber/tests/test_authentication.py (+9/-6) gwibber/gwibber/tests/test_flickr.py (+9/-10) gwibber/gwibber/tests/test_foursquare.py (+5/-5) gwibber/gwibber/tests/test_protocols.py (+2/-6) gwibber/gwibber/utils/account.py (+35/-14) gwibber/gwibber/utils/authentication.py (+3/-3) gwibber/gwibber/utils/base.py (+7/-5) |
To merge this branch: | bzr merge lp:~robru/gwibber/accounts-fixup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Barry Warsaw | Needs Fixing | ||
Review via email: mp+125593@code.launchpad.net |
Commit message
Description of the change
Account class no longer inherits from dict.
The important keys that I was able to identify have been upgraded to instance attributes, I've created an Account.config dict as a catch-all to hold anything else that we may not yet know about, and I put in a logger so that one day we may record in action the full gamut of what values to expect here. It might actually turn out that the config dict is entirely irrelevant, but I'm not sure enough of that to do away with it just yet.
This merge makes changes to the flickr and foursquare plugins so that they can correctly access the new Account attributes, and of course all manner of test fixups as well.
This one should be relatively uncontroversial. The only thing that might be confusing is that I did away with Account.global_id because that information is contained within the new Account.id.
- 1442. By Robert Bruce Park
-
Fixup some access token stuff that got lost in the shuffle.
- 1443. By Robert Bruce Park
-
Fix up some tests that were still testing against dict behavior.
- 1444. By Robert Bruce Park
-
Ok, for real this time, there are NO MORE things using account['dict_key'] syntax.
I confirmed it with grep -R this time ;-)
Robert Bruce Park (robru) wrote : | # |
Barry Warsaw (barry) wrote : | # |
Setting to Needs Fixing because there are some conflicts against lp:~barry/gwibber/py3 now. Sorry about that, but I think you're the best person to resolve those conflicts. Please do so and I'll review/merge.
- 1445. By Robert Bruce Park
-
Rebased onto barry's updated branch, fixup Base._account and Account.id.
All tests pass.
- 1446. By Robert Bruce Park
-
Abolish Account.config and upgrade all it's members to instance attributes.
Preview Diff
1 | === modified file 'gwibber/gwibber/protocols/flickr.py' |
2 | --- gwibber/gwibber/protocols/flickr.py 2012-09-20 22:17:28 +0000 |
3 | +++ gwibber/gwibber/protocols/flickr.py 2012-09-21 16:43:27 +0000 |
4 | @@ -54,28 +54,25 @@ |
5 | :return: The id, called NSID by the Flickr service. |
6 | :rtype: str |
7 | """ |
8 | - if 'user_nsid' not in self.account: |
9 | + if self._account.user_id is None: |
10 | # Try to log in. |
11 | if not self._login(): |
12 | return None |
13 | - return self.account.get('user_nsid') |
14 | + return self._account.user_id |
15 | |
16 | def _locked_login(self, old_token): |
17 | if old_token is None: |
18 | log.debug('Logging into Flickr') |
19 | else: |
20 | log.debug('Re-authenticating to Flickr') |
21 | - result = Authentication(self.account).login() |
22 | + result = Authentication(self._account).login() |
23 | if result is None: |
24 | log.error('No Flickr authentication results received') |
25 | elif 'AccessToken' in result: |
26 | - massaged_result = Results() |
27 | - massaged_result.update_from( |
28 | - result, |
29 | - 'username', 'user_nsid', 'AccessToken', 'TokenSecret', |
30 | - AccessToken='access_token', |
31 | - TokenSecret='secret_token') |
32 | - self.account.update(massaged_result) |
33 | + self._account.user_name = result.get('username') |
34 | + self._account.user_id = result.get('user_nsid') |
35 | + self._account.access_token = result.get('AccessToken') |
36 | + self._account.secret_token = result.get('TokenSecret') |
37 | log.debug('Flickr AccessToken received') |
38 | else: |
39 | log.error('No AccessToken in Flickr session: {!r}', result) |
40 | @@ -105,7 +102,7 @@ |
41 | if 'text' in results: |
42 | results['content'] = results['html'] = results['text'] |
43 | results['service'] = 'flickr' |
44 | - results['account'] = self.account.get('id') |
45 | + results['account'] = self._account.id |
46 | raw_time = data.get('dateupload') |
47 | if raw_time is not None: |
48 | try: |
49 | @@ -142,7 +139,7 @@ |
50 | if user_id is None: |
51 | log.error('Flickr: No NSID available') |
52 | raise AuthorizationError( |
53 | - self.account['id'], 'No Flickr user id available') |
54 | + self._account.id, 'No Flickr user id available') |
55 | # The user is logged into Flickr. |
56 | GET_arguments = dict( |
57 | api_key = API_KEY, |
58 | |
59 | === modified file 'gwibber/gwibber/protocols/foursquare.py' |
60 | --- gwibber/gwibber/protocols/foursquare.py 2012-09-20 16:26:53 +0000 |
61 | +++ gwibber/gwibber/protocols/foursquare.py 2012-09-21 16:43:27 +0000 |
62 | @@ -59,7 +59,7 @@ |
63 | log.debug('{} to FourSquare'.format( |
64 | 'Re-authenticating' if old_token else 'Logging in')) |
65 | |
66 | - result = Authentication(self.account, log).login() |
67 | + result = Authentication(self._account, log).login() |
68 | if result is None: |
69 | log.error('No FourSquare authentication results received.') |
70 | return |
71 | @@ -71,27 +71,27 @@ |
72 | data = get_json(SELF_URL.format(access_token=token)) or {} |
73 | user = data.get('response', {}).get('user', {}) |
74 | uid = user['id'] |
75 | - self.account.update(username=_full_name(user), |
76 | - access_token=token, |
77 | - user_id=uid) |
78 | + self._account.access_token = token |
79 | + self._account.user_name = _full_name(user) |
80 | + self._account.user_id = uid |
81 | log.debug('FourSquare UID is: {}', uid) |
82 | |
83 | def _get_access_token(self): |
84 | """Return an access token, logging in if necessary.""" |
85 | - token = self.account.get('access_token') |
86 | + token = self._account.access_token |
87 | if token is not None: |
88 | return token |
89 | if not self._login(): |
90 | log.error('No FourSquare authenticate results received.') |
91 | return None |
92 | - return self.account.get('access_token') |
93 | + return self._account.access_token |
94 | |
95 | def receive(self): |
96 | """Gets a list of each friend's most recent check-ins.""" |
97 | token = self._get_access_token() |
98 | if token is None: |
99 | raise AuthorizationError( |
100 | - self.account, 'No FourSquare user id available') |
101 | + self._account, 'No FourSquare user id available') |
102 | # We are successfully logged in. |
103 | result = get_json(RECENT_URL.format(access_token=token)) |
104 | |
105 | @@ -109,7 +109,7 @@ |
106 | tz_offset = checkin.get('timeZoneOffset', 0) |
107 | epoch = checkin.get('createdAt') |
108 | self._publish( |
109 | - account_id=self.account['id'], |
110 | + account_id=self._account.id, |
111 | message_id=checkin_id, |
112 | stream='messages', |
113 | sender=_full_name(user), |
114 | |
115 | === modified file 'gwibber/gwibber/service/dispatcher.py' |
116 | --- gwibber/gwibber/service/dispatcher.py 2012-09-18 21:27:54 +0000 |
117 | +++ gwibber/gwibber/service/dispatcher.py 2012-09-21 16:43:27 +0000 |
118 | @@ -142,7 +142,7 @@ |
119 | if not self.online: |
120 | return |
121 | for account in self.account_manager.get_all(): |
122 | - if account['send_enabled']: |
123 | + if account.send_enabled: |
124 | account.protocol('send', message) |
125 | |
126 | @dbus.service.method('com.Gwibber.Service', out_signature='s') |
127 | @@ -160,8 +160,7 @@ |
128 | # features is set to a harmless empty list. |
129 | protocol = protocol_manager.protocols.get(protocol_name) |
130 | features = [operation for operation in dir(protocol) |
131 | - if operation != 'info' and not operation.startswith('_') |
132 | - ] |
133 | + if not operation.startswith('_')] |
134 | return json.dumps(features) |
135 | |
136 | @dbus.service.method('com.Gwibber.Service', |
137 | |
138 | === modified file 'gwibber/gwibber/testing/helpers.py' |
139 | --- gwibber/gwibber/testing/helpers.py 2012-09-20 22:17:28 +0000 |
140 | +++ gwibber/gwibber/testing/helpers.py 2012-09-21 16:43:27 +0000 |
141 | @@ -22,10 +22,18 @@ |
142 | import threading |
143 | |
144 | |
145 | -class FakeAccount(dict): |
146 | +class FakeAuth: |
147 | + pass |
148 | + |
149 | + |
150 | +class FakeAccount: |
151 | """A fake account object for testing purposes.""" |
152 | |
153 | def __init__(self, service=None): |
154 | + self.access_token = None |
155 | + self.user_name = None |
156 | + self.user_id = None |
157 | + self.config = {} |
158 | + self.auth = FakeAuth() |
159 | self.login_lock = threading.Lock() |
160 | - self.global_id = 'fake global id' |
161 | - self['id'] = 'faker/than fake' |
162 | + self.id = 'faker/than fake' |
163 | |
164 | === modified file 'gwibber/gwibber/testing/mocks.py' |
165 | --- gwibber/gwibber/testing/mocks.py 2012-09-21 14:11:34 +0000 |
166 | +++ gwibber/gwibber/testing/mocks.py 2012-09-21 16:43:27 +0000 |
167 | @@ -107,7 +107,7 @@ |
168 | """ |
169 | |
170 | def __init__(self): |
171 | - self.items = [(True, 'key', 'value')] |
172 | + self.items = [(True, 'send_enabled', True)] |
173 | |
174 | def next(self): |
175 | if self.items: |
176 | |
177 | === modified file 'gwibber/gwibber/tests/test_account.py' |
178 | --- gwibber/gwibber/tests/test_account.py 2012-09-20 22:17:28 +0000 |
179 | +++ gwibber/gwibber/tests/test_account.py 2012-09-21 16:43:27 +0000 |
180 | @@ -70,21 +70,16 @@ |
181 | self.account = Account(self.account_service) |
182 | |
183 | def test_account_auth(self): |
184 | - # Test that the constructor initializes the 'auth' key. |
185 | - auth = self.account['auth'] |
186 | - self.assertEqual(len(auth), 4) |
187 | - self.assertEqual(auth['id'], 'fake credentials') |
188 | - self.assertEqual(auth['method'], 'fake method') |
189 | - self.assertEqual(auth['mechanism'], 'fake mechanism') |
190 | - self.assertEqual(auth['parameters'], 'fake parameters') |
191 | - |
192 | - def test_account_global_id(self): |
193 | - # Test the account's global id. |
194 | - self.assertEqual(self.account.global_id, 'fake_id') |
195 | + # Test that the constructor initializes the 'auth' attribute. |
196 | + auth = self.account.auth |
197 | + self.assertEqual(auth.id, 'fake credentials') |
198 | + self.assertEqual(auth.method, 'fake method') |
199 | + self.assertEqual(auth.mechanism, 'fake mechanism') |
200 | + self.assertEqual(auth.parameters, 'fake parameters') |
201 | |
202 | def test_account_id(self): |
203 | # The 'id' key of the account gets the full service name. |
204 | - self.assertEqual(self.account['id'], 'fake_id/fake_service') |
205 | + self.assertEqual(self.account.id, 'fake_id/flickr') |
206 | |
207 | def test_account_service(self): |
208 | # The protocol attribute refers directly to the protocol used. |
209 | @@ -101,7 +96,7 @@ |
210 | def test_on_account_changed(self): |
211 | # Account.on_account_changed() gets called during the Account |
212 | # constructor. Test that it has the expected original key value. |
213 | - self.assertEqual(self.account['key'], 'value') |
214 | + self.assertEqual(self.account.send_enabled, True) |
215 | |
216 | def test_iter_filter(self): |
217 | # The get_settings_iter() filters everything that doesn't start with |
218 | @@ -115,7 +110,7 @@ |
219 | # Start by simulating a change in the account service. |
220 | other_iter = SettingsIterMock() |
221 | other_iter.items = [ |
222 | - (True, 'ant', 'one'), |
223 | + (True, 'send_enabled', False), |
224 | (True, 'bee', 'two'), |
225 | (True, 'cat', 'three'), |
226 | ] |
227 | @@ -129,10 +124,9 @@ |
228 | # Simulate the signal. |
229 | self._callback(self.account_service, self._callback_account) |
230 | # Have the expected updates occurred? |
231 | - self.assertEqual(self.account['ant'], 'one') |
232 | - self.assertEqual(self.account['bee'], 'two') |
233 | - self.assertEqual(self.account['cat'], 'three') |
234 | - self.assertNotIn('dog', self.account) |
235 | + self.assertEqual(self.account.send_enabled, False) |
236 | + self.assertFalse(hasattr(self.account, 'bee')) |
237 | + self.assertFalse(hasattr(self.account, 'cat')) |
238 | |
239 | def test_enabled(self): |
240 | # .enabled() just passes through from the account service. |
241 | @@ -188,7 +182,7 @@ |
242 | pass |
243 | manager = AccountManager(refresh) |
244 | manager.add_new_account(self.account_service) |
245 | - self.assertIn('fake global id', manager._accounts) |
246 | + self.assertIn('faker/than fake', manager._accounts) |
247 | |
248 | def test_account_manager_enabled_event(self): |
249 | # Mimic a reaction to the enabled-event callback. |
250 | @@ -198,7 +192,7 @@ |
251 | refreshed = True |
252 | accounts_manager.get_account().list_services.return_value = [] |
253 | manager = AccountManager(refresh) |
254 | - manager._on_enabled_event(accounts_manager, 'fake global id') |
255 | + manager._on_enabled_event(accounts_manager, 'faker/than fake') |
256 | self.assertTrue(refreshed) |
257 | |
258 | def test_account_manager_delete_account_no_account(self): |
259 | @@ -210,21 +204,20 @@ |
260 | nonlocal refreshed |
261 | refreshed = True |
262 | manager = AccountManager(refresh) |
263 | - self.assertNotIn('fake global id', manager._accounts) |
264 | - manager._on_account_deleted(accounts_manager, 'fake global id') |
265 | - self.assertNotIn('fake global id', manager._accounts) |
266 | + self.assertNotIn('faker/than fake', manager._accounts) |
267 | + manager._on_account_deleted(accounts_manager, 'faker/than fake') |
268 | + self.assertNotIn('faker/than fake', manager._accounts) |
269 | self.assertFalse(refreshed) |
270 | |
271 | def test_account_manager_delete_account(self): |
272 | - # Deleting an account removes the global_id from the mapping. But if |
273 | - # that global id is missing, then it does not cause an exception. |
274 | + # Deleting an account removes the id from the mapping. But if |
275 | + # that id is missing, then it does not cause an exception. |
276 | refreshed = False |
277 | def refresh(): |
278 | nonlocal refreshed |
279 | refreshed = True |
280 | manager = AccountManager(refresh) |
281 | manager.add_new_account(self.account_service) |
282 | - # Now manager._accounts has a 'fake global id' key. |
283 | - manager._on_account_deleted(accounts_manager, 'fake global id') |
284 | - self.assertNotIn('fake global id', manager._accounts) |
285 | + manager._on_account_deleted(accounts_manager, 'faker/than fake') |
286 | + self.assertNotIn('faker/than fake', manager._accounts) |
287 | self.assertTrue(refreshed) |
288 | |
289 | === modified file 'gwibber/gwibber/tests/test_authentication.py' |
290 | --- gwibber/gwibber/tests/test_authentication.py 2012-08-22 22:37:45 +0000 |
291 | +++ gwibber/gwibber/tests/test_authentication.py 2012-09-21 16:43:27 +0000 |
292 | @@ -26,6 +26,8 @@ |
293 | import unittest |
294 | |
295 | from gwibber.utils.authentication import Authentication |
296 | +from gwibber.testing.helpers import FakeAccount |
297 | + |
298 | |
299 | try: |
300 | # Python 3.3 |
301 | @@ -64,16 +66,17 @@ |
302 | """Test authentication.""" |
303 | |
304 | def setUp(self): |
305 | - self.account = dict(auth=dict(id='my id', |
306 | - method='some method', |
307 | - parameters='change me', |
308 | - mechanism=['whatever'])) |
309 | + self.account = FakeAccount() |
310 | + self.account.auth.id = 'my id' |
311 | + self.account.auth.method = 'some method' |
312 | + self.account.auth.parameters = 'change me' |
313 | + self.account.auth.mechanism = ['whatever'] |
314 | self.logger = Logger() |
315 | |
316 | @mock.patch('gwibber.utils.authentication.Signon', FakeSignon) |
317 | def test_successful_login(self): |
318 | # Prevent an error in the callback. |
319 | - self.account['auth']['parameters'] = False |
320 | + self.account.auth.parameters = False |
321 | authenticator = Authentication(self.account, self.logger) |
322 | reply = authenticator.login() |
323 | self.assertEqual(reply, 'auth reply') |
324 | @@ -86,7 +89,7 @@ |
325 | # Trigger an error in the callback. |
326 | class Error: |
327 | message = 'who are you?' |
328 | - self.account['auth']['parameters'] = Error |
329 | + self.account.auth.parameters = Error |
330 | authenticator = Authentication(self.account, self.logger) |
331 | reply = authenticator.login() |
332 | self.assertIsNone(reply) |
333 | |
334 | === modified file 'gwibber/gwibber/tests/test_flickr.py' |
335 | --- gwibber/gwibber/tests/test_flickr.py 2012-09-21 14:11:34 +0000 |
336 | +++ gwibber/gwibber/tests/test_flickr.py 2012-09-21 16:43:27 +0000 |
337 | @@ -66,9 +66,8 @@ |
338 | @mock.patch('gwibber.utils.download.urlopen', |
339 | FakeData('gwibber.tests.data', 'flickr-nophotos.dat')) |
340 | def test_already_logged_in(self): |
341 | - # Try to get the data when the 'user_nsid' key is already in the |
342 | - # account; i.e. already logged in. |
343 | - self.account['user_nsid'] = 'anne' |
344 | + # Try to get the data when already logged in. |
345 | + self.account.user_id = 'anne' |
346 | # There's no data, and no way to test that the user_nsid was actually |
347 | # used, except for the side effect of not getting an |
348 | # AuthorizationError. |
349 | @@ -82,7 +81,7 @@ |
350 | def side_effect(): |
351 | # Sorry, the login is unsuccessful, even though it adds a |
352 | # user_nsid key to the account. |
353 | - self.account['user_nsid'] = 'bart' |
354 | + self.account.user_id = 'bart' |
355 | return False |
356 | with mock.patch.object(self.protocol, '_login', |
357 | side_effect=side_effect): |
358 | @@ -95,7 +94,7 @@ |
359 | # succeeds. |
360 | def side_effect(): |
361 | # Perform a successful login. |
362 | - self.account['user_nsid'] = 'cate' |
363 | + self.account.user_id = 'cate' |
364 | return True |
365 | with mock.patch.object(self.protocol, '_login', |
366 | side_effect=side_effect): |
367 | @@ -135,10 +134,10 @@ |
368 | # AccessToken, but this fails. |
369 | self.assertEqual(self.protocol.images(), []) |
370 | # Make sure our account data got properly updated. |
371 | - self.assertEqual(self.account['username'], 'Bob Dobbs') |
372 | - self.assertEqual(self.account['user_nsid'], 'bob') |
373 | - self.assertEqual(self.account['access_token'], '123') |
374 | - self.assertEqual(self.account['secret_token'], 'abc') |
375 | + self.assertEqual(self.account.user_name, 'Bob Dobbs') |
376 | + self.assertEqual(self.account.user_id, 'bob') |
377 | + self.assertEqual(self.account.access_token, '123') |
378 | + self.assertEqual(self.account.secret_token, 'abc') |
379 | |
380 | def test_get(self): |
381 | # Make sure that the REST GET url looks right. |
382 | @@ -177,7 +176,7 @@ |
383 | # JSON Flickr data into the format expected internally. |
384 | # |
385 | # Start by setting up a fake account id. |
386 | - self.account['id'] = 'lerxst' |
387 | + self.account.id = 'lerxst' |
388 | with mock.patch.object(self.protocol, '_get_nsid', return_value='jim'): |
389 | images = self.protocol.images() |
390 | self.assertEqual(len(images), 3) |
391 | |
392 | === modified file 'gwibber/gwibber/tests/test_foursquare.py' |
393 | --- gwibber/gwibber/tests/test_foursquare.py 2012-09-21 14:11:34 +0000 |
394 | +++ gwibber/gwibber/tests/test_foursquare.py 2012-09-21 16:43:27 +0000 |
395 | @@ -67,8 +67,8 @@ |
396 | return_value=None) |
397 | def test_unsuccessful_authentication(self, *mocks): |
398 | self.assertFalse(self.protocol._login()) |
399 | - self.assertNotIn('username', self.account) |
400 | - self.assertNotIn('user_id', self.account) |
401 | + self.assertIsNone(self.account.user_name) |
402 | + self.assertIsNone(self.account.user_id) |
403 | |
404 | @mock.patch('gwibber.utils.authentication.Authentication.login', |
405 | return_value=dict(AccessToken='tokeny goodness')) |
406 | @@ -80,8 +80,8 @@ |
407 | id='1234567')))) |
408 | def test_successful_authentication(self, *mocks): |
409 | self.assertTrue(self.protocol._login()) |
410 | - self.assertEqual(self.account['username'], 'Bob Loblaw') |
411 | - self.assertEqual(self.account['user_id'], '1234567') |
412 | + self.assertEqual(self.account.user_name, 'Bob Loblaw') |
413 | + self.assertEqual(self.account.user_id, '1234567') |
414 | |
415 | @mock.patch('gwibber.utils.base.Model', TestModel) |
416 | @mock.patch('gwibber.utils.download.urlopen', |
417 | @@ -89,7 +89,7 @@ |
418 | @mock.patch('gwibber.protocols.foursquare.FourSquare._login', |
419 | return_value=True) |
420 | def test_receive(self, *mocks): |
421 | - self.account['access_token'] = 'tokeny goodness' |
422 | + self.account.access_token = 'tokeny goodness' |
423 | self.assertEqual(0, TestModel.get_n_rows()) |
424 | self.protocol.receive() |
425 | self.assertEqual(1, TestModel.get_n_rows()) |
426 | |
427 | === modified file 'gwibber/gwibber/tests/test_protocols.py' |
428 | --- gwibber/gwibber/tests/test_protocols.py 2012-09-20 16:26:53 +0000 |
429 | +++ gwibber/gwibber/tests/test_protocols.py 2012-09-21 16:43:27 +0000 |
430 | @@ -27,6 +27,7 @@ |
431 | |
432 | from gwibber.protocols.flickr import Flickr |
433 | from gwibber.protocols.twitter import Twitter |
434 | +from gwibber.testing.helpers import FakeAccount |
435 | from gwibber.utils.base import Base |
436 | from gwibber.utils.manager import ProtocolManager |
437 | from gwibber.utils.model import ( |
438 | @@ -64,11 +65,6 @@ |
439 | self.assertNotEqual(protocol_class, Base) |
440 | |
441 | |
442 | -class FakeAccount(dict): |
443 | - def __init__(self): |
444 | - self.login_lock = threading.Lock() |
445 | - |
446 | - |
447 | class MyProtocol(Base): |
448 | """Simplest possible protocol implementation to allow testing of Base.""" |
449 | |
450 | @@ -84,7 +80,7 @@ |
451 | _private = noop |
452 | |
453 | def _locked_login(self, old_token): |
454 | - self.account['access_token'] = 'fake_token' |
455 | + self._account.access_token = 'fake_token' |
456 | |
457 | |
458 | class TestProtocols(unittest.TestCase): |
459 | |
460 | === modified file 'gwibber/gwibber/utils/account.py' |
461 | --- gwibber/gwibber/utils/account.py 2012-09-19 20:05:40 +0000 |
462 | +++ gwibber/gwibber/utils/account.py 2012-09-21 16:43:27 +0000 |
463 | @@ -14,6 +14,7 @@ |
464 | |
465 | """The libaccounts account service wrapper.""" |
466 | |
467 | + |
468 | __all__ = [ |
469 | 'Account', |
470 | 'AccountManager', |
471 | @@ -30,7 +31,7 @@ |
472 | from gwibber.utils.model import COLUMN_INDICES, Model |
473 | |
474 | |
475 | -log = logging.getLogger('gwibber.accounts') |
476 | +log = logging.getLogger('gwibber.service') |
477 | |
478 | |
479 | class AccountManager: |
480 | @@ -78,7 +79,7 @@ |
481 | |
482 | def add_new_account(self, account_service): |
483 | new_account = Account(account_service) |
484 | - self._accounts[new_account.global_id] = new_account |
485 | + self._accounts[new_account.id] = new_account |
486 | |
487 | def get_all(self): |
488 | return self._accounts.values() |
489 | @@ -87,30 +88,43 @@ |
490 | return self._accounts.get(account_id, default) |
491 | |
492 | |
493 | -class Account(dict): |
494 | +class AuthData: |
495 | + """This class serves as a sub-namespace for Account instances.""" |
496 | + |
497 | + def __init__(self, auth_data): |
498 | + self.id = auth_data.get_credentials_id() |
499 | + self.method = auth_data.get_method() |
500 | + self.mechanism = auth_data.get_mechanism() |
501 | + self.parameters = auth_data.get_parameters() |
502 | + |
503 | + |
504 | +class Account: |
505 | """A thin wrapper around libaccounts API.""" |
506 | + access_token = None |
507 | + secret_token = None |
508 | + send_enabled = None |
509 | + user_name = None |
510 | + user_id = None |
511 | + auth = None |
512 | + id = None |
513 | |
514 | def __init__(self, account_service): |
515 | self.account_service = account_service |
516 | auth_data = account_service.get_auth_data() |
517 | - self['auth'] = dict( |
518 | - id = auth_data.get_credentials_id(), |
519 | - method = auth_data.get_method(), |
520 | - mechanism = auth_data.get_mechanism(), |
521 | - parameters = auth_data.get_parameters(), |
522 | - ) |
523 | + self.auth = AuthData(account_service.get_auth_data()) |
524 | |
525 | + # The provider in libaccounts should match the name of our protocol. |
526 | account = account_service.get_account() |
527 | - self.global_id = account.id |
528 | - service_name = account_service.get_service().get_name() |
529 | - self['id'] = '{}/{}'.format(self.global_id, service_name) |
530 | - # The provider in libaccounts should match the name of our protocol. |
531 | protocol_name = account.get_provider_name() |
532 | protocol_class = protocol_manager.protocols.get(protocol_name) |
533 | if protocol_class is None: |
534 | raise UnsupportedProtocolError(protocol_name) |
535 | self.protocol = protocol_class(self) |
536 | |
537 | + # account.id is a number, and the protocol_name is a word, so |
538 | + # the resulting id will look something like '6/twitter' or '2/facebook' |
539 | + self.id = '{}/{}'.format(account.id, protocol_name) |
540 | + |
541 | account_service.connect('changed', self._on_account_changed, account) |
542 | self._on_account_changed(account_service, account) |
543 | |
544 | @@ -129,7 +143,11 @@ |
545 | while True: |
546 | success, key, value = settings.next() |
547 | if success: |
548 | - self[key] = value |
549 | + log.debug('{} got {}: {}'.format(self.id, key, value)) |
550 | + # Testing for tuple membership makes this easy to |
551 | + # expand later, if necessary. |
552 | + if key in ('send_enabled',): |
553 | + setattr(self, key, value) |
554 | else: |
555 | break |
556 | |
557 | @@ -138,3 +156,6 @@ |
558 | |
559 | def __eq__(self, other): |
560 | return self.account_service == other.account_service |
561 | + |
562 | + def __ne__(self, other): |
563 | + return self.account_service != other.account_service |
564 | |
565 | === modified file 'gwibber/gwibber/utils/authentication.py' |
566 | --- gwibber/gwibber/utils/authentication.py 2012-09-05 22:09:44 +0000 |
567 | +++ gwibber/gwibber/utils/authentication.py 2012-09-21 16:43:27 +0000 |
568 | @@ -35,12 +35,12 @@ |
569 | self._authenticating = False |
570 | |
571 | def login(self): |
572 | - auth = self.account['auth'] |
573 | - self.auth_session = Signon.AuthSession.new(auth['id'], auth['method']) |
574 | + auth = self.account.auth |
575 | + self.auth_session = Signon.AuthSession.new(auth.id, auth.method) |
576 | self._authenticating = True |
577 | self._loop = GObject.MainLoop() |
578 | self.auth_session.process( |
579 | - auth['parameters'], auth['mechanism'], |
580 | + auth.parameters, auth.mechanism, |
581 | self._login_cb, None) |
582 | if self._authenticating: |
583 | self._loop.run() |
584 | |
585 | === modified file 'gwibber/gwibber/utils/base.py' |
586 | --- gwibber/gwibber/utils/base.py 2012-09-21 14:11:34 +0000 |
587 | +++ gwibber/gwibber/utils/base.py 2012-09-21 16:43:27 +0000 |
588 | @@ -36,11 +36,13 @@ |
589 | MESSAGE_IDX = COLUMN_INDICES['message'] |
590 | IDS_IDX = COLUMN_INDICES['message_ids'] |
591 | |
592 | + |
593 | # This is a mapping from Dee.SharedModel row keys to the DeeModelIters |
594 | # representing the rows matching those keys. It is used for quickly finding |
595 | # duplicates when we want to insert new rows into the model. |
596 | _seen_messages = {} |
597 | |
598 | + |
599 | # Protocol __call__() methods run in threads, so we need to serialize |
600 | # publishing new data into the SharedModel. |
601 | _publish_lock = threading.Lock() |
602 | @@ -110,7 +112,7 @@ |
603 | _SYNCHRONIZE = False |
604 | |
605 | def __init__(self, account): |
606 | - self.account = account |
607 | + self._account = account |
608 | |
609 | def __call__(self, operation, *args, **kwargs): |
610 | """Call an operation, i.e. a method, with arguments in a sub-thread. |
611 | @@ -220,15 +222,15 @@ |
612 | # changed because thread A is already logged in. It does not try to |
613 | # log in again, but also returns True since the access token has |
614 | # changed. IOW, thread B is also already logged in via thread A. |
615 | - old_token = self.account.get('access_token') |
616 | - with self.account.login_lock: |
617 | - if self.account.get('access_token') == old_token: |
618 | + old_token = self._account.access_token |
619 | + with self._account.login_lock: |
620 | + if self._account.access_token == old_token: |
621 | self._locked_login(old_token) |
622 | # This test must be performed while the login lock is acquired, |
623 | # otherwise it's possible for another thread to come in between |
624 | # the release of the login lock and the test, and change the |
625 | # access token. |
626 | - return self.account.get('access_token') != old_token |
627 | + return self._account.access_token != old_token |
628 | |
629 | def _locked_login(self, old_token): |
630 | """Login operation stub. This must be implemented by subclasses.""" |
Ok barry, earlier today I had mentioned that in this branch the tests were passing, but there was actually a bug that I noticed interactively, that the test suite didn't get because it mocked too aggressively. I looked into this further, and it turned out that we had two different FakeAccount classes in two different places, and one of them was still inheriting from dict, which means that some tests that were expecting dictlike behavior from the Account class were still passing, even though Account class itself was not inheriting from dict. In other words, we had some tests that were not written with the intention of testing the Account class, but contained some code that assumed Account was inheriting from dict, and the mock we were using *did* inherit from dict, so the test passed, even though the real live Account class would have failed that test because it was no longer inheriting from dict.
That is all fixed now, I've unified the two different FakeAccount classes, *none* of them are inheriting from dict any longer, and *all* the code is now correct.
As you can see, I've added some instance attributes to the Account class, for the most important things, and then everything else I've just kind of shoved into this new Account.config dict. I still don't fully know every single thing that can possibly go into that dict, but different plugins do it differently. Eg, Flickr has two different tokens that it shoves in there, and FourSquare only has one token. I think ultimately we will want to get rid of the config dict and have instance attributes for everything. Until then though, this is a big leap in the right direction, with some really important instance attributes defined, and Account no longer inheriting from dict.
I've taken care to update Flickr and FourSquare for these changes, but you'll have to make sure whatever you're doing with Facebook takes this into account.