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