diff options
| author | Julian Andres Klode <jak@debian.org> | 2009-03-08 16:46:39 +0100 |
|---|---|---|
| committer | Julian Andres Klode <jak@debian.org> | 2009-03-08 16:46:39 +0100 |
| commit | a81e4f2f93808aeaeac1887aba9b41c1ee1c9b11 (patch) | |
| tree | 6a0a7f10ce059ff04c60813cb7c5bc24e4e867ad | |
| parent | dc7440e55ea06cc4e0a6e53cd7bb6e3eee3af452 (diff) | |
| download | python-apt-a81e4f2f93808aeaeac1887aba9b41c1ee1c9b11.tar.gz | |
* apt/package.py: Introduce the Version class
- Deprecate Package.candidate*() and Package.installed*(), except for
installedFiles.
- Provide Version.get_source() (LP: #118788)
| -rw-r--r-- | apt/package.py | 550 | ||||
| -rw-r--r-- | debian/changelog | 4 | ||||
| -rw-r--r-- | doc/source/apt/package.rst | 5 |
3 files changed, 337 insertions, 222 deletions
diff --git a/apt/package.py b/apt/package.py index ecfd8b61..a44eb2a6 100644 --- a/apt/package.py +++ b/apt/package.py @@ -21,15 +21,19 @@ """Functionality related to packages.""" import gettext import httplib +import os import sys import re import socket +import subprocess import urllib2 +import warnings import apt_pkg +import apt.progress - -__all__ = 'BaseDependency', 'Dependency', 'Origin', 'Package', 'Record' +__all__ = ('BaseDependency', 'Dependency', 'Origin', 'Package', 'Record', + 'Version') # Set a timeout for the changelog download @@ -57,6 +61,10 @@ class BaseDependency(object): self.version = ver self.preDepend = pre + def __repr__(self): + return ('<BaseDependency: name:%r relation:%r version:%r preDepend:%r>' + % (self.name, self.relation, self.version, self.preDepend)) + class Dependency(object): """Represent an Or-group of dependencies. @@ -68,6 +76,26 @@ class Dependency(object): def __init__(self, alternatives): self.or_dependencies = alternatives + def __repr__(self): + return repr(self.or_dependencies) + +class DeprecatedProperty(property): + """A property which gives DeprecationWarning on access. + + This is only used for providing the properties in Package, which have been + replaced by the ones in Version. + """ + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + property.__init__(self, fget, fset, fdel, doc) + self.__doc__ = ':Deprecated: ' + (doc or fget.__doc__ or '') + + def __get__(self, obj, type=None): + warnings.warn("Accessed deprecated property %s.%s, please see the " + "Version class for alternatives." % + ((obj.__class__.__name__ or type.__name__), + self.fget.func_name), DeprecationWarning, 2) + return property.__get__(self, obj, type) class Origin(object): """The origin of a version. @@ -95,10 +123,10 @@ class Origin(object): self.trusted = False def __repr__(self): - return ("<Origin component:'%s' archive:'%s' origin:'%s' label:'%s'" - "site:'%s' isTrusted:'%s'>") % (self.component, self.archive, - self.origin, self.label, - self.site, self.trusted) + return ("<Origin component:%r archive:%r origin:%r label:%r " + "site:%r isTrusted:%r>") % (self.component, self.archive, + self.origin, self.label, + self.site, self.trusted) class Record(object): @@ -139,6 +167,208 @@ class Record(object): """deprecated form of 'key in x'.""" return self._rec.has_key(key) +class Version(object): + """Representation of a package version. + + :since: 0.7.9 + """ + + def __init__(self, package, cand): + self.package = package + self._cand = cand + + def __eq__(self, other): + return self._cand.ID == other._cand.ID + + def __gt__(self, other): + return apt_pkg.VersionCompare(self.version, other.version) > 0 + + def __lt__(self, other): + return apt_pkg.VersionCompare(self.version, other.version) < 0 + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return self._cand.Hash + + def __repr__(self): + return '<Version: package:%r version:%r>' % (self.package.name, + self.version) + + @property + def _records(self): + """Internal helper that moves the Records to the right position.""" + if self.package._records.Lookup(self._cand.FileList[0]): + return self.package._records + + @property + def installed_size(self): + """Return the size of the package when installed.""" + return self._cand.InstalledSize + + @property + def homepage(self): + """Return the homepage for the package.""" + return self._records.Homepage + + @property + def size(self): + """Return the size of the package.""" + return self._cand.Size + + @property + def architecture(self): + """Return the architecture of the package version.""" + return self._cand.Arch + + @property + def downloadable(self): + """Return whether the version of the package is downloadable.""" + return bool(self._cand.Downloadable) + + @property + def version(self): + """Return the version as a string.""" + return self._cand.VerStr + + @property + def summary(self): + """Return the short description (one line summary).""" + desc_iter = self._cand.TranslatedDescription + self.package._records.Lookup(desc_iter.FileList.pop(0)) + return self.package._records.ShortDesc + + @property + def raw_description(self): + """return the long description (raw).""" + return self._records.LongDesc + + @property + def section(self): + """Return the section of the package.""" + return self._cand.Section + + @property + def description(self, format=True, useDots=False): + """Return the formatted long description. + + Return the formated long description according to the Debian policy + (Chapter 5.6.13). + See http://www.debian.org/doc/debian-policy/ch-controlfields.html + for more information. + """ + self.summary # This does the lookup for us. + desc = '' + try: + dsc = unicode(self.package._records.LongDesc, "utf-8") + except UnicodeDecodeError, err: + return _("Invalid unicode in description for '%s' (%s). " + "Please report.") % (self.name, err) + + lines = iter(dsc.split("\n")) + # Skip the first line, since its a duplication of the summary + lines.next() + for raw_line in lines: + if raw_line.strip() == ".": + # The line is just line break + if not desc.endswith("\n"): + desc += "\n\n" + continue + if raw_line.startswith(" "): + # The line should be displayed verbatim without word wrapping + if not desc.endswith("\n"): + line = "\n%s\n" % raw_line[2:] + else: + line = "%s\n" % raw_line[2:] + elif raw_line.startswith(" "): + # The line is part of a paragraph. + if desc.endswith("\n") or desc == "": + # Skip the leading white space + line = raw_line[1:] + else: + line = raw_line + else: + line = raw_line + # Add current line to the description + desc += line + return desc + + @property + def source_name(self): + """Return the name of the source package.""" + try: + return self._records.SourcePkg or self.package.name + except IndexError: + return self.package.name + + @property + def priority(self): + """Return the priority of the package, as string.""" + return self._cand.PriorityStr + + @property + def record(self): + """Return a Record() object for this version.""" + return Record(self._records.Record) + + @property + def dependencies(self): + """Return the dependencies of the package version.""" + depends_list = [] + depends = self._cand.DependsList + for t in ["PreDepends", "Depends"]: + try: + for depVerList in depends[t]: + base_deps = [] + for depOr in depVerList: + base_deps.append(BaseDependency(depOr.TargetPkg.Name, + depOr.CompType, depOr.TargetVer, + (t == "PreDepends"))) + depends_list.append(Dependency(base_deps)) + except KeyError: + pass + return depends_list + + @property + def origins(self): + """Return a list of origins for the package version.""" + origins = [] + for (verFileIter, index) in self._cand.FileList: + origins.append(Origin(self.package, verFileIter)) + return origins + + def fetch_source(self): + """Get the source code of a package""" + src = apt_pkg.GetPkgSrcRecords() + acq = apt_pkg.GetAcquire(apt.progress.TextFetchProgress()) + dsc = None + src.Lookup(self.package.name) + try: + while self.version != src.Version: + src.Lookup(self.package.name) + except AttributeError: + raise ValueError("No source for %r" % self) + for md5, size, path, type in src.Files: + base = os.path.basename(path) + if type == 'dsc': + dsc = base + if os.path.exists(base) and os.path.getsize(base) == size: + fobj = open(base) + try: + if apt_pkg.md5sum(fobj) == md5: + print 'Ignoring already existing file', base + continue + finally: + fobj.close() + apt_pkg.GetPkgAcqFile(acq, src.Index.ArchiveURI(path), md5, size, + base) + acq.Run() + + outdir = src.Package + '-' + apt_pkg.UpstreamVersion(src.Version) + subprocess.check_call(["dpkg-source", "-x", dsc, outdir]) + return os.path.abspath(outdir) + class Package(object): """Representation of a package in a cache. @@ -158,28 +388,23 @@ class Package(object): self._pcache = pcache # python cache in cache.py self._changelog = "" # Cached changelog - def _lookupRecord(self, UseCandidate=True): - """Internal helper that moves the Records to the right position. + def __repr__(self): + return '<Package: name:%r id:%r>' % (self._pkg.Name, self._pkg.ID) - Must be called before _records is accessed. - """ - if UseCandidate: - ver = self._depcache.GetCandidateVer(self._pkg) - else: - ver = self._pkg.CurrentVer + @property + def candidate(self): + """Return the candidate version of the package. - # check if we found a version - if ver is None: - #print "No version for: %s (Candidate: %s)" % (self._pkg.Name, - # UseCandidate) - return False + :since: 0.7.9""" + return Version(self, self._depcache.GetCandidateVer(self._pkg)) - if ver.FileList is None: - print "No FileList for: %s " % self._pkg.Name() - return False - f, index = ver.FileList.pop(0) - self._records.Lookup((f, index)) - return True + @property + def installed(self): + """Return the currently installed version of the package. + + :since: 0.7.9""" + if self._pkg.CurrentVer is not None: + return Version(self, self._pkg.CurrentVer) @property def name(self): @@ -199,142 +424,81 @@ class Package(object): This returns the same value as ID, which is unique.""" return self._pkg.ID - @property + @DeprecatedProperty def installedVersion(self): - """Return the installed version as string.""" - ver = self._pkg.CurrentVer - if ver is not None: - return ver.VerStr - else: - return None + """Return the installed version as string. - @property + Deprecated, please use installed.version instead.""" + return getattr(self.installed, 'version', None) + + @DeprecatedProperty def candidateVersion(self): """Return the candidate version as string.""" - ver = self._depcache.GetCandidateVer(self._pkg) - if ver is not None: - return ver.VerStr - else: - return None - - def _getDependencies(self, ver): - """Get the dependencies for a given version of a package.""" - depends_list = [] - depends = ver.DependsList - for t in ["PreDepends", "Depends"]: - try: - for depVerList in depends[t]: - base_deps = [] - for depOr in depVerList: - base_deps.append(BaseDependency(depOr.TargetPkg.Name, - depOr.CompType, depOr.TargetVer, - (t == "PreDepends"))) - depends_list.append(Dependency(base_deps)) - except KeyError: - pass - return depends_list + return self.candidate.version - @property + @DeprecatedProperty def candidateDependencies(self): """Return a list of candidate dependencies.""" - candver = self._depcache.GetCandidateVer(self._pkg) - if candver is None: - return [] - return self._getDependencies(candver) + return self.candidate.dependencies - @property + @DeprecatedProperty def installedDependencies(self): """Return a list of installed dependencies.""" - ver = self._pkg.CurrentVer - if ver is None: - return [] - return self._getDependencies(ver) + return getattr(self.installed, 'dependencies', []) - @property + @DeprecatedProperty def architecture(self): """Return the Architecture of the package""" - if not self._lookupRecord(): - return None - sec = apt_pkg.ParseSection(self._records.Record) - try: - return sec["Architecture"] - except KeyError: - return None - - def _downloadable(self, useCandidate=True): - """Return True if the package is downloadable.""" - if useCandidate: - ver = self._depcache.GetCandidateVer(self._pkg) - else: - ver = self._pkg.CurrentVer - if ver is None: - return False - return ver.Downloadable + return self.candidate.architecture - @property + @DeprecatedProperty def candidateDownloadable(self): """Return True if the candidate is downloadable.""" - return self._downloadable(True) + return self.candidate.downloadable - @property + @DeprecatedProperty def installedDownloadable(self): """Return True if the installed version is downloadable.""" - return self._downloadable(False) + return getattr(self.installed, 'downloadable', False) - @property + @DeprecatedProperty def sourcePackageName(self): """Return the source package name as string.""" - if not self._lookupRecord(): - if not self._lookupRecord(False): + try: + return self.candidate._records.SourcePkg or self._pkg.Name + except AttributeError: + try: + return self.installed._records.SourcePkg or self._pkg.Name + except AttributeError: return self._pkg.Name - src = self._records.SourcePkg - if src != "": - return src - else: - return self._pkg.Name - @property + @DeprecatedProperty def homepage(self): """Return the homepage field as string.""" - if not self._lookupRecord(): - return None - return self._records.Homepage + return self.candidate.homepage @property def section(self): """Return the section of the package.""" return self._pkg.Section - @property + @DeprecatedProperty def priority(self): """Return the priority (of the candidate version).""" - ver = self._depcache.GetCandidateVer(self._pkg) - if ver: - return ver.PriorityStr - else: - return None + return self.candidate.priority - @property + @DeprecatedProperty def installedPriority(self): """Return the priority (of the installed version).""" - ver = self._depcache.GetCandidateVer(self._pkg) - if ver: - return ver.PriorityStr - else: - return None + return getattr(self.installed, 'priority', None) - @property + @DeprecatedProperty def summary(self): """Return the short description (one line summary).""" - if not self._lookupRecord(): - return "" - ver = self._depcache.GetCandidateVer(self._pkg) - desc_iter = ver.TranslatedDescription - self._records.Lookup(desc_iter.FileList.pop(0)) - return self._records.ShortDesc + return self.candidate.summary - @property - def description(self, format=True, useDots=False): + @DeprecatedProperty + def description(self): """Return the formatted long description. Return the formated long description according to the Debian policy @@ -342,73 +506,22 @@ class Package(object): See http://www.debian.org/doc/debian-policy/ch-controlfields.html for more information. """ - if not format: - return self.rawDescription - if not self._lookupRecord(): - return "" - # get the translated description - ver = self._depcache.GetCandidateVer(self._pkg) - desc_iter = ver.TranslatedDescription - self._records.Lookup(desc_iter.FileList.pop(0)) - desc = "" - try: - dsc = unicode(self._records.LongDesc, "utf-8") - except UnicodeDecodeError, err: - dsc = _("Invalid unicode in description for '%s' (%s). " - "Please report.") % (self.name, err) - lines = dsc.split("\n") - for i in range(len(lines)): - # Skip the first line, since its a duplication of the summary - if i == 0: - continue - raw_line = lines[i] - if raw_line.strip() == ".": - # The line is just line break - if not desc.endswith("\n"): - desc += "\n" - continue - elif raw_line.startswith(" "): - # The line should be displayed verbatim without word wrapping - if not desc.endswith("\n"): - line = "\n%s\n" % raw_line[2:] - else: - line = "%s\n" % raw_line[2:] - elif raw_line.startswith(" "): - # The line is part of a paragraph. - if desc.endswith("\n") or desc == "": - # Skip the leading white space - line = raw_line[1:] - else: - line = raw_line - else: - line = raw_line - # Use dots for lists - if useDots: - line = re.sub(r"^(\s*)(\*|0|o|-) ", ur"\1\u2022 ", line, 1) - # Add current line to the description - desc += line - return desc + return self.candidate.description - @property + @DeprecatedProperty def rawDescription(self): """return the long description (raw).""" - if not self._lookupRecord(): - return "" - return self._records.LongDesc + return self.candidate.raw_description - @property + @DeprecatedProperty def candidateRecord(self): """Return the Record of the candidate version of the package.""" - if not self._lookupRecord(True): - return None - return Record(self._records.Record) + return self.candidate.record - @property + @DeprecatedProperty def installedRecord(self): """Return the Record of the candidate version of the package.""" - if not self._lookupRecord(False): - return None - return Record(self._records.Record) + return getattr(self.installed, 'record', '') # depcache states @@ -464,34 +577,25 @@ class Package(object): # sizes - @property + @DeprecatedProperty def packageSize(self): """Return the size of the candidate deb package.""" - ver = self._depcache.GetCandidateVer(self._pkg) - return ver.Size + return self.candidate.size - @property + @DeprecatedProperty def installedPackageSize(self): """Return the size of the installed deb package.""" - ver = self._pkg.CurrentVer - return ver.Size + return getattr(self.installed, 'size', 0) - @property - def candidateInstalledSize(self, UseCandidate=True): + @DeprecatedProperty + def candidateInstalledSize(self): """Return the size of the candidate installed package.""" - ver = self._depcache.GetCandidateVer(self._pkg) - if ver: - return ver.Size - else: - return None + return self.candidate.installed_size - @property + @DeprecatedProperty def installedSize(self): """Return the size of the currently installed package.""" - ver = self._pkg.CurrentVer - if ver is None: - return 0 - return ver.InstalledSize + return getattr(self.installed, 'installed_size', 0) @property def installedFiles(self): @@ -651,16 +755,18 @@ class Package(object): "check your Internet connection.") return self._changelog - @property + @DeprecatedProperty def candidateOrigin(self): - """Return the Origin() of the candidate version.""" - ver = self._depcache.GetCandidateVer(self._pkg) - if not ver: - return None - origins = [] - for (verFileIter, index) in ver.FileList: - origins.append(Origin(self, verFileIter)) - return origins + """Return a list of Origin() objects for the candidate version.""" + return self.candidate.origins + + @property + def versions(self): + """Return a list of versions. + + :since: 0.7.9 + """ + return [Version(self, ver) for ver in self._pkg.VersionList] # depcache actions @@ -751,26 +857,26 @@ def _test(): pkg = Package(cache, depcache, records, sourcelist, None, pkgiter) print "Name: %s " % pkg.name print "ID: %s " % pkg.id - print "Priority (Candidate): %s " % pkg.priority - print "Priority (Installed): %s " % pkg.installedPriority - print "Installed: %s " % pkg.installedVersion - print "Candidate: %s " % pkg.candidateVersion - print "CandidateDownloadable: %s" % pkg.candidateDownloadable - print "CandidateOrigins: %s" % pkg.candidateOrigin - print "SourcePkg: %s " % pkg.sourcePackageName + print "Priority (Candidate): %s " % pkg.candidate.priority + print "Priority (Installed): %s " % pkg.installed.priority + print "Installed: %s " % pkg.installed.version + print "Candidate: %s " % pkg.candidate.version + print "CandidateDownloadable: %s" % pkg.candidate.downloadable + print "CandidateOrigins: %s" % pkg.candidate.origins + print "SourcePkg: %s " % pkg.candidate.source_name print "Section: %s " % pkg.section - print "Summary: %s" % pkg.summary - print "Description (formated) :\n%s" % pkg.description - print "Description (unformated):\n%s" % pkg.rawDescription - print "InstalledSize: %s " % pkg.installedSize - print "PackageSize: %s " % pkg.packageSize - print "Dependencies: %s" % pkg.installedDependencies - for dep in pkg.candidateDependencies: + print "Summary: %s" % pkg.candidate.summary + print "Description (formated) :\n%s" % pkg.candidate.description + print "Description (unformated):\n%s" % pkg.candidate.raw_description + print "InstalledSize: %s " % pkg.candidate.installed_size + print "PackageSize: %s " % pkg.candidate.size + print "Dependencies: %s" % pkg.installed.dependencies + for dep in pkg.candidate.dependencies: print ",".join("%s (%s) (%s) (%s)" % (o.name, o.version, o.relation, o.preDepend) for o in dep.or_dependencies) - print "arch: %s" % pkg.architecture - print "homepage: %s" % pkg.homepage - print "rec: ", pkg.candidateRecord + print "arch: %s" % pkg.candidate.architecture + print "homepage: %s" % pkg.candidate.homepage + print "rec: ", pkg.candidate.record # now test install/remove progress = apt.progress.OpTextProgress() diff --git a/debian/changelog b/debian/changelog index 81e25b17..1ed96cc3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,10 @@ python-apt (0.7.9~exp3) experimental; urgency=low [ Julian Andres Klode ] * apt/gtk/widgets.py: - Handle older versions of python-gobject which do not ship glib + * apt/package.py: Introduce the Version class + - Deprecate Package.candidate*() and Package.installed*(), except for + installedFiles. + - Provide Version.get_source() (LP: #118788) [ Michael Vogt ] * aptsources/distro.py: diff --git a/doc/source/apt/package.rst b/doc/source/apt/package.rst index 1bd032ab..6781717c 100644 --- a/doc/source/apt/package.rst +++ b/doc/source/apt/package.rst @@ -10,6 +10,11 @@ The Package class .. autoclass:: Package :members: +The Version class +----------------- +.. autoclass:: Version + :members: + Dependency Information ---------------------- |
