Merge lp:~lifeless/lptools/upstream into lp:~dobey/lptools/trunk
- upstream
- Merge into trunk
Proposed by
Robert Collins
Status: | Merged |
---|---|
Approved by: | dobey |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~lifeless/lptools/upstream |
Merge into: | lp:~dobey/lptools/trunk |
Diff against target: |
535 lines (+361/-59) 7 files modified
bin/lp-milestones (+189/-0) bin/lp-review-list (+10/-31) bin/lp-review-notifier (+8/-28) lptools/__init__.py (+20/-0) lptools/config.py (+83/-0) lptools/launchpad.py (+38/-0) setup.py (+13/-0) |
To merge this branch: | bzr merge lp:~lifeless/lptools/upstream |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dobey | Approve | ||
Elliot Murphy | Pending | ||
Review via email: mp+19764@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Oh, and commandant depends on bzrlib anyhow :)
Revision history for this message
Robert Collins (lifeless) wrote : | # |
I've added a delete and rename command to this.
lp:~lifeless/lptools/upstream
updated
- 14. By Robert Collins
-
Faster creation please.
- 15. By Robert Collins
-
Work around launchpadlib wanting absolute urls for.load.
- 16. By Robert Collins
-
Add milestone deletion.
- 17. By Robert Collins
-
Add milestone renaming.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
I've also added 'release' as a subcommand, so one can totally script the release process.
lp:~lifeless/lptools/upstream
updated
- 18. By Robert Collins
-
Add releasing.
- 19. By Robert Collins
-
Correctly handle failures in milestones delete.
Revision history for this message
dobey (dobey) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'bin/lp-milestones' | |||
2 | --- bin/lp-milestones 1970-01-01 00:00:00 +0000 | |||
3 | +++ bin/lp-milestones 2010-02-20 06:50:29 +0000 | |||
4 | @@ -0,0 +1,189 @@ | |||
5 | 1 | #!/usr/bin/python | ||
6 | 2 | # | ||
7 | 3 | # Author: Robert Collins <robert.collins@canonical.com> | ||
8 | 4 | # | ||
9 | 5 | # Copyright 2010 Canonical Ltd. | ||
10 | 6 | # | ||
11 | 7 | # This program is free software: you can redistribute it and/or modify it | ||
12 | 8 | # under the terms of the GNU General Public License version 3, as published | ||
13 | 9 | # by the Free Software Foundation. | ||
14 | 10 | # | ||
15 | 11 | # This program is distributed in the hope that it will be useful, but | ||
16 | 12 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
17 | 13 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
18 | 14 | # PURPOSE. See the GNU General Public License for more details. | ||
19 | 15 | # | ||
20 | 16 | # You should have received a copy of the GNU General Public License along | ||
21 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
22 | 18 | |||
23 | 19 | from __future__ import with_statement | ||
24 | 20 | import time | ||
25 | 21 | import optparse | ||
26 | 22 | import os | ||
27 | 23 | import sys | ||
28 | 24 | |||
29 | 25 | # Might want to make errors import lazy. | ||
30 | 26 | from bzrlib import commands, errors, ui, version_info as bzr_version_info | ||
31 | 27 | from launchpadlib.errors import HTTPError | ||
32 | 28 | |||
33 | 29 | from lptools import config | ||
34 | 30 | |||
35 | 31 | def list_commands(command_names): | ||
36 | 32 | mod = sys.modules[__name__] | ||
37 | 33 | command_names.update(commands._scan_module_for_commands(mod)) | ||
38 | 34 | return command_names | ||
39 | 35 | |||
40 | 36 | |||
41 | 37 | def get_command(cmd_or_None, cmd_name): | ||
42 | 38 | if cmd_name is None: | ||
43 | 39 | return cmd_help() | ||
44 | 40 | try: | ||
45 | 41 | return globals()['cmd_' + cmd_name]() | ||
46 | 42 | except KeyError: | ||
47 | 43 | return cmd_or_None | ||
48 | 44 | |||
49 | 45 | |||
50 | 46 | class LaunchpadCommand(commands.Command): | ||
51 | 47 | """Base class for commands working with launchpad.""" | ||
52 | 48 | |||
53 | 49 | def run_argv_aliases(self, argv, alias_argv=None): | ||
54 | 50 | # This might not be unique-enough for a cachedir; can do | ||
55 | 51 | # lp-milestones/cmdname if needed. | ||
56 | 52 | self.launchpad = config.get_launchpad('lp-milestones') | ||
57 | 53 | return commands.Command.run_argv_aliases(self, argv, alias_argv) | ||
58 | 54 | |||
59 | 55 | |||
60 | 56 | class cmd_create(LaunchpadCommand): | ||
61 | 57 | """Create a milestone. | ||
62 | 58 | |||
63 | 59 | lp-milestone create projectname/seriesname/milestonename | ||
64 | 60 | """ | ||
65 | 61 | |||
66 | 62 | takes_args = ['milestone'] | ||
67 | 63 | |||
68 | 64 | def run(self, milestone): | ||
69 | 65 | components = milestone.split('/') | ||
70 | 66 | if len(components) != 3: | ||
71 | 67 | raise errors.BzrCommandError("milestone (%s) too short or too long." | ||
72 | 68 | % milestone) | ||
73 | 69 | projectname, seriesname, milestonename = components | ||
74 | 70 | # Direct access takes 50% of the time of doing traversal. | ||
75 | 71 | #proj = self.launchpad.projects[projectname] | ||
76 | 72 | #series = proj.getSeries(name=seriesname) | ||
77 | 73 | series = self.launchpad.load(projectname + '/' + seriesname) | ||
78 | 74 | milestone = series.newMilestone(name=milestonename) | ||
79 | 75 | |||
80 | 76 | |||
81 | 77 | class cmd_delete(LaunchpadCommand): | ||
82 | 78 | """Delete a milestone. | ||
83 | 79 | |||
84 | 80 | lp-milestone delete projectname/milestonename | ||
85 | 81 | |||
86 | 82 | Note that this cannot delete a milestone with releases on it (yet). The | ||
87 | 83 | server will throw a 500 error, and you may see | ||
88 | 84 | File "/srv/////lib/lp/registry/model/milestone.py", line 209, in destroySelf | ||
89 | 85 | "You cannot delete a milestone which has a product release " | ||
90 | 86 | AssertionError: You cannot delete a milestone which has a product release associated with it. | ||
91 | 87 | In the trace if you have appropriate access. | ||
92 | 88 | """ | ||
93 | 89 | |||
94 | 90 | takes_args = ['milestone'] | ||
95 | 91 | |||
96 | 92 | def run(self, milestone): | ||
97 | 93 | components = milestone.split('/') | ||
98 | 94 | if len(components) != 2: | ||
99 | 95 | raise errors.BzrCommandError("milestone (%s) too short or too long." | ||
100 | 96 | % milestone) | ||
101 | 97 | m = self.launchpad.load('%s/+milestone/%s' % tuple(components)) | ||
102 | 98 | try: | ||
103 | 99 | m.delete() | ||
104 | 100 | except HTTPError, e: | ||
105 | 101 | if e.response.status == 404: | ||
106 | 102 | pass | ||
107 | 103 | elif e.response.status == 500: | ||
108 | 104 | self.outf.write("Perhaps the milestone has been released?\n") | ||
109 | 105 | self.outf.write("If so you can undo this in the web UI.\n") | ||
110 | 106 | raise | ||
111 | 107 | else: | ||
112 | 108 | raise | ||
113 | 109 | |||
114 | 110 | |||
115 | 111 | class cmd_help(commands.Command): | ||
116 | 112 | """Show help on a command or other topic.""" | ||
117 | 113 | |||
118 | 114 | # Can't use the stock bzrlib help, because the help indices aren't quite | ||
119 | 115 | # generic enough. | ||
120 | 116 | takes_args = ['topic?'] | ||
121 | 117 | def run(self, topic=None): | ||
122 | 118 | if topic is None: | ||
123 | 119 | self.outf.write( | ||
124 | 120 | """lp-milestones -- An lptools command to work with milestones in launchpad. | ||
125 | 121 | https://launchpad.net/lptools/ | ||
126 | 122 | |||
127 | 123 | lp-milestones help commands -- list commands | ||
128 | 124 | """) | ||
129 | 125 | else: | ||
130 | 126 | import bzrlib.help | ||
131 | 127 | bzrlib.help.help(topic) | ||
132 | 128 | |||
133 | 129 | |||
134 | 130 | class cmd_release(LaunchpadCommand): | ||
135 | 131 | """Create a release from a milestone. | ||
136 | 132 | |||
137 | 133 | lp-milestone release projectname/milestonename | ||
138 | 134 | """ | ||
139 | 135 | |||
140 | 136 | takes_args = ['milestone'] | ||
141 | 137 | |||
142 | 138 | def run(self, milestone): | ||
143 | 139 | components = milestone.split('/') | ||
144 | 140 | if len(components) != 2: | ||
145 | 141 | raise errors.BzrCommandError("milestone (%s) too short or too long." | ||
146 | 142 | % milestone) | ||
147 | 143 | m = self.launchpad.load('%s/+milestone/%s' % tuple(components)) | ||
148 | 144 | now = time.strftime('%Y-%m-%d-%X', time.gmtime()) | ||
149 | 145 | # note: there is a bug with how the releases are created, don't be surprised | ||
150 | 146 | # if they are created 'X hours ago' where 'X' is the hour in UTC. | ||
151 | 147 | m.createProductRelease(date_released=now) | ||
152 | 148 | |||
153 | 149 | |||
154 | 150 | class cmd_rename(LaunchpadCommand): | ||
155 | 151 | """Rename a milestone. | ||
156 | 152 | |||
157 | 153 | lp-milestone rename projectname/milestonename newname | ||
158 | 154 | """ | ||
159 | 155 | |||
160 | 156 | takes_args = ['milestone', 'newname'] | ||
161 | 157 | |||
162 | 158 | def run(self, milestone, newname): | ||
163 | 159 | components = milestone.split('/') | ||
164 | 160 | if len(components) != 2: | ||
165 | 161 | raise errors.BzrCommandError("milestone (%s) too short or too long." | ||
166 | 162 | % milestone) | ||
167 | 163 | if '/' in newname: | ||
168 | 164 | raise errors.BzrCommandError( | ||
169 | 165 | "milestones can only be renamed within a project.") | ||
170 | 166 | m = self.launchpad.load('%s/+milestone/%s' % tuple(components)) | ||
171 | 167 | m.name = newname | ||
172 | 168 | m.lp_save() | ||
173 | 169 | |||
174 | 170 | |||
175 | 171 | def do_run_bzr(argv): | ||
176 | 172 | if bzr_version_info > (2, 2, 0): | ||
177 | 173 | # in bzr 2.2 we can disable bzr plugins so bzr commands don't show | ||
178 | 174 | # up. | ||
179 | 175 | return commands.run_bzr(argv, lambda:None, lambda:None) | ||
180 | 176 | else: | ||
181 | 177 | return commands.run_bzr(argv) | ||
182 | 178 | |||
183 | 179 | |||
184 | 180 | def main(): | ||
185 | 181 | commands.Command.hooks.install_named_hook('list_commands', list_commands, | ||
186 | 182 | "list") | ||
187 | 183 | commands.Command.hooks.install_named_hook('get_command', get_command, | ||
188 | 184 | "get") | ||
189 | 185 | ui.ui_factory = ui.make_ui_for_terminal(sys.stdin, sys.stdout, sys.stderr) | ||
190 | 186 | sys.exit(commands.exception_to_return_code(do_run_bzr, sys.argv[1:])) | ||
191 | 187 | |||
192 | 188 | if __name__ == "__main__": | ||
193 | 189 | main() | ||
194 | 0 | 190 | ||
195 | === modified file 'bin/lp-review-list' | |||
196 | --- bin/lp-review-list 2010-02-01 05:30:54 +0000 | |||
197 | +++ bin/lp-review-list 2010-02-20 06:50:30 +0000 | |||
198 | @@ -17,25 +17,21 @@ | |||
199 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
200 | 18 | 18 | ||
201 | 19 | from __future__ import with_statement | 19 | from __future__ import with_statement |
202 | 20 | from ConfigParser import ConfigParser | ||
203 | 21 | import os | ||
204 | 22 | import re | ||
205 | 23 | import subprocess | ||
206 | 24 | import sys | ||
207 | 25 | from threading import Thread | ||
208 | 20 | 26 | ||
209 | 21 | import pygtk | 27 | import pygtk |
210 | 22 | pygtk.require('2.0') | 28 | pygtk.require('2.0') |
211 | 23 | import gobject | 29 | import gobject |
212 | 24 | import gtk | 30 | import gtk |
213 | 25 | import pango | 31 | import pango |
214 | 26 | |||
215 | 27 | from ConfigParser import ConfigParser | ||
216 | 28 | import os | ||
217 | 29 | import re | ||
218 | 30 | import subprocess | ||
219 | 31 | import sys | ||
220 | 32 | |||
221 | 33 | from xdg.BaseDirectory import xdg_cache_home, xdg_config_home | 32 | from xdg.BaseDirectory import xdg_cache_home, xdg_config_home |
222 | 34 | 33 | ||
227 | 35 | from threading import Thread | 34 | from lptools import config |
224 | 36 | |||
225 | 37 | from launchpadlib.launchpad import Launchpad, EDGE_SERVICE_ROOT | ||
226 | 38 | from launchpadlib.credentials import Credentials | ||
228 | 39 | 35 | ||
229 | 40 | VOTES = { "Approve" : "#00ff00", | 36 | VOTES = { "Approve" : "#00ff00", |
230 | 41 | "Needs Fixing" : "#993300", | 37 | "Needs Fixing" : "#993300", |
231 | @@ -65,6 +61,7 @@ | |||
232 | 65 | else: | 61 | else: |
233 | 66 | self.projects = [] | 62 | self.projects = [] |
234 | 67 | 63 | ||
235 | 64 | # XXX: Not currently honoured - not sure if it ever worked. | ||
236 | 68 | if self.config.has_option("lptools", "server"): | 65 | if self.config.has_option("lptools", "server"): |
237 | 69 | self.api_server = self.config.get("lptools", "server") | 66 | self.api_server = self.config.get("lptools", "server") |
238 | 70 | else: | 67 | else: |
239 | @@ -167,7 +164,7 @@ | |||
240 | 167 | self.set_title("Pending Reviews") | 164 | self.set_title("Pending Reviews") |
241 | 168 | self.set_default_size(320, 400) | 165 | self.set_default_size(320, 400) |
242 | 169 | self.connect("destroy", lambda w: gtk.main_quit()) | 166 | self.connect("destroy", lambda w: gtk.main_quit()) |
244 | 170 | self.connect("delete_event", lambda w: gtk.main_quit()) | 167 | self.connect("delete_event", lambda w,x: gtk.main_quit()) |
245 | 171 | 168 | ||
246 | 172 | vbox = gtk.VBox() | 169 | vbox = gtk.VBox() |
247 | 173 | self.add(vbox) | 170 | self.add(vbox) |
248 | @@ -203,10 +200,6 @@ | |||
249 | 203 | col = gtk.TreeViewColumn("Branch", cell, markup=0) | 200 | col = gtk.TreeViewColumn("Branch", cell, markup=0) |
250 | 204 | view.append_column(col) | 201 | view.append_column(col) |
251 | 205 | 202 | ||
252 | 206 | self.cachedir = os.path.join(xdg_cache_home, "review-list") | ||
253 | 207 | if not os.path.isdir(self.cachedir): | ||
254 | 208 | os.makedirs(self.cachedir) | ||
255 | 209 | |||
256 | 210 | self.launchpad = None | 203 | self.launchpad = None |
257 | 211 | self.me = None | 204 | self.me = None |
258 | 212 | 205 | ||
259 | @@ -220,21 +213,7 @@ | |||
260 | 220 | Thread(target=self.__lp_login).start() | 213 | Thread(target=self.__lp_login).start() |
261 | 221 | 214 | ||
262 | 222 | def __lp_login(self): | 215 | def __lp_login(self): |
278 | 223 | credsfile = os.path.join(self.cachedir, "credentials") | 216 | self.launchpad = config.get_launchpad("review-list") |
264 | 224 | |||
265 | 225 | if os.path.exists(credsfile): | ||
266 | 226 | creds = Credentials() | ||
267 | 227 | |||
268 | 228 | with file(credsfile) as f: | ||
269 | 229 | creds.load(f) | ||
270 | 230 | self.launchpad = Launchpad(creds, EDGE_SERVICE_ROOT) | ||
271 | 231 | else: | ||
272 | 232 | self.launchpad = Launchpad.get_token_and_login( | ||
273 | 233 | 'review-list', | ||
274 | 234 | EDGE_SERVICE_ROOT, | ||
275 | 235 | self.cachedir) | ||
276 | 236 | with file(credsfile, "w") as f: | ||
277 | 237 | self.launchpad.credentials.save(f) | ||
279 | 238 | 217 | ||
280 | 239 | self.me = self.launchpad.me | 218 | self.me = self.launchpad.me |
281 | 240 | 219 | ||
282 | 241 | 220 | ||
283 | === modified file 'bin/lp-review-notifier' | |||
284 | --- bin/lp-review-notifier 2010-02-01 05:30:54 +0000 | |||
285 | +++ bin/lp-review-notifier 2010-02-20 06:50:29 +0000 | |||
286 | @@ -17,21 +17,18 @@ | |||
287 | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. | 17 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
288 | 18 | 18 | ||
289 | 19 | from __future__ import with_statement | 19 | from __future__ import with_statement |
290 | 20 | import os | ||
291 | 21 | import sys | ||
292 | 20 | 22 | ||
293 | 21 | import pygtk | ||
294 | 22 | pygtk.require('2.0') | ||
295 | 23 | import gobject | 23 | import gobject |
296 | 24 | import gtk | 24 | import gtk |
298 | 25 | 25 | import pygtk | |
299 | 26 | import pynotify | 26 | import pynotify |
300 | 27 | |||
301 | 28 | import os | ||
302 | 29 | import sys | ||
303 | 30 | |||
304 | 31 | from xdg.BaseDirectory import xdg_cache_home | 27 | from xdg.BaseDirectory import xdg_cache_home |
305 | 32 | 28 | ||
308 | 33 | from launchpadlib.launchpad import Launchpad, EDGE_SERVICE_ROOT | 29 | from lptools import config |
309 | 34 | from launchpadlib.credentials import Credentials | 30 | |
310 | 31 | pygtk.require('2.0') | ||
311 | 35 | 32 | ||
312 | 36 | ICON_NAME = "bzr-icon-64" | 33 | ICON_NAME = "bzr-icon-64" |
313 | 37 | 34 | ||
314 | @@ -40,31 +37,14 @@ | |||
315 | 40 | def __init__(self): | 37 | def __init__(self): |
316 | 41 | self.id = 0 | 38 | self.id = 0 |
317 | 42 | self.cached_candidates = {} | 39 | self.cached_candidates = {} |
337 | 43 | 40 | self.launchpad = config.get_launchpad("review-notifier") | |
319 | 44 | self.cachedir = os.path.join(xdg_cache_home, "review-notifier") | ||
320 | 45 | credsfile = os.path.join(self.cachedir, "credentials") | ||
321 | 46 | |||
322 | 47 | if not os.path.isdir(self.cachedir): | ||
323 | 48 | os.makedirs(self.cachedir) | ||
324 | 49 | |||
325 | 50 | if os.path.exists(credsfile): | ||
326 | 51 | creds = Credentials() | ||
327 | 52 | with file(credsfile) as f: | ||
328 | 53 | creds.load(f) | ||
329 | 54 | self.launchpad = Launchpad(creds, EDGE_SERVICE_ROOT) | ||
330 | 55 | else: | ||
331 | 56 | self.launchpad = Launchpad.get_token_and_login('review-notifier', | ||
332 | 57 | EDGE_SERVICE_ROOT, | ||
333 | 58 | self.cachedir) | ||
334 | 59 | with file(credsfile, "w") as f: | ||
335 | 60 | self.launchpad.credentials.save(f) | ||
336 | 61 | |||
338 | 62 | self.me = self.launchpad.me | 41 | self.me = self.launchpad.me |
339 | 63 | 42 | ||
340 | 64 | print "Allo, %s" % self.me.name | 43 | print "Allo, %s" % self.me.name |
341 | 65 | 44 | ||
342 | 66 | self.projects = [] | 45 | self.projects = [] |
343 | 67 | 46 | ||
344 | 47 | # TODO: get the config from lptools.conf | ||
345 | 68 | for arg in sys.argv: | 48 | for arg in sys.argv: |
346 | 69 | if not arg.endswith("review-notifier"): | 49 | if not arg.endswith("review-notifier"): |
347 | 70 | self.projects.append(arg) | 50 | self.projects.append(arg) |
348 | 71 | 51 | ||
349 | === added directory 'lptools' | |||
350 | === added file 'lptools/__init__.py' | |||
351 | --- lptools/__init__.py 1970-01-01 00:00:00 +0000 | |||
352 | +++ lptools/__init__.py 2010-02-20 06:50:30 +0000 | |||
353 | @@ -0,0 +1,20 @@ | |||
354 | 1 | # Author: Robert Collins <robert.collins@canonical.com> | ||
355 | 2 | # | ||
356 | 3 | # Copyright 2010 Canonical Ltd. | ||
357 | 4 | # | ||
358 | 5 | # This program is free software: you can redistribute it and/or modify it | ||
359 | 6 | # under the terms of the GNU General Public License version 3, as published | ||
360 | 7 | # by the Free Software Foundation. | ||
361 | 8 | # | ||
362 | 9 | # This program is distributed in the hope that it will be useful, but | ||
363 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
364 | 11 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
365 | 12 | # PURPOSE. See the GNU General Public License for more details. | ||
366 | 13 | # | ||
367 | 14 | # You should have received a copy of the GNU General Public License along | ||
368 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
369 | 16 | |||
370 | 17 | """LP-tools is largely command line tools. The package contains various helpers. | ||
371 | 18 | |||
372 | 19 | See lptools.config for configuration support logic. | ||
373 | 20 | """ | ||
374 | 0 | 21 | ||
375 | === added file 'lptools/config.py' | |||
376 | --- lptools/config.py 1970-01-01 00:00:00 +0000 | |||
377 | +++ lptools/config.py 2010-02-20 06:50:30 +0000 | |||
378 | @@ -0,0 +1,83 @@ | |||
379 | 1 | # Author: Robert Collins <robert.collins@canonical.com> | ||
380 | 2 | # | ||
381 | 3 | # Copyright 2010 Canonical Ltd. | ||
382 | 4 | # | ||
383 | 5 | # This program is free software: you can redistribute it and/or modify it | ||
384 | 6 | # under the terms of the GNU General Public License version 3, as published | ||
385 | 7 | # by the Free Software Foundation. | ||
386 | 8 | # | ||
387 | 9 | # This program is distributed in the hope that it will be useful, but | ||
388 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
389 | 11 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
390 | 12 | # PURPOSE. See the GNU General Public License for more details. | ||
391 | 13 | # | ||
392 | 14 | # You should have received a copy of the GNU General Public License along | ||
393 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
394 | 16 | |||
395 | 17 | from __future__ import with_statement | ||
396 | 18 | |||
397 | 19 | """Configuration glue for lptools.""" | ||
398 | 20 | |||
399 | 21 | __all__ = [ | ||
400 | 22 | "ensure_dir", | ||
401 | 23 | "get_launchpad", | ||
402 | 24 | "lptools_cachedir", | ||
403 | 25 | "lptools_credentials_path", | ||
404 | 26 | ] | ||
405 | 27 | |||
406 | 28 | import os.path | ||
407 | 29 | |||
408 | 30 | from launchpadlib.credentials import Credentials | ||
409 | 31 | from launchpadlib.launchpad import EDGE_SERVICE_ROOT | ||
410 | 32 | from xdg.BaseDirectory import xdg_cache_home | ||
411 | 33 | |||
412 | 34 | from lptools import launchpad | ||
413 | 35 | |||
414 | 36 | |||
415 | 37 | def ensure_dir(dir): | ||
416 | 38 | """Ensure that dir exists.""" | ||
417 | 39 | if not os.path.isdir(dir): | ||
418 | 40 | os.makedirs(dir) | ||
419 | 41 | |||
420 | 42 | |||
421 | 43 | def get_launchpad(appname): | ||
422 | 44 | """Get a login to launchpad for lptools caching in cachedir. | ||
423 | 45 | |||
424 | 46 | Note that caching is not multiple-process safe in launchpadlib, and the | ||
425 | 47 | appname parameter is used to create per-app cachedirs. | ||
426 | 48 | |||
427 | 49 | :param appname: The name of the app used to create per-app cachedirs. | ||
428 | 50 | """ | ||
429 | 51 | cachedir = os.path.join(xdg_cache_home, appname) | ||
430 | 52 | ensure_dir(cachedir) | ||
431 | 53 | credspath = lptools_credentials_path() | ||
432 | 54 | if os.path.exists(credspath): | ||
433 | 55 | creds = Credentials() | ||
434 | 56 | with file(credspath) as f: | ||
435 | 57 | creds.load(f) | ||
436 | 58 | return launchpad.Launchpad(creds, EDGE_SERVICE_ROOT, cachedir) | ||
437 | 59 | else: | ||
438 | 60 | result = launchpad.Launchpad.get_token_and_login('lptools', | ||
439 | 61 | EDGE_SERVICE_ROOT, cachedir) | ||
440 | 62 | with file(credspath, "w") as f: | ||
441 | 63 | result.credentials.save(f) | ||
442 | 64 | return result | ||
443 | 65 | |||
444 | 66 | |||
445 | 67 | def lptools_cachedir(): | ||
446 | 68 | """Return the cachedir for common lptools things. | ||
447 | 69 | |||
448 | 70 | This is xdg_cache_home/lptools. | ||
449 | 71 | """ | ||
450 | 72 | return os.path.join(xdg_cache_home, "lptools") | ||
451 | 73 | |||
452 | 74 | |||
453 | 75 | def lptools_credentials_path(): | ||
454 | 76 | """Return the path to the lptools credentials file. | ||
455 | 77 | |||
456 | 78 | This also ensures the path is usable by checking it's containing directory | ||
457 | 79 | exists. | ||
458 | 80 | """ | ||
459 | 81 | cachedir = lptools_cachedir() | ||
460 | 82 | ensure_dir(cachedir) | ||
461 | 83 | return os.path.join(lptools_cachedir(), 'credentials') | ||
462 | 0 | 84 | ||
463 | === added file 'lptools/launchpad.py' | |||
464 | --- lptools/launchpad.py 1970-01-01 00:00:00 +0000 | |||
465 | +++ lptools/launchpad.py 2010-02-20 06:50:30 +0000 | |||
466 | @@ -0,0 +1,38 @@ | |||
467 | 1 | # Author: Robert Collins <robert.collins@canonical.com> | ||
468 | 2 | # | ||
469 | 3 | # Copyright 2010 Canonical Ltd. | ||
470 | 4 | # | ||
471 | 5 | # This program is free software: you can redistribute it and/or modify it | ||
472 | 6 | # under the terms of the GNU General Public License version 3, as published | ||
473 | 7 | # by the Free Software Foundation. | ||
474 | 8 | # | ||
475 | 9 | # This program is distributed in the hope that it will be useful, but | ||
476 | 10 | # WITHOUT ANY WARRANTY; without even the implied warranties of | ||
477 | 11 | # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
478 | 12 | # PURPOSE. See the GNU General Public License for more details. | ||
479 | 13 | # | ||
480 | 14 | # You should have received a copy of the GNU General Public License along | ||
481 | 15 | # with this program. If not, see <http://www.gnu.org/licenses/>. | ||
482 | 16 | |||
483 | 17 | __all__ = [ | ||
484 | 18 | 'Launchpad', | ||
485 | 19 | ] | ||
486 | 20 | |||
487 | 21 | """Wrapper class for launchpadlib with tweaks.""" | ||
488 | 22 | |||
489 | 23 | from launchpadlib.launchpad import Launchpad as UpstreamLaunchpad | ||
490 | 24 | |||
491 | 25 | class Launchpad(UpstreamLaunchpad): | ||
492 | 26 | """Launchpad object with bugfixes.""" | ||
493 | 27 | |||
494 | 28 | def load(self, url_string): | ||
495 | 29 | """Load an object. | ||
496 | 30 | |||
497 | 31 | Extended to support url_string being a relative url. | ||
498 | 32 | |||
499 | 33 | Needed until bug 524775 is fixed. | ||
500 | 34 | """ | ||
501 | 35 | if not url_string.startswith('https:'): | ||
502 | 36 | return UpstreamLaunchpad.load(self, str(self._root_uri) + url_string) | ||
503 | 37 | else: | ||
504 | 38 | return UpstreamLaunchpad.load(self, url_string) | ||
505 | 0 | 39 | ||
506 | === modified file 'setup.py' | |||
507 | --- setup.py 2010-02-01 05:30:54 +0000 | |||
508 | +++ setup.py 2010-02-20 06:50:29 +0000 | |||
509 | @@ -2,6 +2,9 @@ | |||
510 | 2 | 2 | ||
511 | 3 | from glob import glob | 3 | from glob import glob |
512 | 4 | from distutils.core import setup | 4 | from distutils.core import setup |
513 | 5 | import os.path | ||
514 | 6 | |||
515 | 7 | description = file(os.path.join(os.path.dirname(__file__), 'README'), 'rb').read() | ||
516 | 5 | 8 | ||
517 | 6 | setup( | 9 | setup( |
518 | 7 | name='lptools', | 10 | name='lptools', |
519 | @@ -11,7 +14,17 @@ | |||
520 | 11 | author_email='rodney.dawes@canonical.com', | 14 | author_email='rodney.dawes@canonical.com', |
521 | 12 | license='GPLv3', | 15 | license='GPLv3', |
522 | 13 | description='A collection of tools for developers who use launchpad', | 16 | description='A collection of tools for developers who use launchpad', |
523 | 17 | long_description=description, | ||
524 | 14 | py_modules=[], | 18 | py_modules=[], |
525 | 19 | packages=['lptools'], | ||
526 | 15 | scripts=glob('bin/*'), | 20 | scripts=glob('bin/*'), |
527 | 21 | classifiers = [ | ||
528 | 22 | 'Development Status :: 4 - Beta', | ||
529 | 23 | 'Intended Audience :: Developers', | ||
530 | 24 | 'License :: OSI Approved :: GNU General Public License v3 (GPL3)' | ||
531 | 25 | 'Operating System :: OS Independent', | ||
532 | 26 | 'Programming Language :: Python', | ||
533 | 27 | 'Topic :: Software Development', | ||
534 | 28 | ], | ||
535 | 16 | ) | 29 | ) |
536 | 17 | 30 |
This adds an lptools package and reduces some duplicate code; I also added a lp-milestones command with a 'create' command. I'll probably add a few more as I automate my release management process.
Oh, it uses bzrlib's command processing facilities, because they are awesome. You might say 'use commandant instead', but that isn't packaged yet.