ubuntu-dev-tools/ubuntutools/lp/lpapiwrapper.py

367 lines
10 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
#
# lpapiwrapper.py - wrapper class around the LP API for use in the
# ubuntu-dev-tools package
#
# Copyright © 2009 Michael Bienia <geser@ubuntu.com>
#
# 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.
#
# Please see the /usr/share/common-licenses/GPL file for the full text
# of the GNU General Public License license.
#
# Based on code written by Jonathan Davies <jpds@ubuntu.com>
# Uncomment for tracing LP API calls
#import httplib2
#httplib2.debuglevel = 1
import libsupport
from launchpadlib.errors import HTTPError
from launchpadlib.resource import Entry
from udtexceptions import PackageNotFoundException, SeriesNotFoundException, PocketDoesNotExist
__all__ = ['Launchpad', 'LpApiWrapper']
class Launchpad(object):
''' Singleton for LP API access. '''
__lp = None
def login(self):
'''
Enforce a login through the LP API.
'''
if not self.__lp:
self.__lp = libsupport.get_launchpad('ubuntu-dev-tools')
return self
def __getattr__(self, attr):
if not self.__lp:
self.login()
return getattr(self.__lp, attr)
def __call__(self):
return self
Launchpad = Launchpad()
class LpApiWrapper(object):
'''
Wrapper around some common used LP API functions used in
ubuntu-dev-tools.
It also caches LP API objects either as class variables or as
instance variables depending on the expected change of its value.
'''
_archive = None
_devel_series = None
_me = None
_ubuntu = None
_series = dict()
_src_pkg = dict()
_upload_comp = dict()
_upload_pkg = dict()
def __init__(self):
pass
@classmethod
def getMe(cls):
'''
Returns the LP representations of the currently authenticated LP user.
'''
if not cls._me:
cls._me = _PersonTeam(Launchpad.me)
return cls._me
@classmethod
def getUbuntuDistribution(cls):
'''
Returns the LP representation for Ubuntu.
'''
if not cls._ubuntu:
cls._ubuntu = Launchpad.distributions['ubuntu']
return cls._ubuntu
@classmethod
def getUbuntuArchive(cls):
'''
Returns the LP representation for the Ubuntu main archive.
'''
if not cls._archive:
cls._archive = cls.getUbuntuDistribution().main_archive
return cls._archive
@classmethod
def getUbuntuSeries(cls, name_or_version):
'''
Returns the LP representation of a series passed by name (e.g.
'karmic') or version (e.g. '9.10').
If the series is not found: raise SeriesNotFoundException
'''
name_or_version = str(name_or_version)
if name_or_version not in cls._series:
try:
series = cls.getUbuntuDistribution().getSeries(name_or_version = name_or_version)
# Cache with name and version
cls._series[series.name] = DistroSeries(series)
cls._series[series.version] = DistroSeries(series)
except HTTPError:
raise SeriesNotFoundException("Error: Unknown Ubuntu release: '%s'." % name_or_version)
return cls._series[name_or_version]
@classmethod
def getUbuntuDevelopmentSeries(cls):
'''
Returns the LP representation of the current development series of
Ubuntu.
'''
if not cls._devel_series:
dev = cls.getUbuntuDistribution().current_series
cls._devel_series = DistroSeries(dev)
# Cache it in _series if not already done
if dev.name not in cls._series:
cls._series[dev.name] = cls._devel_series
cls._series[dev.version] = cls._devel_series
return cls._devel_series
@classmethod
def getUbuntuSourcePackage(cls, name, series, pocket = 'Release'):
'''
Finds an Ubuntu source package on LP.
Returns a wrapped LP representation of the source package.
If the package does not exist: raise PackageNotFoundException
'''
# Check if pocket has a valid value
if pocket not in ('Release', 'Security', 'Updates', 'Proposed', 'Backports'):
raise PocketDoesNotExist("Pocket '%s' does not exist." % pocket)
# Check if we have already a LP representation of an Ubuntu series or not
if not isinstance(series, DistroSeries):
series = cls.getUbuntuSeries(str(series))
if (name, series, pocket) not in cls._src_pkg:
try:
srcpkg = cls.getUbuntuArchive().getPublishedSources(
source_name = name, distro_series = series._lpobject, pocket = pocket,
status = 'Published', exact_match = True)[0]
cls._src_pkg[(name, series, pocket)] = SourcePackage(srcpkg)
except IndexError:
if pocket == 'Release':
msg = "The package '%s' does not exist in the Ubuntu main archive in '%s'" % \
(name, series.name)
else:
msg = "The package '%s' does not exist in the Ubuntu main archive in '%s-%s'" % \
(name, series.name, pocket.lower())
raise PackageNotFoundException(msg)
return cls._src_pkg[(name, series, pocket)]
@classmethod
def canUploadPackage(cls, package, series = None):
'''
Check if the currently authenticated LP user has upload rights
for package either through component upload rights or
per-package upload rights.
'package' can either be a wrapped LP representation of a source
package or a string and an Ubuntu series. If 'package' doesn't
exist yet in Ubuntu assume 'universe' for component.
'''
if isinstance(package, SourcePackage):
component = package.getComponent()
package = package.getPackageName()
else:
if not series:
# Fall-back to current Ubuntu development series
series = cls.getUbuntuDevelopmentSeries()
try:
component = cls.getUbuntuSourcePackage(package, series).getComponent()
except PackageNotFoundException:
# Probably a new package, assume "universe" as component
component = 'universe'
if component not in cls._upload_comp and package not in cls._upload_pkg:
me = cls.getMe()
archive = cls.getUbuntuArchive()
for perm in archive.getPermissionsForPerson(person = me):
if perm.permission != 'Archive Upload Rights':
continue
if perm.component_name == component:
cls._upload_comp[component] = True
return True
if perm.source_package_name == package:
cls._upload_pkg[package] = True
return True
return False
elif component in cls._upload_comp:
return cls._upload_comp[component]
else:
return cls._upload_pkg[package]
# TODO: check if this is still needed after ArchiveReorg (or at all)
@classmethod
def isPerPackageUploader(cls, package, series = None):
'''
Check if the user has PerPackageUpload rights for package.
'''
if isinstance(package, SourcePackage):
pkg = package.getPackageName()
else:
pkg = package
return cls.canUploadPackage(package, series) and pkg in cls._upload_pkg
@classmethod
def isLpTeamMember(cls, team):
'''
Checks if the user is a member of a certain team on Launchpad.
Returns True if the user is a member of the team otherwise False.
'''
return any(t.name == team for t in cls.getMe().super_teams)
class MetaWrapper(type):
'''
A meta class used for wrapping LP API objects.
'''
def __init__(cls, name, bases, attrd):
super(MetaWrapper, cls).__init__(name, bases, attrd)
if 'resource_type' not in attrd:
raise TypeError('Class needs an associated resource type')
cls._cache = dict()
class BaseWrapper(object):
'''
A base class from which other wrapper classes are derived.
'''
__metaclass__ = MetaWrapper
resource_type = None # it's a base class after all
def __new__(cls, lpobject):
if isinstance(lpobject, str):
if lpobject.startswith('http'):
# might be an LP API URL
# look it up in our cache
data = cls._cache.get(lpobject)
if data:
return data
else:
# try to fetch it from LP
fetch = getattr(cls, 'fetch', None)
if fetch and callable(fetch):
lpobject = fetch(lpobject)
else:
raise NotImplementedError("Don't know how to fetch '%s' from LP" % lpobject)
else:
raise TypeError("'%s' doesn't look like a LP API URL" % lpobject)
if isinstance(lpobject, Entry) and lpobject.resource_type_link in cls.resource_type:
# check if it's already cached
data = cls._cache.get(lpobject.self_link)
if not data:
# create a new instance
data = object.__new__(cls)
data._lpobject = lpobject
# and add it to our cache
cls._cache[lpobject.self_link] = data
return data
else:
raise TypeError("'%s' is not a '%s' object" % (str(lpobject), str(cls.resource_type)))
def __getattr__(self, attr):
return getattr(self._lpobject, attr)
class DistroSeries(BaseWrapper):
'''
Wrapper class around a LP distro series object.
'''
resource_type = 'https://api.edge.launchpad.net/beta/#distro_series'
class SourcePackage(BaseWrapper):
'''
Wrapper class around a LP source package object.
'''
resource_type = 'https://api.edge.launchpad.net/beta/#source_package_publishing_history'
def getPackageName(self):
'''
Returns the source package name.
'''
return self._lpobject.source_package_name
def getVersion(self):
'''
Returns the version of the source package.
'''
return self._lpobject.source_package_version
def getComponent(self):
'''
Returns the component of the source package.
'''
return self._lpobject.component_name
class _PersonTeam(object):
'''
Wrapper class around a LP person or team object.
'''
_cache = dict() # Key is the LP API person/team URL
def __init__(self, personteam):
if isinstance(personteam, Entry) and personteam.resource_type_link in \
('https://api.edge.launchpad.net/beta/#person', 'https://api.edge.launchpad.net/beta/#team'):
self._personteam = personteam
# Add ourself to the cache
if str(personteam) not in self._cache:
self._cache[str(personteam)] = self
else:
raise TypeError('A LP API person or team representation expected.')
def __str__(self):
return '%s (%s)' % (self._personteam.display_name, self._personteam.name)
def __getattr__(self, attr):
return getattr(self._personteam, attr)
@classmethod
def getPersonTeam(cls, name):
'''
Return a _PersonTeam object for the LP user 'name'.
'name' can be a LP id or a LP API URL for that person or team.
'''
if name in cls._cache:
# 'name' is a LP API URL
return cls._cache[name]
else:
if not name.startswith('http'):
# Check if we've cached the 'name' already
for personteam in cls._cache.values():
if personteam.name == name:
return personteam
return _PersonTeam(Launchpad.people[name])