Hi Brad Thanks for adding the samples, I'm sure they will help people understand how to use launchapd lib. Your XSLT rule is broken; I suggest a fix. > === modified file 'launchpadlib/wadl-to-refhtml.xsl' > --- launchpadlib/wadl-to-refhtml.xsl 2009-01-13 17:30:59 +0000 > +++ launchpadlib/wadl-to-refhtml.xsl 2009-01-15 22:34:48 +0000 > @@ -379,6 +379,12 @@ > /+wikiname/ > <id> > > + > + / > + /+commercialsubscription/ > + / > + <commercial_subscription.id> > + This instruction builds a path by concatenating text, and it does not look correct. I see //+commercialsubscription// /me looks at source Yep, I am right. Since the convention is to have wrap the view name in "/"s, this markup should look like the h_w_device rules /+commercialsubscription/ <commercial_subscription.id> > === added directory 'samples' > === added file 'samples/_pythonpath.py' > --- samples/_pythonpath.py 1970-01-01 00:00:00 +0000 > +++ samples/_pythonpath.py 2009-01-15 22:34:48 +0000 > @@ -0,0 +1,14 @@ > +__metaclass__ = type > + > +import sys, os, os.path Put each import on its own line. > + > +# See if we can find launchpadlib and waddlib > +try: > + import launchpadlib > + import wadllib > +except ImportError: > + # Modify sys.path to add trunk/lib. > + HOME = os.environ['HOME'] > + trunk_path = os.environ.get('LP_TRUNK_PATH', > + os.path.join(HOME, 'canonical/lp-branches/trunk')) Wrap the code at 78 characters. ... > === added file 'samples/lpapi.py' > --- samples/lpapi.py 1970-01-01 00:00:00 +0000 > +++ samples/lpapi.py 2009-01-15 22:34:48 +0000 > @@ -0,0 +1,108 @@ > +#!/usr/bin/python2.4 > +import os > +import sys > +from urlparse import urljoin > +import commands > + > +try: > + from launchpadlib.launchpad import ( > + Launchpad, EDGE_SERVICE_ROOT, STAGING_SERVICE_ROOT) > + from launchpadlib.credentials import Credentials > + from launchpadlib.errors import * > + import launchpadlib > +except ImportError: > + print >> sys.stderr, "Usage:" > + print >> sys.stderr, " PYTHONPATH=somebranch/lib %s" % sys.argv[0] > + raise > + > + > +class InvalidCredentials(Exception): > + pass > + > + > +class Retry(Exception): > + pass > + > + > +class LPSystem: Can you add docstrings to these. This is a great base class and exceptions that I expect other people will use and evolve.' > + endpoint = None > + auth_file = None > + def __init__(self, app_name='just testing', use_cache=True): > + home = os.environ['HOME'] > + if use_cache: > + cachedir = os.path.join(home, '.launchpadlib', 'cache') > + if not os.path.exists(cachedir): > + cachedir = None > + else: > + cachedir = None Is ``cachedir`` right? ``cache_dir`` looks PEP-8 friendly. > + try: > + # Load credentials from AUTH_FILE if it exists. > + self.auth_file = os.path.join(home, self.auth_file_name) > + self.credentials = Credentials() > + self.credentials.load(open(self.auth_file)) > + print >> sys.stderr, "Loading credentials..." > + try: > + self.launchpad = Launchpad(self.credentials, self.endpoint, > + cache=cachedir) > + except launchpadlib.errors.HTTPError: > + raise InvalidCredentials, ( > + "Please remove %s and rerun %s to authenticate." % ( > + self.auth_file, sys.argv[0])) > + except IOError: > + # Prompt for authentication process, then save credentials to AUTH_FILE. Wrap the code at 78 characters. > + try: > + self.launchpad = Launchpad.get_token_and_login(app_name, > + self.endpoint, > + cache=cachedir) > + self.launchpad.credentials.save(open(self.auth_file, "w")) > + print >> sys.stderr, "Credentials saved" > + except launchpadlib.errors.HTTPError, err: > + print >> sys.stderr, err.content > + raise > + > + @property > + def url(self): > + return urljoin(self.endpoint, 'projects/') > + > + > +class Edge(LPSystem): > + endpoint = EDGE_SERVICE_ROOT > + auth_file_name = 'edge.auth' > + > + > +class Staging(LPSystem): > + endpoint = STAGING_SERVICE_ROOT > + auth_file_name = 'staging.auth' > + > + > +class Production(LPSystem): > + endpoint = 'https://api.launchpad.net/beta/' > + auth_file_name = 'production.auth' > + > + > +class Dev(LPSystem): > + endpoint = 'https://api.launchpad.dev/beta/' > + auth_file_name = 'dev.auth' > + def __init__(self, app_name='just testing'): > + LPSystem.__init__(self, app_name=app_name, use_cache=False) > + > + > +systems=dict(dev=Dev, > + staging=Staging, > + edge=Edge, > + beta=Edge, > + production=Production, > + prod=Production, > + ) Wrap the "=" in spaces: systems = dict(dev=Dev, Is beta vestigial? Is it used> > + > + > +def lp_factory(sysname, app_name='just_testing'): I think system_name is clearer > + """Get a launchpad API instance for `sysname`.""" I believe double-backquotes denotes a variable in ReST. single-backquotes is a cross-reference. ... === added file 'samples/nopriv-api.py' --- samples/nopriv-api.py 1970-01-01 00:00:00 +0000 +++ samples/nopriv-api.py 2009-01-15 22:34:48 +0000 @@ -0,0 +1,91 @@ +#!/usr/bin/python +# -*-doctest-*- + +""" + >>> import _pythonpath, lpapi + >>> lp = lpapi.lp_factory('dev') + + >>> bzr = lp.projects['bzr'] + >>> print bzr.reviewer_whiteboard + tag:launchpad.net:2008:redacted + >>> bzr.reviewer_whiteboard = "Check on licensing" + >>> print bzr.reviewer_whiteboard + Check on licensing + >>> bzr.lp_save() + ... + Traceback (most recent call last): + ... + HTTPError: HTTP Error 401: Unauthorized + + >>> from operator import attrgetter + >>> def print_projs(projs): + ... for p in sorted(projs, key=attrgetter('name')): + ... print p.name Launchpad style to to never use single letters as variables (except for i and j). >>> def print_projects(projects): ... for project in sorted(projects, key=attrgetter('name')): ... print project.name This doesn't look like it is used. > + > + >>> inactive = lp.projects.licensing_search(active=False) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> active = lp.projects.licensing_search(active=True) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(license_reviewed=False) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(has_zero_licenses=True) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(licenses=['Other/Proprietary']) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> bzr.licenses > + [] > + > + >>> projs = lp.projects.licensing_search(has_zero_licenses=False) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(licenses=['GNU Affero GPL v3']) > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(subscription_expires_after="2005-01-01") Wrap the code at 78 characters. > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > + >>> projs = lp.projects.licensing_search(has_zero_licenses=True, search_text="Bazaar") Wrap the code at 78 characters. > + Traceback (most recent call last): > + ... > + HTTPError: HTTP Error 401: Unauthorized > + > +""" > + > +if __name__ == '__main__': > + # Remove existing credentials. > + import os > + try: > + os.unlink(os.path.join(os.environ['HOME'], 'dev.auth')) > + except OSError: > + pass > + > + # Create correct credentials. > + print "Login as '