Merge lp:~robru/gwibber/foursquare into lp:~barry/gwibber/py3
- foursquare
- Merge into py3
Proposed by
Robert Bruce Park
Status: | Merged |
---|---|
Merged at revision: | 1435 |
Proposed branch: | lp:~robru/gwibber/foursquare |
Merge into: | lp:~barry/gwibber/py3 |
Diff against target: |
442 lines (+241/-39) (has conflicts) 7 files modified
gwibber/gwibber/protocols/foursquare.py (+98/-0) gwibber/gwibber/tests/data/foursquare-full.dat (+1/-0) gwibber/gwibber/tests/test_foursquare.py (+108/-0) gwibber/gwibber/tests/test_model.py (+2/-2) gwibber/gwibber/tests/test_protocols.py (+23/-31) gwibber/gwibber/utils/base.py (+4/-4) gwibber/gwibber/utils/model.py (+5/-2) Text conflict in gwibber/gwibber/protocols/foursquare.py |
To merge this branch: | bzr merge lp:~robru/gwibber/foursquare |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Barry Warsaw | Pending | ||
Review via email: mp+125372@code.launchpad.net |
Commit message
Description of the change
BLAM.
To post a comment you must log in.
lp:~robru/gwibber/foursquare
updated
- 1435. By Robert Bruce Park
-
Add likes count and avatar URL to FourSquare.receive method.
Also committing foursquare-
full.dat, which is a real live json data
dump of a real live foursquare checkin, that we use for testing. It
was supposed to be part of the previous commit, but I forgot to add it. - 1436. By Robert Bruce Park
-
Modify Base._publish to calculate the service name on it's own.
This alleviates the need for every single protocol having to specify
'self.__class_ _.__name_ _.lower( )' every single time that it wants to
call Base._publish. Instead, Base._publish simply calls that ugliness
just once, in one place.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'gwibber/gwibber/protocols/foursquare.py' | |||
2 | --- gwibber/gwibber/protocols/foursquare.py 2012-09-19 22:21:39 +0000 | |||
3 | +++ gwibber/gwibber/protocols/foursquare.py 2012-09-20 03:15:40 +0000 | |||
4 | @@ -19,8 +19,106 @@ | |||
5 | 19 | ] | 19 | ] |
6 | 20 | 20 | ||
7 | 21 | 21 | ||
8 | 22 | import gettext | ||
9 | 23 | import logging | ||
10 | 24 | import datetime | ||
11 | 25 | |||
12 | 26 | from gwibber.errors import AuthorizationError | ||
13 | 27 | from gwibber.utils.authentication import Authentication | ||
14 | 22 | from gwibber.utils.base import Base | 28 | from gwibber.utils.base import Base |
15 | 29 | from gwibber.utils.download import get_json | ||
16 | 30 | from gwibber.utils.results import Results | ||
17 | 31 | |||
18 | 32 | |||
19 | 33 | log = logging.getLogger('gwibber.service') | ||
20 | 34 | _ = gettext.lgettext | ||
21 | 35 | |||
22 | 36 | # The '&v=YYYYMMDD' defines the date that the API was last confirmed | ||
23 | 37 | # to be functional, and is used by foursquare to indicate how old our | ||
24 | 38 | # software is. In the event that they change their API, an old 'v' | ||
25 | 39 | # date will tell them to give us the old, deprecated API behaviors, | ||
26 | 40 | # giving us some time to be notified of API breakage and update | ||
27 | 41 | # accordingly. If you're working on this code and you don't see any | ||
28 | 42 | # bugs with foursquare then feel free to update the date here. | ||
29 | 43 | API_BASE = 'https://api.foursquare.com/v2/' | ||
30 | 44 | TOKEN ='?oauth_token={access_token}&v=20120917' | ||
31 | 45 | SELF_URL = API_BASE + 'users/self' + TOKEN | ||
32 | 46 | CHECKIN_URL = API_BASE + 'checkins/{checkin_id}' + TOKEN | ||
33 | 47 | RECENT_URL = API_BASE + 'checkins/recent' + TOKEN | ||
34 | 48 | |||
35 | 49 | HTML_PREFIX = 'https://foursquare.com/' | ||
36 | 50 | USER_URL = HTML_PREFIX + 'user/{user_id}' | ||
37 | 51 | VENUE_URL = HTML_PREFIX + 'venue/{venue_id}' | ||
38 | 52 | |||
39 | 53 | |||
40 | 54 | def _full_name(user): | ||
41 | 55 | names = (user.get('firstName'), user.get('lastName')) | ||
42 | 56 | return ' '.join(name for name in names if name) | ||
43 | 23 | 57 | ||
44 | 24 | 58 | ||
45 | 25 | class FourSquare(Base): | 59 | class FourSquare(Base): |
46 | 60 | <<<<<<< TREE | ||
47 | 26 | pass | 61 | pass |
48 | 62 | ======= | ||
49 | 63 | def _locked_login(self, old_token): | ||
50 | 64 | log.debug('{} to FourSquare'.format( | ||
51 | 65 | 'Re-authenticating' if old_token else 'Logging in')) | ||
52 | 66 | |||
53 | 67 | result = Authentication(self.account, log).login() | ||
54 | 68 | if result is None: | ||
55 | 69 | log.error('No FourSquare authentication results received.') | ||
56 | 70 | return | ||
57 | 71 | |||
58 | 72 | token = result.get('AccessToken') | ||
59 | 73 | if token is not None: | ||
60 | 74 | data = get_json(SELF_URL.format(access_token=token)) or {} | ||
61 | 75 | user = data.get('response', {}).get('user', {}) | ||
62 | 76 | names = (user.get('firstName'), user.get('lastName')) | ||
63 | 77 | uid = user['id'] | ||
64 | 78 | |||
65 | 79 | self.account.update(username=_full_name(user), | ||
66 | 80 | access_token=token, | ||
67 | 81 | user_id=uid) | ||
68 | 82 | log.debug('FourSquare UID is: ' + uid) | ||
69 | 83 | else: | ||
70 | 84 | log.error('No AccessToken in FourSquare session: {!r}', result) | ||
71 | 85 | |||
72 | 86 | def receive(self): | ||
73 | 87 | """Gets a list of each friend's most recent check-ins.""" | ||
74 | 88 | if 'access_token' not in self.account and not self._login(): | ||
75 | 89 | log.error('FourSquare: {}'.format(_('Unable to authenticate.'))) | ||
76 | 90 | |||
77 | 91 | token = self.account.get('access_token') | ||
78 | 92 | if token is not None: | ||
79 | 93 | result = get_json(RECENT_URL.format(access_token=token)) | ||
80 | 94 | |||
81 | 95 | response_code = result.get('meta', {}).get('code') | ||
82 | 96 | if response_code != 200: | ||
83 | 97 | log.error('FourSquare: Error: {}'.format(result)) | ||
84 | 98 | return | ||
85 | 99 | |||
86 | 100 | checkins = result.get('response', {}).get('recent', []) | ||
87 | 101 | for checkin in checkins: | ||
88 | 102 | user = checkin.get('user', {}) | ||
89 | 103 | avatar = user.get('photo', {}) | ||
90 | 104 | avatar_url = '{prefix}100x100{suffix}'.format(**avatar) | ||
91 | 105 | checkin_id = checkin.get('id') | ||
92 | 106 | tz = checkin.get('timeZoneOffset', 0) | ||
93 | 107 | epoch = checkin.get('createdAt') | ||
94 | 108 | timestamp = datetime.datetime.fromtimestamp( | ||
95 | 109 | epoch - (tz * 36)).isoformat() | ||
96 | 110 | |||
97 | 111 | self._publish( | ||
98 | 112 | account_id=self.account['id'], | ||
99 | 113 | message_id=checkin_id, | ||
100 | 114 | stream='messages', | ||
101 | 115 | sender=_full_name(user), | ||
102 | 116 | from_me=(user.get('relationship') == 'self'), | ||
103 | 117 | timestamp=timestamp, | ||
104 | 118 | message=checkin.get('shout', ''), | ||
105 | 119 | likes=checkin.get('likes', {}).get('count', 0), | ||
106 | 120 | icon_uri=avatar_url, | ||
107 | 121 | url=CHECKIN_URL.format(access_token=token, | ||
108 | 122 | checkin_id=checkin_id), | ||
109 | 123 | ) | ||
110 | 124 | >>>>>>> MERGE-SOURCE | ||
111 | 27 | 125 | ||
112 | === added file 'gwibber/gwibber/tests/data/foursquare-full.dat' | |||
113 | --- gwibber/gwibber/tests/data/foursquare-full.dat 1970-01-01 00:00:00 +0000 | |||
114 | +++ gwibber/gwibber/tests/data/foursquare-full.dat 2012-09-20 03:15:40 +0000 | |||
115 | @@ -0,0 +1,1 @@ | |||
116 | 1 | {"meta":{"code":200},"notifications":[{"type":"notificationTray","item":{"unreadCount":0}}],"response":{"recent":[{"id":"50574c9ce4b0a9a6e84433a0","createdAt":1347898524,"type":"checkin","shout":"Working on gwibber's foursquare plugin.","timeZoneOffset":-300,"user":{"id":"37199983","firstName":"Jimbob","lastName":"Smith","relationship":"self","photo":{"prefix":"https:\/\/irs0.4sqi.net\/img\/user\/","suffix":"\/5IEW3VIX55BBEXAO.jpg"}},"venue":{"id":"4e73f722fa76059700582a27","name":"Pop Soda's Coffee House & Gallery","contact":{"phone":"2044157666","formattedPhone":"(204) 415-7666","twitter":"PopSodasWpg"},"location":{"address":"625 Portage Ave.","crossStreet":"Furby St.","lat":49.88873164336725,"lng":-97.158043384552,"postalCode":"R3B 2G4","city":"Winnipeg","state":"MB","country":"Canada","cc":"CA"},"categories":[{"id":"4bf58dd8d48988d16d941735","name":"Café","pluralName":"Cafés","shortName":"Café","icon":{"prefix":"https:\/\/foursquare.com\/img\/categories_v2\/food\/cafe_","suffix":".png"},"primary":true}],"verified":false,"stats":{"checkinsCount":216,"usersCount":84,"tipCount":8},"url":"http:\/\/www.popsodascoffeehouse.com","likes":{"count":0,"groups":[]},"friendVisits":{"count":0,"summary":"You've been here","items":[{"visitedCount":1,"liked":false,"user":{"id":"37199983","firstName":"Jimbob","lastName":"Smith","relationship":"self","photo":{"prefix":"https:\/\/irs0.4sqi.net\/img\/user\/","suffix":"\/5IEW3VIX55BBEXAO.jpg"}}}]},"beenHere":{"count":1,"marked":true},"specials":{"count":0}},"source":{"name":"foursquare for Android","url":"https:\/\/foursquare.com\/download\/#\/android"},"photos":{"count":0,"items":[]}}]}} | ||
117 | 0 | 2 | ||
118 | === added file 'gwibber/gwibber/tests/test_foursquare.py' | |||
119 | --- gwibber/gwibber/tests/test_foursquare.py 1970-01-01 00:00:00 +0000 | |||
120 | +++ gwibber/gwibber/tests/test_foursquare.py 2012-09-20 03:15:40 +0000 | |||
121 | @@ -0,0 +1,108 @@ | |||
122 | 1 | # Copyright (C) 2012 Canonical Ltd | ||
123 | 2 | # | ||
124 | 3 | # This program is free software: you can redistribute it and/or modify | ||
125 | 4 | # it under the terms of the GNU General Public License version 2 as | ||
126 | 5 | # published by the Free Software Foundation. | ||
127 | 6 | # | ||
128 | 7 | # This program is distributed in the hope that it will be useful, | ||
129 | 8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
130 | 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
131 | 10 | # GNU General Public License for more details. | ||
132 | 11 | # | ||
133 | 12 | # You should have received a copy of the GNU General Public License | ||
134 | 13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
135 | 14 | |||
136 | 15 | """Test the FourSquare plugin.""" | ||
137 | 16 | |||
138 | 17 | __all__ = [ | ||
139 | 18 | 'TestFourSquare', | ||
140 | 19 | ] | ||
141 | 20 | |||
142 | 21 | |||
143 | 22 | import unittest | ||
144 | 23 | |||
145 | 24 | from gi.repository import Dee | ||
146 | 25 | |||
147 | 26 | from gwibber.errors import AuthorizationError | ||
148 | 27 | from gwibber.testing.helpers import FakeAccount, LogPreserver | ||
149 | 28 | from gwibber.testing.mocks import FakeData | ||
150 | 29 | from gwibber.protocols.foursquare import FourSquare | ||
151 | 30 | from gwibber.utils.model import COLUMN_TYPES | ||
152 | 31 | |||
153 | 32 | |||
154 | 33 | try: | ||
155 | 34 | # Python 3.3 | ||
156 | 35 | from unittest import mock | ||
157 | 36 | except ImportError: | ||
158 | 37 | import mock | ||
159 | 38 | |||
160 | 39 | |||
161 | 40 | # Create a test model that will not interfere with the user's environment. | ||
162 | 41 | # We'll use this object as a mock of the real model. | ||
163 | 42 | TestModel = Dee.SharedModel.new('com.Gwibber.TestSharedModel') | ||
164 | 43 | TestModel.set_schema_full(COLUMN_TYPES) | ||
165 | 44 | |||
166 | 45 | |||
167 | 46 | # Disable i18n translations so we only need to test English responses. | ||
168 | 47 | @mock.patch.dict('gwibber.protocols.foursquare.__dict__', {'_': lambda s: s}) | ||
169 | 48 | class TestFourSquare(unittest.TestCase): | ||
170 | 49 | """Test the FourSquare API.""" | ||
171 | 50 | |||
172 | 51 | @classmethod | ||
173 | 52 | def setUpClass(cls): | ||
174 | 53 | cls._log_state = LogPreserver() | ||
175 | 54 | |||
176 | 55 | @classmethod | ||
177 | 56 | def tearDownClass(cls): | ||
178 | 57 | cls._log_state.restore() | ||
179 | 58 | |||
180 | 59 | def setUp(self): | ||
181 | 60 | self.account = FakeAccount() | ||
182 | 61 | self.protocol = FourSquare(self.account) | ||
183 | 62 | |||
184 | 63 | def test_protocol_info(self): | ||
185 | 64 | self.assertEqual(self.protocol.__class__.__name__, 'FourSquare') | ||
186 | 65 | |||
187 | 66 | @mock.patch('gwibber.utils.authentication.Authentication.login', | ||
188 | 67 | return_value=None) | ||
189 | 68 | @mock.patch('gwibber.utils.download.get_json', | ||
190 | 69 | return_value=None) | ||
191 | 70 | def test_unsuccessful_authentication(self, *mocks): | ||
192 | 71 | self.assertFalse(self.protocol._login()) | ||
193 | 72 | self.assertNotIn('username', self.account) | ||
194 | 73 | self.assertNotIn('user_id', self.account) | ||
195 | 74 | |||
196 | 75 | @mock.patch('gwibber.utils.authentication.Authentication.login', | ||
197 | 76 | return_value=dict(AccessToken='tokeny goodness')) | ||
198 | 77 | @mock.patch('gwibber.protocols.foursquare.get_json', | ||
199 | 78 | return_value=dict( | ||
200 | 79 | response=dict( | ||
201 | 80 | user=dict(firstName='Bob', | ||
202 | 81 | lastName='Loblaw', | ||
203 | 82 | id='1234567')))) | ||
204 | 83 | def test_successful_authentication(self, *mocks): | ||
205 | 84 | self.assertTrue(self.protocol._login()) | ||
206 | 85 | self.assertEqual(self.account['username'], 'Bob Loblaw') | ||
207 | 86 | self.assertEqual(self.account['user_id'], '1234567') | ||
208 | 87 | |||
209 | 88 | @mock.patch('gwibber.utils.base.Model', TestModel) | ||
210 | 89 | @mock.patch('gwibber.utils.download.urlopen', | ||
211 | 90 | FakeData('gwibber.tests.data', 'foursquare-full.dat')) | ||
212 | 91 | @mock.patch('gwibber.protocols.foursquare.FourSquare._login', | ||
213 | 92 | return_value=True) | ||
214 | 93 | def test_receive(self, *mocks): | ||
215 | 94 | self.account['access_token'] = 'tokeny goodness' | ||
216 | 95 | self.assertEqual(0, TestModel.get_n_rows()) | ||
217 | 96 | self.protocol.receive() | ||
218 | 97 | self.assertEqual(1, TestModel.get_n_rows()) | ||
219 | 98 | expected = [ | ||
220 | 99 | [['foursquare', 'faker/than fake', '50574c9ce4b0a9a6e84433a0']], | ||
221 | 100 | 'messages', 'Jimbob Smith', '', True, '2012-09-17T14:15:24', | ||
222 | 101 | "Working on gwibber's foursquare plugin.", '', | ||
223 | 102 | 'https://irs0.4sqi.net/img/user/100x100/5IEW3VIX55BBEXAO.jpg', | ||
224 | 103 | 'https://api.foursquare.com/v2/checkins/50574c9ce4b0a9a6e84433a0' + | ||
225 | 104 | '?oauth_token=tokeny goodness&v=20120917', '', '', '', '', 0.0, | ||
226 | 105 | False, '', '', '', '', '', '', '', '', '', '', '', '', '', '', | ||
227 | 106 | '', '', '', '', '', '', ''] | ||
228 | 107 | for i, col in enumerate(TestModel[0]): | ||
229 | 108 | self.assertEqual(col, expected[i]) | ||
230 | 0 | 109 | ||
231 | === modified file 'gwibber/gwibber/tests/test_model.py' | |||
232 | --- gwibber/gwibber/tests/test_model.py 2012-09-19 20:05:40 +0000 | |||
233 | +++ gwibber/gwibber/tests/test_model.py 2012-09-20 03:15:40 +0000 | |||
234 | @@ -34,10 +34,10 @@ | |||
235 | 34 | 34 | ||
236 | 35 | def test_basic_properties(self): | 35 | def test_basic_properties(self): |
237 | 36 | self.assertIsInstance(Model, Dee.SharedModel) | 36 | self.assertIsInstance(Model, Dee.SharedModel) |
239 | 37 | self.assertEqual(Model.get_n_columns(), 39) | 37 | self.assertEqual(Model.get_n_columns(), 37) |
240 | 38 | self.assertEqual(Model.get_n_rows(), 0) | 38 | self.assertEqual(Model.get_n_rows(), 0) |
241 | 39 | self.assertEqual(Model.get_schema(), | 39 | self.assertEqual(Model.get_schema(), |
243 | 40 | ['aas', 's', 's', 's', 's', 'b', 's', 's', 's', 's', | 40 | ['aas', 's', 's', 's', 'b', 's', 's', 's', |
244 | 41 | 's', 's', 's', 's', 's', 's', 'd', 'b', 's', 's', | 41 | 's', 's', 's', 's', 's', 's', 'd', 'b', 's', 's', |
245 | 42 | 's', 's', 's', 's', 's', 's', 's', 's', 's', 's', | 42 | 's', 's', 's', 's', 's', 's', 's', 's', 's', 's', |
246 | 43 | 's', 's', 's', 's', 's', 's', 's', 's', 's']) | 43 | 's', 's', 's', 's', 's', 's', 's', 's', 's']) |
247 | 44 | 44 | ||
248 | === modified file 'gwibber/gwibber/tests/test_protocols.py' | |||
249 | --- gwibber/gwibber/tests/test_protocols.py 2012-09-19 22:21:39 +0000 | |||
250 | +++ gwibber/gwibber/tests/test_protocols.py 2012-09-20 03:15:40 +0000 | |||
251 | @@ -141,12 +141,9 @@ | |||
252 | 141 | self.assertEqual(Model.get_n_rows(), 0) | 141 | self.assertEqual(Model.get_n_rows(), 0) |
253 | 142 | self.assertEqual(TestModel.get_n_rows(), 0) | 142 | self.assertEqual(TestModel.get_n_rows(), 0) |
254 | 143 | base = Base(None) | 143 | base = Base(None) |
261 | 144 | base._publish('a', 'b', 'c', message='a', | 144 | base._publish('a', 'b', message='a') |
262 | 145 | from_me=True, likes=1, liked=True) | 145 | base._publish('a', 'b', message='b') |
263 | 146 | base._publish('a', 'b', 'c', message='b', | 146 | base._publish('a', 'b', message='c') |
258 | 147 | from_me=True, likes=1, liked=True) | ||
259 | 148 | base._publish('a', 'b', 'c', message='c', | ||
260 | 149 | from_me=True, likes=1, liked=True) | ||
264 | 150 | self.assertEqual(Model.get_n_rows(), 0) | 147 | self.assertEqual(Model.get_n_rows(), 0) |
265 | 151 | self.assertEqual(TestModel.get_n_rows(), 3) | 148 | self.assertEqual(TestModel.get_n_rows(), 3) |
266 | 152 | 149 | ||
267 | @@ -155,7 +152,7 @@ | |||
268 | 155 | base = Base(None) | 152 | base = Base(None) |
269 | 156 | self.assertEqual(0, TestModel.get_n_rows()) | 153 | self.assertEqual(0, TestModel.get_n_rows()) |
270 | 157 | with self.assertRaises(TypeError) as cm: | 154 | with self.assertRaises(TypeError) as cm: |
272 | 158 | base._publish('x', 'y', 'z', invalid_argument='not good') | 155 | base._publish('x', 'y', invalid_argument='not good') |
273 | 159 | self.assertEqual(str(cm.exception), | 156 | self.assertEqual(str(cm.exception), |
274 | 160 | 'Unexpected keyword arguments: invalid_argument') | 157 | 'Unexpected keyword arguments: invalid_argument') |
275 | 161 | 158 | ||
276 | @@ -165,7 +162,7 @@ | |||
277 | 165 | base = Base(None) | 162 | base = Base(None) |
278 | 166 | self.assertEqual(0, TestModel.get_n_rows()) | 163 | self.assertEqual(0, TestModel.get_n_rows()) |
279 | 167 | with self.assertRaises(TypeError) as cm: | 164 | with self.assertRaises(TypeError) as cm: |
281 | 168 | base._publish('x', 'y', 'z', bad='no', wrong='yes') | 165 | base._publish('x', 'y', bad='no', wrong='yes') |
282 | 169 | self.assertEqual(str(cm.exception), | 166 | self.assertEqual(str(cm.exception), |
283 | 170 | 'Unexpected keyword arguments: bad, wrong') | 167 | 'Unexpected keyword arguments: bad, wrong') |
284 | 171 | 168 | ||
285 | @@ -176,17 +173,16 @@ | |||
286 | 176 | base = Base(None) | 173 | base = Base(None) |
287 | 177 | self.assertEqual(0, TestModel.get_n_rows()) | 174 | self.assertEqual(0, TestModel.get_n_rows()) |
288 | 178 | self.assertTrue(base._publish( | 175 | self.assertTrue(base._publish( |
293 | 179 | service='facebook', account_id='1/facebook', | 176 | account_id='1/facebook', message_id='1234', stream='messages', |
294 | 180 | message_id='1234', stream='messages', sender='fred', | 177 | sender='fred', sender_nick='freddy', from_me=True, |
295 | 181 | sender_nick='freddy', from_me=True, timestamp='today', | 178 | timestamp='today', message='hello, @jimmy', likes=10, liked=True)) |
292 | 182 | message='hello, @jimmy', likes=10, liked=True)) | ||
296 | 183 | self.assertEqual(1, TestModel.get_n_rows()) | 179 | self.assertEqual(1, TestModel.get_n_rows()) |
297 | 184 | row = TestModel.get_row(0) | 180 | row = TestModel.get_row(0) |
298 | 185 | # For convenience. | 181 | # For convenience. |
299 | 186 | def V(column_name): | 182 | def V(column_name): |
300 | 187 | return row[COLUMN_INDICES[column_name]] | 183 | return row[COLUMN_INDICES[column_name]] |
301 | 188 | self.assertEqual(V('message_ids'), | 184 | self.assertEqual(V('message_ids'), |
303 | 189 | [['facebook', '1/facebook', '1234']]) | 185 | [['base', '1/facebook', '1234']]) |
304 | 190 | self.assertEqual(V('stream'), 'messages') | 186 | self.assertEqual(V('stream'), 'messages') |
305 | 191 | self.assertEqual(V('sender'), 'fred') | 187 | self.assertEqual(V('sender'), 'fred') |
306 | 192 | self.assertEqual(V('sender_nick'), 'freddy') | 188 | self.assertEqual(V('sender_nick'), 'freddy') |
307 | @@ -215,18 +211,16 @@ | |||
308 | 215 | # Insert the first message into the table. The key will be the string | 211 | # Insert the first message into the table. The key will be the string |
309 | 216 | # 'fredhellojimmy' | 212 | # 'fredhellojimmy' |
310 | 217 | self.assertTrue(base._publish( | 213 | self.assertTrue(base._publish( |
315 | 218 | service='facebook', account_id='1/facebook', | 214 | account_id='1/facebook', message_id='1234', stream='messages', |
316 | 219 | message_id='1234', stream='messages', sender='fred', | 215 | sender='fred', sender_nick='freddy', from_me=True, |
317 | 220 | sender_nick='freddy', from_me=True, timestamp='today', | 216 | timestamp='today', message='hello, @jimmy', likes=10, liked=True)) |
314 | 221 | message='hello, @jimmy', likes=10, liked=True)) | ||
318 | 222 | # Insert the second message into the table. Note that because | 217 | # Insert the second message into the table. Note that because |
319 | 223 | # punctuation was stripped from the above message, this one will also | 218 | # punctuation was stripped from the above message, this one will also |
320 | 224 | # have the key 'fredhellojimmy', thus it will be deemed a duplicate. | 219 | # have the key 'fredhellojimmy', thus it will be deemed a duplicate. |
321 | 225 | self.assertTrue(base._publish( | 220 | self.assertTrue(base._publish( |
326 | 226 | service='twitter', account_id='2/twitter', | 221 | account_id='2/twitter', message_id='5678', stream='messages', |
327 | 227 | message_id='5678', stream='messages', sender='fred', | 222 | sender='fred', sender_nick='freddy', from_me=True, |
328 | 228 | sender_nick='freddy', from_me=True, timestamp='today', | 223 | timestamp='today', message='hello jimmy', likes=10, liked=False)) |
325 | 229 | message='hello jimmy', likes=10, liked=False)) | ||
329 | 230 | # See, we get only one row in the table. | 224 | # See, we get only one row in the table. |
330 | 231 | self.assertEqual(1, TestModel.get_n_rows()) | 225 | self.assertEqual(1, TestModel.get_n_rows()) |
331 | 232 | # The first published message wins. | 226 | # The first published message wins. |
332 | @@ -234,8 +228,8 @@ | |||
333 | 234 | self.assertEqual(row[COLUMN_INDICES['message']], 'hello, @jimmy') | 228 | self.assertEqual(row[COLUMN_INDICES['message']], 'hello, @jimmy') |
334 | 235 | # Both message ids will be present, in the order they were published. | 229 | # Both message ids will be present, in the order they were published. |
335 | 236 | self.assertEqual(row[COLUMN_INDICES['message_ids']], | 230 | self.assertEqual(row[COLUMN_INDICES['message_ids']], |
338 | 237 | [['facebook', '1/facebook', '1234'], | 231 | [['base', '1/facebook', '1234'], |
339 | 238 | ['twitter', '2/twitter', '5678']]) | 232 | ['base', '2/twitter', '5678']]) |
340 | 239 | 233 | ||
341 | 240 | @mock.patch('gwibber.utils.base.Model', TestModel) | 234 | @mock.patch('gwibber.utils.base.Model', TestModel) |
342 | 241 | @mock.patch('gwibber.utils.base._seen_messages', {}) | 235 | @mock.patch('gwibber.utils.base._seen_messages', {}) |
343 | @@ -247,17 +241,15 @@ | |||
344 | 247 | self.assertEqual(0, TestModel.get_n_rows()) | 241 | self.assertEqual(0, TestModel.get_n_rows()) |
345 | 248 | # The key for this row is 'fredhellojimmy' | 242 | # The key for this row is 'fredhellojimmy' |
346 | 249 | self.assertTrue(base._publish( | 243 | self.assertTrue(base._publish( |
351 | 250 | service='facebook', account_id='1/facebook', | 244 | account_id='1/facebook', message_id='1234', stream='messages', |
352 | 251 | message_id='1234', stream='messages', sender='fred', | 245 | sender='fred', sender_nick='freddy', from_me=True, |
353 | 252 | sender_nick='freddy', from_me=True, timestamp='today', | 246 | timestamp='today', message='hello, @jimmy', likes=10, liked=True)) |
350 | 253 | message='hello, @jimmy', likes=10, liked=True)) | ||
354 | 254 | self.assertEqual(1, TestModel.get_n_rows()) | 247 | self.assertEqual(1, TestModel.get_n_rows()) |
355 | 255 | # The key for this row is 'tedtholomewhellojimmy' | 248 | # The key for this row is 'tedtholomewhellojimmy' |
356 | 256 | self.assertTrue(base._publish( | 249 | self.assertTrue(base._publish( |
361 | 257 | service='facebook', account_id='1/facebook', | 250 | account_id='1/facebook', message_id='34567', stream='messages', |
362 | 258 | message_id='34567', stream='messages', sender='tedtholomew', | 251 | sender='tedtholomew', sender_nick='teddy', from_me=False, |
363 | 259 | sender_nick='teddy', from_me=False, timestamp='today', | 252 | timestamp='today', message='hello, @jimmy', likes=10, liked=True)) |
360 | 260 | message='hello, @jimmy', likes=10, liked=True)) | ||
364 | 261 | # See? Two rows in the table. | 253 | # See? Two rows in the table. |
365 | 262 | self.assertEqual(2, TestModel.get_n_rows()) | 254 | self.assertEqual(2, TestModel.get_n_rows()) |
366 | 263 | # The first row is the message from fred. | 255 | # The first row is the message from fred. |
367 | 264 | 256 | ||
368 | === modified file 'gwibber/gwibber/utils/base.py' | |||
369 | --- gwibber/gwibber/utils/base.py 2012-09-19 22:21:39 +0000 | |||
370 | +++ gwibber/gwibber/utils/base.py 2012-09-20 03:15:40 +0000 | |||
371 | @@ -25,7 +25,7 @@ | |||
372 | 25 | 25 | ||
373 | 26 | from threading import Lock, Thread | 26 | from threading import Lock, Thread |
374 | 27 | 27 | ||
376 | 28 | from gwibber.utils.model import COLUMN_INDICES, SCHEMA, Model | 28 | from gwibber.utils.model import COLUMN_INDICES, SCHEMA, DEFAULTS, Model |
377 | 29 | 29 | ||
378 | 30 | 30 | ||
379 | 31 | IGNORED = string.punctuation + string.whitespace | 31 | IGNORED = string.punctuation + string.whitespace |
380 | @@ -98,7 +98,7 @@ | |||
381 | 98 | thread.daemon = True | 98 | thread.daemon = True |
382 | 99 | thread.start() | 99 | thread.start() |
383 | 100 | 100 | ||
385 | 101 | def _publish(self, service, account_id, message_id, **kwargs): | 101 | def _publish(self, account_id, message_id, **kwargs): |
386 | 102 | """Publish fresh data into the model, ignoring duplicates. | 102 | """Publish fresh data into the model, ignoring duplicates. |
387 | 103 | 103 | ||
388 | 104 | :param service: The name of the service this message is published | 104 | :param service: The name of the service this message is published |
389 | @@ -125,7 +125,7 @@ | |||
390 | 125 | # The column value is a list of lists (see gwibber/utils/model.py for | 125 | # The column value is a list of lists (see gwibber/utils/model.py for |
391 | 126 | # details), and because the arguments are themselves a list, this gets | 126 | # details), and because the arguments are themselves a list, this gets |
392 | 127 | # initialized as a triply-nested list. | 127 | # initialized as a triply-nested list. |
394 | 128 | args = [[[service, account_id, message_id]]] | 128 | args = [[[self.__class__.__name__.lower(), account_id, message_id]]] |
395 | 129 | # Now iterate through all the column names listed in the SCHEMA, | 129 | # Now iterate through all the column names listed in the SCHEMA, |
396 | 130 | # except for the first, since we just composed its value in the | 130 | # except for the first, since we just composed its value in the |
397 | 131 | # preceding line. Pop matching column values from the kwargs, in the | 131 | # preceding line. Pop matching column values from the kwargs, in the |
398 | @@ -135,7 +135,7 @@ | |||
399 | 135 | # | 135 | # |
400 | 136 | # Missing column values default to the empty string. | 136 | # Missing column values default to the empty string. |
401 | 137 | for column_name, column_type in SCHEMA[1:]: | 137 | for column_name, column_type in SCHEMA[1:]: |
403 | 138 | args.append(kwargs.pop(column_name, '')) | 138 | args.append(kwargs.pop(column_name, DEFAULTS[column_type])) |
404 | 139 | if len(kwargs) > 0: | 139 | if len(kwargs) > 0: |
405 | 140 | raise TypeError('Unexpected keyword arguments: {}'.format( | 140 | raise TypeError('Unexpected keyword arguments: {}'.format( |
406 | 141 | COMMA_SPACE.join(sorted(kwargs)))) | 141 | COMMA_SPACE.join(sorted(kwargs)))) |
407 | 142 | 142 | ||
408 | === modified file 'gwibber/gwibber/utils/model.py' | |||
409 | --- gwibber/gwibber/utils/model.py 2012-09-19 20:05:40 +0000 | |||
410 | +++ gwibber/gwibber/utils/model.py 2012-09-20 03:15:40 +0000 | |||
411 | @@ -27,6 +27,7 @@ | |||
412 | 27 | 'COLUMN_NAMES', | 27 | 'COLUMN_NAMES', |
413 | 28 | 'COLUMN_TYPES', | 28 | 'COLUMN_TYPES', |
414 | 29 | 'COLUMN_INDICES', | 29 | 'COLUMN_INDICES', |
415 | 30 | 'DEFAULTS', | ||
416 | 30 | ] | 31 | ] |
417 | 31 | 32 | ||
418 | 32 | 33 | ||
419 | @@ -54,7 +55,6 @@ | |||
420 | 54 | SCHEMA = ( | 55 | SCHEMA = ( |
421 | 55 | ('message_ids', 'aas'), | 56 | ('message_ids', 'aas'), |
422 | 56 | ('stream', 's'), | 57 | ('stream', 's'), |
423 | 57 | ('transient', 's'), | ||
424 | 58 | ('sender', 's'), | 58 | ('sender', 's'), |
425 | 59 | ('sender_nick', 's'), | 59 | ('sender_nick', 's'), |
426 | 60 | ('from_me', 'b'), | 60 | ('from_me', 'b'), |
427 | @@ -64,7 +64,6 @@ | |||
428 | 64 | ('icon_uri', 's'), | 64 | ('icon_uri', 's'), |
429 | 65 | ('url', 's'), | 65 | ('url', 's'), |
430 | 66 | ('source', 's'), | 66 | ('source', 's'), |
431 | 67 | ('timestring', 's'), | ||
432 | 68 | ('reply_nick', 's'), | 67 | ('reply_nick', 's'), |
433 | 69 | ('reply_name', 's'), | 68 | ('reply_name', 's'), |
434 | 70 | ('reply_url', 's'), | 69 | ('reply_url', 's'), |
435 | @@ -103,3 +102,7 @@ | |||
436 | 103 | 102 | ||
437 | 104 | Model = Dee.SharedModel.new('com.Gwibber.Streams') | 103 | Model = Dee.SharedModel.new('com.Gwibber.Streams') |
438 | 105 | Model.set_schema_full(COLUMN_TYPES) | 104 | Model.set_schema_full(COLUMN_TYPES) |
439 | 105 | |||
440 | 106 | |||
441 | 107 | # This defines default values for the different data types | ||
442 | 108 | DEFAULTS = dict(s='', b=False, d=0) |