Proposal For rpm-objects module

"Adam T. Gautier" <adam_gautier-/[email protected]> Tue, 23 Mar 2004 14:06:47 +0000 (UTC)
Newsgroups gmane.linux.redhat.rpm.python
Message-ID <[email protected]>
I have been working on a python module called "RPM" maybe it should be 
called "rpm-objects"?  Anyway this is a start of the object break down 
but I still have lots of work.  I was planning on getting RPMFile 
started before posting.   I was also going to create a project around 
this on sourceforge or something.  No offense to Duke(I went to Harvard 
so down my nose I look) but would I be able to get access to the CVS 
repository to continue my development?  This is needed because I am 
developing this within the scope of a bigger open-source project called 
tiny-linux(a bit off topic, so I will not talk about here unless asked)  
which requires the rpm objects and a rewrite/feature add of yum.

Below is the code that I have been working on.  I split the objects into:

REMEMBER:  I am somewhat new to rpm-python and so these concepts are 
just what I have thought of so far and input from the community is 
appreciated since I still do not have a total scope of the rpmlib library.

Callback - Which handles the callback using the handler method.  This 
then calls the default noun-verb base method.  This object is designed 
to be subclassed with the noun-verb methods overloaded as needed to 
connect to UI objects (gtk and/or ncurses, ...).

CallbackError - Any errors that happen in the Callback object or 
subclassed Callback object.  This error should be inherited for any 
special callback exception/error and should have i18n support.

RPMFile - This object represents a RPM File... Go Figure...  I think 
this object should handle file IO operations and things like check 
signature, fetch the files Header object, etc.  Obviously not much work 
done on this object.  This object could be subclassed easily if the rpm 
file format changes.

RPMDatabase - Can you guess this one... I have started to fill this 
object out and this where I think that the transactions methods for 
update, install, erase.  And this is where the query methods have been 
added.  This object would do most of the heavy lifting in the module.  
The install and update methods would take an RPMFile object as a 
parameter.  This object can be subclassed to handle changes in the RPM 
Database format.  Also, notice the search(...) method, I exposed the 
rpmlib interface through the object, I think it is bad form but I am new 
to python and for the life of me have not figured out how to create 
public static constant member variables in python.  I think we need to 
have mappings in the objects to the rpmlib constants, just so users 
would not need to go hunting around the rpm source code for the complete 
listing.

RPMError - Default error for all exceptions in RPMDatabase and RPMFile 
objects.  Should this be subclasses into RPMDatabaseError and 
RPMFileError?  Anyway i18n support should be put in this and can be 
subclassed for special cases.

Header - Again, hard to figure out this one...  I have encapsulated most 
of the tags and lifted some code from yum for this one.  The __str__ 
method sucks but it was a quick hack so sue me...  But this object 
should have a constants map to the rpmlib constants and a public 
getAttribute() method so that user could call that directly instead of 
the convenience methods getName(), getSummary(), ...

HeaderError - All header exceptions. needs i18n support.



#!/usr/bin/python -tt
# 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 2 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 Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.


"""
This module is designed as a wrapper around the rpm module.  I found
that information about the rpm module is lacking and somewhat difficult
to implement because of all the hooks into the C code that makes up RPM.
The objects in the module should make the manipulation of RPM files and
the RPM database much easier.
"""
import gzip
import os
import rpm
import sys
import types
from time import ctime

class CallbackError(Exception):
    """
    Exception raised for all errors occuring in the Callback object..

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message   

    def __str__(self):
        return self.message
   
class Callback:
    """
    The Callback object is used to interact with user interfaces.
    The best example of this use would be a status bar showing the
    status of the transaction.
    """
   
    def handler(self, what, bytes, total, h, user):
        """
        The handler method is called as the callback method.  This method
        is already setup to process the basic breakdown of the state in
        which this method is called.  This method should not be overloaded,
        however all the other methods (transaction[...], install[...],
        uninstall[..])

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """
       
        if what == rpm.RPMCALLBACK_TRANS_START:
            self.transactionStart(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_TRANS_PROGRESS:
            self.transactionProgress(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_TRANS_STOP:
            self.transactionStop(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_INST_OPEN_FILE:
            self.installStart(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_INST_PROGRESS:
            self.installProgress(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
            self.installStop(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_UNINST_START:
            self.uninstallStart(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_UNINST_PROGRESS:
            self.uninstallProgress(bytes, total, h, user)
        elif what == rpm.RPMCALLBACK_UNINST_STOP:
            self.unistallStop(bytes, total, h, user)
        else:
            raise CallbackError("Unknown parameter [what="+str(what))

    def transactionStart(self, bytes, total, h, user):
        """
        Overload this method to process transaction starts

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def transactionProgress(self, bytes, total, h, user):
        """
        Overload this method to process transaction progress

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def transactionStop(self, bytes, total, h, user):
        """
        Overload this method to process transaction stop

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def installStart(self, bytes, total, h, user):
        """
        Overload this method to process install start

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def installProgress(self, bytes, total, h, user):
        """
        Overload this method to process install progress

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def installStop(self, bytes, total, h, user):
        """
        Overload this method to process install stop

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def uninstallStart(self, bytes, total, h, user):
        """
        Overload this method to process uninstall start

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def uninstallProgress(self, bytes, total, h, user):
        """
        Overload this method to process uninstall progress

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

    def uninstallStop(self, bytes, total, h, user):
        """
        Overload this method to process uninstall stop

        Parameters:
             what -- TO DO: Define
             bytes -- TO DO: Define
             total -- TO DO: Define
             h -- TO DO: Define
             user -- TO DO: Define
        """       
        pass

class RPMError(Exception):
    """
    Exception raised for all errors occuring in the RPMFile and RPMError
    object..

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message   

    def __str__(self):
        return self.message
   
class RPMFile:
    pass
    """
    The RPMFile object is used to interact with a RPM file.

    TO DO: Write this class.
    """


class RPMDatabase:
    """
    The RPMDatabase object encapsulates all functionality of the RPM
    Database.

    Attrbiutes:
         rpmdb -- Reference to rpm library
    """
   
    def __init__(self, callback):
        """
        Constructor for the RPMDatabase object.  This is where a user would
        define which callback object to use.
        """
        self.callback = callback

    def init(self, rpmroot="/"):
        """
        Initializes connection to rpm database.

        Parameters:
             rpmroot -- TO DO: Not sure what this is...
        """
        if(self.rpmdb is None): # Check that rpmdb is not already 
initialized
            try:   
                self.rpmdb = rpm.TransactionSet(rpmroot)
            except rpm.error, e:
                raise RPMError("RPM database could not be opened.")
        else:
            raise RPMError("RPM database already initialized.");

    def search(self, search=None, tag=None, type=None):
        """
        Generic search method.  This should be used for making complex
        searches into the rpm database.

        Parameters:
             search -- The search terms to use in the search
             tag -- The header tag to use, this defines what field to
                    use in the search
             type -- The type of search to use

        TO DO:  The tag(s) and mire(s) should be defined as global constants
        """
        if(None!=self.rpmdb):
            hdrlist = None
            headers = []

            hdrlist = self.rpmdb.dbMatch() # Loads header list
            # No search requested just return enire list
            if((None==tag) and (None==type) and (None==search)):
                pass
            # Basic search just field and term
            elif((None!=tag) and (None==type) and (None!=search)):
                hdrlist.pattern(tag, search)
            # Full search using field, type, and term
            elif((None!=tag) and (None!=type) and (None!=search)):
                hdrlist.pattern(tag, type, search)
            else:
                raise RPMError("Invalid search parameters 
[field="+str(tag)+\
                               "] [type="+str(type)+"] 
[search="+str(search)+
                               "]")
            # Convert hdrlist from rpm into list of Header objects
            for hdr in hdrlist:
                header = Header(hdr)
                headers.append(header)
            return headers
        else:
            raise RPMError("No RPM database exists for this object.")

    def searchByString(self, search):
        """
        Searches the header NAME field using the atring comparison
        search type.

        SEE: man strcmp(3)
        """
        return self.search(search,
                           rpm.RPMTAG_NAME,
                           rpm.RPMMIRE_STRCMP)

    def searchByRegEx(self, search):
        """
        Searches the header NAME field using the Regular Express search 
type.

        SEE: man regex(7), regcomp(3)
        """
        return self.search(search,
                           rpm.RPMTAG_NAME,
                           rpm.RPMMIRE_REGEX)

    def searchByPathname(self, search):
        """
        Searches the header NAME field using the GLOB search type. GLOB 
uses a
        file path(?) search.

        SEE: man glob(7), fnmatch(3)
        """
        return self.search(search,
                           rpm.RPMTAG_NAME,
                           rpm.RPMMIRE_GLOB)

    def list(self):
        """
        Lists all rpms in the database.
        """
        return self._search()

    def delete(self, package):
        """
        Erase specified package from the rpm database

        Parameters:
             package -- The name of the package to delete.
        """
        if(None!=self.rpmdb):
            self.rpmdb.addErase(package)
            self.rpmdb.run(self.callback.handler, '')
        else:
            raise RPMError("No RPM database exists for this object.");
           
    def close(self):
        """
        Closes the connection to the rpm database.
        """
        if(None!=self.rpmdb):
            self.rpmdb.closeDB()
            self.rpmdb = None
        else:
            raise RPMError("No RPM database exists for this object.");

class HeaderError(Exception):
    """
    Exception raised for all errors occuring in the RPMFile and RPMError
    object..

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, message):
        self.message = message   

    def __str__(self):
        return self.message
       
class Header:
    """
    The Header object encapsulates all functionality needed to interact with
    RPM header info.  This object almost exclusivly READ-ONLY since headers
    should not be modified.
    """
    """for operating on hdrs in and out of the rpmdb
    if the first arg is a string then it's a filename
    otherwise it's an rpm hdr"""
    def __init__(self, header):
        if type(header) is types.StringType:
            try:
                fd = gzip.open(header, 'r')
                try:
                    h = rpm.headerLoad(fd.read())
                except rpm.error, e:
                    raise HeaderError(("Damaged Header %s") % header)
                    h = None
            except IOError,e:
                fd = open(header, 'r')
                try:
                    h = rpm.headerLoad(fd.read())
                except rpm.error, e:
                    raise HeaderError(("Damaged Header %s") % header)
                    h = None
            except ValueError, e:
                raise HeaderError(("Damaged Header %s") % header)
                h = None
            except zlibError, e:
                raise HeaderError(("Damaged Header %s") % header)
                h = None
            fd.close()
        else:
            h = header
        self.hdr = h

    def _getTag(self, tag):
        if self.hdr is None:
            raise HeaderError("Got an empty Header, something has gone 
wrong")
            sys.exit(1)
        return self.hdr[tag]
   
    def isSource(self):
        if self._getTag(rpm.RPMTAG_SOURCE) == 1:
            return 1
        else:
            return 0

    def isPatch(self):
        if self._getTag(rpm.RPMTAG_PATCH) == 1:
            return 1
        else:
            return 0
       
    def getName(self):
        return self._getTag(rpm.RPMTAG_NAME)
       
    def getArchitecture(self):
        return self._getTag(rpm.RPMTAG_ARCH)
       
    def getVersion(self):
        return self._getTag(rpm.RPMTAG_VERSION)
       
    def getRelease(self):
        return self._getTag(rpm.RPMTAG_RELEASE)

    def getGroup(self):
        return self._getTag(rpm.RPMTAG_GROUP)

    def getVendor(self):
        return self._getTag(rpm.RPMTAG_VENDOR)

    def getSize(self):
        return self._getTag(rpm.RPMTAG_SIZE)

    def getLicense(self):
        return self._getTag(rpm.RPMTAG_LICENSE)

    def getSummary(self):
        return self._getTag(rpm.RPMTAG_SUMMARY)

    def getDescription(self):
        return self._getTag(rpm.RPMTAG_DESCRIPTION)   

    def getPackager(self):
        return self._getTag(rpm.RPMTAG_PACKAGER)

    def getBuildHost(self):
        return self._getTag(rpm.RPMTAG_BUILDHOST)

    def getBuildTime(self):
        return self._getTag(rpm.RPMTAG_BUILDTIME)

    def getDistribution(self):
        return self._getTag(rpm.RPMTAG_DISTRIBUTION)

    def getURL(self):
        return self._getTag(rpm.RPMTAG_URL)

    def getProvides(self):
        rtn = self._getTag(rpm.RPMTAG_PROVIDENAME)
        if(None!=rtn):
            return rtn
        else:
            return []
   
    def getRequires(self):
        rtn = self._getTag(rpm.RPMTAG_REQUIRENAME)
        if(None!=rtn):
            return rtn
        else:
            return []

    def __str__(self):
        rtn=""
        if(self.isSource()):
            rtn+="\nSOURCE RPM: "
        elif(self.isPatch()):
            rtn+="\nPATCHED BINARY RPM"
        else:
            rtn+="\nBINARY RPM: "
        rtn+=str(self.getName())+"-"+str(self.getVersion())+"-"+ \
              str(self.getRelease())+"\n"
        
rtn+="-----------------------------------------------------------------------\n"
        rtn+=str(self.getSummary())+"\n"
        
rtn+="-----------------------------------------------------------------------\n"
        rtn+="         Name: "+str(self.getName())+"\n"
        rtn+="      Version: "+str(self.getVersion())+"\n"
        rtn+="      Release: "+str(self.getRelease())+"\n"
        rtn+=" Architecture: "+str(self.getArchitecture())+"\n"
        rtn+="        Group: "+str(self.getGroup())+"\n"
        rtn+="       Vendor: "+str(self.getVendor())+"\n"
        rtn+=" Distribution: "+str(self.getDistribution())+"\n"
        rtn+="     Liscense: "+str(self.getLicense())+"\n\n"
        rtn+="          URL: "+str(self.getURL())+"\n"
        rtn+="     Packager: "+str(self.getPackager())+"\n"
        rtn+="   Build Host: "+str(self.getBuildHost())+"\n"
        rtn+="   Build Time: "+ctime(self.getBuildTime())+"\n"
        rtn+="\n"
        rtn+="\nDescription:\n"
        
rtn+="-----------------------------------------------------------------------\n"
        rtn+=str(self.getDescription())+"\n"
        
rtn+="-----------------------------------------------------------------------\n"
        rtn+="\nProvides:\n"
        
rtn+="-----------------------------------------------------------------------\n"
        for provide in self.getProvides():
            rtn+=" "+str(provide)+"\n"
        
rtn+="-----------------------------------------------------------------------\n"
        rtn+="\nRequires:\n"
        
rtn+="-----------------------------------------------------------------------\n"
        for require in self.getRequires():
            rtn+=" "+str(require)+"\n"
        
rtn+="-----------------------------------------------------------------------\n"
        
rtn+="=======================================================================\n\n"
       
        return rtn