Blob Blame History Raw
From 38cc67385fb1b36aa0881bc5982bc58d75dac464 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hr=C3=A1zk=C3=BD?= <lhrazky@redhat.com>
Date: Wed, 11 Nov 2020 18:45:11 +0100
Subject: [PATCH] Package: add a get_header() method (RhBug:1876606)

Adds get_header() method to the Package class, which returns the rpm
header of an installed package.

= changelog =
msg: Add get_header() method to the Package class
type: enhancement
resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1876606
---
 dnf/package.py        | 24 ++++++++++++++++++++++++
 tests/test_package.py | 12 ++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/dnf/package.py b/dnf/package.py
index baef04fa5b..836e0e4989 100644
--- a/dnf/package.py
+++ b/dnf/package.py
@@ -26,11 +26,13 @@
 from dnf.i18n import _
 
 import binascii
+import dnf.exceptions
 import dnf.rpm
 import dnf.yum.misc
 import hawkey
 import logging
 import os
+import rpm
 
 logger = logging.getLogger("dnf")
 
@@ -95,6 +97,11 @@ def from_repo(self):
 
     @property
     def _header(self):
+        """
+        Returns the header of a locally present rpm package file. As opposed to
+        self.get_header(), which retrieves the header of an installed package
+        from rpmdb.
+        """
         return dnf.rpm._header(self.localPkg())
 
     @property
@@ -164,6 +171,23 @@ def debugsource_name(self):
         src_name = self.source_name if self.source_name is not None else self.name
         return src_name + self.DEBUGSOURCE_SUFFIX
 
+    def get_header(self):
+        """
+        Returns the rpm header of the package if it is installed. If not
+        installed, returns None. The header is not cached, it is retrieved from
+        rpmdb on every call. In case of a failure (e.g. when the rpmdb changes
+        between loading the data and calling this method), raises an instance
+        of PackageNotFoundError.
+        """
+        if not self._from_system:
+            return None
+
+        try:
+            # RPMDBI_PACKAGES stands for the header of the package
+            return next(self.base._ts.dbMatch(rpm.RPMDBI_PACKAGES, self.rpmdbid))
+        except StopIteration:
+            raise dnf.exceptions.PackageNotFoundError("Package not found when attempting to retrieve header", str(self))
+
     @property
     def source_debug_name(self):
         # :api
diff --git a/tests/test_package.py b/tests/test_package.py
index cd4872e631..514e5bf099 100644
--- a/tests/test_package.py
+++ b/tests/test_package.py
@@ -68,6 +68,18 @@ def fn_getter():
             with self.assertRaises(IOError):
                 pkg._header
 
+    # rpm.hdr() is not easy to construct with custom data, we just return a string
+    # instead, as we don't actually need an instance of rpm.hdr for the test
+    @mock.patch("rpm.TransactionSet.dbMatch", lambda self, a, b: iter(["package_header_test_data"]))
+    def test_get_header(self):
+        pkg = self.sack.query().installed().filter(name="pepper")[0]
+        header = pkg.get_header()
+        self.assertEqual(header, "package_header_test_data")
+
+        pkg = self.sack.query().available().filter(name="pepper")[0]
+        header = pkg.get_header()
+        self.assertEqual(header, None)
+
     @mock.patch("dnf.package.Package.rpmdbid", long(3))
     def test_idx(self):
         """ pkg.idx is an int. """