Merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor into lp:lp-community-utils

Status: Needs review
Proposed branch: lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor
Merge into: lp:lp-community-utils
Diff against target: 508 lines (+254/-223)
2 files modified
lp.py (+252/-0)
openerp-nag (+2/-223)
To merge this branch: bzr merge lp:~savoirfairelinux-openerp/lp-community-utils/nag_refactor
Reviewer Review Type Date Requested Status
Stefan Rijnhart (Opener) Needs Fixing
Guewen Baconnier @ Camptocamp code review Pending
Review via email: mp+214445@code.launchpad.net

This proposal supersedes a proposal from 2014-02-06.

Description of the change

Moved functions used in openerp-nag to lp.py so they may be used by other scripts.

As of now, openerp-nag cannot be imported by other scripts due to the dash in its name and the fact it does not end in .py

No code was changed with the exception of the addition of the launchpad_login function which replicates old behaviour.

I have not done any of the cleanup of my old proposal as they can be done later.

Please review this with high priority as any future changed to openerp-nag means I have to rebase this commit.

To post a comment you must log in.
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

Sorry I didn't saw your proposal before (even if I use openerp-nag...) whereas it deserves some attention.
Thanks for your work so far.

Seems good to me even if some changes seems not really necessary to me, surely you had some reasons.

What is the rationale to change the Nag namedtuple to a class? A reason could be that you added methods, but not. On that topic, couldn't we move the big print of the end to a method on Nag() (it seems that it could even be the __str__).

Couldn't already the module be used as a lib? (as there is if __name__ == "__main__":) If the filename is an issue (it is, i we want to import it), maybe we can consider to rename it? Not that I strongly disagree with the split but it seem a bit overkill to me here. But maybe do you have great plans to extend this tool and so it makes more sense ;-)

I strongly disagree to remove the --authenticated option: the Launchpad API is limited with anonymous users and it is used now for the votes (the votes are only visible by authenticated users). BTW you may want to rebase on the master since votes have been integrated meanwhile.

What was the bug with the age?

review: Needs Fixing (code review)
Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal

@gbaconnier-c2c
Good point about adding functions to Nag, the reason for turning it into a class is design and optimization.
It gives the option, like to said, to add functions but also, the ability to inherit from it.

I do have big plans with the lib, for example, automatic diff-validating in the style of (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl), specifically checking style, seeing if the diff introduces pep8 errors, removes them, testing the patches with jenkins, commenting on the MPs, etc.
I have proposed something already: https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-utils/branch_pep8/+merge/205260

As for the --authenticated option, I think I took it away because it wasn't referenced or used, I can put it back now, however I will have no impact. This was a while ago, though, I could have just taken it out accidentally.

The problem with age was that it declared it negatively, then did * -1 again while sorting. I just took out the (-1 * -1 = 1) redundancy. It also has a one off error where an MP from today is displayed as 1 day old.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

 > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
> wrote:
> @gbaconnier-c2c
> Good point about adding functions to Nag, the reason for turning it
> into a class is design and optimization.
The rationale about design is very subjective and the one about
optimization is dubious:

http://hastebin.com/neyedexiju.py

Anyway, if it needs methods, and I think the display should be one, then
it makes sense.

> It gives the option, like to said, to add functions but also, the
> ability to inherit from it.
>
> I do have big plans with the lib, for example, automatic
> diff-validating in the style of
> (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl),
> specifically checking style, seeing if the diff introduces pep8
> errors, removes them, testing the patches with jenkins, commenting on
> the MPs, etc.
> I have proposed something already:
> https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-utils/branch_pep8/+merge/205260
>
>
> As for the --authenticated option, I think I took it away because it
> wasn't referenced or used, I can put it back now, however I will have
> no impact. This was a while ago, though, I could have just taken it
> out accidentally.

That's right at the time you made the proposal, the option seemed to be
not useful.

>
> The problem with age was that it declared it negatively, then did * -1
> again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> also has a one off error where an MP from today is displayed as 1 day
> old.

Ok.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal

> > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
> > wrote:
> > @gbaconnier-c2c
> > Good point about adding functions to Nag, the reason for turning it
> > into a class is design and optimization.
> The rationale about design is very subjective and the one about
> optimization is dubious:
>
> http://hastebin.com/neyedexiju.py

Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
Try this: http://hastebin.com/difomedika.py
That's close to 15% increase in speed for summing members.

>
> Anyway, if it needs methods, and I think the display should be one, then
> it makes sense.
>

I agree

> > It gives the option, like to said, to add functions but also, the
> > ability to inherit from it.
> >
> > I do have big plans with the lib, for example, automatic
> > diff-validating in the style of
> > (https://github.com/torvalds/linux/blob/master/scripts/checkpatch.pl),
> > specifically checking style, seeing if the diff introduces pep8
> > errors, removes them, testing the patches with jenkins, commenting on
> > the MPs, etc.
> > I have proposed something already:
> > https://code.launchpad.net/~savoirfairelinux-openerp/lp-community-
> utils/branch_pep8/+merge/205260
> >
> >
> > As for the --authenticated option, I think I took it away because it
> > wasn't referenced or used, I can put it back now, however I will have
> > no impact. This was a while ago, though, I could have just taken it
> > out accidentally.
>
> That's right at the time you made the proposal, the option seemed to be
> not useful.
>
> >
> > The problem with age was that it declared it negatively, then did * -1
> > again while sorting. I just took out the (-1 * -1 = 1) redundancy. It
> > also has a one off error where an MP from today is displayed as 1 day
> > old.
>
> Ok.

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : Posted in a previous version of this proposal

On 03/10/2014 02:12 PM, Sandy Carter (http://www.savoirfairelinux.com)
wrote:
>> > On 03/07/2014 07:45 PM, Sandy Carter (http://www.savoirfairelinux.com)
>>> wrote:
>>> @gbaconnier-c2c
>>> Good point about adding functions to Nag, the reason for turning it
>>> into a class is design and optimization.
>> The rationale about design is very subjective and the one about
>> optimization is dubious:
>>
>> http://hastebin.com/neyedexiju.py
>
> Declaring a class puts members together in memory and reduces the rate of cache misses, you can see this when performing lookups, but mostly when doing operations.
> Try this: http://hastebin.com/difomedika.py
> That's close to 15% increase in speed for summing members.
>

Enlightening, thanks for the explanation.

Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote : Posted in a previous version of this proposal

@Guewen
> Couldn't already the module be used as a lib? (as there is if __name__ ==
> "__main__":) If the filename is an issue (it is, i we want to import it),
> maybe we can consider to rename it?

The problem right now is that you can't import from it due to its filename (dash in filename and no .py)

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

I would prefer to keep it in one file and rename to openerp_nag.py then. Would that work for you?

review: Needs Information
Revision history for this message
Sandy Carter (http://www.savoirfairelinux.com) (sandy-carter) wrote :

@Stefan, yes that would work, we could even be backwards compatible and have a symlink
openerp_nag -> openerp_nag.py

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Great, setting to Needs Fixing pending such a refactoring.

review: Needs Fixing

Unmerged revisions

29. By Sandy Carter (http://www.savoirfairelinux.com)

Moved common code out of openerp-nag into lp.py so it can be used by other scripts

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'lp.py'
2--- lp.py 1970-01-01 00:00:00 +0000
3+++ lp.py 2014-04-06 16:40:59 +0000
4@@ -0,0 +1,252 @@
5+# Copyright 2012 Canonical Ltd.
6+# Written by:
7+# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
8+# Hacked for the OpenERP Community Reviewers version by:
9+# Guewen Baconnier <guewen.baconnier@camptocamp.com>
10+#
11+# This program is free software: you can redistribute it and/or modify
12+# it under the terms of the GNU General Public License version 3,
13+# as published by the Free Software Foundation.
14+#
15+# This program is distributed in the hope that it will be useful,
16+# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+# GNU General Public License for more details.
19+#
20+# You should have received a copy of the GNU General Public License
21+# along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
23+
24+from __future__ import unicode_literals, print_function
25+
26+import collections
27+import datetime
28+import logging
29+
30+try:
31+ from progressbar import ProgressBar, Bar, Percentage, ETA
32+except ImportError:
33+ ProgressBar = None
34+
35+from launchpadlib.launchpad import Launchpad
36+
37+
38+# Nag:
39+# Indication that we want to nag the 'person'...
40+# ...with the specified 'action'
41+# ...about a particular 'subject'
42+Nag = collections.namedtuple(
43+ "Nag", "person action subject sort_class sort_priority sort_age project_name votes")
44+
45+SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2)
46+
47+
48+def show_lp_object(obj):
49+ print(obj)
50+ print("attributes:", obj.lp_attributes)
51+ for attr in obj.lp_attributes:
52+ print("attribute[%s]: %s" % (attr, getattr(obj, attr)))
53+ print("entries:", obj.lp_entries)
54+ for entry in obj.lp_entries:
55+ print("entry[%s]: %s" % (entry, getattr(obj, entry)))
56+ print("collections:", obj.lp_collections)
57+ print("operations:", obj.lp_operations)
58+ print("-" * 80)
59+
60+
61+class DefaultPolicy:
62+
63+ mps_need_commit_message = False
64+
65+ max_review_age = 0
66+
67+ max_bug_new_age = 7
68+
69+ # minimal number of reviewers before merging
70+ min_approve = 2
71+
72+ # can't be merged before n days
73+ days_before_merge = 5
74+
75+ # can be merged before days_before_merge if at least n approvals
76+ approvals_to_bypass = 3
77+
78+
79+class UTC(datetime.tzinfo):
80+
81+ def dst(self, foo):
82+ return datetime.timedelta(0, 0)
83+
84+ def utcoffset(self, foo):
85+ return datetime.timedelta(0, 0)
86+
87+
88+UTC = UTC()
89+
90+
91+class Votes(object):
92+ """ Holds votes of a proposal """
93+
94+ __states__ = {
95+ 'Approve': 'approve',
96+ 'Needs Fixing': 'needs_fixing',
97+ 'Needs Information': 'needs_information',
98+ 'Abstain': 'abstain',
99+ 'Disapprove': 'disapprove',
100+ 'Resubmit': 'resubmit',
101+ 'Pending': 'pending',
102+ }
103+
104+ __signs__ = {
105+ 'approve': '+',
106+ 'needs_fixing': '!',
107+ 'needs_information': '?',
108+ 'abstain': '~',
109+ 'disapprove': '-',
110+ 'resubmit': 'R',
111+ 'pending': '*',
112+ }
113+
114+ __ignore_for_approval__ = ['abstain', 'pending']
115+
116+ def __init__(self, votes):
117+ self._votes = collections.Counter(
118+ self.__states__[vote.comment.vote if vote.comment else 'Pending']
119+ for vote in votes
120+ ) # no comment is a pending review, usually the team
121+
122+ def __getattr__(self, name):
123+ if name in self._votes:
124+ return self._votes[name]
125+ elif name in self.__states__.values():
126+ return 0
127+ else:
128+ raise AttributeError
129+
130+ def __str__(self):
131+ signs = ['%d%s' % (count, self.__signs__[state]) for
132+ state, count in self._votes.iteritems()]
133+ return ', '.join(signs)
134+
135+ def total(self, for_approval=False):
136+ if for_approval:
137+ return sum(count for state, count in self._votes.iteritems()
138+ if state not in self.__ignore_for_approval__)
139+ return sum(self._votes.values())
140+
141+ @classmethod
142+ def legend(cls):
143+ return dict((state, cls.__signs__[code]) for
144+ state, code in cls.__states__.iteritems())
145+
146+
147+def gen_project_nags(lp, policy, project_name):
148+ # TODO: Detect project groups and redirect the nag to all projects
149+ # underneath that project
150+ # Access the project that we care about
151+ logging.debug("Accessing project %r", project_name)
152+ project = lp.projects[project_name]
153+ # Re-yield all the merge proposal nags
154+ # NOTE: change to yield from in py3k3
155+ for nag in gen_merge_proposal_nags(lp, policy, project):
156+ yield nag
157+
158+
159+def gen_merge_proposal_nags(lp, policy, project):
160+ # XXX: cannnot use utcnow as launchpad returns tz-aware objects here
161+ now = datetime.datetime.now(tz=UTC)
162+ logging.debug("Looking for merge proposals in project %r", project)
163+ # NOTE: Workaround for project.getMergeProposals() crashing on timeouts
164+ try:
165+ merge_proposals = project.getMergeProposals(status='Needs review')
166+ except AttributeError:
167+ logging.warning("The project %s has no code to look at", project)
168+ return
169+ for proposal in merge_proposals:
170+ logging.debug("Looking at merge proposal %r", proposal)
171+ # Skip everything that is still not requested for review
172+ if proposal.date_review_requested is None:
173+ continue
174+ # Skip everything that is already merged
175+ if proposal.date_merged is not None:
176+ continue
177+ votes = Votes(proposal.votes)
178+
179+ # Nag about missing commit message on merge requests
180+ if policy.mps_need_commit_message and proposal.commit_message is None:
181+ yield Nag(
182+ person=proposal.registrant.display_name,
183+ action="set a commit message on merge request",
184+ subject=proposal.web_link,
185+ sort_class=SORT_CLASS_MERGE,
186+ sort_priority=None, # TODO: get from max(linked bugs)
187+ sort_age=(proposal.date_review_requested - now).days,
188+ project_name=project.name,
189+ votes=votes,
190+ )
191+
192+ age = (now - proposal.date_review_requested).days
193+
194+ if (votes.approve == votes.total(for_approval=True) and
195+ (votes.approve >= policy.approvals_to_bypass or
196+ votes.approve >= policy.min_approve and
197+ age >= policy.days_before_merge)):
198+ yield Nag(
199+ person='A committer',
200+ action="consider to merge the proposal",
201+ subject=proposal.web_link,
202+ sort_class=SORT_CLASS_MERGE,
203+ sort_priority=None, # TODO: get from max(linked bugs)
204+ sort_age=(proposal.date_review_requested - now).days,
205+ project_name=project.name,
206+ votes=votes,
207+ )
208+ continue
209+
210+ # Nag about aging merge requests
211+ if age >= policy.max_review_age:
212+ yield Nag(
213+ person='Someone',
214+ action="review the merge request",
215+ subject=proposal.web_link,
216+ sort_class=SORT_CLASS_MERGE,
217+ sort_priority=None, # TODO: get from max(linked bugs)
218+ sort_age=(proposal.date_review_requested - now).days,
219+ project_name=project.name,
220+ votes=votes,
221+ )
222+
223+
224+def gen_bug_nags(lp, policy, project):
225+ now = datetime.datetime.now(tz=UTC)
226+ new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age)
227+ # Nag about bugs that are "New" for too long
228+ logging.debug("Looking at 'new' bugs that are 8 days or older")
229+ aging_new_bugs = project.searchTasks(status='New',
230+ created_before=new_threshold)
231+ for bug_task in aging_new_bugs:
232+ yield Nag(
233+ person='everyone',
234+ action='triage the bug',
235+ subject=bug_task.web_link,
236+ sort_class=SORT_CLASS_BUG,
237+ sort_priority=None, # TODO: convert from importance name
238+ sort_age=None,
239+ votes=None)
240+
241+
242+def parse_projects_file(filename):
243+ """ Parse a file containing name of the projects
244+ A line per project
245+ """
246+ with open(filename, mode='rU') as project_file:
247+ content = project_file.read()
248+
249+ return [name.strip() for name in content.splitlines()]
250+
251+
252+def launchpad_login(anonymous, consumer_name, service_root):
253+ if anonymous:
254+ return Launchpad.login_anonymously(consumer_name, service_root)
255+ else:
256+ return Launchpad.login_with(consumer_name, service_root)
257
258=== modified file 'openerp-nag'
259--- openerp-nag 2014-02-20 16:18:06 +0000
260+++ openerp-nag 2014-04-06 16:40:59 +0000
261@@ -42,234 +42,16 @@
262 from __future__ import unicode_literals, print_function
263
264 import argparse
265-import collections
266-import datetime
267-import logging
268
269 try:
270 from progressbar import ProgressBar, Bar, Percentage, ETA
271 except ImportError:
272 ProgressBar = None
273-from launchpadlib.launchpad import Launchpad
274-
275+from lp import *
276
277 consumer_name = 'OpenERP Community Reviewers Nagging Scripts'
278
279
280-# Nag:
281-# Indication that we want to nag the 'person'...
282-# ...with the specified 'action'
283-# ...about a particular 'subject'
284-Nag = collections.namedtuple(
285- "Nag", "person action subject sort_class sort_priority sort_age project_name votes")
286-
287-SORT_CLASS_BUG, SORT_CLASS_MERGE = range(2)
288-
289-
290-def show_lp_object(obj):
291- print(obj)
292- print("attributes:", obj.lp_attributes)
293- for attr in obj.lp_attributes:
294- print("attribute[%s]: %s" % (attr, getattr(obj, attr)))
295- print("entries:", obj.lp_entries)
296- for entry in obj.lp_entries:
297- print("entry[%s]: %s" % (entry, getattr(obj, entry)))
298- print("collections:", obj.lp_collections)
299- print("operations:", obj.lp_operations)
300- print("-" * 80)
301-
302-
303-class DefaultPolicy:
304-
305- mps_need_commit_message = False
306-
307- max_review_age = 0
308-
309- max_bug_new_age = 7
310-
311- # minimal number of reviewers before merging
312- min_approve = 2
313-
314- # can't be merged before n days
315- days_before_merge = 5
316-
317- # can be merged before days_before_merge if at least n approvals
318- approvals_to_bypass = 3
319-
320-
321-class UTC(datetime.tzinfo):
322-
323- def dst(self, foo):
324- return datetime.timedelta(0, 0)
325-
326- def utcoffset(self, foo):
327- return datetime.timedelta(0, 0)
328-
329-
330-UTC = UTC()
331-
332-
333-class Votes(object):
334- """ Holds votes of a proposal """
335-
336- __states__ = {
337- 'Approve': 'approve',
338- 'Needs Fixing': 'needs_fixing',
339- 'Needs Information': 'needs_information',
340- 'Abstain': 'abstain',
341- 'Disapprove': 'disapprove',
342- 'Resubmit': 'resubmit',
343- 'Pending': 'pending',
344- }
345-
346- __signs__ = {
347- 'approve': '+',
348- 'needs_fixing': '!',
349- 'needs_information': '?',
350- 'abstain': '~',
351- 'disapprove': '-',
352- 'resubmit': 'R',
353- 'pending': '*',
354- }
355-
356- __ignore_for_approval__ = ['abstain', 'pending']
357-
358- def __init__(self, votes):
359- self._votes = collections.Counter(
360- self.__states__[vote.comment.vote if vote.comment else 'Pending']
361- for vote in votes
362- ) # no comment is a pending review, usually the team
363-
364- def __getattr__(self, name):
365- if name in self._votes:
366- return self._votes[name]
367- elif name in self.__states__.values():
368- return 0
369- else:
370- raise AttributeError
371-
372- def __str__(self):
373- signs = ['%d%s' % (count, self.__signs__[state]) for
374- state, count in self._votes.iteritems()]
375- return ', '.join(signs)
376-
377- def total(self, for_approval=False):
378- if for_approval:
379- return sum(count for state, count in self._votes.iteritems()
380- if state not in self.__ignore_for_approval__)
381- return sum(self._votes.values())
382-
383- @classmethod
384- def legend(cls):
385- return dict((state, cls.__signs__[code]) for
386- state, code in cls.__states__.iteritems())
387-
388-
389-def gen_project_nags(lp, policy, project_name):
390- # TODO: Detect project groups and redirect the nag to all projects
391- # underneath that project
392- # Access the project that we care about
393- logging.debug("Accessing project %r", project_name)
394- project = lp.projects[project_name]
395- # Re-yield all the merge proposal nags
396- # NOTE: change to yield from in py3k3
397- for nag in gen_merge_proposal_nags(lp, policy, project):
398- yield nag
399-
400-
401-def gen_merge_proposal_nags(lp, policy, project):
402- # XXX: cannnot use utcnow as launchpad returns tz-aware objects here
403- now = datetime.datetime.now(tz=UTC)
404- logging.debug("Looking for merge proposals in project %r", project)
405- # NOTE: Workaround for project.getMergeProposals() crashing on timeouts
406- try:
407- merge_proposals = project.getMergeProposals(status='Needs review')
408- except AttributeError:
409- logging.warning("The project %s has no code to look at", project)
410- return
411- for proposal in merge_proposals:
412- logging.debug("Looking at merge proposal %r", proposal)
413- # Skip everything that is still not requested for review
414- if proposal.date_review_requested is None:
415- continue
416- # Skip everything that is already merged
417- if proposal.date_merged is not None:
418- continue
419- votes = Votes(proposal.votes)
420-
421- # Nag about missing commit message on merge requests
422- if policy.mps_need_commit_message and proposal.commit_message is None:
423- yield Nag(
424- person=proposal.registrant.display_name,
425- action="set a commit message on merge request",
426- subject=proposal.web_link,
427- sort_class=SORT_CLASS_MERGE,
428- sort_priority=None, # TODO: get from max(linked bugs)
429- sort_age=(proposal.date_review_requested - now).days,
430- project_name=project.name,
431- votes=votes,
432- )
433-
434- age = (now - proposal.date_review_requested).days
435-
436- if (votes.approve == votes.total(for_approval=True) and
437- (votes.approve >= policy.approvals_to_bypass or
438- votes.approve >= policy.min_approve and
439- age >= policy.days_before_merge)):
440- yield Nag(
441- person='A committer',
442- action="consider to merge the proposal",
443- subject=proposal.web_link,
444- sort_class=SORT_CLASS_MERGE,
445- sort_priority=None, # TODO: get from max(linked bugs)
446- sort_age=(proposal.date_review_requested - now).days,
447- project_name=project.name,
448- votes=votes,
449- )
450- continue
451-
452- # Nag about aging merge requests
453- if age >= policy.max_review_age:
454- yield Nag(
455- person='Someone',
456- action="review the merge request",
457- subject=proposal.web_link,
458- sort_class=SORT_CLASS_MERGE,
459- sort_priority=None, # TODO: get from max(linked bugs)
460- sort_age=(proposal.date_review_requested - now).days,
461- project_name=project.name,
462- votes=votes,
463- )
464-
465-
466-def gen_bug_nags(lp, policy, project):
467- now = datetime.datetime.now(tz=UTC)
468- new_threshold = now - datetime.timedelta(days=policy.max_bug_new_age)
469- # Nag about bugs that are "New" for too long
470- logging.debug("Looking at 'new' bugs that are 8 days or older")
471- aging_new_bugs = project.searchTasks(status='New',
472- created_before=new_threshold)
473- for bug_task in aging_new_bugs:
474- yield Nag(
475- person='everyone',
476- action='triage the bug',
477- subject=bug_task.web_link,
478- sort_class=SORT_CLASS_BUG,
479- sort_priority=None, # TODO: convert from importance name
480- sort_age=None,
481- votes=None)
482-
483-
484-def parse_projects_file(filename):
485- """ Parse a file containing name of the projects
486- A line per project
487- """
488- with open(filename, mode='rU') as project_file:
489- content = project_file.read()
490-
491- return [name.strip() for name in content.splitlines()]
492-
493-
494 def main():
495 parser = argparse.ArgumentParser()
496 group = parser.add_argument_group(title="debugging")
497@@ -304,10 +86,7 @@
498 if args.debug:
499 logging.basicConfig(level=logging.DEBUG)
500 # Access Lauchpad object as the current (system) user or anonymously
501- if args.anonymous:
502- lp = Launchpad.login_anonymously(consumer_name, args.service_root)
503- else:
504- lp = Launchpad.login_with(consumer_name, args.service_root)
505+ lp = launchpad_login(args.anonymous, consumer_name, args.service_root)
506
507 if not args.project and not args.projects_file:
508 parser.print_usage()

Subscribers

People subscribed via source and target branches