LaunchpadLib

Dev Week -- Meet launchpadlib -- jml -- Fri, Jan 29

UTC

   1 [17:59] <cjwatson> I'll hand over now to Jonathan Lange, a gentleman and a scholar, who's going to be talking about launchpadlib.
   2 [17:59] <jml> cjwatson, why thank you
   3 [18:00] <jml> as before, please stop me if I go too fast, or hassle me if I go too slow
   4 [18:00] <jml> You might know already, but pretty much no matter what kind of Ubuntu development you are doing, you'll have to interact with Launchpad
   5 [18:00] <jml> Interacting with Launchpad is fun and all, but sometimes you'll want a computer to do the interaction for you
   6 [18:00] <jml> Say you want to suck out some of the data and make a pretty graph
   7 [18:00] <jml> or you want to build a site like http://qa.ubuntu.com/
   8 [18:00] <jml> This session should help you do that...
   9 [18:00] <jml> Before we begin, you should install the 'python-launchpadlib' package now.
  10 [18:01] <jml> Confirm that it works by running:
  11 [18:01] <jml> $ python
  12 [18:01] <jml> and then
  13 [18:01] <jml> >>> import launchpadlib
  14 [18:01] <jml> >>> launchpadlib.__version__
  15 [18:01] <jml> you should get output like:
  16 [18:01] <jml> '1.5.1'
  17 [18:01] <jml> if it's an earlier version, then you need to either update the package, upgrade to a newer version of Ubuntu, or find the PPA.
  18 [18:01] <jml> A moment for those installing the package
  19 [18:01] <jml> Moment over.
  20 [18:01] <jml> This session is going to have four parts: theory, example, gotchas and future.
  21 [18:02] <jml> = 1. Theory =
  22 [18:02] <jml> The Launchpad developers implemented an API for three reasons:
  23 [18:02] <jml> 1. To let you get at your data
  24 [18:02] <jml> 2. To allow you to do project-specific policy stuff
  25 [18:02] <jml> 3. To watch people do cool things we would never have thought of.
  26 [18:02] <jml> (I'm a Launchpad developer, hence the 'we')
  27 [18:02] <jml> We decided to implement the API using something called RESTful Architecture
  28 [18:02] <jml> http://en.wikipedia.org/wiki/Representational_State_Transfer
  29 [18:03] <jml> This means that Launchpad's APIs are actually language neutral
  30 [18:03] <jml> in theory, you can write code to control Launchpad in any language you want
  31 [18:03] <jml> without screenscraping!
  32 [18:03] <jml> You do HTTP requests with operations like GET, POST, PUSH and DELETE and send and receive JSON content that's described by a WADL file
  33 [18:03] <jml> WADL == http://en.wikipedia.org/wiki/Web_Application_Description_Language
  34 [18:03] <jml> Which is all nice and wonderful but you don't really have to care about it, because there's also a Python library
  35 [18:03] <jml> called "launchpadlib"
  36 [18:04] <jml> (have you installed it yet?)
  37 [18:04] <jml> If you don't care at all about Python, now's the time to stop following this session and go to https://help.launchpad.net/API instead
  38 [18:04] <jml> Because I'm about to go into the example.
  39 [18:04] <jml> = 2. Example =
  40 [18:04] <jml> You can follow along from this example by getting the branch at lp:~jml/+junk/bugstats
  41 [18:04] <jml> You do that by running 'bzr branch lp:~jml/+junk/bugstats'
  42 [18:04] <jml> The way to follow along is to change into that branch, and then run 'bzr revert -r 1'
  43 [18:04] <jml> and then when I say "Next revision", do the same thing with "-r 2" instead
  44 [18:04] <jml> and so on
  45 [18:05] <jml> bzr revert -r1
  46 [18:05] <jml> The branch contains a single file 'bugstats.py'
  47 [18:05] <jml> If you look at that file in your favorite text editor, you'll see that it's very very simple
  48 [18:05] <jml> It imports sys, defines a main function that does nothing, and then runs that main function.
  49 [18:05] <jml> Go ahead and run it now
  50 [18:05] <jml> $ python bugstats.py
  51 [18:05] <jml> It should do absolutely nothing, fairly quickly
  52 [18:05] <jml> (at the speed of PYTHON!)
  53 [18:05] <jml> OK. Next revision
  54 [18:06] <jml> bzr revert -r2
  55 [18:06] <jml> (sorry alt-tab fail)
  56 [18:06] <jml> Have a look at this version of bugstats.py. You might need to reload it in your editor.
  57 [18:06] <jml> This is an extremely simple launchpadlib script
  58 [18:06] <jml> It logs in to launchpad and prints out the title of bug number one
  59 [18:06] <jml> I'll break down exactly what the script does in a moment.
  60 [18:06] <jml> But first, you should run the script.
  61 [18:06] <jml> $ python bugstats.py
  62 [18:06] <jml> When you run it, an authorization page will open up in your web browser
  63 [18:07] <jml> This is very important, this is how your Launchpad web credentials get applied to the application
  64 [18:07] <jml> It's done using oauth, which I don't understand.
  65 [18:07] <jml> http://oauth.net/ and http://en.wikipedia.org/wiki/OAuth are probably good places to start though.
  66 [18:07] <jml> For now, pick "Read non-private data"
  67 [18:07] <jml> Then, switch back to your terminal and press enter.
  68 [18:07] <jml> The script should then print the title of bug number one.
  69 [18:07] <jml> Run the script again
  70 [18:07] <jml> Notice that launchpadlib has cached your credentials
  71 [18:08] <jml>     launchpad = Launchpad.login_with(APP_NAME, SERVICE_ROOT, CACHE_DIR)
  72 [18:08] <jml> ^^^ that's the line of code what did it
  73 [18:08] <jml> if you look at the script...
  74 [18:08] <jml> you'll see I've imported 'os' and 'sys', which are standard Python modules.
  75 [18:08] <jml> I've also imported 'Launchpad' and 'STAGING_SERVICE_ROOT' from launchpadlib
  76 [18:08] <jml> Then I've defined an application name, a caching directory and a service root
  77 [18:08] <jml> APP_NAME = 'jml-bug-stats'
  78 [18:08] <jml> CACHE_DIR = os.path.expanduser('~/.launchpadlib/cache')
  79 [18:08] <jml> SERVICE_ROOT = STAGING_SERVICE_ROOT
  80 [18:08] <jml> There's nothing special about the names of the variables, I've just made them ALL CAPS to hint that they are constants
  81 [18:08] <jml> APP_NAME is the name of the application. launchpadlib & Launchpad use this as a marker for authorization. You authorize an application based on its *name*.
  82 [18:09] <jml> Go to https://staging.launchpad.net/people/+me/+oauth-tokens and search for "jml-bug-stats"
  83 [18:09] <jml> you'll only find it if you actually ran the script
  84 [18:09] <jml> if you have run the script, you should find it there, saying when you authorized it and what you authorized it to do
  85 [18:09] <jml> You could revoke the authorization if you wanted to, but you shouldn't do that just now
  86 [18:09] <jml> Anyway, that 'jml-bug-stats' is the app name used in the script.
  87 [18:10] <jml> SERVICE_ROOT is _which_ Launchpad.net instance to use
  88 [18:10] <jml> does everyone know about staging.launchpad.net, edge.launchpad.net and so forth?
  89 [18:10] <jml> ...
  90 [18:10] <jml> that's a yes.
  91 [18:10] <jml> Today, we're using staging. Testing against staging is a good idea, since it's impossible to mess up your live production data by messing with staging.
  92 [18:10] <jml> CACHE_DIR is the place where launchpadlib stores cached credentials and cached json objects
  93 [18:11] <jml> It's worth having a poke around in it sometime
  94 [18:11] <jml> most apps use ~/.launchpadlib/cache
  95 [18:11] <jml> for reasons that escape me
  96 [18:11] <jml> If you run ls -l ~/.launchpadlib/cache/api.staging.launchpad.net/credentials/ you should see a file called 'jml-bug-stats'
  97 [18:11] <jml> That file contains the cached credentials for this app
  98 [18:11] <jml> If you delete it, you'll have to re-authorize the application
  99 [18:11] <jml> moving on to the script proper.
 100 [18:11] <jml> The main() function has us logging into Launchpad and printing a bug title
 101 [18:11] <jml> login_with is a handy convenience function that logs in with an application name, but looks in the cached credentials first to see if you've already logged in. It returns an instance of 'Launchpad'
 102 [18:11] <jml> 'launchpad' is an object with a bunch of pre-defined top-level collections, and is the starting point of any launchpadlib app.
 103 [18:12] <jml> https://help.launchpad.net/API/launchpadlib#The%20top-level%20objects will list all of them for you
 104 [18:12] <jml> Here we use 'bugs', get the bug with id '1' and print its title.
 105 [18:12] <jml> $ python bugstats.py
 106 [18:12] <jml> Microsoft has a majority market share
 107 [18:12] <jml> Easy
 108 [18:12] <jml> Now for something a bit more useful
 109 [18:12] <jml> Next revision
 110 [18:12] <jml> bzr revert -r3
 111 [18:12] <jml> $ python bugstats.py
 112 [18:12] <jml> Please specify a project and a username.
 113 [18:13] <jml> $ python bugstats.py ubuntu jml
 114 [18:13] <jml> Jonathan Lange has had 2 bugs fixed on Ubuntu
 115 [18:13] <jml> it shows the number of bugs someone has had fixed
 116 [18:13] <jml> useful, huh?
 117 [18:13] <jml> take a look at the code
 118 [18:13] <jml> It's got a work-around for a bug in launchpadlib to get the length of a returned collection.
 119 [18:13] <jml> I don't really understand why the bug isn't fixed yet, since it's got to bite almost everyone who writes a non-trivial launchpadlib application.
 120 [18:13] <jml> Anyway, this script is more advanced than the last one.
 121 [18:14] <jml> look at ...
 122 [18:14] <jml>     pillar = launchpad.projects[pillar_name]
 123 [18:14] <jml> "Pillar" is Launchpad developer jargon for a distribution, project or project group. e.g. ubuntu, gnome-do or gnome
 124 [18:14] <jml> you can try running the script with your username and fave project
 125 [18:14] <jml> The script takes the pillar name from the command line, along with a bug reporter name, finds all of the 'fix released' bugs and then prints out a friendly message.
 126 [18:14] <jml>     reporter = launchpad.people[reporter_name]
 127 [18:14] <jml>     fixed_bugtasks = pillar.searchTasks(
 128 [18:14] <jml>         bug_reporter=reporter, status=['Fix Released'])
 129 [18:15] <jml> If I run it with this:
 130 [18:15] <jml> $ python bugstats.py launchpad-code jml
 131 [18:15] <jml> I get:
 132 [18:15] <jml> Jonathan Lange has had 164 bugs fixed on Launchpad Bazaar Integration
 133 [18:15] <jml> To me, the most interesting thing about this script (apart from the cool data) is "how did I figure out to call 'searchTasks'?"
 134 [18:15] <jml> Well, each of the Launchpad instances has generated API documentation at +apidoc
 135 [18:15] <jml> e.g. https://staging.launchpad.net/+apidoc/
 136 [18:15] <jml> or e.g. https://edge.launchpad.net/+apidoc/
 137 [18:15] <jml> I happen to know a bit about the Launchpad object model, and knew I'd need to get a bunch of "bug tasks"
 138 [18:16] <jml> A bug task is that row in the table that has an assignee, status, importance etc. A bug can have many bug tasks.
 139 [18:16] <jml> I went to the API documentation page and had a look around for something that would return a list of bug tasks.
 140 [18:16] <jml> I found "searchTasks", and then gave it a try and it worked
 141 [18:16] <jml> The API documentation also told me that people have display_name attributes and that projects do too.
 142 [18:16] <jml> Note that the API documentation is *not* launchpadlib documentation. It's generic REST API documentation.
 143 [18:16] <jml> That means you often need to translate from the abstract REST docs into concrete Python. It's not that hard.
 144 [18:16] <jml> Anyway the script is pretty simple
 145 [18:16] <jml> <geser> QUESTION: does python-launchpadlib also clean up the cache of old unused files?
 146 [18:16] <jml> geser, No, it does not.
 147 [18:17] <jml> geser, as far as I'm aware, there's absolutely no functionality in launchpadlib for removing cache files under any circumstances
 148 [18:17] <jml> The next revision is something a little bit more complex
 149 [18:17] <jml> bzr revert -r4
 150 [18:17] <jml> This script does almost exactly the same thing, except it tells you how successful the reporter was as a percentage of bugs fixed over bugs filed
 151 [18:18] <jml> The new code is the total_bugtasks line, which finds all bugs of any status
 152 [18:18] <jml>     fixed_bugtasks = pillar.searchTasks(
 153 [18:18] <jml>         bug_reporter=reporter, status=['Fix Released'])
 154 [18:18] <jml>     total_bugtasks = pillar.searchTasks(
 155 [18:18] <jml>         bug_reporter=reporter,
 156 [18:18] <jml>         status=[
 157 [18:18] <jml>             "New",
 158 [18:18] <jml>             "Incomplete",
 159 [18:18] <jml>             "Invalid",
 160 [18:18] <jml>             "Won't Fix",
 161 [18:18] <jml>             "Confirmed",
 162 [18:18] <jml>             "Triaged",
 163 [18:18] <jml>             "In Progress",
 164 [18:18] <jml>             "Fix Committed",
 165 [18:18] <jml>             'Fix Released'])
 166 [18:18] <jml> zoiks!
 167 [18:18] <jml> (I thought I had my newlines done differently)
 168 [18:19] <jml> I had to specify each status manually, because 'searchTasks' defaults to only finding open bugs
 169 [18:19] <jml> Then there's some code to calculate a percentage and print it out nicely
 170 [18:19] <jml>     percentage = 100.0 * length(fixed_bugtasks) / length(total_bugtasks)
 171 [18:19] <jml>     print "%s is %.2f%% successful on bugs in %s" % (
 172 [18:19] <jml>         reporter.display_name, percentage, pillar.display_name)
 173 [18:19] <jml> Let's try running it!
 174 [18:19] <jml> $ python bugstats.py ubuntu jml
 175 [18:19] <jml> Jonathan Lange is 22.22% successful on bugs in Ubuntu
 176 [18:19] <jml> $ python bugstats.py launchpad-code jml
 177 [18:19] <jml> Jonathan Lange is 56.16% successful on bugs in Launchpad Bazaar Integration
 178 [18:20] <jml> Again, you can try it with yourself, or with a friend, or with cjwatson
 179 [18:20] <jml> That's all I have for the example.
 180 [18:20] <jml> = 3. Gotchas =
 181 [18:20] <jml> There are quite a few things that can trip you up with the Launchpad API
 182 [18:20] <jml> We've already bumped into a couple of them: it's got bugs.
 183 [18:20] <jml> https://bugs.edge.launchpad.net/launchpadlib/ for more info on that
 184 [18:21] <jml> The second is that even though it does have documentation, it's not always easy to apply to what you need.
 185 [18:21] <jml> (wtf, "Colin Watson is 79.65% successful on bugs in Ubuntu"!)
 186 [18:21] <jml> I'm going to pimp http://help.launchpad.net/API again, since there really is a fair chunk of good documentation there.
 187 [18:21] <jml> https://help.launchpad.net/API/Examples is good, as is https://help.launchpad.net/API/launchpadlib
 188 [18:21] <jml> However, the docs often aren't up to scratch.
 189 [18:22] <jml> There is only one way for them to get better
 190 [18:22] <jml> People like your good selves must ask questions, get answers, and then edit the help.launchpad.net wiki.
 191 [18:22] <jml> Another gotcha is error messages.
 192 [18:22] <jml> Because launchpadlib is a very, very thin wrapper over a generic RESTful client library, if ever you get an exception raised, it's going to be really unhelpful.
 193 [18:22] <jml> https://help.launchpad.net/API/Examples#Get%20a%20useful%20error%20message%20from%20launchpadlib shows the work-around.
 194 [18:22] <jml> I think I filed a bug about that.
 195 [18:23] <jml> hmm.
 196 [18:23] <jml> there's another gotcha, which is *performance*
 197 [18:23] <jml> For a lot of the things you probably want to do, you'll end up writing code that looks like this:
 198 [18:23] <jml>   for thing in bunch_of_things:
 199 [18:23] <jml>       thing.do_something_on_launchpad()
 200 [18:23] <jml> Code like this is really really slow
 201 [18:23] <jml> It'll do an HTTPS request for each 'thing'
 202 [18:23] <jml> We'd like to have some way of reducing that load, but we don't have any good answers yet
 203 [18:23] <jml> Only two more gotchas left :)
 204 [18:23] <jml> Not everything that's in Launchpad itself is exposed via the API.
 205 [18:23] <jml> Sorry.
 206 [18:23] <jml> It's generally either really, really easy to expose stuff over the API or almost impossible.
 207 [18:24] <jml> If the thing you want falls into the first category, you can probably patch Launchpad yourself.
 208 [18:24] <jml> In any case, *file a bug*. Everything that's not exposed over the API is a bug.
 209 [18:24] <jml> Last gotcha: testing launchpadlib apps is hard
 210 [18:24] <jml> jkakar has done some work on this recently
 211 [18:24] <jml> but I haven't had a chance to look at it.
 212 [18:24] <jml> That's it on the gotchas: bugs, docs, errors, potato programming, unexposed methods, testing
 213 [18:25] <jml> = 4. Future =
 214 [18:25] <jml> There are plenty of other examples of launchpadlib apps out there
 215 [18:25] <jml> bughugger uses launchpadlib (https://launchpad.net/bughugger)
 216 [18:25] <jml> quickly has some templates for using launchpadlib (https://launchpad.net/quickly)
 217 [18:25] <jml> The code that puts branches in https://code.launchpad.net/ubuntu is done entirely with launchpadlib (we talked about those branches last session!)
 218 [18:25] <jml> There's also a stack of projects that are part of a group called 'lpx' that you can find at https://launchpad.net/lpx
 219 [18:25] <jml> and at http://help.launchpad.net/API/Uses there's even more stuff
 220 [18:25] <jml> If ever you need help, ask on #launchpad or #launchpad-dev on freenode or send an email to launchpad-users@lists.launchpad.net
 221 [18:26] <jml> That's it from me.
 222 [18:26] <jml> Questions?
 223 [18:26] <jml> Cool ideas for launchpadlib programs?
 224 [18:26] <jml> General complaints about Launchpad that I can respond soothingly to?
 225 [18:27] <jml> By "complaint", I of course meant "suggestion for improvement"
 226 [18:27] <jml> <vocx> QUESTION: so, Python plays a HUGE part in Ubuntu, no? Other scripting won't feel left out?
 227 [18:27] <jml> A lot of Ubuntu stuff is written in Python.
 228 [18:28] <jml> As I mentioned earlier, you don't *have* to use Python to control Launchpad, but it makes it a lot easier.
 229 [18:28] <jml> <jönöbacon>  QUESTION: Can you make more examples for python-snippets happen?
 230 [18:29] <jml> jonobacon, I can point you at https://help.launchpad.net/API/Examples again, I guess :)
 231 [18:29] <jml> <kamalmostafa> QUESTION: Is there a way to access two JSON values in one "call"... I.e.   launchpad.people[reporter_name].latitude   gets the latitude -- is there a way to also get the longitude at the same time?
 232 [18:30] <jml> kamalmostafa, I'm fairly sure that if you get the object first, then subsequent attribute access doesn't do a separate webapp call.
 233 [18:30] <jml> so...
 234 [18:30] <jml> reporter = launchpad.people[reporter_name]
 235 [18:30] <jml> reporter.latitude, reporter.longtitude
 236 [18:30] <jml> <vocx> QUESTION: is there a need to worry about python 3.0 breaking something?
 237 [18:30] <jml> vocx, wrt launchpadlib or wrt Ubuntu more generally?
 238 [18:31] <jml> vocx, launchpadlib is a Python 2 application. It hasn't been tested with Python 3.
 239 [18:32] <jml> vocx, I'm not the launchpadlib maintainer, but I tend to think of python3 as a completely different language. if a program works on both 2 and 3 it's a happy accident
 240 [18:33] <jml> vocx, no plans to port to 3 yet.
 241 [18:33] <jml> vocx, I guess we'd start doing that when Ubuntu stops providing Python 2
 242 [18:34] <jml> are there any other questions?
 243 [18:34] <jml> QUESTION: Can we convince jonobacon to use metal umlauts in his name so my friends can go back to calling me Jono?
 244 === jml is now known as joneaux
 245 === joneaux is now known as jml
 246 [18:37] <jml> Last chance for questions folks
 247 [18:37] <jml> remember you can get the code for the example by 'bzr branch lp:~jml/+junk/bugstats'
 248 [18:37] <jml> and that the folk on #launchpad-dev are helpful and friendly and want more people doing stuff with launchpadlib
 249 [18:38] <jml> ok folks

MeetingLogs/devweek1001/LaunchpadLib (last edited 2010-02-01 17:53:27 by unassigned)