/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
1
# Copyright (C) 2011 Canonical Ltd
5050.79.1 by John Arbash Meinel
Bring Maxb's code for querying launchpads api via a REST request.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
"""Tools for dealing with the Launchpad API without using launchpadlib.
18
19
The api itself is a RESTful interface, so we can make HTTP queries directly.
20
loading launchpadlib itself has a fairly high overhead (just calling
21
Launchpad.login_anonymously() takes a 500ms once the WADL is cached, and 5+s to
22
get the WADL.
23
"""
24
25
try:
26
    # Use simplejson if available, much faster, and can be easily installed in
27
    # older versions of python
28
    import simplejson as json
29
except ImportError:
30
    # Is present since python 2.6
31
    try:
32
        import json
33
    except ImportError:
34
        json = None
35
36
import urllib
37
import urllib2
38
39
from bzrlib import trace
40
41
42
DEFAULT_SERIES = 'oneiric'
43
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
44
class LatestPublication(object):
45
    """Encapsulate how to find the latest publication for a given project."""
46
47
    LP_API_ROOT = 'https://api.launchpad.net/1.0'
48
49
    def __init__(self, archive, series, project):
50
        self._archive = archive
51
        self._project = project
52
        self._setup_series_and_pocket(series)
53
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
54
    def _setup_series_and_pocket(self, series):
55
        """Parse the 'series' info into a series and a pocket.
56
57
        eg::
58
            _setup_series_and_pocket('natty-proposed')
59
            => _series == 'natty'
60
               _pocket == 'Proposed'
61
        """
62
        self._series = series
63
        self._pocket = None
64
        if self._series is not None and '-' in self._series:
65
            self._series, self._pocket = self._series.split('-', 1)
66
            self._pocket = self._pocket.title()
5050.79.8 by John Arbash Meinel
We should supply pocket=Release when none is supplied.
67
        else:
68
            self._pocket = 'Release'
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
69
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
70
    def _archive_URL(self):
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
71
        """Return the Launchpad 'Archive' URL that we will query.
72
        This is everything in the URL except the query parameters.
73
        """
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
74
        return '%s/%s/+archive/primary' % (self.LP_API_ROOT, self._archive)
75
76
    def _publication_status(self):
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
77
        """Handle the 'status' field.
78
        It seems that Launchpad tracks all 'debian' packages as 'Pending', while
79
        for 'ubuntu' we care about the 'Published' packages.
80
        """
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
81
        if self._archive == 'debian':
82
            # Launchpad only tracks debian packages as "Pending", it doesn't mark
83
            # them Published
84
            return 'Pending'
85
        return 'Published'
86
87
    def _query_params(self):
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
88
        """Get the parameters defining our query.
89
        This defines the actions we are making against the archive.
90
        :return: A dict of query parameters.
91
        """
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
92
        params = {'ws.op': 'getPublishedSources',
93
                  'exact_match': 'true',
94
                  # If we need to use "" shouldn't we quote the project somehow?
95
                  'source_name': '"%s"' % (self._project,),
96
                  'status': self._publication_status(),
97
                  # We only need the latest one, the results seem to be properly
98
                  # most-recent-debian-version sorted
99
                  'ws.size': '1',
100
        }
101
        if self._series is not None:
102
            params['distro_series'] = '/%s/%s' % (self._archive, self._series)
103
        if self._pocket is not None:
104
            params['pocket'] = self._pocket
105
        return params
106
107
    def _query_URL(self):
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
108
        """Create the full URL that we need to query, including parameters."""
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
109
        params = self._query_params()
110
        # We sort to give deterministic results for testing
111
        encoded = urllib.urlencode(sorted(params.items()))
112
        return '%s?%s' % (self._archive_URL(), encoded)
113
114
    def _get_lp_info(self):
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
115
        """Place an actual HTTP query against the Launchpad service."""
116
        if json is None:
117
            return None
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
118
        query_URL = self._query_URL()
119
        try:
120
            req = urllib2.Request(query_URL)
121
            response = urllib2.urlopen(req)
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
122
            json_info = response.read()
123
        # XXX: We haven't tested the HTTPError
124
        except (urllib2.URLError, urllib2.HTTPError), e:
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
125
            trace.mutter('failed to place query to %r' % (query_URL,))
126
            trace.log_exception_quietly()
127
            return None
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
128
        return json_info
129
130
    def _parse_json_info(self, json_info):
131
        """Parse the json response from Launchpad into objects."""
132
        if json is None:
133
            return None
5050.79.4 by John Arbash Meinel
Put several tests behind a Feature object.
134
        try:
135
            return json.loads(json_info)
136
        except Exception:
137
            trace.mutter('Failed to parse json info: %r' % (json_info,))
138
            trace.log_exception_quietly()
139
            return None
5050.79.3 by John Arbash Meinel
All the bits are now hooked up. The slow tests are disabled,
140
141
    def get_latest_version(self):
142
        """Get the latest published version for the given package."""
143
        json_info = self._get_lp_info()
144
        if json_info is None:
145
            return None
146
        info = self._parse_json_info(json_info)
5050.79.4 by John Arbash Meinel
Put several tests behind a Feature object.
147
        if info is None:
148
            return None
149
        try:
150
            entries = info['entries']
151
            if len(entries) == 0:
152
                return None
153
            return entries[0]['source_package_version']
154
        except KeyError:
155
            trace.log_exception_quietly()
156
            return None
5050.79.2 by John Arbash Meinel
Start refactoring lp_api_lite and making it testable.
157
158
5050.79.1 by John Arbash Meinel
Bring Maxb's code for querying launchpads api via a REST request.
159
def get_latest_publication(archive, series, project):
160
    """Get the most recent publication for a given project.
161
162
    :param archive: Either 'ubuntu' or 'debian'
163
    :param series: Something like 'natty', 'sid', etc. Can be set as None. Can
164
        also include a pocket such as 'natty-proposed'.
165
    :param project: Something like 'bzr'
166
    :return: A version string indicating the most-recent version published in
167
        Launchpad. Might return None if there is an error.
168
    """
5050.79.4 by John Arbash Meinel
Put several tests behind a Feature object.
169
    lp = LatestPublication(archive, series, project)
170
    return lp.get_latest_version()