Merge lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 into lp:launchpad
- bugzilla-3.4-init-db-bug-416514
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~gmb/launchpad/bugzilla-3.4-init-db-bug-416514 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+10516@code.launchpad.net |
Commit message
Description of the change
Graham Binns (gmb) wrote : | # |
Abel Deuring (adeuring) wrote : | # |
Hi Graham,
nice branch. No complaints, but I found a few lines with trailing spaces.
Could you remove them?
r=me
Abel
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -111,3 +111,111 @@
> >>> bugzilla.
> CALLED Bugzilla.time()
> datetime.
> +
> +
> +Initializing the bug database
> +------
> +
> +BugzillaAPI implements IExternalBugTra
> +which takes a list of bug IDs to fetch from the remote server and stores
> +those bugs locally for future use.
> +
> + >>> bugzilla.
> + CALLED Bug.get({'ids': [1, 2], 'permissive': True})
> +
> +The bug data is stored as a list of dicts:
> +
> + >>> def print_bugs(bugs):
> + ... for bug in sorted(bugs):
> + ... print "Bug %s:" % bug
> + ... for key in sorted(
> + ... print " %s: %s" % (key, bugzilla.
> + ... print "\n"
> +
> + >>> print_bugs(
> + Bug 1:
> + alias:
trailing space.
> + assigned_to: <email address hidden>
> + component: GPPSystems
> + creation_time: 20080610T16:19:53
> + id: 1
> + internals:...
> + is_open: True
> + last_change_time: 20080610T16:19:53
> + priority: P1
> + product: Marvin
> + resolution: FIXED
> + severity: normal
> + status: RESOLVED
> + summary: That bloody robot still exists.
> + <BLANKLINE>
> + Bug 2:
> + alias: bug-two
> + assigned_to: <email address hidden>
> + component: Crew
> + creation_time: 20080611T09:23:12
> + id: 2
> + internals:...
> + is_open: True
> + last_change_time: 20080611T09:24:29
> + priority: P1
> + product: HeartOfGold
> + resolution:
trailing space.
> + severity: high
> + status: NEW
> + summary: Collect unknown persons in docking bay 2.
> + <BLANKLINE>
> + <BLANKLINE>
> +
> +
> +Storing bugs
> +------------
> +
> +initializeRemo
> +_storeBugs() will only store a bug once, even if it is requested both by
> +alias and ID. We'll reset the test BugzillaAPI's _bugs and _bug_aliases
> +dicts to demonstrate this.
> +
> + >>> bugzilla._bugs = {}
> + >>> bugzilla.
> + >>> bugzilla.
> + CALLED Bug.get({'ids': [2, 'bug-two'], 'permissive': True})
> +
> + >>> for bug in sorted(
> + ... print "Bug %r:" % bug
> + ... for key in sorted(
> + ... print " %s: %s" % (key, bugzilla.
> + ... print "\n"
> + Bug 2:
> + alias: bug-two
> + assigned_to: <email address hidden>
> + component: Crew
> + creation_time: 20080611T09:23:12
> + id: 2
> +...
Preview Diff
1 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt' |
2 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-19 13:12:30 +0000 |
3 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-21 09:32:57 +0000 |
4 | @@ -75,3 +75,147 @@ |
5 | ... |
6 | BugTrackerAuthenticationError: http://thiswillfail.example.com: |
7 | Fault 300: The username or password you entered is not valid. |
8 | + |
9 | + |
10 | +Getting the server time |
11 | +----------------------- |
12 | + |
13 | +To be able to accurately sync with a bug tracker, we need to be able to |
14 | +check the time on the remote server. We use BugzillaAPI.getCurrentDBTime() |
15 | +to get the current time on the remote server. |
16 | + |
17 | + # There's no way to create a UTC timestamp without monkey-patching |
18 | + # the TZ environment variable. Rather than do that, we create our |
19 | + # own datetime and work with that. |
20 | + >>> from datetime import datetime |
21 | + >>> remote_time = datetime(2009, 8, 19, 17, 2, 2) |
22 | + |
23 | + >>> test_transport.local_datetime = remote_time |
24 | + >>> bugzilla.getCurrentDBTime() |
25 | + CALLED Bugzilla.time() |
26 | + datetime.datetime(2009, 8, 19, 17, 2, 2, tzinfo=<UTC>) |
27 | + |
28 | +If the remote system is in a different timezone, getCurrentDBTime() will |
29 | +convert its time to UTC before returning it. |
30 | + |
31 | + >>> test_transport.utc_offset = 60**2 |
32 | + >>> test_transport.timezone = 'CET' |
33 | + >>> bugzilla.getCurrentDBTime() |
34 | + CALLED Bugzilla.time() |
35 | + datetime.datetime(2009, 8, 19, 16, 2, 2, tzinfo=<UTC>) |
36 | + |
37 | +This works whether the UTC offset is positive or negative. |
38 | + |
39 | + >>> test_transport.utc_offset = -5 * 60**2 |
40 | + >>> test_transport.timezone = 'US/Eastern' |
41 | + >>> bugzilla.getCurrentDBTime() |
42 | + CALLED Bugzilla.time() |
43 | + datetime.datetime(2009, 8, 19, 22, 2, 2, tzinfo=<UTC>) |
44 | + |
45 | + |
46 | +Initializing the bug database |
47 | +----------------------------- |
48 | + |
49 | +BugzillaAPI implements IExternalBugTracker.initializeRemoteBugDB(), |
50 | +which takes a list of bug IDs to fetch from the remote server and stores |
51 | +those bugs locally for future use. |
52 | + |
53 | + >>> bugzilla.initializeRemoteBugDB([1, 2]) |
54 | + CALLED Bug.get({'ids': [1, 2], 'permissive': True}) |
55 | + |
56 | +The bug data is stored as a list of dicts: |
57 | + |
58 | + >>> def print_bugs(bugs): |
59 | + ... for bug in sorted(bugs): |
60 | + ... print "Bug %s:" % bug |
61 | + ... for key in sorted(bugzilla._bugs[bug]): |
62 | + ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) |
63 | + ... print "\n" |
64 | + |
65 | + >>> print_bugs(bugzilla._bugs) |
66 | + Bug 1: |
67 | + alias: |
68 | + assigned_to: test@canonical.com |
69 | + component: GPPSystems |
70 | + creation_time: 20080610T16:19:53 |
71 | + id: 1 |
72 | + internals:... |
73 | + is_open: True |
74 | + last_change_time: 20080610T16:19:53 |
75 | + priority: P1 |
76 | + product: Marvin |
77 | + resolution: FIXED |
78 | + severity: normal |
79 | + status: RESOLVED |
80 | + summary: That bloody robot still exists. |
81 | + <BLANKLINE> |
82 | + Bug 2: |
83 | + alias: bug-two |
84 | + assigned_to: marvin@heartofgold.ship |
85 | + component: Crew |
86 | + creation_time: 20080611T09:23:12 |
87 | + id: 2 |
88 | + internals:... |
89 | + is_open: True |
90 | + last_change_time: 20080611T09:24:29 |
91 | + priority: P1 |
92 | + product: HeartOfGold |
93 | + resolution: |
94 | + severity: high |
95 | + status: NEW |
96 | + summary: Collect unknown persons in docking bay 2. |
97 | + <BLANKLINE> |
98 | + <BLANKLINE> |
99 | + |
100 | + |
101 | +Storing bugs |
102 | +------------ |
103 | + |
104 | +initializeRemoteBugDB() uses the _storeBugs() method to store bug data. |
105 | +_storeBugs() will only store a bug once, even if it is requested both by |
106 | +alias and ID. We'll reset the test BugzillaAPI's _bugs and _bug_aliases |
107 | +dicts to demonstrate this. |
108 | + |
109 | + >>> bugzilla._bugs = {} |
110 | + >>> bugzilla._bug_aliases = {} |
111 | + >>> bugzilla.initializeRemoteBugDB([2, 'bug-two']) |
112 | + CALLED Bug.get({'ids': [2, 'bug-two'], 'permissive': True}) |
113 | + |
114 | + >>> for bug in sorted(bugzilla._bugs): |
115 | + ... print "Bug %r:" % bug |
116 | + ... for key in sorted(bugzilla._bugs[bug]): |
117 | + ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) |
118 | + ... print "\n" |
119 | + Bug 2: |
120 | + alias: bug-two |
121 | + assigned_to: marvin@heartofgold.ship |
122 | + component: Crew |
123 | + creation_time: 20080611T09:23:12 |
124 | + id: 2 |
125 | + internals:... |
126 | + is_open: True |
127 | + last_change_time: 20080611T09:24:29 |
128 | + priority: P1 |
129 | + product: HeartOfGold |
130 | + resolution: |
131 | + severity: high |
132 | + status: NEW |
133 | + summary: Collect unknown persons in docking bay 2. |
134 | + <BLANKLINE> |
135 | + <BLANKLINE> |
136 | + |
137 | +Aliases are stored in a separate dict, which contains a mapping between |
138 | +the alias and the bug's actual ID. |
139 | + |
140 | + >>> for alias, bug_id in bugzilla._bug_aliases.items(): |
141 | + ... print "%s: %s" % (alias, bug_id) |
142 | + bug-two: 2 |
143 | + |
144 | +The method _getActualBugId() returns the correct bug ID for a passed bug |
145 | +ID or alias. |
146 | + |
147 | + >>> bugzilla._getActualBugId('bug-two') |
148 | + 2 |
149 | + |
150 | + >>> bugzilla._getActualBugId(2) |
151 | + 2 |
152 | |
153 | === modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt' |
154 | --- lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2009-08-19 09:22:08 +0000 |
155 | +++ lib/lp/bugs/doc/externalbugtracker-bugzilla-lp-plugin.txt 2009-08-21 09:32:57 +0000 |
156 | @@ -1,4 +1,5 @@ |
157 | -= Bugzilla bugtrackers with the Launchpad plugin = |
158 | +Bugzilla bugtrackers with the Launchpad plugin |
159 | +============================================== |
160 | |
161 | These tests cover the BugzillaLPPlugin ExternalBugTracker, which handles |
162 | Bugzilla instances that have the Launchpad plugin installed. |
163 | @@ -25,7 +26,8 @@ |
164 | True |
165 | |
166 | |
167 | -== Authentication == |
168 | +Authentication |
169 | +-------------- |
170 | |
171 | XML-RPC methods that modify data on the remote server require |
172 | authentication. To authenticate, we create a LoginToken of type |
173 | @@ -154,7 +156,8 @@ |
174 | Protocol error: 500 "Internal server error" |
175 | |
176 | |
177 | -== Getting the current time == |
178 | +Getting the current time |
179 | +------------------------ |
180 | |
181 | The BugzillaLPPlugin ExternalBugTracker, like all other |
182 | ExternalBugTrackers, has a getCurrentDBTime() method, which returns the |
183 | @@ -173,7 +176,8 @@ |
184 | datetime.datetime(2008, 5, 16, 15, 53, 20, tzinfo=<UTC>) |
185 | |
186 | |
187 | -== Initializing the remote bug database == |
188 | +Initializing the remote bug database |
189 | +------------------------------------ |
190 | |
191 | The BugzillaLPPlugin implements the standard initializeRemoteBugDB() |
192 | method, taking a list of the bug ids that need to be updated. It uses |
193 | @@ -228,56 +232,13 @@ |
194 | <BLANKLINE> |
195 | <BLANKLINE> |
196 | |
197 | -initializeRemoteBugDB() will only store a bug once, even if it is |
198 | -requested both by alias and ID. We'll reset the test BugzillaLPPlugin's |
199 | -_bugs and _bug_aliases dicts to demonstrate this. |
200 | - |
201 | - >>> bugzilla._bugs = {} |
202 | - >>> bugzilla._bug_aliases = {} |
203 | - >>> bugzilla.initializeRemoteBugDB([2, 'bug-two']) |
204 | - CALLED Launchpad.get_bugs({'ids': [2, 'bug-two'], 'permissive': True}) |
205 | - |
206 | - >>> for bug in sorted(bugzilla._bugs): |
207 | - ... print "Bug %r:" % bug |
208 | - ... for key in sorted(bugzilla._bugs[bug]): |
209 | - ... print " %s: %s" % (key, bugzilla._bugs[bug][key]) |
210 | - ... print "\n" |
211 | - Bug 2: |
212 | - alias: bug-two |
213 | - assigned_to: marvin@heartofgold.ship |
214 | - component: Crew |
215 | - creation_time: 20080611T09:23:12 |
216 | - id: 2 |
217 | - internals:... |
218 | - is_open: True |
219 | - last_change_time: 20080611T09:24:29 |
220 | - priority: P1 |
221 | - product: HeartOfGold |
222 | - resolution: |
223 | - severity: high |
224 | - status: NEW |
225 | - summary: Collect unknown persons in docking bay 2. |
226 | - <BLANKLINE> |
227 | - <BLANKLINE> |
228 | - |
229 | -Aliases are stored in a separate dict, which contains a mapping between |
230 | -the alias and the bug's actual ID. |
231 | - |
232 | - >>> for alias, bug_id in bugzilla._bug_aliases.items(): |
233 | - ... print "%s: %s" % (alias, bug_id) |
234 | - bug-two: 2 |
235 | - |
236 | -The method _getActualBugId() returns the correct bug ID for a passed bug |
237 | -ID or alias. |
238 | - |
239 | - >>> bugzilla._getActualBugId('bug-two') |
240 | - 2 |
241 | - |
242 | - >>> bugzilla._getActualBugId(2) |
243 | - 2 |
244 | - |
245 | - |
246 | -== Getting a list of changed bugs == |
247 | +BugzillaLPPlugin.initializeRemoteBugDB() uses its _storeBugs() method to |
248 | +store bugs. See externalbugtracker-bugzilla-api.txt for details of |
249 | +_storeBugs(). |
250 | + |
251 | + |
252 | +Getting a list of changed bugs |
253 | +------------------------------ |
254 | |
255 | IExternalBugTracker defines a method, getModifiedRemoteBugs(), which |
256 | accepts a list of bug IDs and a datetime as a parameter and returns the |
257 | @@ -337,7 +298,8 @@ |
258 | CALLED Launchpad.get_bugs({'ids': [3], 'permissive': True}) |
259 | |
260 | |
261 | -== Getting remote statuses == |
262 | +Getting remote statuses |
263 | +----------------------- |
264 | |
265 | BugzillaLPPlugin.getRemoteStatus() will return the remote status of a |
266 | given bug as a string. If the bug has a resolution, that will be |
267 | @@ -369,7 +331,8 @@ |
268 | >>> del bugzilla._bugs[999] |
269 | |
270 | |
271 | -== Getting the remote product == |
272 | +Getting the remote product |
273 | +-------------------------- |
274 | |
275 | getRemoteProduct() returns the product a remote bug is associated with |
276 | in Bugzilla. |
277 | @@ -393,7 +356,8 @@ |
278 | BugNotFound: 12345 |
279 | |
280 | |
281 | -== Retrieving remote comments == |
282 | +Retrieving remote comments |
283 | +-------------------------- |
284 | |
285 | BugzillaLPPlugin implments the ISupportsCommentImport interface, which |
286 | means that we can use it to import comments from the remote Bugzilla |
287 | @@ -435,7 +399,8 @@ |
288 | >>> LaunchpadZopelessLayer.switchDbUser(config.checkwatches.dbuser) |
289 | |
290 | |
291 | -=== getCommentIds() === |
292 | +getCommentIds() |
293 | +--------------- |
294 | |
295 | ISupportsCommentImport.getCommentIds() is the method used to get all the |
296 | comment IDs for a given bug on a remote bugtracker. |
297 | @@ -456,7 +421,8 @@ |
298 | BugNotFound: 42 |
299 | |
300 | |
301 | -=== fetchComments() === |
302 | +fetchComments() |
303 | +--------------- |
304 | |
305 | ISupportsCommentImport.fetchComments() is the method used to fetch a |
306 | given set of comments from the remote bugtracker. It takes a bug watch |
307 | @@ -488,7 +454,8 @@ |
308 | time: 20080616T13:22:29 |
309 | |
310 | |
311 | -=== getPosterForComment() === |
312 | +getPosterForComment() |
313 | +--------------------- |
314 | |
315 | ISupportsCommentImport.getPosterForComment() returns a tuple of |
316 | (displayname, email) for the author of a remote comment. |
317 | @@ -507,7 +474,8 @@ |
318 | None trillian |
319 | |
320 | |
321 | -=== getMessageForComment() === |
322 | +getMessageForComment() |
323 | +---------------------- |
324 | |
325 | ISupportsCommentImport.getMessageForComment() returns a Launchpad |
326 | IMessage instance for a given comment. It takes a bug watch, a comment |
327 | @@ -533,7 +501,8 @@ |
328 | 2008-06-16 13:08:08+00:00 |
329 | |
330 | |
331 | -== Pushing comments to remote systems == |
332 | +Pushing comments to remote systems |
333 | +---------------------------------- |
334 | |
335 | BugzillaLPPlugin implements the ISupportsCommentPushing interface, which |
336 | defines the necessary methods for pushing comments to remote servers. |
337 | @@ -583,7 +552,8 @@ |
338 | <BLANKLINE> |
339 | |
340 | |
341 | -== Linking remote bugs to Launchpad bugs == |
342 | +Linking remote bugs to Launchpad bugs |
343 | +------------------------------------- |
344 | |
345 | BugzillaLPPlugin implements the ISupportsBackLinking interface, which |
346 | provides methods to set and retrieve the Launchpad bug that links to a |
347 | @@ -644,7 +614,8 @@ |
348 | 10 |
349 | |
350 | |
351 | -== Working with a specified set of Bugzilla products == |
352 | +Working with a specified set of Bugzilla products |
353 | +------------------------------------------------- |
354 | |
355 | BugzillaLPPlugin can be instructed to only get the data for a set of |
356 | bug IDs if those bugs belong to one of a given set of products. |
357 | @@ -691,7 +662,8 @@ |
358 | 0 |
359 | |
360 | |
361 | -== Getting the products for a set of remote bugs == |
362 | +Getting the products for a set of remote bugs |
363 | +--------------------------------------------- |
364 | |
365 | BugzillaLPPlugin provides a helper method, getProductsForRemoteBugs(), |
366 | which takes a list of bug IDs or aliases and returns the products to |
367 | |
368 | === modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py' |
369 | --- lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-19 13:19:22 +0000 |
370 | +++ lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-21 09:32:57 +0000 |
371 | @@ -430,6 +430,98 @@ |
372 | self.baseurl, |
373 | "Fault %s: %s" % (fault.faultCode, fault.faultString)) |
374 | |
375 | + def _storeBugs(self, remote_bugs): |
376 | + """Store remote bugs in the local `bugs` dict.""" |
377 | + for remote_bug in remote_bugs: |
378 | + self._bugs[remote_bug['id']] = remote_bug |
379 | + |
380 | + # The bug_aliases dict is a mapping between aliases and bug |
381 | + # IDs. We use the aliases dict to look up the correct ID for |
382 | + # a bug. This allows us to reference a bug by either ID or |
383 | + # alias. |
384 | + if remote_bug['alias'] != '': |
385 | + self._bug_aliases[remote_bug['alias']] = remote_bug['id'] |
386 | + |
387 | + def getCurrentDBTime(self): |
388 | + """See `IExternalBugTracker`.""" |
389 | + time_dict = self.xmlrpc_proxy.Bugzilla.time() |
390 | + |
391 | + # Convert the XML-RPC DateTime we get back into a regular Python |
392 | + # datetime. |
393 | + server_db_timetuple = time.strptime( |
394 | + str(time_dict['db_time']), '%Y%m%dT%H:%M:%S') |
395 | + server_db_datetime = datetime(*server_db_timetuple[:6]) |
396 | + |
397 | + # The server's DB time is the one that we want to use. However, |
398 | + # this may not be in UTC, so we need to convert it. Since we |
399 | + # can't guarantee that the timezone data returned by the server |
400 | + # is sane, we work out the server's offset from UTC by looking |
401 | + # at the difference between the web_time and the web_time_utc |
402 | + # values. |
403 | + server_web_time = time.strptime( |
404 | + str(time_dict['web_time']), '%Y%m%dT%H:%M:%S') |
405 | + server_web_datetime = datetime(*server_web_time[:6]) |
406 | + server_web_time_utc = time.strptime( |
407 | + str(time_dict['web_time_utc']), '%Y%m%dT%H:%M:%S') |
408 | + server_web_datetime_utc = datetime(*server_web_time_utc[:6]) |
409 | + |
410 | + server_utc_offset = server_web_datetime - server_web_datetime_utc |
411 | + server_utc_datetime = server_db_datetime - server_utc_offset |
412 | + |
413 | + return server_utc_datetime.replace(tzinfo=pytz.timezone('UTC')) |
414 | + |
415 | + def _getActualBugId(self, bug_id): |
416 | + """Return the actual bug id for an alias or id.""" |
417 | + # See if bug_id is actually an alias. |
418 | + actual_bug_id = self._bug_aliases.get(bug_id) |
419 | + |
420 | + # bug_id isn't an alias, so try turning it into an int and |
421 | + # looking the bug up by ID. |
422 | + if actual_bug_id is not None: |
423 | + return actual_bug_id |
424 | + else: |
425 | + try: |
426 | + actual_bug_id = int(bug_id) |
427 | + except ValueError: |
428 | + # If bug_id can't be int()'d then it's likely an alias |
429 | + # that doesn't exist, so raise BugNotFound. |
430 | + raise BugNotFound(bug_id) |
431 | + |
432 | + # Check that the bug does actually exist. That way we're |
433 | + # treating integer bug IDs and aliases in the same way. |
434 | + if actual_bug_id not in self._bugs: |
435 | + raise BugNotFound(bug_id) |
436 | + |
437 | + return actual_bug_id |
438 | + |
439 | + def _getBugIdsToRetrieve(self, bug_ids): |
440 | + """For a set of bug IDs, return those for which we have no data.""" |
441 | + bug_ids_to_retrieve = [] |
442 | + for bug_id in bug_ids: |
443 | + try: |
444 | + actual_bug_id = self._getActualBugId(bug_id) |
445 | + except BugNotFound: |
446 | + bug_ids_to_retrieve.append(bug_id) |
447 | + |
448 | + return bug_ids_to_retrieve |
449 | + |
450 | + def initializeRemoteBugDB(self, bug_ids): |
451 | + """See `IExternalBugTracker`.""" |
452 | + # First, discard all those bug IDs about which we already have |
453 | + # data. |
454 | + bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) |
455 | + |
456 | + # Pull the bug data from the remote server. permissive=True here |
457 | + # prevents Bugzilla from erroring if we ask for a bug it doesn't |
458 | + # have. |
459 | + response_dict = self.xmlrpc_proxy.Bug.get({ |
460 | + 'ids': bug_ids_to_retrieve, |
461 | + 'permissive': True, |
462 | + }) |
463 | + remote_bugs = response_dict['bugs'] |
464 | + |
465 | + self._storeBugs(remote_bugs) |
466 | + |
467 | |
468 | class BugzillaLPPlugin(BugzillaAPI): |
469 | """An `ExternalBugTracker` to handle Bugzillas using the LP Plugin.""" |
470 | @@ -472,18 +564,6 @@ |
471 | raise BugTrackerAuthenticationError( |
472 | self.baseurl, message) |
473 | |
474 | - def _storeBugs(self, remote_bugs): |
475 | - """Store remote bugs in the local `bugs` dict.""" |
476 | - for remote_bug in remote_bugs: |
477 | - self._bugs[remote_bug['id']] = remote_bug |
478 | - |
479 | - # The bug_aliases dict is a mapping between aliases and bug |
480 | - # IDs. We use the aliases dict to look up the correct ID for |
481 | - # a bug. This allows us to reference a bug by either ID or |
482 | - # alias. |
483 | - if remote_bug['alias'] != '': |
484 | - self._bug_aliases[remote_bug['alias']] = remote_bug['id'] |
485 | - |
486 | def getModifiedRemoteBugs(self, bug_ids, last_checked): |
487 | """See `IExternalBugTracker`.""" |
488 | # We marshal last_checked into an xmlrpclib.DateTime since |
489 | @@ -538,12 +618,7 @@ |
490 | """See `IExternalBugTracker`.""" |
491 | # First, discard all those bug IDs about which we already have |
492 | # data. |
493 | - bug_ids_to_retrieve = [] |
494 | - for bug_id in bug_ids: |
495 | - try: |
496 | - actual_bug_id = self._getActualBugId(bug_id) |
497 | - except BugNotFound: |
498 | - bug_ids_to_retrieve.append(bug_id) |
499 | + bug_ids_to_retrieve = self._getBugIdsToRetrieve(bug_ids) |
500 | |
501 | # Next, grab the bugs we still need from the remote server. |
502 | # We pass permissive=True to ensure that Bugzilla won't error if |
503 | @@ -573,30 +648,6 @@ |
504 | server_utc_time = datetime(*server_timetuple[:6]) |
505 | return server_utc_time.replace(tzinfo=pytz.timezone('UTC')) |
506 | |
507 | - def _getActualBugId(self, bug_id): |
508 | - """Return the actual bug id for an alias or id.""" |
509 | - # See if bug_id is actually an alias. |
510 | - actual_bug_id = self._bug_aliases.get(bug_id) |
511 | - |
512 | - # bug_id isn't an alias, so try turning it into an int and |
513 | - # looking the bug up by ID. |
514 | - if actual_bug_id is not None: |
515 | - return actual_bug_id |
516 | - else: |
517 | - try: |
518 | - actual_bug_id = int(bug_id) |
519 | - except ValueError: |
520 | - # If bug_id can't be int()'d then it's likely an alias |
521 | - # that doesn't exist, so raise BugNotFound. |
522 | - raise BugNotFound(bug_id) |
523 | - |
524 | - # Check that the bug does actually exist. That way we're |
525 | - # treating integer bug IDs and aliases in the same way. |
526 | - if actual_bug_id not in self._bugs: |
527 | - raise BugNotFound(bug_id) |
528 | - |
529 | - return actual_bug_id |
530 | - |
531 | def getRemoteStatus(self, bug_id): |
532 | """See `IExternalBugTracker`.""" |
533 | actual_bug_id = self._getActualBugId(bug_id) |
534 | |
535 | === modified file 'lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt' |
536 | --- lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-18 16:39:17 +0000 |
537 | +++ lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-21 09:32:57 +0000 |
538 | @@ -576,3 +576,68 @@ |
539 | Traceback (most recent call last): |
540 | ... |
541 | Fault: <Fault 300: 'The username or password you entered is not valid.'> |
542 | + |
543 | + |
544 | +Getting the current time |
545 | +------------------------ |
546 | + |
547 | +The Bugzilla.time() method allows us to retrieve a dict of the time on |
548 | +the remote server. |
549 | + |
550 | + >>> time_dict = server.Bugzilla.time() |
551 | + >>> for key in sorted(time_dict): |
552 | + ... print "%s: %s" % (key, time_dict[key]) |
553 | + db_time: 20080501T01:01:01 |
554 | + tz_name: UTC |
555 | + tz_offset: +0000 |
556 | + tz_short_name: UTC |
557 | + web_time: 20080501T01:01:01 |
558 | + web_time_utc: 20080501T01:01:01 |
559 | + |
560 | +If the remote server is in a different timezone, the db_time and |
561 | +web_time items will be in the server's local timezone whilst |
562 | +web_time_utc will be in UTC. |
563 | + |
564 | + >>> bugzilla_transport.utc_offset = 60**2 |
565 | + >>> bugzilla_transport.timezone = 'CET' |
566 | + >>> time_dict = server.Bugzilla.time() |
567 | + >>> for key in sorted(time_dict): |
568 | + ... print "%s: %s" % (key, time_dict[key]) |
569 | + db_time: 20080501T01:01:01 |
570 | + tz_name: CET |
571 | + tz_offset: +0100 |
572 | + tz_short_name: CET |
573 | + web_time: 20080501T01:01:01 |
574 | + web_time_utc: 20080501T00:01:01 |
575 | + |
576 | + |
577 | +Getting bugs from the server |
578 | +---------------------------- |
579 | + |
580 | +The Bugzilla API method Bug.get() allows us to get one or more bugs from |
581 | +the remote server. It takes a list of bug IDs to return and returns a |
582 | +list of dicts containing those bugs' data. |
583 | + |
584 | + >>> return_value = server.Bug.get( |
585 | + ... {'ids': [1], 'permissive': True}) |
586 | + >>> [bug_dict] = return_value['bugs'] |
587 | + >>> for key in sorted(bug_dict): |
588 | + ... print "%s: %s" % (key, bug_dict[key]) |
589 | + alias: |
590 | + assigned_to: test@canonical.com |
591 | + component: GPPSystems |
592 | + creation_time: 20080610T16:19:53 |
593 | + id: 1 |
594 | + internals:... |
595 | + is_open: True |
596 | + last_change_time: 20080610T16:19:53 |
597 | + priority: P1 |
598 | + product: Marvin |
599 | + resolution: FIXED |
600 | + severity: normal |
601 | + status: RESOLVED |
602 | + summary: That bloody robot still exists. |
603 | + |
604 | +Note that further tests for this functionality can be found in the |
605 | +"Launchpad.get_bugs()" section, above. This is because these two methods |
606 | +are synonymous. |
607 | |
608 | === modified file 'lib/lp/bugs/tests/externalbugtracker.py' |
609 | --- lib/lp/bugs/tests/externalbugtracker.py 2009-08-18 16:41:18 +0000 |
610 | +++ lib/lp/bugs/tests/externalbugtracker.py 2009-08-21 09:32:57 +0000 |
611 | @@ -522,7 +522,9 @@ |
612 | # what BugZilla will return. |
613 | local_time = xmlrpclib.DateTime(local_datetime.timetuple()) |
614 | |
615 | - utc_date_time = local_datetime - timedelta(seconds=self.utc_offset) |
616 | + utc_offset_delta = timedelta(seconds=self.utc_offset) |
617 | + utc_date_time = local_datetime - utc_offset_delta |
618 | + |
619 | utc_time = xmlrpclib.DateTime(utc_date_time.timetuple()) |
620 | return { |
621 | 'local_time': local_time, |
622 | @@ -757,7 +759,11 @@ |
623 | |
624 | # Map namespaces onto method names. |
625 | methods = { |
626 | - 'Bugzilla': ['version'], |
627 | + 'Bug': ['get'], |
628 | + 'Bugzilla': [ |
629 | + 'time', |
630 | + 'version', |
631 | + ], |
632 | 'Test': ['login_required'], |
633 | 'User': ['login'], |
634 | } |
635 | @@ -794,6 +800,29 @@ |
636 | 300, |
637 | "The username or password you entered is not valid.") |
638 | |
639 | + def time(self): |
640 | + """Return a dict of the local time and associated data.""" |
641 | + # We cheat slightly by calling the superclass to get the time |
642 | + # data. We do this the old fashioned way because XML-RPC |
643 | + # Transports don't support new-style classes. |
644 | + time_dict = TestBugzillaXMLRPCTransport.time(self) |
645 | + offset_hours = (self.utc_offset / 60) / 60 |
646 | + offset_string = '+%02d00' % offset_hours |
647 | + |
648 | + return { |
649 | + 'db_time': time_dict['local_time'], |
650 | + 'tz_name': time_dict['tz_name'], |
651 | + 'tz_offset': offset_string, |
652 | + 'tz_short_name': time_dict['tz_name'], |
653 | + 'web_time': time_dict['local_time'], |
654 | + 'web_time_utc': time_dict['utc_time'], |
655 | + } |
656 | + |
657 | + def get(self, arguments): |
658 | + """Return a list of bug dicts for a given set of bug ids.""" |
659 | + # This method is actually just a synonym for get_bugs(). |
660 | + return self.get_bugs(arguments) |
661 | + |
662 | |
663 | class TestMantis(Mantis): |
664 | """Mantis ExternalSystem for use in tests. |
This branch adds an initializeRemot eBugDB( ) method to the BugzillaAPI cker. I've
ExternalBugTracker, in accordance with IExternalBugTra
refactored a couple of methods out of BugzillaLPPlugin, which descends
from BugzillaAPI, and into the BugzillaAPI class because they are useful
utilities.
I've also refactored the associated tests into the bugzilla-api doctest.
Note that this branch is based on a branch that hasn't yet landed (but
which is r=cprov). Please use the diff below rather than the one
generated by the merge proposal.
Launchpad lint
--------------
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: bugs/doc/ externalbugtrac ker-bugzilla- api.txt bugs/doc/ externalbugtrac ker-bugzilla- lp-plugin. txt bugs/externalbu gtracker/ bugzilla. py bugs/tests/ bugzilla- xmlrpc- transport. txt bugs/tests/ externalbugtrac ker.py
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Diff of changes
---------------
=== modified file 'lib/lp/ bugs/doc/ externalbugtrac ker-bugzilla- api.txt' bugs/doc/ externalbugtrac ker-bugzilla- api.txt 2009-08-20 15:43:37 +0000 bugs/doc/ externalbugtrac ker-bugzilla- api.txt 2009-08-21 09:51:57 +0000 getCurrentDBTim e() datetime( 2009, 8, 19, 22, 2, 2, tzinfo=<UTC>) ------- ------- ------- -- cker.initialize RemoteBugDB( ), initializeRemot eBugDB( [1, 2]) bugzilla. _bugs[bug] ): _bugs[bug] [key]) bugzilla. _bugs) teBugDB( ) uses the _storeBugs() method to store bug data.
--- lib/lp/
+++ lib/lp/
@@ -111,3 +111,111 @@
>>> bugzilla.
CALLED Bugzilla.time()
datetime.
+
+
+Initializing the bug database
+------
+
+BugzillaAPI implements IExternalBugTra
+which takes a list of bug IDs to fetch from the remote server and stores
+those bugs locally for future use.
+
+ >>> bugzilla.
+ CALLED Bug.get({'ids': [1, 2], 'permissive': True})
+
+The bug data is stored as a list of dicts:
+
+ >>> def print_bugs(bugs):
+ ... for bug in sorted(bugs):
+ ... print "Bug %s:" % bug
+ ... for key in sorted(
+ ... print " %s: %s" % (key, bugzilla.
+ ... print "\n"
+
+ >>> print_bugs(
+ Bug 1:
+ alias:
+ assigned_to: <email address hidden>
+ component: GPPSystems
+ creation_time: 20080610T16:19:53
+ id: 1
+ internals:...
+ is_open: True
+ last_change_time: 20080610T16:19:53
+ priority: P1
+ product: Marvin
+ resolution: FIXED
+ severity: normal
+ status: RESOLVED
+ summary: That bloody robot still exists.
+ <BLANKLINE>
+ Bug 2:
+ alias: bug-two
+ assigned_to: <email address hidden>
+ component: Crew
+ creation_time: 20080611T09:23:12
+ id: 2
+ internals:...
+ is_open: True
+ last_change_time: 20080611T09:24:29
+ priority: P1
+ product: HeartOfGold
+ resolution:
+ severity: high
+ status: NEW
+ summary: Collect unknown persons in docking bay 2.
+ <BLANKLINE>
+ <BLANKLINE>
+
+
+Storing bugs
+------------
+
+initializeRemo
+_storeBugs() will only store a bug once, even if it is requested both by
+alias and ID...