lpltk/0000775000000000017510000000000012071171760007114 5ustar lpltk/NEWS0000664000000000017510000000562011753031405007613 0ustar May 24, 2011 - Version 0.4.5 ============================ The past year has seen a lot of growth for lpltk. Wrapper classes have been quite fully fleshed out, and a good bit of convenience functionality has been added as well. Brad has added a few tutorial scripts to the examples directory. Some unit tests are starting to be written and can be invoked via the run-tests script. It seems launchpadlib has been moving towards use of gnome-keyring over credentials files, so with this version we're re-introducing credential file management into lpltk. -- Bryce July 22, 2010 - Version 0.4 =========================== Brad Figg has been hammering out wrapper classes for Launchpad API objects. These do call caching and provide other convenience functions to simplify use of launchpadlib. LaunchpadLib now mostly handles credentials now via login_anonymously() and login_with(), so we've transitioned launchpadlib-toolkit (or lpltk as we're nicknaming it now) to using those calls rather than manage the credentials at this level. While this package is not intended to be a collection of scripts, we've included a few small tools in the scripts directory that may be useful in other scripts, where you need to e.g. dynamically look up the name of the current version of ubuntu under development. -- Bryce June, 2010 - Version 0.3 ======================== python-launchpadlib-toolkit's scope is gradually increasing. This version adds several useful scripts from Arsenal that require nothing more than the toolkit itself, and that may be of general interest. -- Bryce Oct 09, 2009 - Version 0.2 ========================== This version adds some fault handling and assorted fixes. -- Bryce Sept 29, 2009 - Version 0.1 =========================== This is a first draft of the python-launchpadlib-toolkit package. The principle motivation is the widespread duplication of effort in implementing credential handling across a vast array of launchpadlib applications. At Plumbers' it seems there is strong desire for more collaboration between LPL scripters. Since pretty much every LPL script needs to do credentials management, standardizing this into a sharable library seems like a logical first step. There's so much similarity from one credentials implementation to another that this probably could have started from anyone's code. I opted to start with my own implementation from Arsenal since that's the one I'm most familiar with. I've integrated some good implementation ideas from Markus Korn's work on ilaunchpad-shell and ubuntu-dev-tools, such as the nifty DebugStdOut trickery, the use of environment variables for configuration, and the essense of his HTTPError exception handling. One of my major design goals was to keep it simple, so there's been several ideas I've left out simply on the grounds that I wasn't sure if they'd be that useful in real-world cases. Stuff can always be added later as it's proven necessary! -- Bryce lpltk/Makefile0000664000000000017510000000142311756537714010572 0ustar APPNAME=lpltk APPURL=http://launchpad.net/lpltk # Run all the tests check: ./run-tests clean: rm -rf doc/api find . -name '*.pyc' -print0 | xargs -0 rm -f find . -name '*~' -print0 | xargs -0 rm -f # Check for common & easily catchable Python mistakes. pyflakes: pyflakes $(APPNAME) # Check for coding standard violations. pep8: find . -name '*.py' -print0 | xargs -0 pep8 --ignore E221,E222 find . -name '*.py' -print0 | xargs -0 pep8 --ignore E221,E222 --repeat | wc -l apidocs: mkdir -p doc pydoctor --add-package $(APPNAME) \ --make-html --html-output=doc/api \ --project-name=$(APPNAME) \ --project-url=$(APPURL) lint: pyflakes pep8 .PHONY: check lint pyflakes pep8 apidocs clean # Ignore pyflakes exit code so pep8 will run for 'make lint' .IGNORE: pyflakes lpltk/tests/0000775000000000017510000000000012002341133010241 5ustar lpltk/tests/test-bug-task.py0000664000000000017510000000204111773460511013322 0ustar #!/usr/bin/python import os import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk import LaunchpadService class TestBugTask(unittest.TestCase): # setUp # def setUp(self): self.configuration = {} self.configuration['launchpad_services_root'] = 'staging' def test_to_dict(self): ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) bug_task = bug.tasks[0] d = bug_task.to_dict() self.assertEqual(d['target'], "Launchpad itself") self.assertEqual(d['date_created'], "2004-12-21T16:33:36.359671+00:00") self.assertEqual(len(d.keys()), 8) def test_status_age(self): import datetime ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) bug_task = bug.tasks[0] self.assertEqual(datetime.timedelta, type(bug_task.status_age())) if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/tests/test-tags.py0000664000000000017510000001030511753031405012537 0ustar #!/usr/bin/python import os import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk.LaunchpadService import LaunchpadService from lpltk.utils import o2str class TestTags(unittest.TestCase): # setUp # def setUp(self): self.configuration = {} self.configuration['launchpad_services_root'] = 'staging' # tearDown # def tearDown(self): ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) tags = [] for tag in bug.tags: tags.append(tag) for tag in tags: bug.tags.remove(o2str(tag)) bug.tags.append('feature') # test__init__ # def test__init__(self): # Tags object creation # try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) tags = bug.tags finally: bug = None ls = None # test__contains__ # def test__contains__(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) self.assertTrue('feature' in bug.tags) self.assertTrue('foo' not in bug.tags) finally: bug = None ls = None # test__setitem__ # def test__setitem__(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) bug.tags[0] = 'setitem' self.assertTrue('setitem' == bug.tags[0]) bug.tags[0] = 'feature' self.assertTrue('feature' == bug.tags[0]) finally: bug = None ls = None # test__getitem__ # def test__getitem__(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) self.assertTrue('feature' == bug.tags[0]) finally: bug = None ls = None # test__iter__ # def test__iter__(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) ct = 0 for tag in bug.tags: ct += 1 self.assertTrue('feature' == bug.tags[0]) self.assertTrue(ct == 1) finally: bug = None ls = None # test_len # def test_len(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) self.assertTrue(len(bug.tags) == 1) finally: bug = None ls = None # test_append # def test_append(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) bug.tags.append('append') self.assertTrue(len(bug.tags) == 2) self.assertTrue('append' in bug.tags) self.assertTrue('append' == bug.tags[0]) finally: bug = None ls = None # test_extend # def test_extend(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) tags_ct = len(bug.tags) # We don't know which order the tests will be run bug.tags.extend(['extend', 'extend2']) self.assertTrue(len(bug.tags) == (tags_ct + 2)) self.assertTrue('extend' in bug.tags) self.assertTrue('extend2' in bug.tags) finally: bug = None ls = None # test_remove # def test_remove(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) tags_ct = len(bug.tags) # We don't know which order the tests will be run bug.tags.extend(['remove1', 'remove2']) self.assertTrue(len(bug.tags) == (tags_ct + 2)) self.assertTrue('remove1' in bug.tags) self.assertTrue('remove2' in bug.tags) bug.tags.remove('remove1') bug.tags.remove('remove2') finally: bug = None ls = None if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/tests/test-bug.py0000664000000000017510000000637011753031405012365 0ustar #!/usr/bin/python import os import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk import LaunchpadService class TestBug(unittest.TestCase): # setUp # def setUp(self): self.configuration = {} self.configuration['launchpad_services_root'] = 'staging' # test__init__ # def test__init__(self): # Basic instance creation. # try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) except: self.assertTrue(False) finally: bug = None ls = None # test_get_title # def test_get_title(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) title = bug.title self.assertEqual(title, 'Custom information for each translation team') finally: bug = None ls = None # test_set_title # def test_set_title(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) original_title = bug.title bug.title = "Testing Bug.title property" self.assertEqual(bug.title, 'Testing Bug.title property') bug.title = original_title self.assertEqual(bug.title, original_title) finally: bug = None ls = None # test_get_description # def test_get_description(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) description = bug.description if "It would be nice if we could easily add *MUST READ* type of links" not in description: self.assertTrue(False) finally: bug = None ls = None # test_set_description # def test_set_description(self): try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) original_description = bug.description bug.description = "Testing Bug.title property" self.assertEqual(bug.description, 'Testing Bug.title property') bug.description = original_description self.assertEqual(bug.description, original_description) finally: bug = None ls = None # test_owner_full_name # def test_owner_full_name(self): ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) self.assertEqual("Jordi Mallach", bug.owner.full_name) # test_owner_first_name # def test_owner_first_name(self): ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) self.assertEqual("Jordi", bug.owner.first_name) def test_to_dict(self): ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) d = bug.to_dict() self.assertEqual(d['id'], 3) self.assertEqual(d['owner_name'], "jordi") self.assertEqual(d['date_created'], "Tue Dec 21 00:00:00 2004") self.assertEqual(len(d.keys()), 26) if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/tests/test-attachments.py0000664000000000017510000001242412001205315014106 0ustar #!/usr/bin/python import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk.LaunchpadService import LaunchpadService from lpltk.attachments import * from lpltk.person import * class TestTags(unittest.TestCase): # setUp # def setUp(self): self.configuration = {} self.configuration['launchpad_services_root'] = 'edge' self.configuration['read_only'] = True # tearDown # def tearDown(self): pass # test__init__ # def test__init__(self): # Attachments object creation # print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) attachments = bug.attachments print "Class: %s" %(type(attachments)) print "Atts: %d" %(len(attachments)) for attachment in attachments: print " Type: %s" %(type(attachment)) print " Title: %s" %(attachment.title) print " Kind: %s" %(attachment.kind) print finally: bug = None ls = None def test_attachment_owner(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) attachments = bug.attachments for attachment in attachments: assert(attachment.owner) print " Owner: %s" %(attachment.owner.lpperson) print " First: %s" %(attachment.owner.first_name) print " Msg: %s chars" %(len(attachment.message.content)) print " Age: %s days" %(attachment.age) print finally: bug = None ls = None def test_attachment_contents(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) attachments = bug.attachments for attachment in attachments: print " filename: %s" %(attachment.filename) print " content-type: %s" %(attachment.content_type) print " content: %s chars" %(len(attachment.content)) print " patch? %s" %(attachment.is_patch()) print " archive? %s" %(attachment.is_archive()) print finally: bug = None ls = None def test_containment(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) attachments = bug.attachments # Test containment attachment = attachments[0] if not attachment in attachments: raise AssertionError else: print "Located attachment in collection" finally: bug = None ls = None def test_requirements(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) attachments = bug.attachments missing = attachments.check_required_files(['*.tar.gz', 'alsa.*', 'faslkjasdfnfds*asdfdsaa']) if len(missing)>1: print "Unexpectedly missing files %s" %(missing) raise AssertionError print "Required files present and accounted for, except: %s" %(missing) finally: bug = None ls = None def test_owner_filtered_attachments(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(107232) lark = Person(bug, ls.launchpad.people['florent-g']) attachments = bug.attachments # Test len() print " %d attachments before filtering" %(len(attachments)) attachments.add_filter(filter_owned_by_person, [lark]) print " %d attachments after filtering" %(len(attachments)) print # Test iteration for attachment in attachments: print " Type: %s" %(type(attachment)) print " Title: %s" %(attachment.title) print " Owner: %s" %(attachment.owner.full_name) print finally: bug = None ls = None def test_filename_filtered_attachments(self): print try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(570228) attachments = bug.attachments glob_patterns = ["*Xorg*Log*", "*Dmesg.txt"] print " %d attachments before filtering" %(len(attachments)) attachments.add_filter(filter_filename_matches_globs, glob_patterns) print " %d attachments after filtering on filenames" %(len(attachments)) age = 0 for attachment in attachments: age = attachment.age attachments.add_filter(filter_age_between, [age-1, age+1]) print " %d attachments after filtering on age" %(len(attachments)) print finally: bug = None ls = None if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/tests/test-launchpad-service.py0000664000000000017510000000712711753031405015206 0ustar #!/usr/bin/python import os import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk import LaunchpadService class TestLaunchpadService(unittest.TestCase): # setUp # def setUp(self): pass # test__init__ # def test__init__(self): # Basic instance creation. # try: ls = LaunchpadService() ls = None except: self.assertTrue(False) # Verify default configuration parameters are set correctly. # try: ls = LaunchpadService() self.assertEqual(ls.config['launchpad_client_name'], 'lpltk') self.assertEqual(ls.config['launchpad_services_root'], 'production') self.assertEqual(ls.config['project_name'], '') self.assertEqual(ls.config['read_only'], False) self.assertEqual(ls.config['launchpad_cachedir'], os.path.join(os.path.expanduser('~'), '.cache', ls.config['launchpad_client_name'])) ls = None except: self.assertTrue(False) # Verify that the default configuration parameters will be overridden by # the passed in config dictionary. # try: cfg = {} cfg['launchpad_client_name'] = 'bilbo' cfg['launchpad_services_root'] = 'edge' cfg['project_name'] = 'linux' cfg['read_only'] = True cfg['launchpad_cachedir'] = 'deadEnd' ls = LaunchpadService(cfg) for k in cfg: self.assertEqual(ls.config[k], cfg[k]) ls = None except: self.assertTrue(False) # Verify that we actually connected to Launchpad by getting back the title # or bug #1 # try: ls = LaunchpadService() self.assertEqual(ls.launchpad.bugs[1].title, 'Microsoft has a majority market share') ls = None except: self.assertTrue(False) # Verify that I can connect to Launchpad in "read_only" mode. # try: cfg = {} cfg['read_only'] = True ls = LaunchpadService(cfg) self.assertEqual(ls.launchpad.bugs[1].title, 'Microsoft has a majority market share') ls = None except: self.assertTrue(False) # test_get_launchpad_bug # def test_get_launchpad_bug(self): try: ls = LaunchpadService() b = ls.get_launchpad_bug(1) self.assertEqual(b.title, 'Microsoft has a majority market share') ls = None except: self.assertTrue(False) # test_load_project # def test_load_project(self): # Verify that a specific projct can be loaded # try: ls = LaunchpadService() p = ls.load_project('ubuntu') self.assertEqual(p.display_name, 'Ubuntu') self.assertEqual(p.owner.display_name, 'Ubuntu Drivers') ls = None except: self.assertTrue(False) # test_reset # def test_reset(self): try: ls = LaunchpadService() ls.reset() b = ls.get_launchpad_bug(1) self.assertEqual(b.title, 'Microsoft has a majority market share') ls = None except: self.assertTrue(False) # test_new_bug # def test_new_bug(self): pass if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/tests/test_script_syntax.sh0000775000000000017510000000241711753031405014567 0ustar #!/bin/bash display_result() { if [ $1 == 0 ]; then echo "pass ${2} ${3}" else echo "FAIL ${2} ${3}" fi } ignorable() { for ext in '~' '~c' .pyc .o ; do if [ ${file_name%${ext}} != ${file_name} ]; then return 0 fi done return 1 } # Basic syntax checking for script in scripts/* ; do file_type=$(file -b ${script}) file_name=$(basename $script) # Compiled objects and other cruft if ignorable ${file_name} ; then continue fi # Bash case ${file_type} in *troff*) ;; *byte-compiled*) ;; *bash*) bash -n $script >/dev/null 2>&1 display_result $? 'bash' ${script} ;; *Bourne*) bash -n $script >/dev/null 2>&1 display_result $? 'bash' ${script} ;; *perl*) perl -c $script >/dev/null 2>&1 display_result $? 'perl' ${script} ;; *python*script*) python -m py_compile ${script} display_result $? 'python' ${script} if [ -e ${script}c ]; then rm ${script}c fi ;; *) echo "Unknown script type '${file_type}'" display_result 1 ${script} esac done lpltk/tests/test-template.py0000664000000000017510000000140311753031405013413 0ustar #!/usr/bin/python import sys, os.path sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) import unittest from lpltk.LaunchpadService import LaunchpadService class TestTags(unittest.TestCase): # setUp # def setUp(self): self.configuration = {} self.configuration['launchpad_services_root'] = 'staging' # tearDown # def tearDown(self): pass # test__init__ # def test__init__(self): # Tags object creation # try: ls = LaunchpadService(self.configuration) bug = ls.get_bug(3) finally: bug = None ls = None if __name__ == '__main__': unittest.main() # vi:set ts=4 sw=4 expandtab: lpltk/doc/0000775000000000017510000000000011756537721007675 5ustar lpltk/doc/api/0000775000000000017510000000000011756537722010447 5ustar lpltk/doc/api/lpltk.bug_activity.html0000664000000000017510000000247311756540206015150 0ustar lpltk.bug_activity : API documentation

l.bug_activity : module documentation

Part of lpltk

Undocumented
Class Activity Undocumented
Class BugActivity Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.specifications.html0000664000000000017510000000221711756540206015456 0ustar lpltk.specifications : API documentation

l.specifications : module documentation

Part of lpltk

Undocumented
Class Specifications Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.html0000664000000000017510000001406011756540205012452 0ustar lpltk : API documentation

lpltk : package documentation

Undocumented
Module LaunchpadService No module docstring; 2/2 classes documented
Module attachment Undocumented
Module attachments No module docstring; 0/2 classes, 4/5 functions documented
Module bug Undocumented
Module bug_activity Undocumented
Module bug_target Undocumented
Module bug_task Undocumented
Module bug_tasks Undocumented
Module bugs Undocumented
Module debug No module docstring; 1/1 classes, 0/4 functions documented
Module distribution Undocumented
Module distribution_source_package Undocumented
Module distributions Undocumented
Module distro_series Undocumented
Module message Undocumented
Module messages Undocumented
Module milestone Undocumented
Module milestones Undocumented
Module nomination Undocumented
Module nominations Undocumented
Module person Undocumented
Module ppa No module docstring; 1/2 classes, 1/3 functions documented
Module project Undocumented
Module project_series Undocumented
Module projects Undocumented
Module specification Undocumented
Module specifications Undocumented
Module tags Undocumented
Module utils Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.debug.html0000664000000000017510000000566711756540206013555 0ustar lpltk.debug : API documentation

l.debug : module documentation

Part of lpltk

No module docstring
Function dbg Undocumented
Function err Undocumented
Function die Undocumented
Class DebugStdOut Debug version of STDOUT to redact out the oauth credentials
Function dump_launchpad_object Undocumented
def dbg(msg):
Undocumented
def err(msg):
Undocumented
def die(msg):
Undocumented
def dump_launchpad_object(i):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.specification.Specification.html0000664000000000017510000003364011756540206020056 0ustar lpltk.specification.Specification : API documentation

l.s.Specification(object) : class documentation

Part of lpltk.specification View In Hierarchy

Undocumented
Method __init__ Undocumented
Method owner Undocumented
Method approver Undocumented
Method drafter Undocumented
Method assignee Undocumented
Method starter Undocumented
Method completer Undocumented
Method date_created Undocumented
Method date_started Undocumented
Method date_completed Undocumented
Method importance Undocumented
Method definition_status Undocumented
Method implementation_status Undocumented
Method lifecycle_status Undocumented
Method milestone Undocumented
Method approved Undocumented
Method complete Undocumented
Method started Undocumented
Method name Undocumented
Method summary Undocumented
Method title Undocumented
Method whiteboard Undocumented
Method url Undocumented
Method bugs Undocumented
Method dependencies Undocumented
def __init__(self, service, specification):
Undocumented
@property
def owner(self):
Undocumented
@property
def approver(self):
Undocumented
@property
def drafter(self):
Undocumented
@property
def assignee(self):
Undocumented
@property
def starter(self):
Undocumented
@property
def completer(self):
Undocumented
@property
def date_created(self):
Undocumented
@property
def date_started(self):
Undocumented
@property
def date_completed(self):
Undocumented
@property
def importance(self):
Undocumented
@property
def definition_status(self):
Undocumented
@property
def implementation_status(self):
Undocumented
@property
def lifecycle_status(self):
Undocumented
@property
def milestone(self):
Undocumented
@property
def approved(self):
Undocumented
@property
def complete(self):
Undocumented
@property
def started(self):
Undocumented
@property
def name(self):
Undocumented
@property
def summary(self):
Undocumented
@property
def title(self):
Undocumented
@property
def whiteboard(self):
Undocumented
@property
def url(self):
Undocumented
@property
def bugs(self):
Undocumented
@property
def dependencies(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distributions.Distributions.html0000664000000000017510000001025211756540206020214 0ustar lpltk.distributions.Distributions : API documentation

l.d.Distributions(object) : class documentation

Part of lpltk.distributions View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bugs.html0000664000000000017510000000213511756540206013412 0ustar lpltk.bugs : API documentation

l.bugs : module documentation

Part of lpltk

Undocumented
Class Bugs Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_target.html0000664000000000017510000000217111756540206014575 0ustar lpltk.bug_target : API documentation

l.bug_target : module documentation

Part of lpltk

Undocumented
Class BugTarget Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.project.html0000664000000000017510000000215411756540206014121 0ustar lpltk.project : API documentation

l.project : module documentation

Part of lpltk

Undocumented
Class Project Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.ppa.html0000664000000000017510000000565511756540205013243 0ustar lpltk.ppa : API documentation

l.ppa : module documentation

Part of lpltk

No module docstring
Function encode Undocumented
Function expand_ppa_line Convert an abbreviated ppa name of the form 'ppa:$name' to a
Class CurlCallback Undocumented
Function get_ppa_info_from_lp Undocumented
Class AddPPASigningKeyThread thread class for adding the signing key in the background
def encode(s):
Undocumented
def expand_ppa_line(abrev, distro_codename):
Convert an abbreviated ppa name of the form 'ppa:$name' to a proper sources.list line of the form 'deb ...'
def get_ppa_info_from_lp(owner_name, ppa_name):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_tasks.BugTasks.html0000664000000000017510000001414611756540206016163 0ustar lpltk.bug_tasks.BugTasks : API documentation

l.b.BugTasks(object) : class documentation

Part of lpltk.bug_tasks View In Hierarchy

No class docstring
Method __init__ Undocumented
Method filtered_tasks Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method set_filter_by_distro Only include tasks that are targeted to the given distro
Method set_filter_by_status Include tasks only in the specified state
Method set_filter_by_bug_tracker_type Include only tasks for upstream projects using this type of bug tracker
def __init__(self, service, lp_tasks):
Undocumented
def filtered_tasks(self):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def set_filter_by_distro(self, distroname='Ubuntu'):
Only include tasks that are targeted to the given distro
def set_filter_by_status(self, state):
Include tasks only in the specified state

The regular bug statuses "Incomplete", "Fix Committed", et al are supported, as well as special keywords 'open' and 'complete'.

def set_filter_by_bug_tracker_type(self, bugtracker_type):
Include only tasks for upstream projects using this type of bug tracker
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.ppa.AddPPASigningKeyThread.html0000664000000000017510000000561411756540205017406 0ustar lpltk.ppa.AddPPASigningKeyThread : API documentation

l.p.AddPPASigningKeyThread(Thread) : class documentation

Part of lpltk.ppa View In Hierarchy

thread class for adding the signing key in the background
Method __init__ Undocumented
Method run Undocumented
Method add_ppa_signing_key Query and add the corresponding PPA signing key.
def __init__(self, ppa_path, keyserver=None):
Undocumented
def run(self):
Undocumented
def add_ppa_signing_key(self, ppa_path):
Query and add the corresponding PPA signing key.

The signing key fingerprint is obtained from the Launchpad PPA page, via a secure channel, so it can be trusted.

API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.project.Project.html0000664000000000017510000001070711756540206015531 0ustar lpltk.project.Project : API documentation

l.p.Project(object) : class documentation

Part of lpltk.project View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __str__ Undocumented
Method owner Undocumented
Method name Undocumented
Method display_name Undocumented
Method search_tasks Undocumented
Method self_link Undocumented
def __init__(self, service, lp_project):
Undocumented
def __str__(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def name(self):
Undocumented
@property
def display_name(self):
Undocumented
def search_tasks(self, **params):
Undocumented
@property
def self_link(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/index.html0000664000000000017510000000215711756540205012437 0ustar API Documentation for lpltk

API Documentation for lpltk

Entry Points

About

This documentation was automatically generated by pydoctor at 2012-05-21 15:24:05.

lpltk/doc/api/lpltk.distro_series.html0000664000000000017510000000220711756540205015327 0ustar lpltk.distro_series : API documentation

l.distro_series : module documentation

Part of lpltk

Undocumented
Class DistroSeries Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_target.BugTarget.html0000664000000000017510000000411711756540206016462 0ustar lpltk.bug_target.BugTarget : API documentation

l.b.BugTarget(object) : class documentation

Part of lpltk.bug_target View In Hierarchy

Undocumented
Method __init__ Undocumented
Method name Undocumented
def __init__(self, service, bug, lp_bug_target):
Undocumented
@property
def name(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distributions.html0000664000000000017510000000221211756540206015350 0ustar lpltk.distributions : API documentation

l.distributions : module documentation

Part of lpltk

Undocumented
Class Distributions Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_activity.Activity.html0000664000000000017510000001012411756540206016733 0ustar lpltk.bug_activity.Activity : API documentation

l.b.Activity(object) : class documentation

Part of lpltk.bug_activity View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service, bug):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.nominations.Nominations.html0000664000000000017510000001016111756540206017303 0ustar lpltk.nominations.Nominations : API documentation

l.n.Nominations(object) : class documentation

Part of lpltk.nominations View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service, bug):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachments.Attachments.html0000664000000000017510000002603511756540206017244 0ustar lpltk.attachments.Attachments : API documentation

l.a.Attachments(object) : class documentation

Part of lpltk.attachments View In Hierarchy

No class docstring
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method download_in_dir Undocumented
Method extract_archives Undocumented
Method try_gzip Undocumented
Method force_mimetype Undocumented
Method add_filter Add filter f to constrain the list of attachments.
Method check_required_files Check that collection includes required filenames
Method __fetch_if_needed Undocumented
Method __gzip_if_needed Undocumented
Method __find_mime_type Undocumented
Method __get_tar_members Undocumented
Method __exceeds_tar_limit Undocumented
Method __get_zip_members Undocumented
Method __exceeds_zip_limit Undocumented
def __init__(self, tkbug):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
def __gzip_if_needed(self, attachment):
Undocumented
def __find_mime_type(self, attachment):
Undocumented
def __get_tar_members(self, tarattachment):
Undocumented
def __exceeds_tar_limit(self, fd):
Undocumented
def __get_zip_members(self, zipattachment):
Undocumented
def __exceeds_zip_limit(self, fd):
Undocumented
def download_in_dir(self, download_dir):
Undocumented
def extract_archives(self, extract_dir, extract_limit=None):
Undocumented
def try_gzip(self, gzip_dir):
Undocumented
def force_mimetype(self):
Undocumented
def add_filter(self, f, params):
Add filter f to constrain the list of attachments.

f is a function which takes as arguments an Attachment object, and a list of parameters specific to the given filter.

def check_required_files(self, glob_patterns):
Check that collection includes required filenames

Given a list of glob filename patterns, looks through the attachments to verify at least one attachment fulfils the required file pattern. Returns a list of globs that were not matched. Returns an empty list if all requirements were met.

API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachment.html0000664000000000017510000000217311756540206014604 0ustar lpltk.attachment : API documentation

l.attachment : module documentation

Part of lpltk

Undocumented
Class Attachment Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.message.html0000664000000000017510000000215411756540206014077 0ustar lpltk.message : API documentation

l.message : module documentation

Part of lpltk

Undocumented
Class Message Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.projects.Projects.html0000664000000000017510000001001611756540205016067 0ustar lpltk.projects.Projects : API documentation

l.p.Projects(object) : class documentation

Part of lpltk.projects View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug.html0000664000000000017510000000307611756540206013234 0ustar lpltk.bug : API documentation

l.bug : module documentation

Part of lpltk

Undocumented
Function fix_time Undocumented
Class Bug No class docstring; 7/36 methods documented
def fix_time(t):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug.Bug.html0000664000000000017510000005307411756540206013753 0ustar lpltk.bug.Bug : API documentation

l.b.Bug(object) : class documentation

Part of lpltk.bug View In Hierarchy

No class docstring
Method __init__ Undocumented
Method date_created Undocumented
Method age Age of bug in days
Method milestone_found Undocumented
Method date_last_updated Undocumented
Method age_last_updated Age of last update to bug in days
Method date_last_message Undocumented
Method age_last_message Age of last comment to bug in days
Method private 0 Undocumented
Method private Undocumented
Method security_related 0 Undocumented
Method security_related Undocumented
Method title 0 A one-line summary of the problem being described by the bug.
Method title Undocumented
Method description 0 As complete as possible description of the bug/issue being reported as a bug.
Method description Undocumented
Method tags Undocumented
Method releases Undocumented
Method owner Undocumented
Method owner_name Undocumented
Method attachments Undocumented
Method properties Returns dict of key: value pairs found in the bug description
Method messages Undocumented
Method messages_count Undocumented
Method tasks Undocumented
Method add_comment Add a new comment to an existing bug.
Method nominations Undocumented
Method add_nomination Undocumented
Method activity Undocumented
Method duplicate_of Undocumented
Method duplicates_count Undocumented
Method subscriptions_count Undocumented
Method heat Undocumented
Method date_latest_patch_uploaded Undocumented
Method has_patch Undocumented
Method is_expirable Undocumented
Method users_affected_count Undocumented
Method users_unaffected_count Undocumented
Method gravity An implementation of the "gravity" value as defined at:
Method to_dict Converts the bug to a serializable dict.
def __init__(self, service, bug_number, commit_changes=True):
Undocumented
@property
def date_created(self):
Undocumented
def age(self):
Age of bug in days
@property
def milestone_found(self):
Undocumented
@property
def date_last_updated(self):
Undocumented
def age_last_updated(self):
Age of last update to bug in days
@property
def date_last_message(self):
Undocumented
def age_last_message(self):
Age of last comment to bug in days
@property
def private 0(self):
Undocumented
@private.setter
def private(self, value):
Undocumented
@property
def security_related 0(self):
Undocumented
@security_related.setter
def security_related(self, value):
Undocumented
@property
def title 0(self):
A one-line summary of the problem being described by the bug.
@title.setter
def title(self, value):
Undocumented
@property
def description 0(self):
As complete as possible description of the bug/issue being reported as a bug.
@description.setter
def description(self, value):
Undocumented
@property
def tags(self):
Undocumented
@property
def releases(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def owner_name(self):
Undocumented
@property
def attachments(self):
Undocumented
@property
def properties(self):
Returns dict of key: value pairs found in the bug description

This parses the bug report description into a more programmatically digestable dictionary form.

@property
def messages(self):
Undocumented
@property
def messages_count(self):
Undocumented
@property
def tasks(self):
Undocumented
def add_comment(self, content, subject=None, avoid_dupes=False):
Add a new comment to an existing bug.

This is the equivalent of newMessage. If no subject is provided, it will craft one using the bug title. If avoid_dupes is set, the routine will check to see if this comment has already been posted, to avoid accidentally spamming; the routine will return False in this case.

@property
def nominations(self):
Undocumented
def add_nomination(self, series):
Undocumented
@property
def activity(self):
Undocumented
@property
def duplicate_of(self):
Undocumented
@property
def duplicates_count(self):
Undocumented
@property
def subscriptions_count(self):
Undocumented
@property
def heat(self):
Undocumented
@property
def date_latest_patch_uploaded(self):
Undocumented
def has_patch(self):
Undocumented
def is_expirable(self, days_old=None):
Undocumented
@property
def users_affected_count(self):
Undocumented
@property
def users_unaffected_count(self):
Undocumented
def gravity(self):
An implementation of the "gravity" value as defined at: https://wiki.canonical.com/UbuntuEngineering/DefectAnalysts/GravityHeuristics
def to_dict(self, quick=False):
Converts the bug to a serializable dict.

Specify quick=True to skip data which can be more expensive to look up. Execution should be about twice as fast.

API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.LaunchpadService.LaunchpadService.html0000664000000000017510000002050311756540206021110 0ustar lpltk.LaunchpadService.LaunchpadService : API documentation

l.L.LaunchpadService : class documentation

Part of lpltk.LaunchpadService View In Hierarchy

Manages connection to Launchpad services.
Method __init__ Initialize the Arsenal instance.
Method reset Re-establish access to Launchpad and reload the specific project if one is
Method get_bug Undocumented
Method get_launchpad_bug Fetch a Launchpad bug object for a specific Launchpad bug id.
Method load_project Connect to a specific Launchpad project.
Method person Get a Person by the given name.
Method get_team_members Get the direct members of a Launchpad team.
Method distributions Undocumented
Method projects Undocumented
Method create_bug Undocumented
Method _load_user_config Load configuration from ~/.lpltkrc
def __init__(self, config=None):

Initialize the Arsenal instance.

The user's configuration (if one exists) is loaded and incorporated into the standard options. Access to Launchpad is initialized.

Configuration values to override can be passed in through config. For example:

 lp = LaunchpadService(config={
       'launchpad_services_root': 'edge',
       'read_only':               True
       })

 lp = LaunchpadService(config={
       'launchpad_client_name':   'my-lpltoolkit-project',
       'launchpad_services_root': 'https://api.launchpad.dev'
       })

 lp = LaunchpadService(config={
       'launchpad_version':       '1.0',
       'bot':                     True
       })
def reset(self):
Re-establish access to Launchpad and reload the specific project if one is specified.
def get_bug(self, bug_number):
Undocumented
def get_launchpad_bug(self, bug_number):
Fetch a Launchpad bug object for a specific Launchpad bug id.
def load_project(self, project):
Connect to a specific Launchpad project.
def person(self, name):
Get a Person by the given name.
def get_team_members(self, team):
Get the direct members of a Launchpad team.
def _load_user_config(self):
Load configuration from ~/.lpltkrc

If the users home directory contains a configuration file, load that in. The name of the configuration file is '.lpltkrc'. The format of the file is json. The json format should be an array. The contents of that array will be merged with the default one 'self.config' in this class.

@property
def distributions(self):
Undocumented
@property
def projects(self):
Undocumented
def create_bug(self, project, package, title, description, tags=):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.debug.DebugStdOut.html0000664000000000017510000000427711756540206015741 0ustar lpltk.debug.DebugStdOut : API documentation

l.d.DebugStdOut(object) : class documentation

Part of lpltk.debug View In Hierarchy

Debug version of STDOUT to redact out the oauth credentials so that if the debug output is posted to a bug, it will not include this sensitive info.
Method write Undocumented
Method __getattr__ Undocumented
def write(self, txt):
Undocumented
def __getattr__(self, name):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distribution_source_package.html0000664000000000017510000000231411756540206020223 0ustar lpltk.distribution_source_package : API documentation

l.distribution_source_package : module documentation

Part of lpltk

Undocumented
Class DistributionSourcePackage Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.__init__.html0000664000000000017510000000157711756540206014222 0ustar lpltk.__init__ : API documentation

l.__init__ : module documentation

Part of lpltk

Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distro_series.DistroSeries.html0000664000000000017510000002141611756540205017750 0ustar lpltk.distro_series.DistroSeries : API documentation

l.d.DistroSeries(object) : class documentation

Part of lpltk.distro_series View In Hierarchy

Undocumented
Method __init__ Undocumented
Method date_created Undocumented
Method date_released Undocumented
Method owner Undocumented
Method driver Undocumented
Method status Undocumented
Method active Undocumented
Method supported Undocumented
Method description Undocumented
Method display_name Undocumented
Method full_series_name Undocumented
Method name Undocumented
Method summary Undocumented
Method title Undocumented
Method active_milestones Undocumented
def __init__(self, service, bug, lp_distro_series):
Undocumented
@property
def date_created(self):
Undocumented
@property
def date_released(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def driver(self):
Undocumented
@property
def status(self):
Undocumented
@property
def active(self):
Undocumented
@property
def supported(self):
Undocumented
@property
def description(self):
Undocumented
@property
def display_name(self):
Undocumented
@property
def full_series_name(self):
Undocumented
@property
def name(self):
Undocumented
@property
def summary(self):
Undocumented
@property
def title(self):
Undocumented
@property
def active_milestones(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.LaunchpadService.LaunchpadServiceError.html0000664000000000017510000000350311756540206022123 0ustar lpltk.LaunchpadService.LaunchpadServiceError : API documentation

l.L.LaunchpadServiceError(Exception) : class documentation

Part of lpltk.LaunchpadService View In Hierarchy

LaunchpadServiceError

An exception class that will be raised if there are any errors initializing a LaunchpadService instance.

Method __init__ Undocumented
def __init__(self, error):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.person.html0000664000000000017510000000214711756540206013763 0ustar lpltk.person : API documentation

l.person : module documentation

Part of lpltk

Undocumented
Class Person Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.nomination.Nomination.html0000664000000000017510000001606211756540205016742 0ustar lpltk.nomination.Nomination : API documentation

l.n.Nomination(object) : class documentation

Part of lpltk.nomination View In Hierarchy

Undocumented
Method __init__ Undocumented
Method date_created Undocumented
Method date_decided Undocumented
Method decider Undocumented
Method owner Undocumented
Method status Undocumented
Method distro_series Undocumented
Method target Undocumented
Method product_series Undocumented
Method decline Undocumented
Method approve Undocumented
Method can_approve Undocumented
def __init__(self, service, bug, lp_nomination):
Undocumented
@property
def date_created(self):
Undocumented
@property
def date_decided(self):
Undocumented
@property
def decider(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def status(self):
Undocumented
@property
def distro_series(self):
Undocumented
@property
def target(self):
Undocumented
@property
def product_series(self):
Undocumented
def decline(self):
Undocumented
def approve(self):
Undocumented
def can_approve(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.tags.BugTags.html0000664000000000017510000001541111756540206014744 0ustar lpltk.tags.BugTags : API documentation

l.t.BugTags(object) : class documentation

Part of lpltk.tags View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __setitem__ Undocumented
Method __delitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method append Undocumented
Method extend Undocumented
Method remove Undocumented
Method __save_tags Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, tkbug):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __setitem__(self, key, value):
Undocumented
def __delitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __save_tags(self):
Undocumented
def __fetch_if_needed(self):
Undocumented
def append(self, item):
Undocumented
def extend(self, items):
Undocumented
def remove(self, item):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachment.Attachment.html0000664000000000017510000002554211756540206016700 0ustar lpltk.attachment.Attachment : API documentation

l.a.Attachment(object) : class documentation

Part of lpltk.attachment View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __del__ Undocumented
Method __len__ Undocumented
Method __eq__ Undocumented
Method __ne__ Undocumented
Method title Undocumented
Method kind Undocumented
Method url Undocumented
Method content_type Undocumented
Method message Undocumented
Method owner Undocumented
Method age Undocumented
Method data Undocumented
Method remotefd Undocumented
Method filename Undocumented
Method content Undocumented
Method is_patch Undocumented
Method is_archive_type Undocumented
Method is_archive Undocumented
Method __find_mimetype Undocumented
def __init__(self, tkbug, lpattachment, force_mimetype=False):
Undocumented
def __del__(self):
Undocumented
def __len__(self):
Undocumented
def __eq__(self, other):
Undocumented
def __ne__(self, other):
Undocumented
def __find_mimetype(self):
Undocumented
@property
def title(self):
Undocumented
@property
def kind(self):
Undocumented
@property
def url(self):
Undocumented
@property
def content_type(self):
Undocumented
@property
def message(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def age(self):
Undocumented
@property
def data(self):
Undocumented
@property
def remotefd(self):
Undocumented
@property
def filename(self):
Undocumented
@property
def content(self):
Undocumented
def is_patch(self):
Undocumented
def is_archive_type(self, type):
Undocumented
def is_archive(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.nomination.html0000664000000000017510000000217211756540205014625 0ustar lpltk.nomination : API documentation

l.nomination : module documentation

Part of lpltk

Undocumented
Class Nomination Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachments.LocalAttachment.Data.html0000664000000000017510000000452111756540206020700 0ustar lpltk.attachments.LocalAttachment.Data : API documentation

l.a.L.Data : class documentation

Part of lpltk.attachments.LocalAttachment View In Hierarchy

Undocumented
Class Fd Undocumented
Method set_path Undocumented
Method open Undocumented
def set_path(self, path):
Undocumented
def open(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_task.html0000664000000000017510000000215711756540206014255 0ustar lpltk.bug_task : API documentation

l.bug_task : module documentation

Part of lpltk

Undocumented
Class BugTask Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.nominations.html0000664000000000017510000000220011756540206015001 0ustar lpltk.nominations : API documentation

l.nominations : module documentation

Part of lpltk

Undocumented
Class Nominations Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distribution_source_package.DistributionSourcePackage.html0000664000000000017510000001002211756540206025331 0ustar lpltk.distribution_source_package.DistributionSourcePackage : API documentation

l.d.DistributionSourcePackage(object) : class documentation

Part of lpltk.distribution_source_package View In Hierarchy

Undocumented
Method __init__ Undocumented
Method display_name Undocumented
Method name Undocumented
Method title Undocumented
Method search_tasks Undocumented
def __init__(self, service, lp_distribution_source_package):
Undocumented
@property
def display_name(self):
Undocumented
@property
def name(self):
Undocumented
@property
def title(self):
Undocumented
def search_tasks(self, **params):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/nameIndex.html0000664000000000017510000017034311756540205013243 0ustar Index Of Names

Index Of Names

A

B

C

D

E

F

G

H

I

K

L

M

N

O

P

R

S

T

U

W

_

lpltk/doc/api/lpltk.milestone.Milestone.html0000664000000000017510000001201211756540206016402 0ustar lpltk.milestone.Milestone : API documentation

l.m.Milestone(object) : class documentation

Part of lpltk.milestone View In Hierarchy

Undocumented
Method __init__ Undocumented
Method code_name Undocumented
Method date_targeted Undocumented
Method is_active Undocumented
Method name Undocumented
Method summary Undocumented
Method title Undocumented
Method raw Undocumented
def __init__(self, service, lp_milestone, commit_changes=True):
Undocumented
@property
def code_name(self):
Undocumented
@property
def date_targeted(self):
Undocumented
@property
def is_active(self):
Undocumented
@property
def name(self):
Undocumented
@property
def summary(self):
Undocumented
@property
def title(self):
Undocumented
@property
def raw(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bugs.Bugs.html0000664000000000017510000000763411756540206014322 0ustar lpltk.bugs.Bugs : API documentation

l.b.Bugs(object) : class documentation

Part of lpltk.bugs View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service, lp_bugs):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.milestone.html0000664000000000017510000000216611756540206014455 0ustar lpltk.milestone : API documentation

l.milestone : module documentation

Part of lpltk

Undocumented
Class Milestone Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/apidocs.css0000664000000000017510000001415611756540205012600 0ustar /* body { */ /* background: white url("http://twistedmatrix.com/images/TwistedMatrixLabs.png"); */ /* background-repeat: no-repeat; */ /* background-position: 100% 10%; */ /* } */ table.children { border-top: thin black solid; border-bottom: thin black solid; } .undocumented { color: grey; } h1 { background: rgb(112, 176, 240); border: thin black solid; padding-left: 1ex; } h2 { background: rgb(200, 230, 255); border: thin black solid; padding-left: 1ex; } #part { font-style: italic; } #bigTable, #showSplitLink, #showBigLink, #moreSubclassesLink { display: none; } .showIfJS { display: none; } .hideIfJS { display: inline; } .toplevel { margin-top: 2em; } div.toplevel p { margin-top: 0em; } .function { background: lightgreen; } .method { background: #90ee90; } .classmethod { background: #c0ee90; } .staticmethod { background: #90eec0; } .attribute { background: #ddbbff; } .class { background: #add8e6; } .interface { background: #ffddff; } .module { background: lightyellow;} .package { background: #ffeeaa; } .basefunction { background: #cceecc; } .basemethod { background: #cceecc; } .baseclassmethod { background: #e0f7cc; } .basestaticmethod { background: #ccf7e0; } .baseattribute { background: #ffffff; } .baseclass { background: #d6ecf3; } .baseinterface { background: #ffffff; } .private { color: grey; background: lightgrey; } div.function { margin: 1em; font-family: monospace; background: rgb(232, 240, 248); } div.functionHeader { font-weight: bold; color: #000080; } a.functionSourceLink { text-decoration: none; font-style: italic; font-weight: normal; } a:hover.functionSourceLink { text-decoration: underline; } div.functionHeader a { color: black; } a.jslink { color: #007FFF; } span.tag { font-weight: bold; font-family: monospace; } span.arg { padding-right: 4ex; font-family: monospace; } .children { margin: 1em; } th a { text-decoration: none; } .lineno { text-align: center; } .functionBody { margin-left: 2em; padding-top: 0.25em; padding-bottom: 1em; font-family: sans-serif; } .interfaceinfo { padding-left: 2em; color: blue; font-size: 75%; } .fieldName { font-weight: bold; } td p { margin-top: 0.0em; } td.fieldName { vertical-align: baseline; } tr.fieldStart td { padding-top: 0.5em; } table.fieldTable td { padding-left: 0.5em; padding-right: 0.5em; } .fieldArg { font-weight: bold; color: #000080; font-family: monospace; } td.fieldArg { vertical-align: baseline; } .letterlinks { font-size: 75%; padding-left: 8ex; } /* for the difflib-generated table */ table.diff {font-family:Courier; border:medium;} .diff_header {background-color:#e0e0e0} td.diff_header {text-align:right} .diff_next {background-color:#c0c0c0} .diff_add {background-color:#aaffaa} .diff_chg {background-color:#ffff77} .diff_sub {background-color:#ffaaaa} .preview { margin: 0; } .error { background-color: #ff3333; } .errormessage { margin: 0em 0em 0em 2em; font-size: 75%; color: #773333; } /* Syntax Highlighting for Source Code * - doctest examples are displayed in a 'pre.py-doctest' block. * If the example is in a details table entry, then it will use * the colors specified by the 'table pre.py-doctest' line. * - Source code listings are displayed in a 'pre.py-src' block. * Each line is marked with 'span.py-line' (used to draw a line * down the left margin, separating the code from the line * numbers). Line numbers are displayed with 'span.py-lineno'. * The expand/collapse block toggle button is displayed with * 'a.py-toggle' (Note: the CSS style for 'a.py-toggle' should not * modify the font size of the text.) * - If a source code page is opened with an anchor, then the * corresponding code block will be highlighted. The code * block's header is highlighted with 'py-highlight-hdr'; and * the code block's body is highlighted with 'py-highlight'. * - The remaining py-* classes are used to perform syntax * highlighting (py-string for string literals, py-name for names, * etc.) */ pre.py-doctest { padding: .5em; margin: 1em; background: #e8f0f8; color: #000000; border: 1px solid #708890; } table pre.py-doctest { background: #dce4ec; color: #000000; } pre.py-src { border: 2px solid #000000; background: #f0f0f0; color: #000000; } .py-line { border-left: 2px solid #000000; margin-left: .2em; padding-left: .4em; } .py-lineno { font-style: italic; font-size: 90%; padding-left: .5em; } a.py-toggle { text-decoration: none; } div.py-highlight-hdr { border-top: 2px solid #000000; border-bottom: 2px solid #000000; background: #d8e8e8; } div.py-highlight { border-bottom: 2px solid #000000; background: #d0e0e0; } .py-prompt { color: #005050; font-weight: bold;} .py-more { color: #005050; font-weight: bold;} .py-string { color: #006030; } .py-comment { color: #003060; } .py-keyword { color: #600000; } .py-output { color: #404040; } .py-name { color: #000050; } .py-name:link { color: #000050 !important; } .py-name:visited { color: #000050 !important; } .py-number { color: #005000; } .py-defname { color: #000060; font-weight: bold; } .py-def-name { color: #000060; font-weight: bold; } .py-base-class { color: #000060; } .py-param { color: #000060; } .py-docstring { color: #006030; } .py-decorator { color: #804020; } /* Use this if you don't want links to names underlined: */ /*a.py-name { text-decoration: none; }*/lpltk/doc/api/lpltk.attachments.html0000664000000000017510000001054111756540206014765 0ustar lpltk.attachments : API documentation

l.attachments : module documentation

Part of lpltk

No module docstring
Class LocalAttachment Undocumented
Class Attachments No class docstring; 2/18 methods documented
Function filter_owned_by_person File owned by specific person(s) (e.g. original bug reporter)
Function filter_filename_matches_globs Filename matches one of a set of glob patterns (e.g. Xorg.*.log)
Function filter_size_between File size is within [min, max] bounds
Function filter_age_between File was attached to bug between [min, max] days
Function filter_is_patch Undocumented
def filter_owned_by_person(attachment, persons):
File owned by specific person(s) (e.g. original bug reporter)
def filter_filename_matches_globs(attachment, glob_patterns):
Filename matches one of a set of glob patterns (e.g. Xorg.*.log)
def filter_size_between(attachment, sizes):
File size is within [min, max] bounds
def filter_age_between(attachment, ages_in_days):
File was attached to bug between [min, max] days
def filter_is_patch(attachment, is_patch):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.messages.Messages.html0000664000000000017510000001001511756540206016023 0ustar lpltk.messages.Messages : API documentation

l.m.Messages(object) : class documentation

Part of lpltk.messages View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, tkbug):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.person.Person.html0000664000000000017510000001565611756540206015241 0ustar lpltk.person.Person : API documentation

l.p.Person(object) : class documentation

Part of lpltk.person View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __eq__ Undocumented
Method __ne__ Undocumented
Method username Undocumented
Method display_name Undocumented
Method full_name Undocumented
Method first_name Undocumented
Method email_addresses Undocumented
Method karma Undocumented
Method lpperson Undocumented
Method subscribed_package_names Undocumented
Method super_teams Undocumented
def __init__(self, tkbug, lpperson):
Undocumented
def __eq__(self, other):
Undocumented
def __ne__(self, other):
Undocumented
@property
def username(self):
Undocumented
@property
def display_name(self):
Undocumented
@property
def full_name(self):
Undocumented
@property
def first_name(self):
Undocumented
@property
def email_addresses(self):
Undocumented
@property
def karma(self):
Undocumented
@property
def lpperson(self):
Undocumented
@property
def subscribed_package_names(self):
Undocumented
@property
def super_teams(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.messages.html0000664000000000017510000000216111756540206014260 0ustar lpltk.messages : API documentation

l.messages : module documentation

Part of lpltk

Undocumented
Class Messages Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.tags.html0000664000000000017510000000214311756540205013406 0ustar lpltk.tags : API documentation

l.tags : module documentation

Part of lpltk

Undocumented
Class BugTags Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.LaunchpadService.html0000664000000000017510000000256211756540206015676 0ustar lpltk.LaunchpadService : API documentation

l.LaunchpadService : module documentation

Part of lpltk

No module docstring
Class LaunchpadServiceError LaunchpadServiceError
Class LaunchpadService Manages connection to Launchpad services.
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachments.LocalAttachment.Data.Fd.html0000664000000000017510000000445111756540206021232 0ustar lpltk.attachments.LocalAttachment.Data.Fd : API documentation

l.a.L.D.Fd(file) : class documentation

Part of lpltk.attachments.LocalAttachment.Data View In Hierarchy

Undocumented
Method content_type Undocumented
Method len Undocumented
@property
def content_type(self):
Undocumented
@property
def len(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.milestones.html0000664000000000017510000000217311756540206014636 0ustar lpltk.milestones : API documentation

l.milestones : module documentation

Part of lpltk

Undocumented
Class Milestones Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distribution.Distribution.html0000664000000000017510000002120311756540206017644 0ustar lpltk.distribution.Distribution : API documentation

l.d.Distribution(object) : class documentation

Part of lpltk.distribution View In Hierarchy

No class docstring
Method __init__ Undocumented
Method __str__ Undocumented
Method owner Undocumented
Method display_name Undocumented
Method all_series Returns a list of all the series registered for this distro
Method supported_series Returns a list of series that are supported
Method current_series The current series under development
Method stable_series The latest stable release series
Method all_milestones Undocumented
Method active_milestones Undocumented
Method get_source_package Undocumented
Method get_series Undocumented
Method search_tasks Undocumented
Method get_specification Undocumented
def __init__(self, service, lp_distribution):
Undocumented
def __str__(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def display_name(self):
Undocumented
@property
def all_series(self):
Returns a list of all the series registered for this distro
@property
def supported_series(self):
Returns a list of series that are supported
@property
def current_series(self):
The current series under development
@property
def stable_series(self):
The latest stable release series
@property
def all_milestones(self):
Undocumented
@property
def active_milestones(self):
Undocumented
def get_source_package(self, source_pkg):
Undocumented
def get_series(self, string):
Undocumented
def search_tasks(self, **params):
Undocumented
def get_specification(self, specification_name):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.milestones.Milestones.html0000664000000000017510000000707511756540206016765 0ustar lpltk.milestones.Milestones : API documentation

l.m.Milestones(object) : class documentation

Part of lpltk.milestones View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
def __init__(self, service, lp_milestones):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.message.Message.html0000664000000000017510000000772711756540206015475 0ustar lpltk.message.Message : API documentation

l.m.Message(object) : class documentation

Part of lpltk.message View In Hierarchy

Undocumented
Method __init__ Undocumented
Method owner Undocumented
Method content Undocumented
Method date_created Undocumented
Method parent Undocumented
Method subject Undocumented
def __init__(self, tkbug, lpmessage):
Undocumented
@property
def owner(self):
Undocumented
@property
def content(self):
Undocumented
@property
def date_created(self):
Undocumented
@property
def parent(self):
Undocumented
@property
def subject(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/undoccedSummary.html0000664000000000017510000011715711756540205014501 0ustar Summary of Undocumented Objects

Summary of Undocumented Objects

lpltk/doc/api/lpltk.project_series.html0000664000000000017510000000221511756540206015471 0ustar lpltk.project_series : API documentation

l.project_series : module documentation

Part of lpltk

Undocumented
Class ProjectSeries Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_activity.BugActivity.html0000664000000000017510000001124111756540206017372 0ustar lpltk.bug_activity.BugActivity : API documentation

l.b.BugActivity(object) : class documentation

Part of lpltk.bug_activity View In Hierarchy

Undocumented
Method __init__ Undocumented
Method date_changed Undocumented
Method person Undocumented
Method old_value Undocumented
Method new_value Undocumented
Method what_changed Undocumented
Method message Undocumented
def __init__(self, service, bug, lp_bug_activity):
Undocumented
@property
def date_changed(self):
Undocumented
@property
def person(self):
Undocumented
@property
def old_value(self):
Undocumented
@property
def new_value(self):
Undocumented
@property
def what_changed(self):
Undocumented
@property
def message(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.specifications.Specifications.html0000664000000000017510000001033411756540206020417 0ustar lpltk.specifications.Specifications : API documentation

l.s.Specifications(object) : class documentation

Part of lpltk.specifications View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __len__ Undocumented
Method __getitem__ Undocumented
Method __iter__ Undocumented
Method __contains__ Undocumented
Method __fetch_if_needed Undocumented
def __init__(self, service, lp_specifications):
Undocumented
def __len__(self):
Undocumented
def __getitem__(self, key):
Undocumented
def __iter__(self):
Undocumented
def __contains__(self, item):
Undocumented
def __fetch_if_needed(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/moduleIndex.html0000664000000000017510000000720311756540205013602 0ustar Module Index

Module Index

lpltk/doc/api/lpltk.ppa.CurlCallback.html0000664000000000017510000000401511756540205015551 0ustar lpltk.ppa.CurlCallback : API documentation

l.p.CurlCallback : class documentation

Part of lpltk.ppa View In Hierarchy

Undocumented
Method __init__ Undocumented
Method body_callback Undocumented
def __init__(self):
Undocumented
def body_callback(self, buf):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_task.BugTask.html0000664000000000017510000005023011756540206015607 0ustar lpltk.bug_task.BugTask : API documentation

l.b.BugTask(object) : class documentation

Part of lpltk.bug_task View In Hierarchy

Undocumented
Method __init__ Undocumented
Method __str__ Undocumented
Method owner Undocumented
Method owner_name Undocumented
Method bug_target_display_name Undocumented
Method bug_target_name Undocumented
Method bug Undocumented
Method status 0 Undocumented
Method status Undocumented
Method title Undocumented
Method is_complete Undocumented
Method importance 0 Undocumented
Method importance Undocumented
Method assignee 0 Undocumented
Method assignee Undocumented
Method assignee_name Undocumented
Method date_assigned Undocumented
Method date_closed Undocumented
Method date_confirmed Undocumented
Method date_created Undocumented
Method date_fix_committed Undocumented
Method date_fix_released Undocumented
Method date_in_progress Undocumented
Method date_incomplete Undocumented
Method date_left_closed Undocumented
Method date_left_new Undocumented
Method date_triaged Undocumented
Method date_importance_set Undocumented
Method milestone Undocumented
Method upstream_product Undocumented
Method upstream_bug_tracker Undocumented
Method related_tasks Undocumented
Method target 0 Undocumented
Method target Undocumented
Method bug_watch Undocumented
Method similar_bugs Undocumented
Method web_link Undocumented
Method to_dict Undocumented
def __init__(self, service, lp_bug_task):
Undocumented
def __str__(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def owner_name(self):
Undocumented
@property
def bug_target_display_name(self):
Undocumented
@property
def bug_target_name(self):
Undocumented
@property
def bug(self):
Undocumented
@property
def status 0(self):
Undocumented
@status.setter
def status(self, value):
Undocumented
@property
def title(self):
Undocumented
@property
def is_complete(self):
Undocumented
@property
def importance 0(self):
Undocumented
@importance.setter
def importance(self, value):
Undocumented
@property
def assignee 0(self):
Undocumented
@assignee.setter
def assignee(self, value):
Undocumented
@property
def assignee_name(self):
Undocumented
@property
def date_assigned(self):
Undocumented
@property
def date_closed(self):
Undocumented
@property
def date_confirmed(self):
Undocumented
@property
def date_created(self):
Undocumented
@property
def date_fix_committed(self):
Undocumented
@property
def date_fix_released(self):
Undocumented
@property
def date_in_progress(self):
Undocumented
@property
def date_incomplete(self):
Undocumented
@property
def date_left_closed(self):
Undocumented
@property
def date_left_new(self):
Undocumented
@property
def date_triaged(self):
Undocumented
@property
def date_importance_set(self):
Undocumented
@property
def milestone(self):
Undocumented
@property
def upstream_product(self):
Undocumented
@property
def upstream_bug_tracker(self):
Undocumented
@property
def related_tasks(self):
Undocumented
@property
def target 0(self):
Undocumented
@target.setter
def target(self, value):
Undocumented
@property
def bug_watch(self):
Undocumented
@property
def similar_bugs(self):
Undocumented
@property
def web_link(self):
Undocumented
def to_dict(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.bug_tasks.html0000664000000000017510000000222211756540206014431 0ustar lpltk.bug_tasks : API documentation

l.bug_tasks : module documentation

Part of lpltk

Undocumented
Class BugTasks No class docstring; 3/9 methods documented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.specification.html0000664000000000017510000000221211756540206015266 0ustar lpltk.specification : API documentation

l.specification : module documentation

Part of lpltk

Undocumented
Class Specification Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.attachments.LocalAttachment.html0000664000000000017510000000346211756540206020033 0ustar lpltk.attachments.LocalAttachment : API documentation

l.a.LocalAttachment(object) : class documentation

Part of lpltk.attachments View In Hierarchy

Undocumented
Class Data Undocumented
Method __init__ Undocumented
def __init__(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.distribution.html0000664000000000017510000000224411756540206015172 0ustar lpltk.distribution : API documentation

l.distribution : module documentation

Part of lpltk

Undocumented
Class Distribution No class docstring; 4/14 methods documented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.utils.html0000664000000000017510000000737111756540206013621 0ustar lpltk.utils : API documentation

l.utils : module documentation

Part of lpltk

Undocumented
Function o2str Undocumented
Function typecheck_Collection Undocumented
Function typecheck_Entry Undocumented
Function load_file Undocumented
Function write_file Undocumented
Function file_age Undocumented
def o2str(obj):
Undocumented
def typecheck_Collection(obj):
Undocumented
def typecheck_Entry(obj):
Undocumented
def load_file(filename):
Undocumented
def write_file(filename, text):
Undocumented
def file_age(filename):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/classIndex.html0000664000000000017510000001477111756540205013432 0ustar Class Hierarchy

Class Hierarchy

lpltk/doc/api/lpltk.projects.html0000664000000000017510000000216011756540205014300 0ustar lpltk.projects : API documentation

l.projects : module documentation

Part of lpltk

Undocumented
Class Projects Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/doc/api/lpltk.project_series.ProjectSeries.html0000664000000000017510000001425611756540206020261 0ustar lpltk.project_series.ProjectSeries : API documentation

l.p.ProjectSeries(object) : class documentation

Part of lpltk.project_series View In Hierarchy

Undocumented
Method __init__ Undocumented
Method date_created Undocumented
Method owner Undocumented
Method driver Undocumented
Method status Undocumented
Method active Undocumented
Method display_name Undocumented
Method name Undocumented
Method summary Undocumented
Method title Undocumented
def __init__(self, service, bug, lp_project_series):
Undocumented
@property
def date_created(self):
Undocumented
@property
def owner(self):
Undocumented
@property
def driver(self):
Undocumented
@property
def status(self):
Undocumented
@property
def active(self):
Undocumented
@property
def display_name(self):
Undocumented
@property
def name(self):
Undocumented
@property
def summary(self):
Undocumented
@property
def title(self):
Undocumented
API Documentation for lpltk, generated by pydoctor at 2012-05-21 15:24:05.
lpltk/debian/0000775000000000017510000000000012071171760010336 5ustar lpltk/debian/rules0000775000000000017510000000011611756535217011426 0ustar #!/usr/bin/make -f %: dh "$@" --with python2 --buildsystem python_distutils lpltk/debian/changelog0000664000000000017510000001724612071171752012223 0ustar python-launchpadlib-toolkit (2.3) raring; urgency=low [Brendan Donegan] * Add lp-file-bug script [Bryce Harrington] * launchpad-service-status: Handle additional date formats (LP: #1041902) * Add support for querying publishing information about source packages * Add routine to get the subscribed DistributionSourcePackages * Add property to provide the primary archive * Add lookup of the current testing series (for Debian) * Catch ElementTree exceptions, usually caused by cache corruption -- Bryce Harrington Wed, 02 Jan 2013 18:51:13 -0800 python-launchpadlib-toolkit (2.2) quantal; urgency=low * Add TKCollection abstract base class * Add TKEntry abstract base class * Add BugWatch wrapper class * Add uri() to several classes to get the self_link * distribution.py: Handle undefined series * LaunchpadService.py: - Deprecate person(); users should switch to get_person() now - Make API more consistently return TK objs rather than LPL ones - Add get_team_participants() * bug_task.py / bug_tasks.py: - Enable filtering the list of bugs in various ways - Check person existance before use - Add is_complete_upstream() - Add set_filter_importance_length() * bug.py / bugs.py: - Add age_latest_patch() - Fix timezone comparison error * message.py / messages.py: - Enable filtering the list of messages in various ways - Add routine to return list of poster names for message collections -- Bryce Harrington Wed, 22 Aug 2012 11:25:46 -0700 python-launchpadlib-toolkit (2.1) quantal; urgency=low * New upstream release - Update reports to include quantal and other recent version info - Quell more error messages due to launchpad goofs - Disable use of json-pickle for now (nothing is using it yet) - Generate API docs via Makefile - Allow sorting by dupe count in reports - New utouch-report-improvements script - New most-duplicates script - Clear out remainder of contribs dir * debian/rules: - Switch to using dh_python2 -- Bryce Harrington Thu, 07 Jun 2012 13:41:28 -0700 python-launchpadlib-toolkit (2.0) quantal; urgency=low * json_check: New script to validate JSON files, including launchpadlib cache files. -- Bryce Harrington Fri, 18 May 2012 12:35:59 -0700 python-launchpadlib-toolkit (1.0) precise-proposed; urgency=low * launchpad-service-status: Backport fix from lpltk 2.x (commit #149) to check blog screenscrape results. This tool determines if there is a launchpad outage by examining the launchpad blog at http://blog.launchpad.net/category/notifications/feed. It can happen there are no outages listed, in which case outage_start and outage_end will be undefined. Don't attempt to test against these parameters if this is the case. (LP: #988312) * Increment version number to 1.0 (stable release), as per UDS-Q session decision. All changes on the 1.x branch will be stable bug fixes henceforth. -- Bryce Harrington Fri, 18 May 2012 12:22:04 -0700 python-launchpadlib-toolkit (0.5) precise; urgency=low * Add Specification class * Add gravity() - heuristic for identifying bugs of interest * Add releases() - list of Ubuntu releases the bug is confirmed affecting * Add some basic tests * Transition to DHPython2 -- Bryce Harrington Thu, 09 Feb 2012 09:13:42 -0800 python-launchpadlib-toolkit (0.4.5) precise; urgency=low * bug_tasks: provide filters to constrain type of bug tasks being iterated. * bug_task: upstream product * person: retrieve list of non-hidden email addresses * Add support for credentials_file management. * Add an optional mechanism to check comments before adding a new one * Add filters to the BugTasks object. * Add type checking for Launchpad objects and collections * Add several more properties and attributes * Add caching of codenames for supported / stable distro series * Fix various bugs -- Bryce Harrington Tue, 24 May 2011 12:08:31 -0700 python-launchpadlib-toolkit (0.4.4) natty; urgency=low * service: support the qastaging server * bug_task: expose upstream_bug_tracker * person: provide email addresses * person: karma * bug: return age of various bug items * ...plus a couple handful's of bug fixes -- Bryce Harrington Fri, 08 Apr 2011 19:36:56 -0700 python-launchpadlib-toolkit (0.4.3) natty; urgency=low * distribution: expose current_series * milestones: add a new milestones collection * distribution: expose all_milestones * distribution: expose active_milestones * distribution_source_package: convert milestones to launchpad milestones in search parameters -- Andy Whitcroft Mon, 28 Feb 2011 17:10:47 +0000 python-launchpadlib-toolkit (0.4.2) natty; urgency=low * Add new script launchpad-service-status which looks to see if launchpad is up and running. * control: Add dependency on python-feedparser * bug_task: - add all of the date accessors exported by launchpad - add a "setter" property for task Importance -- Bryce Harrington Wed, 09 Feb 2011 16:00:27 -0800 python-launchpadlib-toolkit (0.4.1) natty; urgency=low * Added --prefix=/usr to distutils for executable install in correct directory, fix ftbfs (LP: #701207) -- Angel Abad Mon, 10 Jan 2011 21:45:26 +0100 python-launchpadlib-toolkit (0.4) natty; urgency=low * Update close-fix-committed-bugs to use lpltk instead of arsenal * Restrict example scripts to read-only anonymous access. This allows them to run without needing the user to set up credentials. * Add key:value parsing of bug descriptions * Improve error handling of auth errors * Add example script to print out bugs matching status and date * Add several tutorials and simple examples * New classes and methods for interacting with bugs -- Bryce Harrington Thu, 23 Dec 2010 20:12:10 -0800 python-launchpadlib-toolkit (0.3.1~lucid) lucid; urgency=low * Added --no-compile to distutils -- Kamran Riaz Khan Sat, 14 Aug 2010 10:45:44 +0500 python-launchpadlib-toolkit (0.3~karmic) karmic; urgency=low [Brian Murray] * Catch failures to import LPNET_SERVICE_ROOT [Bryce Harrington] * Add scripts/current-ubuntu-series-name: Displays current development series for Ubuntu * Add scripts/ls-series: Lists all series for Ubuntu * Add scripts/find-similar-bugs: Searches for what LP thinks might be duplicates of the given bug. * Add scripts/close-fix-committed-bugs: Closes all Fix Committed bugs (such as when doing a release) * Add scripts/ls-assigned-bugs: Lists user's assigned bugs [ Kamran Riaz Khan ] * Added support for send-attachments-upstream -- Kamran Riaz Khan Sat, 14 Aug 2010 01:03:41 +0500 python-launchpadlib-toolkit (0.2.1) maverick; urgency=low * Fix parameter name in debug routine. * First upload to Ubuntu -- Bryce Harrington Thu, 17 Jun 2010 17:21:21 -0700 python-launchpadlib-toolkit (0.2) karmic; urgency=low * Add handling of socket.error exceptions, which can be triggered when the Launchpad service is unreachable (such as from a busted router). -- Bryce Harrington Tue, 06 Oct 2009 11:19:08 -0700 python-launchpadlib-toolkit (0.1) karmic; urgency=low * New revision, split off from arsenal, with ideas brazenly thieved from Markus Korn's launchpadlib-shell. * debian packaging borrowed/adapted from python-launchpad-bugs -- Bryce Harrington Tue, 29 Sep 2009 16:16:09 -0700 lpltk/debian/control0000664000000000017510000000133011756535162011747 0ustar Source: python-launchpadlib-toolkit Section: python Priority: extra Maintainer: Arsenal Developers Build-Depends: debhelper (>= 7.0.50~), python (>= 2.6.6-3~), python-all, python-distutils-extra (>= 2.28), Standards-Version: 3.9.2 Vcs-Bzr: https://code.launchpad.net/~arsenal-devel/lpltk/2.x Package: python-launchpadlib-toolkit Section: python Architecture: all Depends: ${misc:Depends}, ${python:Depends}, python (>= 2.4), python-launchpadlib, python-feedparser XB-Python-Version: ${python:Versions} Description: convenience library for launchpadlib Classes to manage credentials and access bug information in Launchpad using the Launchpad API. . https://launchpad.net/lpltk lpltk/debian/compat0000664000000000017510000000000211753031405011531 0ustar 5 lpltk/debian/docs0000664000000000017510000000000711753031405011203 0ustar README lpltk/debian/copyright0000664000000000017510000000233311753031405012267 0ustar This package was debianized by Bryce Harrington on Mon Sep 28 23:54:18 PDT 2009 It was downloaded from #TBD Upstream Author: Bryce Harrington Copyright: # Written by Bryce Harrington # (C) Canonical, Ltd. Licensed under the GPL License: This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. The Debian packaging is (C) 2006, Daniel Holbach and is licensed under the GPL, see above. lpltk/README0000664000000000017510000001000511753031405007765 0ustar LaunchpadLib Toolkit (lpltk) ============================ lpltk is a set of classes for interacting with the Launchpad service API library. It takes care of certain common chores such as obtaining and managing login credentials. By factoring this common code out of individual launchpadlib projects, it makes them more concise and more compatible with one another. Basic Usage =========== For most purposes, simply create the LaunchpadService object, which will attend to the credentials automatically. Then retrieve a project object (such as "ubuntu") and from there you can use all the regular Launchpad API calls. In other words: import lpltk lp = lpltk.LaunchpadService() prj = lp.load_project("ubuntu") See example-ls-series.py for a complete working example. Also try running it with LPLTK_DEBUG turned on to see how to get debug info: $ ./example-ls-series.py $ LPLTK_DEBUG=1 ./example-ls-series.py For more elaborate real-world scripts, refer to the Arsenal project at: http://launchpad.net/arsenal Environment Variables ===================== Configuration of the LaunchpadService behavior can be done via environmental variables. This is done principly to make it easy to debug issues without needing to tweak source code. The following environment variables are supported: LPLTK_DEBUG Prints extra debugging messages to stderr if defined. If set to a numerical argument, it sets the httpdlib2 debuglevel to that value; the http debug output will be filtered to redact your oauth token and signature, so you can attach debug info to public bugs without revealing anything sensitive. LPSTAGING Use the staging service root rather than the live (edge) service. This allows testing of your script without risking actual changes to the official Launchpad. LPCONFIG Use this directory path for configuration files, rather than the default of ~/.config/. The directory will be created if it does not already exist. LPCACHE Use this directory path for cache files (such as credentials), rather than the default of ~/.cache/. The directory will be created if it does not already exist. API - LaunchpadService ====================== lp = LaunchpadService([config_path], [cache_path], [consumer]) Creates the Launchpad service object and retrieve credentials from Launchpad. LPSTAGING, LPCONFIG, and LPCACHE must be specified prior to instantiating the LaunchpadService object in order to have any effect. config_path and cache_path allow the calling script to define the default paths if not specified by the environment variables. On any unrecoverable error an exception is thrown. lp.load_project(project) Loads the named project (such as "ubuntu" distro), returning its launchpadapi object. lp.reset() Reloads the credentials and project (if any is specified). This is handy for recovering from Launchpad out-of-service or other such transient errors. lp.name: The credentialed consumer name (default: lpltk) lp.launchpad: The launchpadlib launchpad object lp.project: The loaded Launchpad project (default: None) API - Debug =========== dbg(msg), err(msg), die(msg) Convenience routines for printing error and debug messages to stderr. dbg() only prints if LPLTK_DEBUG is defined. StdOut, DebugStdOut STDOUT overloads for debug output filtering. Set sys.stdout to DebugStdOut to filter out oauth token and signature details. Wishlist ======== * Unit tests * API documentation * Get this package into Main * Add a framework for storing config elements beyond just credentials * Integrate into existing launchpadlib scripts/applications * Wrapper classes for Bugs, SourcePackages, etc. * Strengthen the fault handling, provide better diagnostics, and be more robust against Launchpad service failures lpltk/run-tests0000775000000000017510000000043111753031405011001 0ustar #!/bin/bash for t in tests/test*; do echo $t file_type=$(file -b $t) case ${file_type} in *python*) python ${t} ;; *Bourne*) bash ${t} ;; *bash*) bash ${t} ;; *perl*) perl ${t} ;; *) echo "Unknown" ;; esac echo done lpltk/examples/0000775000000000017510000000000011753031405010727 5ustar lpltk/examples/tut.040000775000000000017510000000201511753031405011711 0ustar #!/usr/bin/env python # # Tutorial: # The difference between this tutorial/example and tut.01 is that this shows # how to point at one of the other Launchpad services. # # The staging service, exists for testing various things. This is running # against a snapshot of the database. Changes made on the staging server are # not permanent changes and will disapear when the database is reloaded (once # each day). # from lpltk.service import LaunchpadService # LpltkTutorial # class LpltkTutorial(): # main # def main(self): configuration = {} configuration['launchpad_services_root'] = 'staging' # By passing in the configuration dictionary, we've instructed the class # to connect to the Launchpad staging server. # lp = LaunchpadService(configuration) print("We've successfully established a connection to Launchpad services.") if __name__ == '__main__': app = LpltkTutorial() app.main() # vi:set ts=4 sw=4 expandtab: lpltk/examples/tut.020000775000000000017510000000154011753031405011711 0ustar #!/usr/bin/env python # # Tutorial: # Now that a connection has been established with the Launchpad service, # demonstrate one way of getting access to the informtation for a single # bug. # from lpltk.service import LaunchpadService # LpltkTutorial # class LpltkTutorial(): # main # def main(self): lp = LaunchpadService() # Connect to Launchpad # Get an instance of a bug object for a well known bug. Print out # the title of the bug. # bug = lp.get_bug(1) print("Title: %s" % bug.title) # Note: To see all the methods and properties on any python object # you can always print out the results from a dir() call. # print dir(bug) if __name__ == '__main__': app = LpltkTutorial() app.main() # vi:set ts=4 sw=4 expandtab: lpltk/examples/tut.010000775000000000017510000000151211753031405011707 0ustar #!/usr/bin/env python # # Tutorial: # Show how to establish a connection to Launchpad services. This must # be done in every script which tries to obtain information from # Launchpad. # from lpltk.service import LaunchpadService # LpltkTutorial # class LpltkTutorial(): # main # def main(self): # By instantiating a LaunchpadService object, a connection is # made with Launchpad, credentials are exchanged, the sign-on # processes is executed and credentials are cached. Once this # succeeds, further communication with Launchpad can happen. # lp = LaunchpadService() print("We've successfully established a connection to Launchpad services.") if __name__ == '__main__': app = LpltkTutorial() app.main() # vi:set ts=4 sw=4 expandtab: lpltk/examples/tut.030000775000000000017510000000323011753031405011710 0ustar #!/usr/bin/env python # # Tutorial: # We've seen how to get the basic information from a bug getting more # information isn't much different. It usually just involves additional # objects that some of the bug properties return. # from lpltk.service import LaunchpadService # LpltkHelloWorld # class LpltkHelloWorld(): # __init__ # def __init__(self): return # main # def main(self): try: lp = LaunchpadService() bug = lp.get_bug(1) # The title just returns a simple string. # title = bug.title # Getting the origninal bug submitter means going through a "person" # object wich we get via the bug.owner property. # submitter = bug.owner.display_name # Getting a list of all the tags that are currently applied to a bug # comes from the bug.tags property. # tags = bug.tags # Important dates related to a bug: # bug.date_created - Date the bug was first entered into Launchpad # bug.date_last_updated - When the bug was last updated (status, tags, etc.) # bug.date_last_message - When the last comment was added to the bug. # created = bug.date_created updated = bug.date_last_updated date_last_message = bug.date_last_message # Handle the user presses . # except KeyboardInterrupt: pass return if __name__ == '__main__': app = LpltkHelloWorld() app.main() # vi:set ts=4 sw=4 expandtab: lpltk/examples/tut.050000775000000000017510000000442111753031405011715 0ustar #!/usr/bin/env python # # Tutorial: # The difference between this tutorial/example and tut.01 is that this shows # how to point at one of the other Launchpad services. # # The staging service, exists for testing various things. This is running # against a snapshot of the database. Changes made on the staging server are # not permanent changes and will disapear when the database is reloaded (once # each day). # from lpltk.service import LaunchpadService from datetime import datetime # LpltkTutorial # class LpltkTutorial(): # main # def main(self): configuration = {} # By passing in the configuration dictionary, we've instructed the class # to connect to the Launchpad staging server. # lp = LaunchpadService(configuration) # The service.distributions property is a collection of distributions. We # pretty much only care about one, 'ubuntu'. # distro = lp.distributions['ubuntu'] # Within a distribution are many source packages. We actually care about # several, but _mostly_ the 'linux' source package. # source_package = distro.get_source_package('linux') # Searching for bug tasks, the search can be quite complicated and made up # of several components. The following can be combined in many ways to get # the search you want. The search happens on the server and returns a # collection of bug tasks that match the search criteria. # # tasks = pkg.search_tasks(tags=search_tags, tags_combinator=search_tags_combinator, # status=self.cfg['task_search_status'], modified_since=since) # search_tags = [] # A list of the tags we care about search_tags_combinator = "All" search_status = ["New"] # A list of the bug statuses that we care about search_since = datetime(year=2010, month=11, day=29) tasks = source_package.search_tasks(status=search_status, modified_since=search_since) for task in tasks: bug = task.bug print(bug.id) if __name__ == '__main__': app = LpltkTutorial() app.main() # vi:set ts=4 sw=4 expandtab: lpltk/AUTHORS0000664000000000017510000000043512026437062010166 0ustar Maintainers: Bryce Harrington Contributions, ideas, and inspiration from: Markus Korn Brad Figg Brian Murray Brendan Donegan lpltk/test-watch.diff0000664000000000017510000000563112002341047012025 0ustar === added file 'tests/test-watch.py' --- tests/test-watch.py 1970-01-01 00:00:00 +0000 +++ tests/test-watch.py 2012-07-18 03:55:43 +0000 @@ -0,0 +1,99 @@ +#!/usr/bin/python + +import sys, os.path +sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))) + +import unittest +from lpltk.LaunchpadService import LaunchpadService +from lpltk.person import * + +class TestTags(unittest.TestCase): + + def setUp(self): + self.configuration = {} + self.configuration['launchpad_services_root'] = 'edge' + self.configuration['read_only'] = True + self.bug_id = 235643 + + def tearDown(self): + pass + + def _get_first_watch(self): + ls = LaunchpadService(self.configuration) + bug = ls.get_bug(self.bug_id) + return bug.bug_watches[0] + + def test_bug(self): + bug_watch = self._get_first_watch() + self.assertEqual(bug_watch.bug.id, bug.id) + + def test_bug_tasks(self): + bug_watch = self._get_first_watch() + self.assertEqual(len(bug_watch.bug_tasks), 3) + + def test_date_created(self): + bug_watch = self._get_first_watch() + self.assertEqual(bug_watch.date_created, "2008-05-28") + + def test_date_last_changed(self): + bug_watch = self._get_first_watch() + self.assertNotNone(bug_watch.date_last_changed) + + def test_date_last_checked(self): + bug_watch = self._get_first_watch() + self.assertNotNone(bug_watch.date_last_checked) + + def test_date_next_checked(self): + bug_watch = self._get_first_watch() + self.assertNotNone(bug_watch.date_next_checked) + + def test_last_error_type(self): + bug_watch = self._get_first_watch() + self.assertNotNone(bug_watch.last_error_type) + + def test_owner(self): + bug_watch = self._get_first_watch() + self.assertNotNone(bug_watch.owner) + self.assertEqual(bug_watch.owner.name, "aus") + + def test_remote_bug(self): + bug_watch = self._get_first_watch() + + + def test_remote_importance(self): + bug_watch = self._get_first_watch() + + + def test_remote_status(self): + bug_watch = self._get_first_watch() + + + def test_title(self): + bug_watch = self._get_first_watch() + + + def test_url(self): + bug_watch = self._get_first_watch() + + + def test_watch_owner(self): + ls = LaunchpadService(self.configuration) + bug = ls.get_bug(self.bug_id) + + def test_containment(self): + ls = LaunchpadService(self.configuration) + bug = ls.get_bug(self.bug_id) + bug_watches = bug.bug_watches + + # Test containment + bug_watch = bug_watches[0] + if not bug_watch in bug_watches: + raise AssertionError + else: + print "Located bug watch in collection" + + +if __name__ == '__main__': + unittest.main() + +# vi:set ts=4 sw=4 expandtab: lpltk/setup.py0000775000000000017510000000221512026437121010625 0ustar #!/usr/bin/env python # Copyright (C) Canonical, Ltd. Licensed under the GPL from distutils.core import setup import os import re # look/set what version we have changelog = "debian/changelog" if os.path.exists(changelog): head=open(changelog).readline() match = re.compile(".*\((.*)\).*").match(head) if match: version = match.group(1) setup( name = 'python-launchpadlib-toolkit', version = version, platforms = ['any'], requires = ['json', 'httplib2', 'datetime', 'shutil', 'gzip', 'tarfile', 'launchpadlib', ], # Note for make lint also needs 'pydoctor', 'pep8' packages = ['lpltk'], scripts = [ 'scripts/current-ubuntu-supported-releases', 'scripts/current-ubuntu-development-codename', 'scripts/current-ubuntu-release-codename', 'scripts/launchpad-service-status', 'scripts/ls-assigned-bugs', 'scripts/find-similar-bugs', 'scripts/close-fix-committed-bugs', 'scripts/lp-file-bug', ], ) lpltk/COPYING0000664000000000017510000004313411753031405010151 0ustar GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. lpltk/scripts/0000775000000000017510000000000012050554506010603 5ustar lpltk/scripts/syncreq0000664000000000017510000002163111753300023012205 0ustar #!/usr/bin/python # -*- coding: utf-8 -*- # (C) 2008 Canonical Ltd., Authors # # Authors: # Martin Pitt # Steve Kowalik # Michael Bienia # Daniel Hahler # Iain Lane # Jonathan Patrick Davies # Bryce Harrington # # ############################################################### # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; version 2 or newer. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # See file /usr/share/common-licenses/GPL-2 for more details. # # ############################################################### # This is a simplified version of the requestsync script from # ubuntu-dev-tools, that uses launchpadlib (via Arsenal) as the # backend. from arsenal.arsenal_lib import * from lpltk import LaunchpadService import getopt import subprocess import urllib import urllib2 from debian_bundle.changelog import Version def usage(): print '''Usage: syncreq [-h|-d sync-version|-e|-v base-version] [rationale] Files sync requests for a source package. In some cases, the base version (fork point from Debian) cannot be determined automatically, and you will get a complete Debian changelog. Specify the correct base version of the package in Ubuntu using the -v flag. Also see the requestsync script in the ubuntu-dev-tools package. Options: -h Print this help -d Debian version to sync -e Sync from Debian Experimental -v Base version in Ubuntu Stock Rationales: --all-changes-upstream --fakesync --no-change ''' sys.exit(1) # TODO: Generalize this and move it into Arsenal proper # It should return a data structure with all the collected info def source_info(package, distro, release): '''Determine current package version in ubuntu or debian.''' print 'rmadison -u %s -a source -s %s %s' % (distro, release, package) madison = subprocess.Popen(['rmadison', '-u', distro, '-a', 'source', \ '-s', release, package], stdout=subprocess.PIPE) out = madison.communicate()[0] assert (madison.returncode == 0) try: assert out except AssertionError: print "%s doesn't appear to exist in %s." % (package, distro) sys.exit(1) for l in out.splitlines(): print "Received: ", l (pkg, version, rel, builds) = l.split('|') component = 'main' if rel.find('/') != -1: component = rel.split('/')[1] if builds.find('source') != -1: return (version.strip(), component.strip()) print "%s doesn't appear to exist in %s, specify -n for a package not in Ubuntu." % (package, release) sys.exit(1) # TODO: Generalize and put into Arsenal def debian_changelog(package, component, base_version): '''Return the Debian changelog from the latest up to the given version (exclusive).''' ch = '' subdir = package[0] if package.startswith('lib'): subdir = 'lib%s' % package[3] debian_changelog_url = 'http://packages.debian.org/changelogs/pool/%s/%s/%s/current/changelog.txt' % (component, subdir, package) try: debianChangelogPage = urllib2.urlopen(debian_changelog_url) except urllib2.HTTPError, error: print >> sys.stderr, "Unable to connect to packages.debian.org. " \ "Received a %s." % error.code print >> sys.stderr, debian_changelog_url sys.exit(1) for l in debianChangelogPage: if l.startswith(package): ch_version = l[ l.find("(")+1 : l.find(")") ] if Version(ch_version) <= base_version: break ch += l return ch if __name__ == '__main__': lp = LaunchpadService() d = lp.launchpad.distributions["ubuntu"] base_version = None ubu_version = '0' deb_series = 'unstable' deb_version = None rationale = None try: opts, args = getopt.gnu_getopt(sys.argv[1:], 'hd:ev:', ('all-changes-upstream', 'fakesync', 'no-change')) except getopt.GetoptError: usage() for o, a in opts: if o == '-h': usage() sys.exit() if o == '-d': deb_version = a if o == '-e': deb_series = 'experimental' if o == '-v': base_version = a if o == '--all-changes-upstream': rationale = "\n * All ubuntu changes are present in the upstream release\n\n" if o == '--fakesync': rationale = "\n * The prior ubuntu version was only due to need for a fakesync\n\n" if o == '--no-change': rationale = "\n * The prior ubuntu version was a rebuild; there were no source changes\n\n" # TODO: permit specifying multiple packages to sync if len(args) < 1: usage() elif base_version and len(args) > 1: print "Error: Cannot use -v option with more than one source-package argument\n" usage() # TODO: Implement requestsync's checkExistingReports() using launchpadlib # TODO: Move this into arsenal # Determine the current development target ubu_series = None num_development = 0 ubuntu = lp.launchpad.distributions["ubuntu"] for series in ubuntu.series: if series.status == "Active Development" or series.status == "Pre-release Freeze": num_development += 1 ubu_series = series.name if num_development > 1: # TODO: Provide cmdline option for this situation print "Error: More than one release series is in development. Can't decide which to use." sys.exit(1) elif num_development < 1: print "Error: No release series in development" sys.exit(1) if not ubu_series: print "Error: Could not determine release" sys.exit(1) # TODO: Check whether we're pre-feature-freeze or not #ubuntu.active_milestones package = args[0] (ubu_version, ubu_component) = source_info(package, 'ubuntu', ubu_series) # TODO: If qa.debian.org is down, this fails... if deb_version == None: (deb_version, deb_component) = source_info(package, 'debian', deb_series) else: # Wildly guess it's in main deb_component = 'main' report = unicode("Please sync this package from debian to ubuntu\n", "utf-8") if ubu_version == deb_version: print 'The versions in Debian and Ubuntu are the same already (%s).' % (deb_version) sys.exit(0) if not base_version: base_version = Version(ubu_version) # TODO: Detect if there are ubuntu changes, and prompt to explain why they can be dropped if rationale: report += rationale # TODO: Determine if we're post-UVF, and if so prepend UVFe title = "[Sync Request] Please sync %s (%s) from Debian [%s] (%s)" % (package, deb_version, deb_series, ubu_component) print title report += 'Changelog since current %s version %s:\n\n' % (ubu_series, ubu_version) try: report += unicode(debian_changelog(package, deb_component, base_version), 'utf-8', errors='ignore') except: report = "" print "ERROR: Invalid changelog for " + package + " " + ubu_version raise # TODO: Permit editing the report before sending it? # print report.encode('utf-8') + "\n\n" print "Filing bug against %s: %s" %(package, title) print report d = lp.launchpad.distributions["ubuntu"] target = d.getSourcePackage(name = package) bug = lp.launchpad.bugs.createBug(title = title, description = report, target = target) print 'Sync request filed as bug #%i: https://launchpad.net/bugs/%i' % (bug.id, bug.id) bug.bug_tasks[0].status = "Confirmed" bug.bug_tasks[0].importance = "Wishlist" bug.bug_tasks[0].lp_save() person = lp.launchpad.people['ubuntu-archive'] bug.subscribe(person = person) print " * %s: sync req# %d" % (package, bug.id) # TODO: # rmadison -u ubuntu -a source -s jaunty xserver-xorg-video-mga # rmadison -u debian -a source -s experimental xserver-xorg-video-mga # [Sync Request] Please sync xserver-xorg-video-mga (1:1.4.9.dfsg-2) from Debian [experimental] (main) # Traceback (most recent call last): # File "./syncreq", line 191, in # report += debian_changelog(package, deb_component, base_version).encode('utf-8') + "\n" # UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4378: ordinal not in range(128) # Rerunning script avoided the error the second time lpltk/scripts/ls-assigned-bugs0000775000000000017510000000106111753031405013673 0ustar #!/usr/bin/python from lpltk import LaunchpadService #lp = LaunchpadService(config={'read_only':True}) lp = LaunchpadService() d = lp.load_project("ubuntu") print lp.launchpad.me.display_name for bugtask in d.searchTasks(assignee=lp.launchpad.me): print bugtask.title print " Reported by: ",bugtask.owner.display_name print " Importance: ",bugtask.importance print " Status: ",bugtask.status print " Assigned on: ",bugtask.date_assigned if bugtask.milestone: print " Milestone: ",bugtask.milestone.name print lpltk/scripts/ls-person0000775000000000017510000000056612050554506012462 0ustar #!/usr/bin/python from lpltk import LaunchpadService #lp = LaunchpadService(config={'read_only':True}) lp = LaunchpadService() p = lp.get_person('gandelman-a') print(p.username) print(p.display_name) print(p.full_name) print(p.first_name) print(p.karma) print(p.email_addresses) for key in p.lpperson.gpg_keys: print " ", key.keyid print " ", key.fingerprint lpltk/scripts/ls-tag.py0000775000000000017510000000145011753300023012337 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService if len(sys.argv) < 2: sys.stderr.write("Usage: %s \n" %(sys.argv[0]) ) sys.exit(1) total_count = 0 tag = sys.argv[1] source_pkgs = sys.argv[2:] lp = LaunchpadService(config={'read_only':True}) d = lp.launchpad.distributions["ubuntu"] print "== Bugs tagged '" + tag + "' ==" for source_pkg in source_pkgs: count = 0 print "=== " + source_pkg + " ===" s = d.getSourcePackage(name = source_pkg) for bugtask in s.searchTasks(tags=tag): bug = ArsenalBug(bugtask.bug, lp.launchpad) print bug.id, " " + bugtask.status + " " + bug.title count += 1 total_count += 1 print total_count, " total bugs tagged '" + tag + "'" lpltk/scripts/test0000775000000000017510000000424111753031405011506 0ustar #!/usr/bin/python # TODO: Cleanup import statements import os import sys import httplib2 import socket import launchpadlib from launchpadlib.launchpad import Launchpad from lpltk import LaunchpadService from lpltk.debug import * read_only = True class LaunchpadService: """ TODO: * Document class * Document LPLTK_DEBUG environment option """ def __init__(self, consumer='lpltk', service_root='production', read_only=False): """ If launchpad cannot be accessed, will throw an exception (TODO). @param consumer: TODO @param service_root: TODO @param read_only: TODO """ self.consumer = consumer self.service_root = service_root self.read_only = read_only ''' Debugging ''' if "LPLTK_DEBUG" in os.environ: httplib2.debuglevel = os.getenv("LPLTK_DEBUG", None) sys.stdout = DebugStdOut() dbg("Launchpadlib Version: %s" %(launchpadlib.__version__)) dbg("Login With: %s %s" %(self.consumer, self.service_root)) dbg("Read Only: %s" %(self.read_only)) if not self._get_creds(): err("Error: Could not retrieve credentials from launchpad") def _get_creds(self): if self.read_only: # TODO: Only supported on 1.5.4 or newer self.launchpad = Launchpad.login_anonymously(self.consumer, self.service_root) else: self.launchpad = Launchpad.login_with(self.consumer, self.service_root) return True # Need >= 1.5.1 if read_only: lp = LaunchpadService(read_only=True, service_root="edge") print lp.launchpad.bugs[1].title else: lp = LaunchpadService(read_only=False) print 'Hello, %s!' % lp.launchpad.me.display_name #lp = LaunchpadService() #d = lp.load_project("ubuntu") #for bugtask in d.searchTasks(assignee=lp.launchpad.me): # print bugtask.title # print " Reported by: ",bugtask.owner.display_name # print " Importance: ",bugtask.importance # print " Status: ",bugtask.status # print " Assigned on: ",bugtask.date_assigned # if bugtask.milestone: # print " Milestone: ",bugtask.milestone.name # print lpltk/scripts/current-ubuntu-release-codename0000775000000000017510000000124311757224327016732 0ustar #!/usr/bin/python import os.path import sys from lpltk import LaunchpadService from lpltk.utils import ( load_file, write_file, file_age ) cache_file = os.path.join( "/tmp/lpltk.cache", os.path.basename(sys.argv[0]) ) # First try using the cached information if os.path.exists(cache_file): if file_age(cache_file) < 24*60*60: print load_file(cache_file) sys.exit(0) try: lp = LaunchpadService(config={'read_only':True}) d = lp.distributions['ubuntu'] text = d.stable_series.name assert(type(text) in [str, unicode]) print text write_file(cache_file, text) except: sys.exit(7) sys.exit(0) lpltk/scripts/current-ubuntu-development-codename0000775000000000017510000000127211753031405017623 0ustar #!/usr/bin/python import os.path import sys from lpltk import LaunchpadService from lpltk.utils import ( load_file, write_file, file_age ) cache_file = os.path.join( "/tmp/lpltk.cache", os.path.basename(sys.argv[0]) ) # First try using the cached information if os.path.exists(cache_file): if file_age(cache_file) < 24*60*60: print load_file(cache_file) sys.exit(0) # Next try retrieving it from Launchpad, printing, and caching try: lp = LaunchpadService(config={'read_only':True}) d = lp.distributions['ubuntu'] text = d.current_series.name print text write_file(cache_file, text) except: sys.exit(7) sys.exit(0) lpltk/scripts/find-similar-bugs0000775000000000017510000000056711753031405014052 0ustar #!/usr/bin/env python import sys from lpltk import LaunchpadService #lp = LaunchpadService(config={'read_only':True}) lp = LaunchpadService() if len(sys.argv) < 2: print "Usage: similar-bugs [bug-id]" sys.exit(2) bug_id = sys.argv[1] bug = lp.launchpad.bugs[bug_id] for dupe in bug.bug_tasks[0].findSimilarBugs(): print "%12d %s" %(dupe.id, dupe.title) lpltk/scripts/cgit-patch0000775000000000017510000000626511753300023012555 0ustar #!/usr/bin/env python import os import sys import re import subprocess def retrieve_url_content(url): process = subprocess.Popen(['wget', url, '-O', '-'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = process.communicate() return stdout class CgitPatch: # Freedesktop-style git re_command_fdo = re.compile("(http.*)\/(\w+)\/\?id=(\w+)") # SourceForge-style git re_command_sf = re.compile("(http.*)\?p=([\w\/\-]+);a=(\w+);h=(\w+)") def __init__(self, git_url): self._filename = None self._content = None self._content_lines = None self.commit_id = None self.package = None self.url = None m = CgitPatch.re_command_fdo.match(git_url) if m: base_url = m.group(1) command = m.group(2) self.commit_id = m.group(3) self.package = base_url.split('/').pop() self.url = git_url if command != 'patch': self.url = "%s/patch/?id=%s" %(base_url, self.commit_id) return m = CgitPatch.re_command_sf.match(git_url) if m: base_url = m.group(1) command = m.group(3) self.commit_id = m.group(4) self.package = base_url.split('/').pop() self.url = git_url if command != 'patch': self.url = "%s?p=%s;a=commit;h=%s" %(base_url, self.package, self.commit_id) return assert 1 == 0, "Unrecognized url form %s" %(git_url) @property def content(self): '''List of lines retrieved from the URL''' if self._content is None: self._content = retrieve_url_content(self.url) return self._content @property def content_lines(self): if self._content_lines is None: self._content_lines = self.content.split("\n") return self._content_lines def set_filename(self, filename): self._filename = filename def get_filename(self): if self._filename is None: for line in self.content_lines: if line[:8] == 'Subject:': subject = re.sub(r' +', '_', line[9:]).lower() self._filename = "%s.patch" %(re.sub(r'\W+', '', subject)) break # TODO: Check current directory for a series file; if it exists, then # number the patch consecutively. return self._filename filename = property(get_filename, set_filename) def is_valid(self): if self.commit_id is None: return False for line in self.content_lines: if line[5:45] == self.commit_id: return True return False if __name__ == '__main__': if len(sys.argv)<2: print "Usage: cgit-patch [patch-filename]" sys.exit(1) url = sys.argv[1] patch = CgitPatch(url) if len(sys.argv)>2: patch.filename = sys.argv[2] if not patch.is_valid(): sys.stderr.write("Error: Failed to download patch\n") sys.exit(1) file = open(patch.filename, 'w') file.write(patch.content) file.close() print patch.filename lpltk/scripts/json_check0000775000000000017510000000351112015221371012627 0ustar #!/usr/bin/env python import os.path import sys import re import json import glob opt_verbose = False re_header = re.compile("^([\.\-\w]+): (.*)$") re_json = re.compile("^[\{\[].*") re_xml = re.compile("^[\<].*$") def msg(text): if opt_verbose: print(text) def json_check(filename): json_data = '' header = {} with open(filename, 'r') as FILE: for line in FILE: if line.isspace(): msg("isspace") continue m = re_header.search(line) if m: header[m.group(1)] = m.group(1) msg("header") continue m = re_json.search(line) if m: msg("json data") json_data += line continue m = re_xml.search(line) if m: # Ignore file msg("ignoring file") break msg("Unknown content type in %s" %(filename)) return True try: if not json_data or json_data.isspace(): msg("%s: no json data" %(filename)) else: j = json.loads(json_data) msg("%s: %d objects" %(filename, len(j))) return True except: msg("%s: %s" %(filename, 'INVALID JSON')) return False if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: sys.argv[0] ") sys.exit(1) for arg in sys.argv[1:]: for path in glob.glob(arg): if os.path.isfile(path): if not json_check(path): print(path) continue for root, dirs, files in os.walk(path): for filename in files: if not json_check(os.path.join(root, filename)): print(filename) lpltk/scripts/lp-file-bug0000775000000000017510000000272412026464556012651 0ustar #!/usr/bin/python import os import sys from argparse import ArgumentParser from lpltk import LaunchpadService from subprocess import check_call from tempfile import NamedTemporaryFile def main(): parser = ArgumentParser("A script for filing new bugs in Launchpad.") parser.add_argument("title", help="The bug title used to describe it.") parser.add_argument("--project","-p", help="The project to file the bug against.", default="ubuntu") parser.add_argument("--package","-P", help="The package to file the bug against.") args = parser.parse_args() lp = LaunchpadService() # TODO: Get the description for 'your favourite editor' editor = os.environ.get('EDITOR','nano').split() tempfile = NamedTemporaryFile(delete=False) editor.append(tempfile.name) try: check_call(editor) except OSError: print("Failed to open preferred editor '%s'" % editor) description = tempfile.read() try: bug = lp.create_bug(args.project, args.package, title=args.title, description=description) print(bug.lpbug.web_link) except: print("Unable to file bug in:") print("\tproject: %s" % args.project) if args.package: print("\tpackage: %s" % args.package) print("Please check that the project and/or package name are correct.") if __name__ == "__main__": sys.exit(main()) lpltk/scripts/current-ubuntu-supported-releases0000775000000000017510000000132411753031405017354 0ustar #!/usr/bin/python import os.path import sys from lpltk import LaunchpadService from lpltk.utils import ( load_file, write_file, file_age ) cache_file = os.path.join( "/tmp/lpltk.cache", os.path.basename(sys.argv[0]) ) # First try using the cached information if os.path.exists(cache_file): if file_age(cache_file) < 24*60*60: print load_file(cache_file) sys.exit(0) try: lp = LaunchpadService(config={'read_only':True}) d = lp.distributions['ubuntu'] names = [] for series in d.supported_series: names.append(series.name) text = "\n".join(names) print text write_file(cache_file, text) except: sys.exit(7) sys.exit(0) lpltk/scripts/launchpad-service-status0000775000000000017510000000474412026706234015460 0ustar #!/usr/bin/python # Script to check if launchpad is up and usable. # # Since this script does some http queries it is a bit slow. The intent # is to run it periodically to 'test' launchpad and cache the result # locally; cronjobs can then check this cached status value to see if # launchpad is available, and if not, bail out and not bother trying to # run. import re import sys import feedparser from datetime import ( datetime, timedelta ) import launchpadlib from launchpadlib.launchpad import Launchpad consumer='lpltk' service_root='production' def expected_outage(): """Returns tuple of (start, stop) time for the current or next expected outage""" (expected_start, expected_end) = (None, None) d = feedparser.parse('http://blog.launchpad.net/category/notifications/feed') latest_entry = d['entries'][0] notice_html = latest_entry['content'][0]['value'] date_format_long = ' %H.%M UTC %d of %B %Y' date_format_short = ' %H.%M UTC %Y-%m-%d' m = re.search("Starts:.*>(.*)<", notice_html) if m: raw_text = m.group(1) text = re.sub(r'(\d+)(?:st|nd|rd|th) ', r'\1 ', raw_text) try: expected_start = datetime.strptime(text, date_format_long) except: expected_start = datetime.strptime(text, date_format_short) m = re.search('Expected.*back.*by.*>(.*)<', notice_html) if m: raw_text = m.group(1) text = re.sub(r'(\d+)(?:st|nd|rd|th) ', r'\1 ', raw_text) try: expected_end = datetime.strptime(text, date_format_long) except: expected_end = datetime.strptime(text, date_format_short) return (expected_start, expected_end) (outage_start, outage_end) = expected_outage() # Are we in the middle of an expected outage? if outage_start and outage_end: if datetime.now() > outage_start and datetime.now() < outage_end: print 'OFFLINE EXPECTED' sys.exit(3) # Will we be offline shortly? pending_offline_time = timedelta(hours=2) if datetime.now() + pending_offline_time > outage_start: print 'PENDING OFFLINE' sys.exit(2) # Can we at least reach launchpad's api service in minimal read-only mode? try: lp = Launchpad.login_anonymously(consumer, service_root) # Need >= 1.5.1 except: print 'OFFLINE UNKNOWN' sys.exit(9) # See if we can access read-write too try: lp = Launchpad.login_with(consumer, service_root) print 'UP READWRITE' except: print 'UP READONLY' sys.exit(1) sys.exit(0) lpltk/scripts/unsub-sponsors.py0000775000000000017510000000177211753300023014177 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService lp = LaunchpadService() d = lp.launchpad.distributions["ubuntu"] sponsors = [ 'ubuntu-main-sponsors', 'ubuntu-universe-sponsors' ] message = """ I'm unsubscribing the Sponsors' Team for now. Please re-subscribe when ready. """ def unsub(bugnr): bug = lp.get_bug(sys.argv[1]) print bug.id, " ", bug.title for s in bug.subscriptions: if s.person.name in sponsors: try: # TODO: launchpadlib's API doesn't permit a person argument (LP: #321738) bug.unsubscribe(s.person) print "Unsubscribed ",s.person.name except: print >> sys.stderr, "Failed to unsubscribe ",s.person.name raise def main(): if len(sys.argv) < 2: print >> sys.stderr, "Usage: unsub " sys.exit(1) for arg in sys.argv[1:]: unsub(arg) if __name__ == '__main__': main() lpltk/scripts/test_attachments.py0000775000000000017510000000440611753300023014526 0ustar #!/usr/bin/python import sys from datetime import ( datetime, timedelta ) import launchpadlib from launchpadlib.launchpad import Launchpad from launchpadlib.errors import HTTPError bugnum = 422119 consumer = 'lpltk' service_root = 'production' lp = Launchpad.login_anonymously(consumer, service_root) bug = lp.bugs[bugnum] last_time = datetime.now() def ts(start_time): global last_time now = datetime.now() run_time = (now - start_time) call_time = (now - last_time) last_time = now return "[%8.4f (%6.4f sec)]" %( run_time.microseconds/1000000.0 + run_time.seconds, call_time.microseconds/1000000.0 + call_time.seconds ) def main(): print "%-10s %s" %( bug.id, bug.title ) print "== attachments ==" for a in bug.attachments: s = datetime.now() print "\n\n" print "%8s %-15s %s" % (ts(s), "title:", a.title) print "%8s %-15s %s" % (ts(s), "ispatch:", a.type) print "%8s %-15s %s" % (ts(s), "http_etag:", a.http_etag) print "%8s %-15s %s" % (ts(s), "a.url:", a.self) m = a.message print "%8s %-15s %s" % (ts(s), "subject:", m.subject.encode('utf-8')) print "%8s %-15s %s" % (ts(s), "owner:", m.owner.display_name.encode('utf-8')) print "%8s %-15s %s" % (ts(s), "created:", m.date_created) fb = a.data.open() print "%8s %-15s %s" % (ts(s), "modified:", fb.last_modified) print "%8s %-15s %s" % (ts(s), "fb.url:", fb.url) print "%8s %-15s %s" % (ts(s), "content-type:", fb.content_type) print "%8s %-15s %s" % (ts(s), "filename:", fb.filename.encode('utf-8')) print "%8s %-15s %s" % (ts(s), "isatty:", fb.isatty()) print "%8s %-15s %s" % (ts(s), "len:", fb.len) print "%8s %-15s %s" % (ts(s), "mode:", fb.mode) print "%8s %-15s %s" % (ts(s), "pos:", fb.pos) print "%8s %-15s %s" % (ts(s), "softspace:", fb.softspace) print "%8s %-15s %s" % (ts(s), "content:", len(fb.read())) print "%8s" %(ts(s)) return 0 sys.exit(main()) lpltk/scripts/ls-sourcepackage.py0000775000000000017510000000311411753300023014377 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService if len(sys.argv) < 2: sys.stderr.write("Usage: %s \n" %(sys.argv[0]) ) sys.exit(1) total_count = 0 source_pkgs = sys.argv[1:] lp = LaunchpadService(config={'read_only':True}) d = lp.launchpad.distributions["ubuntu"] tags = {} for source_pkg in source_pkgs: s = d.getSourcePackage(name = source_pkg) print source_pkg if s.upstream_product is None: print " No upstream product" continue bugtracker = s.upstream_product.bug_tracker if bugtracker is None: # Maybe there's one on the project group project_group = s.upstream_product.project_group if project_group is not None: bugtracker = project_group.bug_tracker if bugtracker is not None: print " upstream bugtracker:", bugtracker.name print " - base_url:", bugtracker.base_url print " - type:", bugtracker.bug_tracker_type print " - lp_plugin:", bugtracker.has_lp_plugin print " - remote_product:", s.upstream_product.remote_product print " - sourceforge:", s.upstream_product.sourceforge_project else: print " No upstream bug tracker registered" if s.upstream_product.project_group is not None: print " project_group:", s.upstream_product.project_group.name if s.upstream_product.development_focus is not None: print " development focus:", s.upstream_product.development_focus.name print " wiki:", s.upstream_product.wiki_url print lpltk/scripts/search-attachments0000775000000000017510000000240511753031405014305 0ustar #!/usr/bin/env python import re import sys from lpltk import LaunchpadService if len(sys.argv) < 4: print "Usage: search-attachments " sys.exit(2) source_pkg = sys.argv[1] attach_regex = sys.argv[2] text_regex = ' '.join(sys.argv[3:]) lp = LaunchpadService(config={'read_only':True}) d = lp.distributions["ubuntu"] re_text = re.compile(text_regex) re_filename = re.compile(attach_regex) s = d.get_source_package(source_pkg) # TODO: Search by given tag (current devel release) for bug_task in s.search_tasks(tags="precise", tags_combinator="All", omit_duplicates=False): bug = bug_task.bug found_attachments = [] for a in bug.attachments: if not re_filename.search(a.title): continue hosted_file = a.data hosted_file_buffer = hosted_file.open() content = hosted_file_buffer.read() if re_text.search(content): found_attachments.append(a) if len(found_attachments)>0: print "%d: %s %s" %(bug.id, bug_task.status, bug.title) dupe = bug.duplicate_of if dupe: print " + Dupe of %d: %s" %(dupe.id, dupe.title) lpltk/scripts/ls-series0000775000000000017510000000154011753031405012434 0ustar #!/usr/bin/env python from lpltk import LaunchpadService lp = LaunchpadService(config={'read_only':True}) d = lp.load_project("ubuntu") next_milestone = None for series in d.series: print "%-10s %-20s %-25s" % (series.name, series.status, series.date_created) if series.status == "Active Development" or series.status == "Pre-release Freeze": for milestone in series.active_milestones: print "%10s %-20s %-25s" % ("-->", milestone.name, milestone.date_targeted) if milestone.date_targeted and not next_milestone: next_milestone = milestone elif next_milestone and milestone.date_targeted < next_milestone.date_targeted: next_milestone = milestone print print "The next milestone is ", next_milestone.name lpltk/scripts/unsub-all.py0000775000000000017510000000277211753300023013062 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService lp = LaunchpadService() d = lp.launchpad.distributions["ubuntu"] # Retrieve all bugs subscriber is subscribed to def unsub(person, bugnr): bug = lp.get_bug(sys.argv[1]) print bug.id, " ", bug.title for s in bug.subscriptions: if s.person.name in subscribers: try: # TODO: launchpadlib's API doesn't permit a person argument (LP: #321738) bug.unsubscribe(s.person) print "Unsubscribed ",s.person.name except: print >> sys.stderr, "Failed to unsubscribe ",s.person.name raise def main(): if len(sys.argv) < 3: print >> sys.stderr, "Usage: unsub [package [...]]" sys.exit(1) # TODO: Parameterize subscriber = sys.argv[1] persons = lp.launchpad.people.find(text = subscriber) #if len(persons) > 1: # sys.stderr.write("Error: More than one match to %s\n" % subscriber) # sys.exit(1) person = persons[0] for pkg in sys.argv[2:]: print pkg s = d.getSourcePackage(name = pkg) for bugtask in s.searchTasks(bug_subscriber = person): bug = ArsenalBug(bugtask.bug, lp.launchpad) title = bug.title.decode("utf-8") print "%-7s %-11s %s" % (bug.id, bugtask.status, title) bug.bug.unsubscribe(person = person) if __name__ == '__main__': main() lpltk/scripts/close-fix-committed-bugs0000775000000000017510000000407211753031405015343 0ustar #!/usr/bin/env python # Copyright 2010 Bryce Harrington # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys from lpltk import LaunchpadService if len(sys.argv) != 2: print 'Usage: %s ' % sys.argv[0] sys.exit(1) project_name = sys.argv[1] try: lp = LaunchpadService() project = lp.load_project(project_name) except: sys.stderr.write("Could not connect to launchpad\n") sys.exit(7) def truncate(text, max_size): if len(text) <= max_size: return text return text[:max_size].rsplit(' ', 1)[0]+"..." def get_next_milestone(project): next_milestone = None for series in project.series: if series.status == "Active Development" or series.status == "Pre-release Freeze": for milestone in series.active_milestones: if not next_milestone or not next_milestone.date_targeted or \ milestone.date_targeted < next_milestone.date_targeted: next_milestone = milestone if next_milestone: return next_milestone else: return None next_milestone = get_next_milestone(project) if not next_milestone: print "No milestone coming up" sys.exit(0) for bugtask in project.searchTasks(status = "Fix Committed", milestone = next_milestone): bugtask.status = 'Fix Released' bugtask.lp_save() print "%7s %s-->Fix Released %-40s" %(bugtask.bug.id, bugtask.status, truncate(bugtask.bug.title,60) ) sys.exit(0) lpltk/scripts/ls-upstream-bugs.py0000775000000000017510000000275711753300023014375 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService if len(sys.argv) < 1: sys.stderr.write("Usage: %s \n" %(sys.argv[0]) ) sys.exit(1) total_count = 0 source_pkgs = sys.argv[1:] lp = LaunchpadService(config={'read_only':True}) d = lp.launchpad.distributions["ubuntu"] print "== Bugs open upstream ==" print for source_pkg in source_pkgs: s = d.getSourcePackage(name = source_pkg) bug_tracker_url = None up = s.upstream_product if (up is not None and up.bug_tracker is not None): bug_tracker_url = up.bug_tracker.base_url print "%s (%s - %s):" %(source_pkg, up.name, bug_tracker_url) upstream_tasks = s.searchTasks(status_upstream = 'open_upstream') print "%d upstreamed bugs for %s" %(len(list(upstream_tasks)), source_pkg) count = 0 for bugtask in upstream_tasks: print bugtask.bug.id, bugtask.bug_target_display_name for sibling_task in bugtask.related_tasks: watch = sibling_task.bug_watch if watch is None: print " %s" %(sibling_task.bug_target_display_name) continue watch_tracker = watch.bug_tracker.base_url print " %s [%s]" %(sibling_task.bug_target_display_name, watch_tracker) if watch_tracker == bug_tracker_url: count += 1 break print "%d officially upstreamed bugs for %s" %(count, source_pkg) lpltk/scripts/ls-all-tags.py0000775000000000017510000000142611753300023013273 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService if len(sys.argv) < 2: sys.stderr.write("Usage: %s \n" %(sys.argv[0]) ) sys.exit(1) total_count = 0 source_pkgs = sys.argv[1:] lp = LaunchpadService(config={'read_only':True}) d = lp.launchpad.distributions["ubuntu"] tags = {} for source_pkg in source_pkgs: print "== Tags in '"+source_pkg+"' ==" s = d.getSourcePackage(name = source_pkg) for bugtask in s.searchTasks(): for tag in bugtask.bug.tags: if not tags.has_key(tag): #print bugtask.bug.id," ",tag tags[tag] = 1 else: tags[tag] += 1 for tag in tags.keys(): print "%-20s %d" % (tag, tags[tag]) lpltk/scripts/ls-gitcommit-bugs.py0000775000000000017510000000367011753300023014524 0ustar #!/usr/bin/python from arsenal.arsenal_lib import * from lpltk import LaunchpadService if len(sys.argv) < 1: sys.stderr.write("Usage: %s \n" %(sys.argv[0]) ) sys.exit(1) total_count = 0 source_pkgs = sys.argv[1:] lp = LaunchpadService(config={'read_only':True}) d = lp.launchpad.distributions["ubuntu"] regex_hex = re.compile('[0-9a-f]+[0-9][a-f]+[0-9a-f]+') print "== Bugs with git commits ==" try: for source_pkg in source_pkgs: count = 0 print "=== " + source_pkg + " ===" for bugtask in d.searchTasks(search_text=source_pkg): if bugtask.bug_target_display_name != source_pkg + " (Ubuntu)": continue bug = ArsenalBug(bugtask.bug, lp.launchpad) title = "%d %s %s" % (bug.id, bugtask.status, bug.title) #print " -- ", bug.id, " -- " # Search description for git commit strings # TODO: Make sure it's either 8 chars or 40 chars commits = regex_hex.findall(bug.description) if len(commits) > 0: for s in commits: if len(s) == 40: if title: print title title = "" print " commit: " + s for m in bug.bug.messages: if not m.content: continue commits = regex_hex.findall(m.content) if len(commits) == 0: continue for s in commits: if len(s) == 40: if title: print title title = "" print " commit: " + s + " in comment" except HTTPError, e: print "*** Encountered HTTP error ***" print e.content time.sleep(600) lp.reset() d = lp.launchpad.distributions["ubuntu"] lpltk/lpltk/0000775000000000017510000000000012064737656010260 5ustar lpltk/lpltk/message.py0000664000000000017510000000327311777436362012263 0ustar #!/usr/bin/python from utils import ( o2str, typecheck_Entry ) from person import Person # Message # class Message(object): # __init__ # def __init__(self, tkbug, lpmessage): self.__tkbug = tkbug self.__commit_changes = tkbug.commit_changes self.__message = typecheck_Entry(lpmessage) self.__owner = None self.__content = None self.__date_created = None self.__parent = None self.__subject = None # owner # @property def owner(self): if self.__owner == None: self.__owner = Person(self.__tkbug, self.__message.owner) return self.__owner # content @property def content(self): if self.__content == None: self.__content = o2str(self.__message.content) return self.__content # date_created @property def date_created(self): if self.__date_created == None: self.__date_created = self.__message.date_created return self.__date_created # age @property def age(self): '''Number of days since message was posted''' t = self.date_created now = t.now(t.tzinfo) return 0 + (now - t).days # parent @property def parent(self): if self.__parent == None: self.__parent = Message(self.__tkbug, self.__message.parent) return self.__parent # subject @property def subject(self): if self.__subject == None: self.__subject = o2str(self.__message.subject) return self.__subject # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/projects.py0000664000000000017510000000174111753031405012445 0ustar #!/usr/bin/python from project import Project class Projects(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service): self.__service = service self.__projects = None # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return Project(self.__service, self.__projects[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for project in self.__projects: d = Project(self.__service, project) yield d # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__projects == None: self.__projects = self.__service.launchpad.projects # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/bug_task.py0000664000000000017510000003415412001160646012414 0ustar #!/usr/bin/python import datetime from person import Person from bug import Bug from milestone import Milestone from utils import (o2str, typecheck_Entry) # BugTask # class BugTask(object): # __init__ # def __init__(self, service, lp_bug_task): self.__service = service self.lp_bug_task = typecheck_Entry(lp_bug_task) self.__assignee = None self.__bug_target_display_name = None self.__bug_target_name = None self.__bug_watch = None self.__date_assigned = None self.__date_closed = None self.__date_confirmed = None self.__date_created = None self.__date_fix_committed = None self.__date_fix_released = None self.__date_importance_set = None self.__date_in_progress = None self.__date_incomplete = None self.__date_left_closed = None self.__date_left_new = None self.__date_triaged = None self.__importance = None self.__is_complete = None self.__milestone = None self.__owner = None self.__similar_bugs = None self.__status = None self.__status_age = None self.__target = None self.__title = None self.__upstream_bug_tracker = None self.__upstream_product = None self.__web_link = None # __str__ # def __str__(self): return self.display_name # owner # @property def owner(self): if self.__owner is None: lp_owner = self.lp_bug_task.owner if lp_owner is not None: self.__owner = Person(None, lp_owner) return self.__owner @property def owner_name(self): if self.owner is not None: return o2str(self.owner.username) else: return None # bug_target_display_name # @property def bug_target_display_name(self): if self.__bug_target_display_name is None: self.__bug_target_display_name = self.lp_bug_task.bug_target_display_name return self.__bug_target_display_name # bug_target_name # @property def bug_target_name(self): if self.__bug_target_name is None: self.__bug_target_name = self.lp_bug_task.bug_target_name return self.__bug_target_name # bug # @property def bug(self): return Bug(self.__service, self.lp_bug_task.bug.id) # status # @property def status(self): return self.lp_bug_task.status @status.setter def status(self, value): if value == 'Incomplete' and self.lp_bug_task.status == 'Incomplete': self.lp_bug_task.status = 'New' self.lp_bug_task.lp_save() self.lp_bug_task.status = value self.lp_bug_task.lp_save() return # title # @property def title(self): if self.__title is None: self.__title = self.lp_bug_task.title return self.__title # is_complete # @property def is_complete(self): if self.__is_complete is None: self.__is_complete = self.lp_bug_task.is_complete return self.__is_complete # is_complete_upstream # @property def is_complete_upstream(self): """Returns true if bug has been fixed in the official upstream bug tracker for this package's upstream product, false if it's still open, and None if the status is indeterminate.""" bug_tracker_url = None try: up = bug.target.upstream_product bug_tracker_url = up.bug_tracker.base_url except: return None for sibling_task in self.lp_bug_task.related_tasks: if sibling_task.bug_watch is None: continue bt = sibling_task.bug_watch.bug_tracker if bt is not None: if bt.base_url == bug_tracker_url: if sibling_task.is_complete: return True else: return False return None # importance # @property def importance(self): if self.__importance is None: self.__importance = self.lp_bug_task.importance return self.__importance @importance.setter def importance(self, value): self.lp_bug_task.importance = value self.lp_bug_task.lp_save() return # assignee # @property def assignee(self): if self.__assignee is None: lp_assignee = self.lp_bug_task.assignee if lp_assignee is not None: self.__assignee = Person(None, lp_assignee) return self.__assignee @assignee.setter def assignee(self, value): self.lp_bug_task.assignee = value self.lp_bug_task.lp_save() return @property def assignee_name(self): if self.assignee is not None: return o2str(self.assignee.username) else: return None # date_assigned # @property def date_assigned(self): if self.__date_assigned is None: self.__date_assigned = self.lp_bug_task.date_assigned return self.__date_assigned # date_closed # @property def date_closed(self): if self.__date_closed is None: self.__date_closed = self.lp_bug_task.date_closed return self.__date_closed # date_confirmed # @property def date_confirmed(self): if self.__date_confirmed is None: self.__date_confirmed = self.lp_bug_task.date_confirmed return self.__date_confirmed # date_created # @property def date_created(self): if self.__date_created is None: self.__date_created = self.lp_bug_task.date_created return self.__date_created # date_fix_committed # @property def date_fix_committed(self): if self.__date_fix_committed is None: self.__date_fix_committed = self.lp_bug_task.date_fix_committed return self.__date_fix_committed # date_fix_released # @property def date_fix_released(self): if self.__date_fix_released is None: self.__date_fix_released = self.lp_bug_task.date_fix_released return self.__date_fix_released # date_in_progress # @property def date_in_progress(self): if self.__date_in_progress is None: self.__date_in_progress = self.lp_bug_task.date_in_progress return self.__date_in_progress # date_incomplete # @property def date_incomplete(self): if self.__date_incomplete is None: self.__date_incomplete = self.lp_bug_task.date_incomplete return self.__date_incomplete # date_left_closed # @property def date_left_closed(self): if self.__date_left_closed is None: self.__date_left_closed = self.lp_bug_task.date_left_closed return self.__date_left_closed # date_left_new # @property def date_left_new(self): if self.__date_left_new is None: self.__date_left_new = self.lp_bug_task.date_left_new return self.__date_left_new # date_triaged # @property def date_triaged(self): if self.__date_triaged is None: self.__date_triaged = self.lp_bug_task.date_triaged return self.__date_triaged # determine the date the importance was set using bug activity # @property def date_importance_set(self): if (self.__date_importance_set is None and self.importance not in ['Undecided', 'Unknown']): target = self.bug_target_name # continue checking the activity log in case the task went # High -> Medium -> High for activity in self.bug.activity: if (activity.new_value == self.importance and activity.what_changed.split(':')[0] in target): self.__date_importance_set = activity.date_changed return self.__date_importance_set # milestone # @property def milestone(self): if self.__milestone is None: milestone = self.lp_bug_task.milestone if milestone is not None: self.__milestone = Milestone(self.__service, milestone) return self.__milestone # product # @property def upstream_product(self): if self.__upstream_product is None: target = self.lp_bug_task.target if target is not None: self.__upstream_product = target.upstream_product return self.__upstream_product # bugtracker # @property def upstream_bug_tracker(self): if self.__upstream_bug_tracker is None: if self.upstream_product is None: return None self.__upstream_bug_tracker = self.upstream_product.bug_tracker if self.__upstream_bug_tracker is None: # If project is in a project group, the group may have bt info project_group = self.upstream_product.project_group if project_group: self.__upstream_bug_tracker = project_group.bug_tracker return self.__upstream_bug_tracker # related_tasks # @property def related_tasks(self): # The following import is done here to work around a circular import # issue. bug_tasks imports bug. # from bug_tasks import BugTasks return BugTasks(self.__service, self.lp_bug_task.related_tasks_collection) # target # @property def target(self): if self.__target is None: self.__target = self.lp_bug_task.target return self.__target @target.setter def target(self, value): self.lp_bug_task.target = value self.lp_bug_task.lp_save() # bug_watch # @property def bug_watch(self): if self.__bug_watch is None: self.__bug_watch = self.lp_bug_task.bug_watch return self.__bug_watch # similar_bugs # @property def similar_bugs(self): if self.__similar_bugs is None: self.__similar_bugs = self.lp_bug_task.findSimilarBugs() return Bugs(self.__service, self.__similar_bugs) # web_link # @property def web_link(self): if self.__web_link is None: self.__web_link = self.lp_bug_task.web_link return self.__web_link # return a datetime.timedelta of the length a time the task has been in # this status # def status_age(self): now = datetime.datetime.now() status = self.status if status in ['Invalid', 'Expired', "Won't Fix"]: try: self.__status_age = now - self.date_closed.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None # if the task has been reopened use date_left_closed and ignore other # date_states elif self.date_left_closed: try: self.__status_age = now - self.date_left_closed.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None # only if it has not left the new state should we use date_created elif status == 'New' and self.date_left_new is None: try: self.__status_age = now - self.date_created.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'Incomplete': try: self.__status_age = now - self.date_incomplete.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'Confirmed': try: self.__status_age = now - self.date_confirmed.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'Triaged': try: self.__status_age = now - self.date_triaged.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'In Progress': try: self.__status_age = now - self.date_in_progress.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'Fix Committed': try: self.__status_age = now - self.date_fix_committed.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None elif status == 'Fix Released': try: self.__status_age = now - self.date_fix_released.replace(tzinfo=None) except AttributeError as error: if 'NoneType' in error.message: return None return self.__status_age # to_dict # def to_dict(self): package = self.bug_target_display_name source_pkg = package.replace(" (Ubuntu)", "") return { 'date_created' : o2str(self.date_created), 'date_closed' : o2str(self.date_closed), 'date_fix_released': o2str(self.date_fix_released), 'is_complete' : self.is_complete, 'assignee' : self.assignee_name, 'target' : source_pkg, 'status' : o2str(self.status), 'importance' : o2str(self.importance), } # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/attachments.py0000664000000000017510000002317211753031405013131 0ustar #!/usr/bin/python import gzip import os import shutil import tarfile from copy import copy from locale import getpreferredencoding from zipfile import ZipFile from attachment import Attachment from fnmatch import fnmatch # Attachments # # A collection class for files added into launchpad, also known # as an attachment. # class LocalAttachment(object): class Data: class Fd(file): @property def content_type(self): return None @property def len(self): stat = os.stat(self.name) return stat.st_size def set_path(self, path): self.__path = path def open(self): if self.__path: return LocalAttachment.Data.Fd(self.__path) def __init__(self): self.data = LocalAttachment.Data() class Attachments(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, tkbug): self.__tkbug = tkbug self.__commit_changes = tkbug.commit_changes self.__attachments = None self.__filters = [] self.__download = False self.__download_dir = None self.__extract = False self.__extract_dir = None self.__extract_limit = None self.__gzip = False self.__gzip_dir = None self.__force_mimetype = False # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): return list(self.__iter__())[key] # __iter__ # def __iter__(self): self.__fetch_if_needed() for attachment in self.__attachments: if self.__gzip: attachment = self.__gzip_if_needed(attachment) included = True a = Attachment(self.__tkbug, attachment, self.__force_mimetype) for f, params in self.__filters: if not f(a, params): included = False if included: if self.__extract and \ a.is_archive_type('tar') and \ not self.__exceeds_tar_limit(a.remotefd): for member in self.__get_tar_members(a): yield member elif self.__extract and \ a.is_archive_type('zip') and \ not self.__exceeds_zip_limit(a.remotefd): for member in self.__get_zip_members(a): yield member else: if self.__download and \ not isinstance(attachment, LocalAttachment): tmpfile = os.path.join(self.__download_dir, a.title) with open(tmpfile, 'w+b') as localfd: localfd.write(a.content) yield a # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__attachments == None: self.__attachments = self.__tkbug.lpbug.attachments_collection def __gzip_if_needed(self, attachment): filters = dict(self.__filters) if not filters.has_key(filter_size_between): return attachment minsize, maxsize = filters[filter_size_between] remotefd = attachment.data.open() if remotefd.len > maxsize: gzip_attachment = LocalAttachment() gzip_attachment.title = attachment.title + '.gz' gzip_attachment.type = attachment.type tmpfile = os.path.join(self.__gzip_dir, gzip_attachment.title) gzipfd = gzip.open(tmpfile, 'w+b') gzipfd.write(remotefd.read()) gzipfd.close() gzip_attachment.data.set_path(tmpfile) attachment = gzip_attachment remotefd.close() return attachment def __find_mime_type(self, attachment): for pattern, mimetype in self.MIMETYPES.iteritems(): if fnmatch(attachment.title, pattern): return mimetype def __get_tar_members(self, tarattachment): tar = tarfile.open(fileobj=tarattachment.remotefd) attachments = [] for member in tar: if member.isfile(): attachment = LocalAttachment() attachment.title = os.path.basename(member.name) if (fnmatch(attachment.title, '*.diff') or fnmatch(attachment.title, '*.patch')): attachment.type = 'Patch' else: attachment.type = None tar.extract(member, self.__extract_dir) oldpath = os.path.join(self.__extract_dir, member.name) newpath = os.path.join(self.__extract_dir, os.path.basename(member.name)) shutil.move(oldpath, newpath) attachment.data.set_path(newpath) attachments.append(Attachment(self.__tkbug, attachment, self.__force_mimetype)) return attachments def __exceeds_tar_limit(self, fd): if not self.__extract_limit: return False tar = tarfile.open(fileobj=fd) result = len(tar.getnames()) > self.__extract_limit fd.seek(0) return result def __get_zip_members(self, zipattachment): zip = ZipFile(file=zipattachment.remotefd) attachments = [] for member in [zip.open(name) for name in zip.namelist()]: if member.name[-1] == '/': continue attachment = LocalAttachment() attachment.title = os.path.basename(member.name) if (fnmatch(attachment.title, '*.diff') or fnmatch(attachment.title, '*.patch')): attachment.type = 'Patch' else: attachment.type = None zip.extract(member.name, self.__extract_dir) oldpath = os.path.join(self.__extract_dir, member.name) newpath = os.path.join(self.__extract_dir, os.path.basename(member.name)) shutil.move(oldpath, newpath) attachment.data.set_path(newpath) attachments.append(Attachment(self.__tkbug, attachment, self.__force_mimetype)) return attachments def __exceeds_zip_limit(self, fd): if not self.__extract_limit: return False zip = ZipFile(file=fd) result = len(zip.namelist()) > self.__extract_limit fd.seek(0) return result def download_in_dir(self, download_dir): self.__download = True self.__download_dir = download_dir def extract_archives(self, extract_dir, extract_limit=None): self.__extract = True self.__extract_dir = extract_dir self.__extract_limit = extract_limit def try_gzip(self, gzip_dir): self.__gzip = True self.__gzip_dir = gzip_dir def force_mimetype(self): self.__force_mimetype = True def add_filter(self, f, params): """ Add filter f to constrain the list of attachments. f is a function which takes as arguments an Attachment object, and a list of parameters specific to the given filter. """ self.__filters.append( (f, params) ) def check_required_files(self, glob_patterns): """ Check that collection includes required filenames Given a list of glob filename patterns, looks through the attachments to verify at least one attachment fulfils the required file pattern. Returns a list of globs that were not matched. Returns an empty list if all requirements were met. """ missing = [] for glob_pattern in glob_patterns: found = False for a in self.__iter__(): if fnmatch(a.filename, glob_pattern): found = True break if not found: missing.append(glob_pattern) return missing # Filters def filter_owned_by_person(attachment, persons): """ File owned by specific person(s) (e.g. original bug reporter) """ return bool(attachment.owner and attachment.owner in persons) def filter_filename_matches_globs(attachment, glob_patterns): """ Filename matches one of a set of glob patterns (e.g. Xorg.*.log) """ filename = attachment.title for glob_pattern in glob_patterns: if fnmatch(filename, glob_pattern): return False return True def filter_size_between(attachment, sizes): """ File size is within [min, max] bounds """ assert(len(sizes) == 2) min_size = sizes[0] max_size = sizes[1] return bool(min_size <= len(attachment) and len(attachment) <= max_size) def filter_age_between(attachment, ages_in_days): """ File was attached to bug between [min, max] days """ assert(len(ages_in_days) == 2) min_age = ages_in_days[0] max_age = ages_in_days[1] return bool(min_age <= attachment.age and attachment.age <= max_age) def filter_is_patch(attachment, is_patch): return attachment.is_patch() == is_patch # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/__init__.py0000664000000000017510000000020711753031405012347 0ustar from lpltk.LaunchpadService import LaunchpadService from lpltk.debug import dbg, err, die, StdOut, DebugStdOut, dump_launchpad_object lpltk/lpltk/bug.py0000664000000000017510000004143612001153017011365 0ustar #!/usr/bin/python import re import sys from utils import o2str from datetime import datetime from tags import BugTags from attachments import Attachments from person import Person from messages import Messages from nominations import Nominations from bug_activity import Activity def fix_time(t): if type(t) is unicode: t = t.split(".")[0] t = t.split("+")[0] return datetime.strptime(t, "%Y-%m-%dT%H:%M:%S") else: return t # Bug # # A class that provides a convenient interface to a Launchpad bug. # class Bug(object): # __init__ # # Initialize the Bug instance from a Launchpad bug. # def __init__(self, service, bug_number, commit_changes=True): self.service = service launchpad = service.launchpad self.lpbug = launchpad.bugs[bug_number] self.id = self.lpbug.id self.commit_changes = commit_changes # As defined in: https://wiki.canonical.com/UbuntuEngineering/DefectAnalysts/GravityHeuristics # self.tag_weights = { 'apport-bug' : 50, 'apport-package' : 100, 'apport-crash' : 100, 'apport-kerneloops' : 150, 'regression-release' : 200, 'regression-proposed': 250, 'regression-updates' : 300, 'iso-tracker' : 300 } # Cached copies so we don't go back to launchpad as often # self.__activity = None self.__attachments = None self.__date_created = None self.__date_last_message = None self.__date_last_updated = None self.__date_latest_patch_uploaded = None self.__description = None self.__duplicate_of = None self.__gravity = None self.__heat = None self.__milestone_found = None self.__nominations = None self.__owner = None self.__private = None self.__properties = None self.__releases = None self.__security_related = None self.__tags = None self.__title = None self.__users_affected_count = None self.__users_unaffected_count = None #-------------------------------------------------------------------------- # date_created / age # # (read-only) # @property def date_created(self): if self.__date_created is None: self.__date_created = fix_time(self.lpbug.date_created) return self.__date_created def age(self): ''' Age of bug in days ''' dlm = self.date_created now = dlm.now(dlm.tzinfo) return (now - dlm).days @property def milestone_found(self): if self.__milestone_found is None: d = self.service.launchpad.distributions['ubuntu'] milestone_found = None releases = self.releases for s in d.series: # Only consider the series if it is marked as affected by this bug if s.name not in releases: continue for lp_milestone in s.all_milestones: if lp_milestone.date_targeted is None: continue date_created = self.date_created.replace(tzinfo=None) if (lp_milestone.date_targeted < date_created and milestone_found is not None and lp_milestone.date_targeted > milestone_found.date_targeted): milestone_found = lp_milestone if milestone_found: self.__milestone_found = milestone_found.name return self.__milestone_found #-------------------------------------------------------------------------- # date_last_updated / age_last_updated # # (read-only) # @property def date_last_updated(self): if self.__date_last_updated is None: self.__date_last_updated = fix_time(self.lpbug.date_last_updated) return self.__date_last_updated def age_last_updated(self): ''' Age of last update to bug in days ''' dlm = self.date_last_updated now = dlm.now(dlm.tzinfo) return (now - dlm).days #-------------------------------------------------------------------------- # date_last_message / age_last_message # # (read-only) # @property def date_last_message(self): if self.__date_last_message is None: self.__date_last_message = fix_time(self.lpbug.date_last_message) return self.__date_last_message def age_last_message(self): ''' Age of last comment to bug in days ''' dlm = self.date_last_message now = dlm.now(dlm.tzinfo) return (now - dlm).days #-------------------------------------------------------------------------- # private # # (read-only) # @property def private(self): if self.__private is None: self.__private = self.lpbug.private return self.__private # private # # (read-only) # @private.setter def private(self, value): self.lpbug.private = value if self.commit_changes: self.lpbug.lp_save() self.__private = value return #-------------------------------------------------------------------------- # security_related # # (read-only) # @property def security_related(self): if self.__security_related is None: self.__security_related = self.lpbug.security_related return self.__security_related # security_related # # (read-only) # @security_related.setter def security_related(self, value): self.lpbug.security_related = value if self.commit_changes: self.lpbug.lp_save() self.__security_related = value return #-------------------------------------------------------------------------- # title # @property def title(self): '''A one-line summary of the problem being described by the bug.''' if self.__title is None: self.__title = o2str(self.lpbug.title) return self.__title @title.setter def title(self, value): if not isinstance(value, str): raise TypeError("Must be a string") self.lpbug.title = value if self.commit_changes: self.lpbug.lp_save() self.__title = value #-------------------------------------------------------------------------- # description # @property def description(self): '''As complete as possible description of the bug/issue being reported as a bug.''' if self.__description is None: self.__description = o2str(self.lpbug.description) return self.__description @description.setter def description(self, value): if not isinstance(value, str): raise TypeError("Must be a string") self.lpbug.description = value if self.commit_changes: self.lpbug.lp_save() self.__description = value #-------------------------------------------------------------------------- # tags # @property def tags(self): return BugTags(self) #-------------------------------------------------------------------------- # releases - List of Ubuntu releases tagged as being affected # @property def releases(self): if self.__releases is None: self.__releases = [] ubuntu_releases = [] d = self.service.launchpad.distributions['ubuntu'] for s in d.series: ubuntu_releases.append("%s" %(s.name)) for bug_tag in self.tags: tag = o2str(bug_tag) if tag in ubuntu_releases: self.__releases.append(tag) return self.__releases #-------------------------------------------------------------------------- # owner # @property def owner(self): if self.__owner is None: self.__owner = Person(self, self.lpbug.owner) return self.__owner @property def owner_name(self): if self.owner is not None: return o2str(self.owner.username) else: return None #-------------------------------------------------------------------------- # attachments # @property def attachments(self): return Attachments(self) #-------------------------------------------------------------------------- # properties # @property def properties(self): '''Returns dict of key: value pairs found in the bug description This parses the bug report description into a more programmatically digestable dictionary form. ''' if self.__properties is None: re_kvp = re.compile("^(\s*)([\.\-\w]+):\s*(.*)$") re_error = re.compile("^Error:\s*(.*)$") self.__properties = {} last_key = {'': 'bar'} for line in self.description.split("\n"): m = re_kvp.match(line) if not m: continue level = m.group(1) item = m.group(2) value = m.group(3) key = item if len(level) > 0: key = "%s.%s" %(last_key[''], item) last_key[level] = item m = re_error.match(value) if not m: self.__properties[key] = value return self.__properties #-------------------------------------------------------------------------- # messages # @property def messages(self): return Messages(self) @property def messages_count(self): return len(self.messages) @property def messages_past_month(self): '''The list of messages posted within the past 30 days''' messages = Messages(self) messages.set_filter_by_max_age(30) return messages #-------------------------------------------------------------------------- # tasks # @property def tasks(self): # The following import is done here to work around a circular import # issue. bug_tasks imports bug. # from bug_tasks import BugTasks return BugTasks(self.service, self.lpbug.bug_tasks_collection) #-------------------------------------------------------------------------- # add_comment # def add_comment(self, content, subject=None, avoid_dupes=False): '''Add a new comment to an existing bug. This is the equivalent of newMessage. If no subject is provided, it will craft one using the bug title. If avoid_dupes is set, the routine will check to see if this comment has already been posted, to avoid accidentally spamming; the routine will return False in this case. ''' if avoid_dupes: # TODO: Actually only need to consider the last ~40 messages for m in self.lpbug.messages: if m.content == content: return False self.lpbug.newMessage(content=content, subject=subject) return True #-------------------------------------------------------------------------- # nominations # @property def nominations(self): return Nominations(self.service, self) # add a nomination # def add_nomination(self, series): self.lpbug.addNomination(target=series) #-------------------------------------------------------------------------- # activity # @property def activity(self): return Activity(self.service, self) # duplicate_of # @property def duplicate_of(self): if self.__duplicate_of is None: self.__duplicate_of = self.lpbug.duplicate_of return self.__duplicate_of # duplicates_count @property def duplicates_count(self): return self.lpbug.number_of_duplicates # subscriptions_count @property def subscriptions_count(self): return len(list(self.lpbug.subscriptions)) # heat # @property def heat(self): if self.__heat is None: self.__heat = self.lpbug.heat return self.__heat # date_latest_patch_uploaded # @property def date_latest_patch_uploaded(self): if self.__date_latest_patch_uploaded is None: self.__date_latest_patch_uploaded = self.lpbug.latest_patch_uploaded return self.__date_latest_patch_uploaded # age_last_patch # @property def age_latest_patch(self): '''Number of days since patch was uploaded''' t = self.date_created now = t.now(t.tzinfo) return 0 + (now - t).days # has_patch # def has_patch(self): if self.date_latest_patch_uploaded is None: return False return True # is_expirable # def is_expirable(self, days_old=None): return self.lpbug.isExpirable(days_old=days_old) #-------------------------------------------------------------------------- # users (un)affected # @property def users_affected_count(self): if self.__users_affected_count is None: self.__users_affected_count = self.lpbug.users_affected_count return self.__users_affected_count @property def users_unaffected_count(self): if self.__users_unaffected_count is None: self.__users_unaffected_count = self.lpbug.users_unaffected_count return self.__users_unaffected_count #-------------------------------------------------------------------------- # gravity # def gravity(self): ''' An implementation of the "gravity" value as defined at: https://wiki.canonical.com/UbuntuEngineering/DefectAnalysts/GravityHeuristics ''' if self.__gravity is None: self.__gravity = (6 * self.duplicates_count + 4 * self.subscriptions_count + 2 * self.users_affected_count) # Add the weights associated with certain tags. # for tag in self.tags: try: self.__gravity += self.tag_weights[tag] except KeyError: pass # Just ignore any tags on the bug that we don't have a weight for return self.__gravity def to_dict(self, quick=False): '''Converts the bug to a serializable dict. Specify quick=True to skip data which can be more expensive to look up. Execution should be about twice as fast. ''' data = { 'id': self.id, 'title': self.title, 'duplicate_of': self.duplicate_of, 'date_created': o2str(self.date_created), 'age': self.age(), 'date_last_updated': o2str(self.date_last_updated), 'age_last_updated': self.age_last_updated(), 'date_last_message': o2str(self.date_last_message), 'age_last_message': self.age_last_message(), 'date_latest_patch_uploaded': o2str(self.date_latest_patch_uploaded), 'gravity': self.gravity(), 'heat': self.heat, 'tags': o2str(list(self.tags)), 'description': self.description, 'properties': self.properties, } if not quick: # These items are a bit more heavy; omit them for better performance details = { # Costs about 20% 'has_patch': self.has_patch(), 'private': self.private, 'security_related': self.security_related, 'is_expirable': self.is_expirable(), # Cost about 20% 'messages_count': self.messages_count, 'duplicates_count': self.duplicates_count, 'subscriptions_count': self.subscriptions_count, 'users_affected_count': self.users_affected_count, 'users_unaffected_count': self.users_unaffected_count, # Costs 15% 'owner_name': self.owner_name, # Costs 80%! 'releases': self.releases, # Needs measured 'milestone_found': self.milestone_found, } data.update(details) return data # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/milestones.py0000664000000000017510000000161311753031405012774 0ustar #!/usr/bin/python from milestone import Milestone from utils import typecheck_Collection class Milestones(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, lp_milestones): self.__service = service self.__lp_milestones = typecheck_Collection(lp_milestones) # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): return Milestone(self.__service, self.__lp_milestones[key]) # __iter__ # def __iter__(self): for milestone in self.__lp_milestones: d = Milestone(self.__service, milestone) yield d # __contains__ # def __contains__(self, item): return item in self.__iter__() # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/tags.py0000664000000000017510000000400611753031405011547 0ustar #!/usr/bin/python class BugTags(object): # __init__ # def __init__(self, tkbug): self.__tkbug = tkbug self.__tags = None self.__commit_changes = tkbug.commit_changes # __len__ # def __len__(self): self.__fetch_if_needed() return len(self.__tags) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return self.__tags[key] # __setitem__ # def __setitem__(self, key, value): self.__fetch_if_needed() self.__tags[key] = value self.__save_tags() # __delitem__ # def __delitem__(self, key): self.__fetch_if_needed() del self.__tags[key] self.__save_tags() # __iter__ # def __iter__(self): self.__fetch_if_needed() for tag in self.__tags: yield tag # __contains__ # def __contains__(self, item): self.__fetch_if_needed() return item in self.__tags # __save_tags # def __save_tags(self): if self.__commit_changes: self.__tkbug.lpbug.tags = self.__tags self.__tkbug.lpbug.lp_save() # __fetch_if_needed # def __fetch_if_needed(self): if self.__tags == None: self.__tags = self.__tkbug.lpbug.tags # append # def append(self, item): self.__fetch_if_needed() if not isinstance(item, str): raise TypeError("Must be a string") self.__tags.append(item) self.__save_tags() # extend # def extend(self, items): self.__fetch_if_needed() if not isinstance(items, list): raise TypeError("Must be a list") self.__tags.extend(items) self.__save_tags() # remove # def remove(self, item): self.__fetch_if_needed() if not isinstance(item, str): raise TypeError("Must be a string") self.__tags.remove(item) self.__save_tags() # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/debug.py0000664000000000017510000000245311776670425011723 0ustar # Utility routines and classes for debugging info # (C) Copyright 2009, Markus Korn # (C) Copyright 2009, Bryce Harrington import re, os, sys def dbg(msg): if "LPLTK_DEBUG" in os.environ: sys.stderr.write("%s\n" %(msg)) def err(msg): sys.stderr.write("Error: %s\n" %(msg)) def die(msg): sys.stderr.write("Fatal: %s\n" %(msg)) sys.exit(1) StdOut = sys.stdout class DebugStdOut(object): ''' Debug version of STDOUT to redact out the oauth credentials so that if the debug output is posted to a bug, it will not include this sensitive info. ''' RE_OAUTH_TOKEN = re.compile(r"oauth_token=\"([^\"]{2})[^\"]*") RE_OAUTH_SIGNATURE = re.compile(r"oauth_signature=\"([^\"]{3})[^\"]*") def write(self, txt): txt = DebugStdOut.RE_OAUTH_SIGNATURE.sub(r"""oauth_signature="\1YYYYYYY""", txt) txt = DebugStdOut.RE_OAUTH_TOKEN.sub(r"""oauth_token="\1XXXXXXXX""", txt) StdOut.write(txt) def __getattr__(self, name): return getattr(StdOut, name) def dump_launchpad_object(i): print(repr(i)) print(" attr: ", sorted(i.lp_attributes)) print(" ops: ", sorted(i.lp_operations)) print(" coll: ", sorted(i.lp_collections)) print(" entr: ", sorted(i.lp_entries)) print("\n") lpltk/lpltk/project_series.py0000664000000000017510000000443011753031405013632 0ustar #!/usr/bin/python from person import Person from utils import (o2str, typecheck_Entry) class ProjectSeries(object): # __init__ # def __init__(self, service, bug, lp_project_series): self.__service = service self.__bug = bug self.__lp_project_series = typecheck_Entry(lp_project_series) self.__date_created = None self.__owner = None self.__status = None self.__driver = None self.__active = None self.__display_name = None self.__name = None self.__summary = None self.__title = None @property def date_created(self): if self.__date_created == None: self.__date_created = self.__lp_project_series.date_created return self.__date_created @property def owner(self): if self.__owner == None: self.__owner = Person(self.__bug, self.__lp_project_series.owner) return self.__owner @property def driver(self): if self.__driver == None: self.__driver = Person(self.__bug, self.__lp_project_series.driver) return self.__driver @property def status(self): if self.__status == None: self.__status = self.__lp_project_series.status return self.__status @property def active(self): if self.__active == None: self.__active = self.__lp_project_series.active return self.__active @property def display_name(self): if self.__display_name == None: self.__display_name = o2str(self.__lp_project_series.displayname) return self.__display_name @property def name(self): if self.__name == None: self.__name = o2str(self.__lp_project_series.name) return self.__name @property def summary(self): if self.__summary == None: self.__summary = o2str(self.__lp_project_series.summary) return self.__summary @property def title(self): if self.__title == None: self.__title = o2str(self.__lp_project_series.title) return self.__title # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/utils.py0000664000000000017510000000357311753031405011761 0ustar #!/usr/bin/python import os.path from decimal import Decimal from lazr.restfulclient.resource import ( Collection, Entry, ) # o2str # # Convert a unicode, decimal.Decimal, or datetime object to a str. # def o2str(obj): retval = None if type(obj) == str: return obj elif type(obj) == unicode: return obj.encode('ascii', 'ignore') elif type(obj) == Decimal: return str(obj) elif type(obj) == list: new_list = [] for item in obj: new_list.append(o2str(item)) return new_list elif str(type(obj)) == "": return obj.ctime() else: #print str(type(obj)) return obj # typecheck_Collection # # Raises TypeError if the given object is not a Launchpad 'Collection' object # def typecheck_Collection(obj): if type(obj) != Collection: raise TypeError, "Object is of type %s, but must be Launchpad Collection object" %(type(obj)) return obj # typecheck_Entry # # Raises TypeError if the given object is not a Launchpad 'Entry' object # def typecheck_Entry(obj): if type(obj) != Entry: raise TypeError, "Object is of type %s, but must be Launchpad Entry object" %(type(obj)) return obj # vi:set ts=4 sw=4 expandtab: # load_file # # Returns the contents of a text file # def load_file(filename): f = open(filename, 'r') text = f.read() f.close() return text # write_file # # Writes text into a given file path, creating path if needed # def write_file(filename, text): path = os.path.dirname(filename) if not os.path.isdir(path): os.makedirs(path) f = open(filename, 'w') f.write(text) f.close() return # file_age # # Returns age of file in seconds # def file_age(filename): from time import time file_time = os.stat(filename).st_mtime return time() - file_time lpltk/lpltk/milestone.py0000664000000000017510000000216411753031405012613 0ustar #!/usr/bin/python from utils import ( o2str, typecheck_Entry ) # Milestone # # A class that provides a convenient interface to a Launchpad milestone. # class Milestone(object): # __init__ # # Initialize the Bug instance from a Launchpad bug. # def __init__(self, service, lp_milestone, commit_changes=True): self.__service = service self.__lp_milestone = typecheck_Entry(lp_milestone) self.__commit_changes = commit_changes @property def code_name(self): return o2str(self.__lp_milestone.code_name) @property def date_targeted(self): return self.__lp_milestone.date_targeted @property def is_active(self): return self.__lp_milestone.is_active @property def name(self): return o2str(self.__lp_milestone.name) @property def summary(self): return o2str(self.__lp_milestone.summary) @property def title(self): return o2str(self.__lp_milestone.title) @property def raw(self): return self.__lp_milestone # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/bug_watch.py0000664000000000017510000000630112015222454012552 0ustar #!/usr/bin/python class BugWatch(object): def __init__(self, service, lp_bug_watch): self.__service = service self.lp_bug_watch = typecheck_Entry(lp_bug_watch) self.__bug = None self.__bug_tasks = None self.__date_created = None self.__date_last_changed = None self.__date_last_checked = None self.__date_next_checked = None self.__last_error_type = None self.__owner = None self.__remote_bug = None self.__remote_importance = None self.__remote_status = None self.__title = None self.__url = None def __str__(self): return "%s %s" %(self.__remote_bug, self.__title) @property def bug(self): if self.__bug is None: lp_bug = self.lp_bug_watch.bug if lp_bug is not None: self.__bug = Bug(self.__service, lp_bug.id) return self.__bug @property def bug_tasks(self): if self.__bug_tasks is None: lp_bug_tasks = self.lp_bug_watch.bug_tasks self.__bug_tasks = BugTasks(self.__services, lp_bug_tasks) return self.__bug_tasks @property def date_created(self): if self.__date_created is None: self.__date_created = self.lp_bug_watch.date_created return self.__date_created @property def date_last_changed(self): if self.__date_last_changed is None: self.__date_last_changed = self.lp_bug_watch.date_last_changed return self.__date_last_changed @property def date_last_checked(self): if self.__date_last_checked is None: self.__date_last_checked = self.lp_bug_watch.date_last_checked return self.__date_last_checked @property def date_next_checked(self): if self.__date_next_checked is None: self.__date_next_checked = self.lp_bug_watch.date_next_checked return self.__date_next_checked @property def last_error_type(self): if self.__last_error_type is None: self.__last_error_type = self.lp_bug_watch.last_error_type return self.__last_error_type @property def owner(self): if self.__owner is None: lp_owner = self.lp_bug_watch.owner if lp_owner is not None: self.__owner = Person(None, lp_owner) return self.__owner @property def remote_bug(self): if self.__remote_bug is None: self.__remote_bug = self.lp_bug_watch.remote_bug return self.__remote_bug @property def remote_importance(self): if self.__remote_importance is None: self.__remote_importance = self.lp_bug_watch.remote_importance return self.__remote_importance @property def remote_status(self): if self.__remote_status is None: self.__remote_status = self.lp_bug_watch.remote_status return self.__remote_status @property def title(self): if self.__title is None: self.__title = self.lp_bug_watch.title return self.__title @property def url(self): if self.__url is None: self.__url = self.lp_bug_watch.url return self.__url lpltk/lpltk/bugs.py0000664000000000017510000000175411753031405011560 0ustar #!/usr/bin/python from bug import Bug from utils import typecheck_Collection class Bugs(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, lp_bugs): self.__service = service self.__bugs = typecheck_Collection(lp_bugs) # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return Bug(self.__service, self.__bugs[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for bug in self.__bugs: b = Bug(self.__service, bug) yield b # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__bugs == None: self.__bugs = self.__service.launchpad.bugs # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/distributions.py0000664000000000017510000000202111753031405013506 0ustar #!/usr/bin/python from distribution import Distribution class Distributions(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service): self.__service = service self.__distributions = None # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return Distribution(self.__service, self.__distributions[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for distro in self.__distributions: d = Distribution(self.__service, distro) yield d # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__distributions == None: self.__distributions = self.__service.launchpad.distributions # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/tkentry.py0000775000000000017510000000534412001641456012322 0ustar #!/usr/bin/python def lp_to_tk(service, obj): '''Factory function to convert lp objects to corresponding lpltk classes''' # Look up the proper tk class to cast to if not hasattr(obj, 'resource_type_link'): return obj lpclass = obj.resource_type_link.split('#')[-1:][0] # Entry types if lpclass == 'team': from person import Person return Person(None, obj) elif lpclass == 'distro_series': from distro_series import DistroSeries return DistroSeries(service, None, obj) # Collection types elif lpclass == 'milestone-page-resource': from milestones import Milestones return Milestones(service, obj) elif lpclass == 'specification-page-resource': from specifications import Specifications return Specifications(service, obj) return None class TKEntry(object): def __init__(self, service, lp_entry): self.__dict__ = { '_service': service, '_lp_entry': lp_entry, } attrs = [] attrs.extend(self._lp_entry.lp_attributes) attrs.extend(self._lp_entry.lp_entries) attrs.extend(self._lp_entry.lp_collections) for key in attrs: if key[0] != '_': self.__dict__["_"+key] = None def __getattr__(self, attr_name): if attr_name[0] != '_': assert(self._lp_entry is not None) assert("_"+attr_name in self.__dict__.keys()) if self.__dict__["_"+attr_name] is None: obj = self._lp_entry.__getattr__(attr_name) self.__dict__["_"+attr_name] = lp_to_tk(self._service, obj) return self.__dict__["_"+attr_name] if __name__ == "__main__": from lpltk import LaunchpadService class Distribution(TKEntry): def __init__(self, service, lp_distribution): super(Distribution, self).__init__(service, lp_distribution) def my_function(self): '''This shows that the dynamically added parameters are available in any member function''' return "%s %s" %(self._display_name, self._current_series.name) lp = LaunchpadService(config={'read_only':True}) lp_distro = lp.launchpad.distributions['ubuntu'] obj = Distribution(lp, lp_distro) print "Name:", obj.display_name print "Owner:", obj.owner print "Driver:", obj.driver print "Registrant:", obj.registrant print "Current:", obj.current_series print "Date Created:", obj.date_created print "Active Milestones:", obj.active_milestones print "All Milestones:", obj.all_milestones print "All Specifications:", obj.all_specifications print "Valid Specifications:", obj.valid_specifications print "Calculated:", obj.my_function() lpltk/lpltk/nomination.py0000664000000000017510000000530211753031405012764 0ustar #!/usr/bin/python from person import Person from project_series import ProjectSeries from distro_series import DistroSeries from bug_target import BugTarget from utils import typecheck_Entry class Nomination(object): # __init__ # def __init__(self, service, bug, lp_nomination): self.__service = service self.__bug = bug self.__lp_nomination = typecheck_Entry(lp_nomination) self.__date_created = None self.__date_decided = None self.__status = None self.__distro_series = None self.__product_series = None self.__target = None self.__can_approve = None @property def date_created(self): if self.__date_created == None: self.__date_created = self.__lp_nomination.date_created return self.__date_created @property def date_decided(self): if self.__date_decided == None: self.__date_decided = self.__lp_nomination.date_decided return self.__date_decided @property def decider(self): if self.__decider == None: self.__decider = Person(self.__bug, self.__lp_nomination.decider) return self.__decider @property def owner(self): if self.__owner == None: self.__owner = Person(self.__bug, self.__lp_nomination.owner) return self.__owner @property def status(self): if self.__status == None: self.__status = self.__lp_nomination.status return self.__status @property def distro_series(self): if self.__distro_series == None: lp_ds = self.__lp_nomination.distroseries if lp_ds != None: self.__distro_series = DistroSeries(self.__service, self.__bug, lp_ds) return self.__distro_series @property def target(self): if self.__target == None: self.__target = BugTarget(self.__service, self.__bug, self.__lp_nomination.target) return self.__target @property def product_series(self): if self.__product_series == None: lp_ps = self.__lp_nomination.productseries if lp_ps != None: self.__product_series = ProjectSeries(self.__service, self.__bug, lp_ps) return self.__product_series def decline(self): self.__lp_nomination.decline() def approve(self): self.__lp_nomination.approve() def can_approve(self): if self.__can_approve == None: self.__can_approve = self.__lp_nomination.canApprove() return self.__can_approve # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/distribution.py0000664000000000017510000001205112055775131013336 0ustar #!/usr/bin/python from bug_tasks import BugTasks from distribution_source_package import DistributionSourcePackage from distro_series import DistroSeries from milestones import Milestones from person import Person from specification import Specification from utils import typecheck_Entry # Distribution # class Distribution(object): # __init__ # def __init__(self, service, lp_distribution): self.__service = service self.__lp_distribution = typecheck_Entry(lp_distribution) self.__owner = None self.__display_name = None self.__all_series = None self.__supported_series = None self.__current_series = None self.__stable_series = None self.__testing_series = None self.__all_milestones = None self.__active_milestones = None self.__primary_archive = None # __str__ # def __str__(self): return self.display_name # owner # @property def owner(self): if self.__owner == None: self.__owner = Person(None, self.__lp_distribution.owner) return self.__owner # display_name # @property def display_name(self): if self.__display_name == None: self.__display_name = self.__lp_distribution.display_name return self.__display_name # all_series # @property def all_series(self): '''Returns a list of all the series registered for this distro''' if self.__all_series == None: self.__all_series = [] for series in self.__lp_distribution.series: self.__all_series.append(DistroSeries(self.__service, None, series)) return self.__all_series # supported_series # @property def supported_series(self): '''Returns a list of series that are supported''' if self.__supported_series == None: self.__supported_series = [] for series in self.all_series: if series.status == 'Supported': self.__supported_series.append(series) return self.__supported_series # current_series # @property def current_series(self): '''The current series under development''' if self.__current_series == None: lp_series = self.__lp_distribution.current_series if lp_series is not None: self.__current_series = DistroSeries(self.__service, None, lp_series) return self.__current_series # stable_series # @property def stable_series(self): '''The latest stable release series''' if self.__stable_series is not None: return self.__stable_series for series in self.all_series: if series.status == "Current Stable Release": self.__stable_series = series return self.__stable_series @property def testing_series(self): '''The testing release series''' if self.__testing_series is not None: return self.__testing_series for series in self.all_series: if series.status == "Future": self.__testing_series = series return self.__testing_series # all_milestones # @property def all_milestones(self): if self.__all_milestones == None: self.__all_milestones = Milestones(self.__service, self.__lp_distribution.all_milestones) return self.__all_milestones # active_milestones # @property def active_milestones(self): if self.__active_milestones == None: self.__active_milestones = Milestones(self.__service, self.__lp_distribution.active_milestones) return self.__active_milestones # get_source_package # def get_source_package(self, source_pkg): source_package = self.__lp_distribution.getSourcePackage(name = source_pkg) if source_package is None: return None return DistributionSourcePackage(self.__service, source_package) # get_series # def get_series(self, string): return DistroSeries(self.__service, None, self.__lp_distribution.getSeries(name_or_version = string)) # searchTasks # def search_tasks(self, **params): #bt = BugTasks(self.__service, self.lp_project.searchTasks(**params)) bt = BugTasks(self.__service, self.__lp_distribution.searchTasks(**params)) return bt # get_specification - Blueprints # def get_specification(self, specification_name): specification = self.__lp_distribution.getSpecification(name = specification_name) if specification is None: return None return Specification(self.__service, specification) @property def primary_archive(self): if self.__primary_archive == None: self.__primary_archive = self.__lp_distribution.getArchive(name='primary') return self.__primary_archive # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/publication.py0000664000000000017510000000347212055775260013142 0ustar #!/usr/bin/python from utils import (typecheck_Entry, o2str) class SourcePackagePublication(object): def __init__(self, lp_publication): self.lp_publication = typecheck_Entry(lp_publication) self.__pocket = None self.__upload_status = None self.__sponsor = None self.__signer = None @property def pocket(self): if self.__pocket is None: self.__pocket = o2str(self.lp_publication.pocket) return self.__pocket @property def upload_status(self): if self.__upload_status is None: upl = self.lp_publication.packageupload if upl is None: self.__upload_status = '' else: self.__upload_status = o2str(upl.status) return self.__upload_status def to_dict(self): def _get_name(obj): if obj is None: return '' return o2str(obj.name) return { 'pocket': self.pocket, 'status': o2str(self.lp_publication.status), 'upload_status': self.upload_status, 'section': o2str(self.lp_publication.section_name), 'component': o2str(self.lp_publication.component_name), 'version': o2str(self.lp_publication.source_package_version), 'published': o2str(self.lp_publication.date_published), 'series': _get_name(self.lp_publication.distro_series), 'publisher': _get_name(self.lp_publication.creator), 'creator': _get_name(self.lp_publication.package_creator), 'signer': _get_name(self.lp_publication.package_signer), 'sponsor': _get_name(self.lp_publication.sponsor), } # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/specifications.py0000664000000000017510000000220311753031405013611 0ustar #!/usr/bin/python from specification import Specification from utils import typecheck_Collection class Specifications(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, lp_specifications): self.__service = service self.__specifications = typecheck_Collection(lp_specifications) # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return Specification(self.__service, self.__specifications[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for specification in self.__specifications: s = Specification(self.__service, specification) yield s # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__specifications == None: self.__specifications = self.__service.launchpad.specifications # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/bug_tasks.py0000664000000000017510000001376412001114127012574 0ustar #!/usr/bin/python import sys from datetime import timedelta, datetime from bug_task import BugTask from utils import ( typecheck_Collection, typecheck_Entry, ) class BugTasks(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, lp_tasks): self.__service = service self.__lp_tasks = typecheck_Collection(lp_tasks) self.__filter_distro = None self.__filter_status = None self.__filter_bug_tracker_type = None self.__filter_reopened = None self.__filter_status_length = None self.__filter_importance_length = None self.__filtered_tasks = None def filtered_tasks(self): if self.__filtered_tasks is None: self.__filtered_tasks = list(self.__iter__()) return self.__filtered_tasks # __len__ # def __len__(self): return len(self.filtered_tasks()) # __getitem__ # def __getitem__(self, key): tasks = self.filtered_tasks() return tasks[key] # __iter__ # def __iter__(self): for task in self.__lp_tasks: task = BugTask(self.__service, task) if self.__filter_distro: if self.__filter_distro not in task.bug_target_display_name: continue if self.__filter_status: status = self.__filter_status is_complete = task.is_complete if status == 'open' and is_complete: continue if status == 'complete' and not is_complete: continue if status not in ['open', 'complete'] and task.status != status: continue if self.__filter_bug_tracker_type: bt_type = self.__filter_bug_tracker_type upstream_product = task.target.upstream_product if upstream_product is None: continue bt = upstream_product.bug_tracker if bt is None: # Is this part of a project group? project_group = upstream_product.project_group if project_group is not None: bt = project_group.bug_tracker if bt is None: sys.stderr.write("No bug tracker found for upstream product\n") continue sys.stderr.write("Bug tracker type: %s (looking for %s)\n" %(bt.bug_tracker_type, bt_type)) if bt.bug_tracker_type != bt_type: continue if self.__filter_reopened: if not task.date_left_closed: continue if self.__filter_status_length: status_length = self.__filter_status_length # status_age will be None for bug tasks with out date_ info if task.status_age() is None: continue if status_length.startswith('>'): if task.status_age() <= timedelta(int(status_length.replace('>', ''))): continue elif status_length.startswith('<'): if task.status_age() >= timedelta(int(status_length.replace('<', ''))): continue elif status_length.startswith('='): if task.status_age() == timedelta(int(status_length.replace('=', ''))): continue if self.__filter_importance_length: importance_length = self.__filter_importance_length now = datetime.utcnow().replace(tzinfo=None) dis = task.date_importance_set if dis is None: continue else: dis = dis.replace(tzinfo=None) if importance_length.startswith('>'): if dis >= (now - timedelta(int(importance_length.replace('>', '')))): continue elif importance_length.startswith('<'): if dis <= (now - timedelta(int(importance_length.replace('<', '')))): continue elif importance_length.startswith('='): if dis == (now - timedelta(int(importance_length.replace('=', '')))): continue yield task # __contains__ # def __contains__(self, item): return item in self.__iter__() # Filters def set_filter_by_distro(self, distroname='Ubuntu'): '''Only include tasks that are targeted to the given distro''' self.__filter_distro = distroname self.__filtered_tasks = None def set_filter_by_status(self, state): '''Include tasks only in the specified state The regular bug statuses "Incomplete", "Fix Committed", et al are supported, as well as special keywords 'open' and 'complete'. ''' self.__filter_status = state self.__filtered_tasks = None def set_filter_by_bug_tracker_type(self, bugtracker_type): '''Include only tasks for upstream projects using this type of bug tracker''' self.__filter_bug_tracker_type = bugtracker_type self.__filtered_tasks = None def set_filter_reopened(self): '''Include only tasks that have been set to an open state from a closed one''' self.__filter_reopened = True self.__filtered_tasks = None def set_filter_status_length(self, status_length): '''Include only tasks that have been in their state <>= status_length in days''' self.__filter_status_length = status_length self.__filtered_tasks = None def set_filter_importance_length(self, importance_length): '''Include only tasks that have had their importance <>= importance_length in days''' self.__filter_importance_length = importance_length self.__filtered_tasks = None # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/tkcollection.py0000775000000000017510000000370712002340545013311 0ustar #!/usr/bin/python from utils import typecheck_Collection class TKCollection(object): def __init__(self, tk_type, service, lp_objects=None): '''Initialize the instance from a Launchpad bug.''' self._tk_type = tk_type self._service = service self._lp_objects = None if lp_objects: self._lp_objects = typecheck_Collection(lp_objects) def __len__(self): return len(list(self.__iter__())) def __getitem__(self, key): self._fetch_if_needed() return self._tk_type(self._service, self._lp_objects[key]) def __iter__(self): self._fetch_if_needed() for obj in self._lp_objects: o = self._get(obj) yield o def __contains__(self, item): return item in self.__iter__() def _fetch_if_needed(self): if self._lp_objects == None: self._lp_objects = self._fetch_all() assert(self._lp_objects is not None) def _get(self, obj): return self._tk_type(self._service, obj) def _fetch_all(self): '''Override this routine in the subclass to retrieve the necessary data from launchpad and return it as a LPCollection of LP objects.''' import inspect raise TypeError('Abstract method `%s.%s` called' %( type(self).__name__, inspect.stack()[0][3])) if __name__ == "__main__": from lpltk import LaunchpadService from distribution import Distribution class MyDists(Collection): def __init__(self, lp): Collection.__init__(self, Distribution, lp) def _fetch_all(self): return self._service.launchpad.distributions lp = LaunchpadService(config={'read_only':True}) objects = MyDists(lp) for obj in objects: print obj.display_name, if obj.current_series: print obj.current_series.name else: print "" # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/attachment.py0000664000000000017510000001035711777436404012765 0ustar #!/usr/bin/python from message import Message from fnmatch import fnmatch from utils import ( o2str, typecheck_Entry ) # Attachment # class Attachment(object): TAR_ARCHIVE_TYPES = [ 'application/x-tar', ] TAR_ARCHIVE_EXTS = [ '*.tar.gz', '*.tgz', '*.tar.bz2', ] ZIP_ARCHIVE_TYPES = [ 'application/zip' ] ZIP_ARCHIVE_EXTS = [ '*.zip' ] MIMETYPES = { '*.txt' : 'text/plain', '*.log' : 'text/plain', } # __init__ # def __init__(self, tkbug, lpattachment, force_mimetype=False): self.__tkbug = tkbug self.__commit_changes = tkbug.commit_changes self.__attachment = typecheck_Entry(lpattachment) self.__force_mimetype = force_mimetype self.__title = None self.__type = None self.__message = None self.__data = None self.__remotefd = None self.__content = None self.__filename = None self.__url = None # __del__ # def __del__(self): if self.__remotefd: self.__remotefd.close() # __len__ # def __len__(self): return self.remotefd.len def __eq__(self, other): return self.__attachment == other.__attachment def __ne__(self, other): return self.__attachment != other.__attachment def __find_mimetype(self): for pattern, mimetype in self.MIMETYPES.iteritems(): if fnmatch(self.title, pattern): return mimetype # title # @property def title(self): if self.__title == None: self.__title = o2str(self.__attachment.title) return self.__title # kind # @property def kind(self): if self.__type == None: self.__type = self.__attachment.type return self.__type # url # @property def url(self): if self.__url == None: self.__url = "%s/+files/%s" %( self.__attachment.web_link, self.filename) return self.__url # content_type # @property def content_type(self): if self.__force_mimetype: return self.__find_mimetype() else: return self.remotefd.content_type # message # @property def message(self): if self.__message == None: self.__message = Message(self.__tkbug, self.__attachment.message) return self.__message # owner # @property def owner(self): if not self.message: return None return self.message.owner # age (in days) # @property def age(self): if not self.message: return None return self.message.age # data # @property def data(self): if self.__data == None: self.__data = self.__attachment.data return self.__data # remotefd # @property def remotefd(self): if self.__remotefd == None: self.__remotefd = self.data.open() return self.__remotefd # filename # @property def filename(self): if self.__filename == None: self.__filename = self.remotefd.filename return self.__filename # content # @property def content(self): if self.__content == None: self.__content = self.remotefd.read() return self.__content def is_patch(self): return bool(self.kind == 'Patch') def is_archive_type(self, type): type = type.upper() if not hasattr(self, '%s_ARCHIVE_TYPES' % type): return False archive_types = getattr(self, '%s_ARCHIVE_TYPES' % type) archive_exts = getattr(self, '%s_ARCHIVE_EXTS' % type) if self.remotefd.content_type in archive_types: return True for ext in archive_exts: if fnmatch(self.title, ext): return True return False def is_archive(self): return self.is_archive_type('tar') or self.is_archive_type('zip') # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/distro_series.py0000664000000000017510000001067112055762033013500 0ustar #!/usr/bin/python from milestones import Milestones from person import Person from utils import ( o2str, typecheck_Entry ) from publication import SourcePackagePublication class DistroSeries(object): # __init__ # def __init__(self, service, bug, lp_distro_series): self.__service = service self.__bug = bug self.__lp_archive = None self.__lp_distro_series = typecheck_Entry(lp_distro_series) self.__date_created = None self.__date_released = None self.__owner = None self.__status = None self.__driver = None self.__active = None self.__supported = None self.__description = None self.__display_name = None self.__full_series_name = None self.__name = None self.__summary = None self.__title = None self.__active_milestones = None @property def date_created(self): if self.__date_created == None: self.__date_created = self.__lp_distro_series.date_created return self.__date_created @property def date_released(self): if self.__date_released == None: self.__date_released = self.__lp_distro_series.datereleased return self.__date_released @property def owner(self): if self.__owner == None: self.__owner = Person(self.__bug, self.__lp_distro_series.owner) return self.__owner @property def driver(self): if self.__driver == None: self.__driver = Person(self.__bug, self.__lp_distro_series.driver) return self.__driver @property def status(self): if self.__status == None: self.__status = self.__lp_distro_series.status return self.__status @property def active(self): if self.__active == None: self.__active = self.__lp_distro_series.active return self.__active @property def supported(self): if self.__supported == None: self.__supported = self.__lp_distro_series.supported return self.__supported @property def description(self): if self.__description == None: self.__description = o2str(self.__lp_distro_series.description) return self.__description @property def display_name(self): if self.__display_name == None: self.__display_name = o2str(self.__lp_distro_series.displayname) return self.__display_name @property def full_series_name(self): if self.__full_series_name == None: self.__full_series_name = o2str(self.__lp_distro_series.fullseriesname) return self.__full_series_name @property def name(self): if self.__name == None: self.__name = o2str(self.__lp_distro_series.name) return self.__name @property def summary(self): if self.__summary == None: self.__summary = o2str(self.__lp_distro_series.summary) return self.__summary @property def title(self): if self.__title == None: self.__title = o2str(self.__lp_distro_series.title) return self.__title @property def active_milestones(self): if self.__active_milestones == None: self.__active_milestones = Milestones(self.__service, self.__lp_distro_series.active_milestones) return self.__active_milestones def get_source_publications(self, source_package_name): '''Returns all Release/Proposed/etc. publications for source package''' if self.__lp_archive == None: lp_distro = self.__lp_distro_series.distribution self.__lp_archive = lp_distro.getArchive(name='primary') try: # This returns the latest version for each pocket (Updates, Security, etc.) pubs = self.__lp_archive.getPublishedSources( source_name=source_package_name, exact_match=True, status='Published', distro_series=self.__lp_distro_series) for pub in pubs: yield SourcePackagePublication(pub) except IndexError: pass def get_package_uploads(self, series): pass # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/nominations.py0000664000000000017510000000210511753031405013145 0ustar #!/usr/bin/python from nomination import Nomination class Nominations(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, bug): self.__service = service self.__bug = bug self.__lp_nominations = None # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return Nomination(self.__service, self.__bug, self.__lp_nominations[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for nom in self.__lp_nominations: n = Nomination(self.__service, self.__bug, nom) yield n # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__lp_nominations == None: self.__lp_nominations = self.__bug.lpbug.getNominations() # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/LaunchpadService.py0000775000000000017510000002376612064737656014073 0ustar #!/usr/bin/python import os import sys import json import httplib2 import launchpadlib from xml.etree.ElementTree import ParseError from launchpadlib.launchpad import * from debug import * from bug import Bug from distributions import Distributions from projects import Projects from person import Person class LaunchpadServiceError(Exception): """LaunchpadServiceError An exception class that will be raised if there are any errors initializing a LaunchpadService instance. """ # __init__ # def __init__(self, error): self.msg = error class LaunchpadService: """ Manages connection to Launchpad services. """ def __init__(self, config=None): """Initialize the Arsenal instance. The user's configuration (if one exists) is loaded and incorporated into the standard options. Access to Launchpad is initialized. Configuration values to override can be passed in through config. For example:: lp = LaunchpadService(config={ 'launchpad_services_root': 'edge', 'read_only': True }) lp = LaunchpadService(config={ 'launchpad_client_name': 'my-lpltoolkit-project', 'launchpad_services_root': 'https://api.launchpad.dev' }) lp = LaunchpadService(config={ 'launchpad_version': '1.0', 'bot': True }) """ self.project = None # Setting LPLTK_DEBUG environment variable enables debug messages in this # class and in httplib2 as well. # if "LPLTK_DEBUG" in os.environ: httplib2.debuglevel = os.getenv("LPLTK_DEBUG", None) sys.stdout = DebugStdOut() # Default configuration parameters # self.config = {} self.config['launchpad_client_name'] = 'lpltk' self.config['launchpad_services_root'] = 'production' # 'staging' 'edge' 'production' self.config['project_name'] = '' self.config['bot'] = False self.config['read_only'] = False # FIXME: 'read_only' doesn't seem very descriptive here. self.config['launchpad_version'] = 'devel' # The configuration dictionary which is ~/.lpltkrc will override the # default configuration parameters. # self._load_user_config() # The config dictionary passed into this method override all config parameters. # if config != None: for k in config.keys(): self.config[k] = config[k] # So that we can use any change a user may have made via their # config file, we need to set the 'launchpad_cachedir' and # 'launchpad_creddir' after loading the user's config file. # if 'launchpad_cachedir' not in self.config: self.config['launchpad_cachedir'] = os.path.join(os.path.expanduser('~'), '.cache', self.config['launchpad_client_name']) if 'launchpad_creddir' not in self.config: self.config['launchpad_creddir'] = os.path.join(os.path.expanduser('~'), '.config', self.config['launchpad_client_name']) if self.config['launchpad_services_root'] == 'qastaging': self.config['launchpad_services_root'] = 'https://api.qastaging.launchpad.net' if self.config['launchpad_creddir']: filename_parts = [ 'credentials', self.config['launchpad_services_root'].replace('/','_') ] if self.config['bot']: filename_parts.insert(0, 'bot') self.config['launchpad_credentials_file'] = os.path.join( self.config['launchpad_creddir'], '-'.join(filename_parts)) if not os.path.exists(self.config['launchpad_creddir']): os.makedirs(self.config['launchpad_creddir'],0700) self.reset() return def reset(self): """ Re-establish access to Launchpad and reload the specific project if one is specified. """ dbg("Launchpadlib Version: %s" %(launchpadlib.__version__)) dbg("Login With: %s %s %s" %(self.config['launchpad_client_name'], self.config['launchpad_services_root'], self.config['launchpad_credentials_file'])) dbg("Read Only: %s" %(self.config['read_only'])) dbg("Bot: %s" %(self.config['bot'])) try: if self.config['read_only']: self.launchpad = Launchpad.login_anonymously( self.config['launchpad_client_name'], service_root=self.config['launchpad_services_root'], version=self.config['launchpad_version']) else: self.launchpad = Launchpad.login_with( self.config['launchpad_client_name'], service_root=self.config['launchpad_services_root'], launchpadlib_dir=self.config['launchpad_cachedir'], credentials_file=self.config['launchpad_credentials_file'], version=self.config['launchpad_version']) if self.config['project_name'] != '': self.load_project(self.config['project_name']) except ParseError as e: # This indcates possibly corrupted cache # TODO: Cleanup cache automatically? dbg("Error: Error parsing JSON from Launchpad. Cache corruption?") dbg(" + Try clearing lpltk cache?") dbg(e) except: # Eventually, it would be nice if this handled LP exceptions and gave more helpful # error messages. But for now it should just raise what it's given. # raise return # get_bug # def get_bug(self, bug_number): return Bug(self, bug_number) # get_launchpad_bug # def get_launchpad_bug(self, bug_number): """ Fetch a Launchpad bug object for a specific Launchpad bug id. """ return self.launchpad.bugs[bug_number] def load_project(self, project): """ Connect to a specific Launchpad project. """ try: self.project = self.launchpad.projects[project] except KeyError: raise LaunchpadServiceError("%s is not a recognized Launchpad project." % (project)) if self.project is None: try: self.project = self.launchpad.distributions[project] except KeyError: raise LaunchpadServiceError("%s is not a recognized Launchpad distribution." % (project)) self.config['project_name'] = project return self.project def get_person(self, name): """ Get a Person by the given username. """ try: person = Person(None, self.launchpad.people[name], service=self) except KeyError: raise LaunchpadServiceError("%s is not a person or team in Launchpad." % (name)) return person # Deprecated def person(self, name): """ Alias for get_person """ return self.get_person(name) def get_team_participants(self, team): """ Get all direct and indirect members of a Launchpad team. """ team = self.get_person(team) if team.lpperson.is_team is False: raise LaunchpadServiceError("%s is not a team so has no members." % (team)) people = [] for person in team.lpperson.participants: people.append(Person(None, person)) return people def get_team_members(self, team): """ Get only direct members of a Launchpad team. """ team = self.get_person(team) if team.lpperson.is_team is False: raise LaunchpadServiceError("%s is not a team so has no members." % (team)) people = [] for person in team.lpperson.members: people.append(Person(None, person)) return people def _load_user_config(self): """ Load configuration from ~/.lpltkrc If the users home directory contains a configuration file, load that in. The name of the configuration file is '.lpltkrc'. The format of the file is json. The json format should be an array. The contents of that array will be merged with the default one 'self.config' in this class. """ if 'configuration_file' in self.config: cfg_path = self.config['configuration_file'] else: cfg_path = os.path.join(os.path.expanduser('~'), ".lpltkrc") if os.path.exists(cfg_path): with open(cfg_path, 'r') as f: user_config = json.load(f) for k in user_config.keys(): self.config[k] = user_config[k] #-------------------------------------------------------------------------- # distributions # @property def distributions(self): return Distributions(self) #-------------------------------------------------------------------------- # projects # @property def projects(self): return Projects(self) #-------------------------------------------------------------------------- # create_bug # # Create a new, launchpad bug. # def create_bug(self, project, package, title, description, tags=[]): proj = self.projects[project] target = proj.self_link if package: target += "/+source/" + package lp_bug = self.launchpad.bugs.createBug(target=target, title=title, description=description, tags=[]) return self.get_bug(lp_bug.id) # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/distribution_source_package.py0000664000000000017510000000320312055761457016376 0ustar #!/usr/bin/python from milestone import Milestone from utils import typecheck_Entry # DistributionSourcePackage # class DistributionSourcePackage(object): # __init__ # def __init__(self, service, lp_distribution_source_package): self.__service = service self.lp_source_package = typecheck_Entry(lp_distribution_source_package) self.__display_name = None self.__name = None self.__title = None # display_name # @property def display_name(self): if self.__display_name == None: self.__display_name = self.lp_source_package.display_name return self.__display_name # name # @property def name(self): if self.__name == None: self.__name = self.lp_source_package.name return self.__name # title # @property def title(self): if self.__title == None: self.__title = self.lp_source_package.title return self.__title # searchTasks # def search_tasks(self, **params): from bug_tasks import BugTasks if 'milestone' in params and isinstance(params['milestone'], Milestone): params['milestone'] = params['milestone'].raw bt = BugTasks(self.__service, self.lp_source_package.searchTasks(**params)) return bt # ------------------------------------------------------------------------ # uri - The self_link for the distribution source package @property def uri(self): return self.lp_source_package.self_link # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/bug_activity.py0000664000000000017510000000523411753031405013306 0ustar #!/usr/bin/python from person import Person from utils import ( o2str, typecheck_Entry ) class Activity(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, service, bug): self.__service = service self.__bug = bug self.__lp_activities = None # __len__ # def __len__(self): return len(list(self.__iter__())) # __getitem__ # def __getitem__(self, key): self.__fetch_if_needed() return BugActivity(self.__service, self.__bug, self.__lp_activities[key]) # __iter__ # def __iter__(self): self.__fetch_if_needed() for activity in self.__lp_activities: n = BugActivity(self.__service, self.__bug, activity) yield n # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__lp_activities == None: self.__lp_activities = self.__bug.lpbug.activity class BugActivity(object): # __init__ # def __init__(self, service, bug, lp_bug_activity): self.__service = service self.__bug = bug self.__lp_bug_activity = typecheck_Entry(lp_bug_activity) self.__date_changed = None self.__person = None self.__old_value = None self.__new_value = None self.__message = None self.__what_changed = None @property def date_changed(self): if self.__date_changed == None: self.__date_changed = self.__lp_bug_activity.datechanged return self.__date_changed @property def person(self): if self.__person == None: self.__person = Person(self.__bug, self.__lp_bug_activity.person) return self.__person @property def old_value(self): if self.__old_value == None: self.__old_value = self.__lp_bug_activity.oldvalue return self.__old_value @property def new_value(self): if self.__new_value == None: self.__new_value = self.__lp_bug_activity.newvalue return self.__new_value @property def what_changed(self): if self.__what_changed == None: self.__what_changed = self.__lp_bug_activity.whatchanged return self.__what_changed @property def message(self): if self.__message == None: self.__message = o2str(self.__lp_bug_activity.message) return self.__message # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/person.py0000664000000000017510000001051512055556064012132 0ustar #!/usr/bin/python from utils import ( o2str, typecheck_Entry ) # Person # # A class that provides a convenient interface to a Launchpad person. # (as returned from a call to the bug.owner property) # class Person(object): # __init__ # # Initialize the Person instance from a Launchpad bug. # def __init__(self, tkbug, lpperson, service=None): if not tkbug is None: self.__commit_changes = tkbug.commit_changes self.__tkbug = tkbug self.__person = typecheck_Entry(lpperson) self.__service = service self.__username = None self.__full_name = None self.__karma = None self.__email_addresses = None def __eq__(self, other): if other is None: return self.__person is None return self.__person == other.__person def __ne__(self, other): if other is None: return not self.__person is None return self.__person != other.__person #-------------------------------------------------------------------------- # username # @property def username(self): if self.__username is None: self.__username = self.lpperson.name return self.__username #-------------------------------------------------------------------------- # display_name # @property def display_name(self): return self.full_name #-------------------------------------------------------------------------- # full_name # @property def full_name(self): if self.__full_name is None: self.__full_name = o2str(self.__person.display_name) return self.__full_name #-------------------------------------------------------------------------- # first_name # @property def first_name(self): if self.__full_name is None: self.__full_name = o2str(self.__person.display_name) return self.__full_name.split(' ')[0] #-------------------------------------------------------------------------- # email_addresses - list of confirmed email addresses, if not hidden # @property def email_addresses(self): if self.__person is None: return None if self.__email_addresses is None: self.__email_addresses = [] if not self.__person.hide_email_addresses: for email_obj in self.__person.confirmed_email_addresses: self.__email_addresses.append(email_obj.email) return self.__email_addresses #-------------------------------------------------------------------------- # karma # @property def karma(self): if self.__karma is None: self.__karma = self.__person.karma return self.__karma #-------------------------------------------------------------------------- # lpperson # @property def lpperson(self): return self.__person #-------------------------------------------------------------------------- # subscribed_packages - Returns a list of source packages the team is # subscribed to # @property def subscribed_packages(self): from distribution_source_package import DistributionSourcePackage for pkg in self.__person.getBugSubscriberPackages(): yield DistributionSourcePackage(self.__service, pkg) #-------------------------------------------------------------------------- # subscribed_package_names - Returns a list of strings corresponding to # source packages that the person (team) is subscribed to. # @property def subscribed_package_names(self): for pkg in self.subscribed_packages: yield pkg.display_name.split(' ')[0] #-------------------------------------------------------------------------- # teams - Returns a list of names of teams this person belongs to. # @property def super_teams(self): teams = [] for team in self.__person.super_teams: teams.append(o2str(team.name)) return teams # ------------------------------------------------------------------------ # uri - The self_link for the person @property def uri(self): return self.__person.self_link # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/bug_target.py0000664000000000017510000000101511753031405012731 0ustar #!/usr/bin/python from utils import typecheck_Entry class BugTarget(object): # __init__ # def __init__(self, service, bug, lp_bug_target): self.__service = service self.__bug = bug self.__lp_bug_target = typecheck_Entry(lp_bug_target) self.__name = None @property def name(self): if self.__name == None: self.__name = self.__lp_bug_target.name return self.__name # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/ppa.py0000644000000000017510000001055211776670760011414 0ustar # software-properties PPA support # # Copyright (c) 2004-2009 Canonical Ltd. # # Author: Michael Vogt # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA import apt_pkg import json import re import subprocess from threading import Thread import pycurl DEFAULT_KEYSERVER = "hkp://keyserver.ubuntu.com:80/" # maintained until 2015 LAUNCHPAD_PPA_API = 'https://launchpad.net/api/1.0/~%s/+archive/%s' # None means use pycurl default LAUNCHPAD_PPA_CERT = None def encode(s): return re.sub("[^a-zA-Z0-9_-]","_", s) def expand_ppa_line(abrev, distro_codename): """ Convert an abbreviated ppa name of the form 'ppa:$name' to a proper sources.list line of the form 'deb ...' """ # leave non-ppa: lines unchanged if not abrev.startswith("ppa:"): return (abrev, None) # FIXME: add support for dependency PPAs too (once we can get them # via some sort of API, see LP #385129) abrev = abrev.split(":")[1] ppa_owner = abrev.split("/")[0] try: ppa_name = abrev.split("/")[1] except IndexError, e: ppa_name = "ppa" sourceslistd = apt_pkg.config.find_dir("Dir::Etc::sourceparts") line = "deb http://ppa.launchpad.net/%s/%s/ubuntu %s main" % ( ppa_owner, ppa_name, distro_codename) filename = "%s/%s-%s-%s.list" % ( sourceslistd, encode(ppa_owner), encode(ppa_name), distro_codename) return (line, filename) class CurlCallback: def __init__(self): self.contents = '' def body_callback(self, buf): self.contents = self.contents + buf def get_ppa_info_from_lp(owner_name, ppa_name): lp_url = LAUNCHPAD_PPA_API % (owner_name, ppa_name) # we ask for a JSON structure from lp_page, we could use # simplejson, but the format is simple enough for the regexp callback = CurlCallback() curl = pycurl.Curl() curl.setopt(pycurl.SSL_VERIFYPEER, 1) curl.setopt(pycurl.SSL_VERIFYHOST, 2) curl.setopt(pycurl.WRITEFUNCTION, callback.body_callback) # only useful for testing if LAUNCHPAD_PPA_CERT: curl.setopt(pycurl.CAINFO, LAUNCHPAD_PPA_CERT) curl.setopt(pycurl.URL, str(lp_url)) curl.setopt(pycurl.HTTPHEADER, ["Accept: application/json"]) curl.perform() curl.close() lp_page = callback.contents return json.loads(lp_page) class AddPPASigningKeyThread(Thread): " thread class for adding the signing key in the background " def __init__(self, ppa_path, keyserver=None): Thread.__init__(self) self.ppa_path = ppa_path self.keyserver = (keyserver if keyserver is not None else DEFAULT_KEYSERVER) def run(self): self.add_ppa_signing_key(self.ppa_path) def add_ppa_signing_key(self, ppa_path): """Query and add the corresponding PPA signing key. The signing key fingerprint is obtained from the Launchpad PPA page, via a secure channel, so it can be trusted. """ owner_name, ppa_name, distro = ppa_path[1:].split('/') try: ppa_info = get_ppa_info_from_lp(owner_name, ppa_name) except pycurl.error as e: print("Error reading %s: %s" % (lp_url, e[1])) return False try: signing_key_fingerprint = ppa_info["signing_key_fingerprint"] except IndexError as e: print("Error: can't find signing_key_fingerprint at %s" % lp_url) return False res = subprocess.call( ["apt-key", "adv", "--keyserver", self.keyserver, "--recv", signing_key_fingerprint]) return (res == 0) if __name__ == "__main__": import sys owner_name, ppa_name = sys.argv[1].split(":")[1].split("/") print(get_ppa_info_from_lp(owner_name, ppa_name)) lpltk/lpltk/messages.py0000664000000000017510000000504312001116471012414 0ustar #!/usr/bin/python from message import Message class Messages(object): # __init__ # # Initialize the instance from a Launchpad bug. # def __init__(self, tkbug): self.__tkbug = tkbug self.__commit_changes = tkbug.commit_changes self.__messages = None self.__filter_since_status_change = False self.__filter_by_max_age = None self.__filtered_messages = None def filtered_messages(self): if self.__filtered_messages is None: self.__filtered_messages = list(self.__iter__()) return self.__filtered_messages # __len__ # def __len__(self): return len(self.filtered_messages()) # __getitem__ # def __getitem__(self, key): messages = self.filtered_messages() return messages[key] # __iter__ # def __iter__(self): self.__fetch_if_needed() for msg in self.__messages: m = Message(self.__tkbug, msg) if self.__filter_by_max_age: if m.age > self.__filter_by_max_age: continue elif self.__filter_since_status_change: if m.age > self.__tkbug.status_age: continue yield m # __contains__ # def __contains__(self, item): return item in self.__iter__() # __fetch_if_needed # def __fetch_if_needed(self): if self.__messages == None: self.__messages = self.__tkbug.lpbug.messages_collection def set_filter_by_max_age(self, days): '''Only include messages posted within the given number of days''' self.__filter_by_max_age = days self.__filtered_messages = None def set_filter_since_status_change(self): '''Only include messages created since the status was changed''' self.__filter_since_status_change = True self.__filtered_messages = None # non_owner_count # def non_owner_count(self): '''Number of messages by someone other than the original reporter''' count = 0 for message in self.__iter__(): if message.owner != self.__tkbug.owner: count += 1 return count # owners # @property def owner_usernames(self): '''List of People who have posted one or more of these Messages''' owners = {} for message in self.__iter__(): owners[message.owner.username] = 1 return owners.keys() # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/project.py0000664000000000017510000000254111753031405012261 0ustar #!/usr/bin/python from person import Person from bug_tasks import BugTasks from utils import typecheck_Entry # Project # class Project(object): # __init__ # def __init__(self, service, lp_project): self.__service = service self.lp_project = typecheck_Entry(lp_project) self.__owner = None self.__display_name = None self.__name = None # __str__ # def __str__(self): return self.display_name # owner # @property def owner(self): if self.__owner == None: self.__owner = Person(None, self.lp_project.owner) return self.__owner # name # @property def name(self): if self.__name == None: self.__name = self.lp_project.name return self.__name # display_name # @property def display_name(self): if self.__display_name == None: self.__display_name = self.lp_project.display_name return self.__display_name # searchTasks # def search_tasks(self, **params): bt = BugTasks(self.__service, self.lp_project.searchTasks(**params)) return bt # self_link # @property def self_link(self): return self.lp_project.self_link # vi:set ts=4 sw=4 expandtab: lpltk/lpltk/specification.py0000664000000000017510000001402611753031405013434 0ustar #!/usr/bin/python from person import Person from bugs import Bugs from utils import ( o2str, typecheck_Entry ) class Specification(object): # __init__ # def __init__(self, service, specification): self.__service = service self.__lp_specification = typecheck_Entry(specification) self.__owner = None self.__approver = None self.__drafter = None self.__assignee = None self.__starter = None self.__completer = None self.__date_created = None self.__date_started = None self.__date_completed = None self.__priority = None self.__definition_status = None self.__implementation_status = None self.__lifecycle_status = None self.__milestone = None self.__approved = None self.__complete = None self.__started = None self.__name = None self.__summary = None self.__title = None self.__whiteboard = None self.__url = None self.__bugs = None self.__dependencies = None # People @property def owner(self): if self.__owner == None: self.__owner = Person(self.__bug, self.__lp_specification.owner) return self.__owner @property def approver(self): if self.__approver == None: self.__approver = Person(self.__bug, self.__lp_specification.approver) return self.__approver @property def drafter(self): if self.__drafter == None: self.__drafter = Person(self.__bug, self.__lp_specification.drafter) return self.__drafter @property def assignee(self): if self.__assignee == None: self.__assignee = Person(self.__bug, self.__lp_specification.assignee) return self.__assignee @property def starter(self): if self.__starter == None: self.__starter = Person(self.__bug, self.__lp_specification.starter) return self.__starter @property def completer(self): if self.__completer == None: self.__completer = Person(self.__bug, self.__lp_specification.completer) return self.__completer # Dates @property def date_created(self): if self.__date_created == None: self.__date_created = self.__lp_specification.date_created return self.__date_created @property def date_started(self): if self.__date_started == None: self.__date_started = self.__lp_specification.date_started return self.__date_started @property def date_completed(self): if self.__date_completed == None: self.__date_completed = self.__lp_specification.date_completed return self.__date_completed # State tracking @property def importance(self): if self.__importance == None: self.__importance = self.__lp_specification.importance return self.__importance @property def definition_status(self): if self.__definition_status == None: self.__definition_status = self.__lp_specification.definition_status return self.__definition_status @property def implementation_status(self): if self.__implementation_status == None: self.__implementation_status = self.__lp_specification.implementation_status return self.__implementation_status @property def lifecycle_status(self): if self.__lifecycle_status == None: self.__lifecycle_status = self.__lp_specification.lifecycle_status return self.__lifecycle_status @property def milestone(self): if self.__milestone == None: self.__milestone = self.__lp_specification.milestone return self.__milestone # State tests @property def approved(self): if self.__approved == None: self.__approved = self.__lp_specification.direction_approved return self.__approved @property def complete(self): if self.__complete == None: self.__complete = self.__lp_specification.complete return self.__complete @property def started(self): if self.__started == None: self.__started = self.__lp_specification.started return self.__started # Text @property def name(self): if self.__name == None: self.__name = o2str(self.__lp_specification.name) return self.__name @property def summary(self): if self.__summary == None: self.__summary = o2str(self.__lp_specification.summary) return self.__summary @property def title(self): if self.__title == None: self.__title = o2str(self.__lp_specification.title) return self.__title @property def whiteboard(self): if self.__whiteboard == None: self.__whiteboard = o2str(self.__lp_specification.whiteboard) return self.__whiteboard @property def url(self): if self.__url == None: self.__url = self.__lp_specification.specification_url return self.__url # Collections @property def bugs(self): if self.__bugs == None: self.__bugs = Bugs(self.__service, self.__lp_specification.bugs) return self.__bugs @property def dependencies(self): if self.__dependencies == None: # Work around circular dependencies from specifications import Specifications self.__dependencies = Specifications(self.__service, self.__lp_specification.dependencies_collection) return self.__dependencies # vi:set ts=4 sw=4 expandtab: lpltk/COPYING.LIB0000664000000000017510000006363711753031405010570 0ustar GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it!