Package: python-apt Version: 0.9.2 Severity: wishlist Tags: patch
>From 0d295006a98769cd6151c78b3a078ad92d8047ee Mon Sep 17 00:00:00 2001 From: Michael Wisheu <mich...@5challer.de> Date: Sun, 29 Dec 2013 11:57:19 +0100 Subject: [PATCH] * apt/cache.py: - Fixed PEP8 linter and pyflakes issues - Added 'InstalledFilter' to get a filtered cache that only contains the currently installed packages. * apt/packages.py: - Fixed PEP8 linter issues - Removed special handling of 'collections' import as all supported distributions have Python 2.6 or newer by now. - Replaced faulty 'BaseDependency.__dstr' with easier to read compat code. - Added new properties to 'Dependency' and 'BaseDependency' to get the target package versions that could satisfy a dependency.
--- apt/cache.py | 52 ++++++----- apt/package.py | 279 ++++++++++++++++++++++++++++++++++++++++++++----------- debian/changelog | 16 +++- 3 files changed, 268 insertions(+), 79 deletions(-) diff --git a/apt/cache.py b/apt/cache.py index 43fb55d..897c2be 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -40,6 +40,7 @@ class FetchFailedException(IOError): class LockFailedException(IOError): """Exception that is thrown when locking fails.""" + class CacheClosedException(Exception): """Exception that is thrown when the cache is used after close().""" @@ -53,7 +54,7 @@ class Cache(object): list of available packages. The cache can be used like a mapping from package names to Package - objects (although only getting items is supported). + objects (although only getting items is supported). Keyword arguments: progress -- a OpProgress object @@ -74,7 +75,7 @@ class Cache(object): self._fullnameset = set() self._changes_count = -1 self._sorted_set = None - + self.connect("cache_post_open", self._inc_changes_count) self.connect("cache_post_change", self._inc_changes_count) if memonly: @@ -86,17 +87,17 @@ class Cache(object): apt_pkg.config.clear("APT") apt_pkg.config.set("Dir", rootdir) apt_pkg.init_config() - if os.path.exists(rootdir+"/etc/apt/apt.conf"): + if os.path.exists(rootdir + "/etc/apt/apt.conf"): apt_pkg.read_config_file(apt_pkg.config, rootdir + "/etc/apt/apt.conf") - if os.path.isdir(rootdir+"/etc/apt/apt.conf.d"): + if os.path.isdir(rootdir + "/etc/apt/apt.conf.d"): apt_pkg.read_config_dir(apt_pkg.config, rootdir + "/etc/apt/apt.conf.d") apt_pkg.config.set("Dir::State::status", rootdir + "/var/lib/dpkg/status") # also set dpkg to the rootdir path so that its called for the # --print-foreign-architectures call - apt_pkg.config.set("Dir::bin::dpkg", + apt_pkg.config.set("Dir::bin::dpkg", os.path.join(rootdir, "usr", "bin", "dpkg")) # create required dirs/files when run with special rootdir # automatically @@ -105,7 +106,6 @@ class Cache(object): # recognized (LP: #320665) apt_pkg.init_system() self.open(progress) - def _inc_changes_count(self): """Increase the number of changes""" @@ -165,8 +165,8 @@ class Cache(object): i = last = 0 size = len(self._cache.packages) for pkg in self._cache.packages: - if progress is not None and last+100 < i: - progress.update(i/float(size)*100) + if progress is not None and last + 100 < i: + progress.update(i / float(size) * 100) last = i # drop stuff with no versions (cruft) if pkg.has_versions: @@ -289,16 +289,14 @@ class Cache(object): # now check the result (this is the code from apt-get.cc) failed = False - transient = False err_msg = "" for item in fetcher.items: if item.status == item.STAT_DONE: continue if item.STAT_IDLE: - transient = True continue err_msg += "Failed to fetch %s %s\n" % (item.desc_uri, - item.error_text) + item.error_text) failed = True # we raise a exception if the download failed or it was cancelt @@ -349,7 +347,6 @@ class Cache(object): if fetcher is None: fetcher = apt_pkg.Acquire(progress) - return self._fetch_archives(fetcher, apt_pkg.PackageManager(self._depcache)) @@ -362,12 +359,12 @@ class Cache(object): else: return bool(pkg.has_provides and not pkg.has_versions) - def get_providing_packages(self, pkgname, candidate_only=True, + def get_providing_packages(self, pkgname, candidate_only=True, include_nonvirtual=False): """Return a list of all packages providing a package. - + Return a list of packages which provide the virtual package of the - specified name. + specified name. If 'candidate_only' is False, return all packages with at least one version providing the virtual package. Otherwise, @@ -378,7 +375,7 @@ class Cache(object): packages providing pkgname, even if pkgname is not itself a virtual pkg. """ - + providers = set() get_candidate_ver = self._depcache.get_candidate_ver try: @@ -423,7 +420,8 @@ class Cache(object): old_sources_list = apt_pkg.config.find("Dir::Etc::sourcelist") old_sources_list_d = apt_pkg.config.find("Dir::Etc::sourceparts") old_cleanup = apt_pkg.config.find("APT::List-Cleanup") - apt_pkg.config.set("Dir::Etc::sourcelist", os.path.abspath(sources_list)) + apt_pkg.config.set("Dir::Etc::sourcelist", + os.path.abspath(sources_list)) apt_pkg.config.set("Dir::Etc::sourceparts", "xxx") apt_pkg.config.set("APT::List-Cleanup", "0") slist = apt_pkg.SourceList() @@ -559,9 +557,9 @@ class Cache(object): @property def dpkg_journal_dirty(self): """Return True if the dpkg was interrupted - + All dpkg operations will fail until this is fixed, the action to - fix the system if dpkg got interrupted is to run + fix the system if dpkg got interrupted is to run 'dpkg --configure -a' as root. """ dpkg_status_dir = os.path.dirname( @@ -644,6 +642,19 @@ class Filter(object): return True +class InstalledFilter(Filter): + """ Filter that returns all installed packages + + .. versionadded:: 0.9.2 + """ + + def apply(self, pkg): + if pkg.is_installed: + return True + else: + return False + + class MarkedChangesFilter(Filter): """ Filter that returns all marked changes """ @@ -711,7 +722,6 @@ class FilteredCache(object): #print "filterCachePostChange()" self._reapply_filter() - # def connect(self, name, callback): # self.cache.connect(name, callback) @@ -750,8 +760,6 @@ def _test(): for pkg in changes: assert pkg.name - - # see if fetching works for dirname in ["/tmp/pytest", "/tmp/pytest/partial"]: if not os.path.exists(dirname): diff --git a/apt/package.py b/apt/package.py index 04d4ddd..7414fb2 100644 --- a/apt/package.py +++ b/apt/package.py @@ -19,6 +19,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Functionality related to packages.""" +import collections import httplib import os import sys @@ -26,17 +27,6 @@ import re import socket import subprocess import urllib2 -import warnings -try: - from collections import Mapping, Sequence -except ImportError: - # (for Python < 2.6) pylint: disable-msg=C0103 - Sequence = Mapping = object - -try: - from collections import Sequence -except ImportError: - Sequence = object import apt_pkg import apt.progress.text @@ -45,6 +35,14 @@ from apt_pkg import gettext as _ __all__ = ('BaseDependency', 'Dependency', 'Origin', 'Package', 'Record', 'Version', 'VersionList') +# Replaces for compatibility '<' with '<=' and '>' with '>='. Furthermore it +# can be used for relation validation. Source: +# http://www.debian.org/doc/debian-policy/ch-relationships.html +RELATION_COMPAT = { + '>': '>=', '<': '<=', # Translation of deprecated relations + '>>': '>>', '<<': '<<', '>=': '>=', '<=': '<=', '=': '=', '': '', # 1:1 +} + def _file_is_same(path, size, md5): """Return ``True`` if the file is the same.""" @@ -58,35 +56,135 @@ class FetchError(Exception): class BaseDependency(object): - """A single dependency. + """A single dependency.""" - Attributes defined here: - name - The name of the dependency - relation - The relation (>,>=,==,<,<=,) - version - The version depended on - rawtype - The type of the dependendy (e.g. 'Recommends') - pre_depend - Boolean value whether this is a pre-dependency. - """ + def __init__(self, cache, dep): + self._cache = cache # apt.cache.Cache + self._dep = dep # apt_pkg.Dependency + + @property + def installed_targets(self): + """`Version` object list of installed packages that satisfy the dep. + + Returns a list with all `Version` objects of installed packages that + satisfy the dependency. The returned list will be empty if no installed + package satisfies the dependency. + + .. versionadded:: 0.9.2 + """ + inst_targets = [] + for target in self.targets: # apt.package.Version + if target.installed: + inst_targets.append(target) + return inst_targets + + @property + def name(self): + """Return the name of the dependencies target package. + + If the package is not part of the system's preferred architecture, + return the same as :attr:`fullname`, otherwise return the same + as :attr:`shortname` + + .. versionchanged:: 0.9.2 + As part of multi-arch, this field now may include architecture + information. + """ + return self._dep.target_pkg.get_fullname(True) + + @property + def fullname(self): + """Return the full name of the dependencies target package. - class __dstr(str): - """Helper to make > match >> and < match <<""" + .. versionadded:: 0.9.2 + """ + return self._dep.target_pkg.get_fullname(False) - def __eq__(self, other): - return str.__eq__(self, other) or str.__eq__(2 * self, other) + @property + def shortname(self): + """Return the short name of the dependencies target package. - def __ne__(self, other): - return str.__eq__(self, other) and str.__ne__(2 * self, other) + .. versionadded:: 0.9.2 + """ + return self._dep.target_pkg.name - def __init__(self, name, rel, ver, pre, rawtype=None): - self.name = name - self.relation = len(rel) == 1 and self.__dstr(rel) or rel - self.version = ver - self.pre_depend = pre - self.rawtype = rawtype + @property + def pre_depend(self): + """Return boolean whether this is a pre-dependency.""" + return (self._dep.dep_type_untranslated == "PreDepends") + + @property + def rawstr(self): + """Return the dependency as string. + + The string is similar to how the dependency would be listed in the + control file of a package. + + Examples: + package + package (> 1.0) + """ + name = self.shortname + version = self.version + relation = self.relation + if not relation or not version: + return name + else: + return "{0} ({1} {2})".format(name, relation, version) + + @property + def rawtype(self): + """Return the dependeny type as string. + + Possible return values are: + 'Breaks', 'Conflicts', 'Depends', 'Enhances', + 'PreDepends', 'Recommends', 'Replaces', 'Suggests' + """ + return self._dep.dep_type_untranslated + + @property + def relation(self): + """The relation of the dependency as string. + + In case of an unversioned dependency the empty string will be returned. + + Possible return values are: + '', '>>', '>=', '=', '<<', '<=' + """ + return RELATION_COMPAT[self._dep.comp_type] + + @property + def targets(self): + """`Version` object list of packages that could satisfy the dep. + + Returns a list with all `Version` objects of packages that could satisfy + the dependency. The returned list will be empty if no available package + could satisfy the dependency. + + .. versionadded:: 0.9.2 + """ + target_vers = [] + for _target_ver in self._dep.all_targets(): # apt_pkg.Version + _target_pkg_name = _target_ver.parent_pkg.get_fullname(False) + _target_ver_str = _target_ver.ver_str + target_pkg = self._cache[_target_pkg_name] # apt.package.Package + pkg_vers = target_pkg.versions # apt.package.VersionList + target_ver = pkg_vers[_target_ver_str] # apt.package.Version + if target_ver not in target_vers: + target_vers.append(target_ver) + return target_vers + + @property + def version(self): + """The version of the dependency as string. + + In case of an unversioned dependency the empty string will be returned. + """ + return self._dep.target_ver def __repr__(self): - return ('<BaseDependency: name:%r relation:%r version:%r preDepend:%r>' - % (self.name, self.relation, self.version, self.pre_depend)) + return ('<BaseDependency: name:%r relation:%r version:%r rawtype:%r>' + % (self.name, self.relation, self.version, self.rawtype)) class Dependency(list): @@ -97,13 +195,73 @@ class Dependency(list): """ def __init__(self, alternatives): + assert alternatives, "Or-group without dependencies" super(Dependency, self).__init__() self.extend(alternatives) @property + def installed_targets(self): + """`Version` object list of installed packages that satisfy the or-dep. + + Returns a list with all `Version` objects of installed packages that + satisfy the Or-group of dependencies. The returned list will be empty if + no installed package satisfies the Or-group of dependencies. + + .. versionadded:: 0.9.2 + """ + inst_targets = [] + for dep in self: # apt.package.BaseDependency + for inst_target in dep.installed_targets: # apt.package.Version + if inst_target not in inst_targets: + inst_targets.append(inst_target) + return inst_targets + + @property def or_dependencies(self): return self + @property + def rawstr(self): + """Return the Or-group of dependencies as string. + + The string is similar to how the Or-group of dependencies would be + listed in the control file of a package. + + Examples: + package2 | package1 + libpackage (=> 1.2) | package (< 1.2) + """ + return " | ".join(map(lambda base_dep: base_dep.rawstr, self)) + + @property + def rawtype(self): + """Return the Or-group dependencies type as string. + + Possible return values are: + 'Breaks', 'Conflicts', 'Depends', 'Enhances', + 'PreDepends', 'Recommends', 'Replaces', 'Suggests' + """ + # There is always at least one dependency (BaseDependency) in an + # Or-group and all dependencies in an Or-group have the same rawtype + return self[0].rawtype + + @property + def targets(self): + """`Version` object list of packages that could satisfy the or-dep. + + Returns a list with all `Version` objects of packages that could satisfy + the Or-group of dependencies. The returned list will be empty if no + available package could satisfy the Or-group of dependencies. + + .. versionadded:: 0.9.2 + """ + targets = [] + for dep in self: # apt.package.BaseDependency + for target in dep.targets: # apt.package.Version + if target not in targets: + targets.append(target) + return targets + class Origin(object): """The origin of a version. @@ -140,7 +298,7 @@ class Origin(object): self.site, self.trusted) -class Record(Mapping): +class Record(collections.Mapping): """Record in a Packages file Represent a record as stored in a Packages file. You can use this like @@ -208,8 +366,8 @@ class Version(object): """ def __init__(self, package, cand): - self.package = package - self._cand = cand + self.package = package # apt.package.Package() + self._cand = cand # apt_pkg.Version() def _cmp(self, other): try: @@ -274,6 +432,14 @@ class Version(object): return self.package._pcache._records @property + def installed(self): + """Wether this version of the package is installed or not. + + .. versionadded:: 0.9.2 + """ + return (self.package.installed == self) + + @property def installed_size(self): """Return the size of the package when installed.""" return self._cand.installed_size @@ -407,28 +573,30 @@ class Version(object): return Record(self._records.record) def get_dependencies(self, *types): - """Return a list of Dependency objects for the given types.""" + """Return a list of Dependency objects for the given types. + The types can be: + 'Breaks', 'Conflicts', 'Depends', 'Enhances', 'PreDepends', + 'Recommends', 'Replaces', 'Suggests' + """ depends_list = [] depends = self._cand.depends_list for type_ in types: - try: - for dep_ver_list in depends[type_]: - base_deps = [] - for dep_or in dep_ver_list: - base_deps.append(BaseDependency(dep_or.target_pkg.name, - dep_or.comp_type, dep_or.target_ver, - (type_ == "PreDepends"), - rawtype=type_)) - depends_list.append(Dependency(base_deps)) - except KeyError: - pass + dep_lists = depends.get(type_) # [[apt_pkg.Dependency, ...], ...] + if dep_lists is None: + continue # No dependencies of this type + for dep_list in dep_lists: # [apt_pkg.Dependency, ...] + base_deps = [] + for dep in dep_list: # apt_pkg.Dependency + base_deps.append( + BaseDependency(self.package._pcache, dep)) + depends_list.append(Dependency(base_deps)) return depends_list @property def provides(self): """ Return a list of names that this version provides.""" return [p[0] for p in self._cand.provides_list] - + @property def enhances(self): """Return the list of enhances for the package version.""" @@ -612,7 +780,7 @@ class Version(object): return os.path.abspath(dsc) -class VersionList(Sequence): +class VersionList(collections.Sequence): """Provide a mapping & sequence interface to all versions of a package. This class can be used like a dictionary, where version strings are the @@ -633,8 +801,8 @@ class VersionList(Sequence): """ def __init__(self, package, slice_=None): - self._package = package # apt.package.Package() - self._versions = package._pkg.version_list # [apt_pkg.Version(), ...] + self._package = package # apt.package.Package() + self._versions = package._pkg.version_list # [apt_pkg.Version(), ...] if slice_: self._versions = self._versions[slice_] @@ -659,7 +827,7 @@ class VersionList(Sequence): return (Version(self._package, ver) for ver in self._versions) def __contains__(self, item): - if isinstance(item, Version): # Sequence interface + if isinstance(item, Version): # Sequence interface item = item.version # Dictionary interface. for ver in self._versions: @@ -917,10 +1085,10 @@ class Package(object): src_section = "main" # use the section of the candidate as a starting point section = self.candidate.section - + # get the source version src_ver = self.candidate.source_version - + try: # try to get the source version of the pkg, this differs # for some (e.g. libnspr4 on ubuntu) @@ -1173,7 +1341,6 @@ def _test(): print "homepage: %s" % pkg.candidate.homepage print "rec: ", pkg.candidate.record - print cache["2vcard"].get_changelog() for i in True, False: print "Running install on random upgradable pkgs with AutoFix: %s " % i diff --git a/debian/changelog b/debian/changelog index 52a1000..b3783cc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,24 @@ python-apt (0.9.2) UNRELEASED; urgency=low + [ Michael Vogt ] * apt/cache.py: - when using apt.Cache(rootdir=/some/dir) only read the APT configuration from this rootdir instead of /etc (closes: #728274) - -- Michael Vogt <michael.v...@ubuntu.com> Sat, 23 Nov 2013 08:49:51 +0100 + [ Michael Schaller ] + * apt/cache.py: + - Fixed PEP8 linter and pyflakes issues + - Added 'InstalledFilter' to get a filtered cache that only contains the + currently installed packages. + * apt/packages.py: + - Fixed PEP8 linter issues + - Removed special handling of 'collections' import as all supported + distributions have Python 2.6 or newer by now. + - Replaced faulty 'BaseDependency.__dstr' with easier to read compat code. + - Added new properties to 'Dependency' and 'BaseDependency' to get the + target package versions that could satisfy a dependency. + + -- Michael Schaller <mich...@5challer.de> Sat, 28 Dec 2013 21:25:43 +0100 python-apt (0.9.1) unstable; urgency=low -- 1.8.3.2