/[gnue]/gnue-common/src/datasources/GDataObjects.py
ViewVC logotype

Diff of /gnue-common/src/datasources/GDataObjects.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.72.2.2 by siesel, Fri Oct 10 12:18:48 2003 UTC revision 1.72.2.3 by jcater, Wed Nov 19 02:07:06 2003 UTC
# Line 22  Line 22 
22  # GDataObjects.py  # GDataObjects.py
23  #  #
24  # DESCRIPTION:  # DESCRIPTION:
25  # Class  # This is a temporary backwards compatability file.
26    # Should be removed at some point.
27  #  #
28  # NOTES:  # NOTES:
29  #  #
30  # HISTORY:  # TODO: Generate warnings whenever anything in this file is accessed.
31  #  #
32    
33  from gnue.common.apps import GDebug  from Exceptions import *
34  from gnue.common.datasources import GConditions  from drivers.Base import *
 import string  
   
 postingRecordset = None  
   
 class Error(StandardError):  
   # Base exception  
   pass  
   
 class LoginError(Error):  
   # Raised when invalid login user/pass was provided  
   # Client should attempt to get better information and  
   # try again  
   pass  
   
 class ConnectError(Error):  
   # Raised when connection data is invalid (e.g., host not found, etc).  
   # Client should probably not attempt to relogin.  Exit gracefully  
   # with a reason.  
   pass  
   
 class ProviderNotSupportedError(Error):  
   # Raised when a datasource type is requested that the dbdriver  
   # does not support (e.g., not all dbdrivers support raw sql mode.)  
   pass  
   
 class ObjectTypeNotAvailableError(Error):  
   # Raised when a datasource type is requested that the dbdriver  
   # does not support (e.g., not all dbdrivers support raw sql mode.)  
   pass  
   
 class ReadOnlyError(Error):  
   # Raised when an attempt is made to update a read-only data object.  
   pass  
   
 class MasterDetailFieldMismatch(Error):  
   # Raised when a the number of master fields doesn't match the  
   # number of detail fields. (e.g., masterlink="id,subid"  
   # and detaillink="id" would be a problem; must be 1:1)  
   pass  
   
 class ConnectionError(Error):  
   # Generic error reading from the database connection  
   pass  
   
 class DataTypeNotAvailable(Error):  
   # Raised when a datatype is not supported by an database  
   # during writing a schema definition to the database  
   pass  
   
 class NoWriteSchemaSupport(Error):  
   # Raised when a database adapter doesn't support  
   # writing Schema to datasource  
   pass  
   
   
   
 ###########################################################  
 #  
 #  
 #  
 ###########################################################  
 class DataObject:  
   
   def __init__(self, connection):  
     self._connection = connection  
   
     self.masterlink = ""  
     self.detaillink = ""  
   
     self._masterfields = []  
     self._detailfields = []  
     self._staticCondition = None  
   
     self._masterObject = None  
     self._detailObjects = []  
     self._dataConnection = None  
     self._resultSetClass = ResultSet  
     self._fieldReferences = {}  # Set by GDataSource; lists all fields  
                                 # a client explicitly references  
   
     self._unboundFieldReferences = {}  # Contains names of all unbound  
                                        # field references  
   
     self._defaultValues = {}  
   
     self._unicodeMode = 0 # if set to true, datasources will unicode  
                           # strings instead of strings in local encoding  
                           # and if non unicode strings are passed to the  
                           # db driver a warning is raised (in 0.6.0 an  
                           # exception will be raised)  
   
     # TODO: This is to keep old code from breaking.  
     # TODO: 0.5.1 was last version to support  
     # TODO triggerExtensions (functionality is now  
     # TODO: encapsulated in Connection objects.  
     self.triggerExtensions = connection  
   
   # Do we have a master datasource?  
   def hasMaster(self):  
     return self._masterObject != None  
   
   # Do not over-ride by vendor code  
   def createResultSet(self, conditions={}, readOnly=0, masterRecordSet=None, sql=""):  
     return self._createResultSet(  
        GConditions.combineConditions(conditions, self._staticCondition),  
        readOnly=readOnly, masterRecordSet=masterRecordSet, sql=sql)  
   
   # Designed to be replaced by vendor-specific code  
   def _createResultSet(self, conditions={}, readOnly=0, masterRecordSet=None, \  
                        sql=""):  
     pass  
   
   # Do not over-ride by vendor code  
   def getQueryString(self,conditions={},forDetailSQL=None,additionalSQL=""):  
     return self._buildQuery(conditions,forDetailSQL,additionalSQL)  
   
   def createEmptyResultSet(self, readOnly=0, masterRecordSet=None):  
     return self._createEmptyResultSet(readOnly, masterRecordSet)  
   
   # Designed to be replaced by vendor-specific code  
   def _createEmptyResultSet(self, readOnly=0, masterRecordSet=None):  
     cond = GConditions.GCondition()  
     ceq = GConditions.GCeq(cond)  
     GConditions.GCConst(ceq,1,"number")  
     GConditions.GCConst(ceq,0,"number")  
     return self.createResultSet(conditions=cond, readOnly=readOnly,  
                                 masterRecordSet=masterRecordSet)  
   
   
   # Add a detail data object.  This dataobject will create a new resultset  
   # everytime this dataobject changes (new record, etc).  The optional  
   # handler will be called after the detail dataobject is notified.  The  
   # client application may wish to add a handler to know when the detail  
   # has been requeried.  handler is a method that takes two arguments:  
   # the master ResultSet and the detail ResultSet  
   def addDetailDataObject(self, dataObject, handler=None, **params):  
   
     dataObject.__dict__.update(params)  
   
     GDebug.printMesg (1,"Adding a master/detail relationship to DataObject")  
     dataObject._masterObject = self  
     dataObject._masterfields = string.split(hasattr(dataObject,'masterlink') and \  
                                  string.lower(dataObject.masterlink) or "", ',')  
     dataObject._detailfields = string.split(hasattr(dataObject,'detaillink') and \  
                                  string.lower(dataObject.detaillink) or "", ',')  
   
     if len(dataObject._masterfields) != len(dataObject._detailfields):  
       tmsg = _("master=%s; detail=%s") % (dataObject._masterfields, dataObject._detailfields)  
       raise MasterDetailFieldMismatch, tmsg  
   
     # Make sure "master" fields will be in our future query  
     for field in dataObject._masterfields:  
       self._fieldReferences[string.strip(field)] = ""  
   
     for field in dataObject._detailfields:  
       dataObject._fieldReferences[string.strip(field)] = ""  
   
     self._detailObjects.append ([dataObject, handler])  
   
   
   #  
   # Connect to database from a GConnection object  
   #  
   def connect(self):  
     self._dataConnection = self._connection.native  
     self._postConnect()  
   
   def commit(self):  
     pass  
   
   def rollback(self):  
     pass  
   
   
   #  
   # Schema (metadata) functions  
   #  
   
   # TODO: DEPRECATED!!!!!  
   def getSchemaTypes(self):  
     print "WARNING: Your app is calling GDataObject.getSchemaTypes()"  
     return self._connection.schema.types[:]  
   
   # TODO: DEPRECATED!!!!!  
   def getSchemaList(self, type=None):  
     print "WARNING: Your app is calling GDataObject.getSchemaList()"  
     return self._connection.schema.find(type=type)  
   
   # TODO: DEPRECATED!!!!!  
   def getSchemaByName(self, name, type=None):  
     print "WARNING: Your app is calling GDataObject.getSchemaByName()"  
     return self._connection.schema.findfirst(name=name, type=type)  
   
   
   
   
   # Called when new record master in master/detail is queried  
   def _masterRecordChanged(self, master):  
     GDebug.printMesg (5, 'Master Record Changed')  
     criteria = {}  
   
     # If a detail result set has already been created for a particular  
     # master record set, then just return/reuse this old set (after all,  
     # it may contain uncommitted changes)  
     if (not master.current._cachedDetailResultSets.has_key(self)) or \  
         ( not int(gConfig('CacheDetailRecords')) and \  
           not master.current._cachedDetailResultSets[self].isPending() ):  
       doQuery = None  
       for i in range(0, len(self._masterfields)):  
         GDebug.printMesg(10,"Adding criteria")  
         criteria[string.strip(self._detailfields[i])] = \  
             master.current.getField(string.strip(self._masterfields[i]))  
   
         #If all are set to None then this will prevent the details  
         #from being queried.  This happens are startup with blank master  
         #datasources.  
         doQuery = doQuery or master.current.getField(string.strip(self._masterfields[i]))  
   
         GDebug.printMesg(10,master.current.getField(self._masterfields[i]))  
       if doQuery:  
         master.current.addDetailResultSet(self.createResultSet(\  
              conditions=criteria, masterRecordSet=master.current))  
       else:  
         master.current.addDetailResultSet(self.createEmptyResultSet())  
 ##      master.current._cachedDetailResultSets[self] = \  
 ##          self.createResultSet(conditions=criteria, masterRecordSet=master.current)  
     return master.current._cachedDetailResultSets[self]  
   
   
 ###########################################################  
 #  
 #  
 #  
 ###########################################################  
 class ResultSet:  
   
   def __init__(self, dataObject, cursor=None,defaultValues={},masterRecordSet=None):  
      self._dataObject = dataObject  
      self._recordSetClass = RecordSet  
      self._cursor = cursor  
      self._cachedRecords = []  
      self._currentRecord = -1  
      self._masterRecordSet = masterRecordSet  
      self._readonly = 0  
      self._recordCount = 0  
      self._postingRecord = None  
   
      self._defaultValues = {}  
      self._defaultValues.update(defaultValues)  
   
      self.current = None  
   
      if masterRecordSet:  
        masterRecordSet.addDetailResultSet(self)  
   
   # Since we are overriding __len__  
   def __nonzero__(self):  
     return 1  
   
   # Return the # of records  
   def __len__(self):  
     return self.getRecordCount()  
   
   def __getitem__(self, index):  
     rs = self.getRecord(index)  
     if not rs:  
       raise IndexError  
     else:  
       return rs  
   
         
   # Returns whether this result set is read only or not  
   def isReadOnly(self):  
     return self._readonly  
   
   
   # Returns 1=At first record, 0=Not first record  
   def isFirstRecord(self):  
     return (self._currentRecord == 0)  
   
   
   # Returns 1=At last record, 0=Not last record  
   def isLastRecord(self):  
     if self._currentRecord < len(self._cachedRecords) - 1 or \  
        self._cacheNextRecord():  
       return 0  
     else:  
       return 1  
   
   
   # returns -1=No records in memory, #=Current record #  
   def getRecordNumber(self):  
     return self._currentRecord  
   
   
   # returns # of records currently loaded  
   def getCacheCount(self):  
     return len(self._cachedRecords)  
   
   # returns # of records the  
   def getRecordCount(self):  
     return self._recordCount  > 0 and self._recordCount or self.getCacheCount()  
   
   # Get a specific record (0=based)  
   def getRecord(self, record):  
     while (record + 1 > len(self._cachedRecords)) and self._cacheNextRecord():  
       pass  
   
     if record + 1 > len(self._cachedRecords):  
       return None  
     else:  
       return self._cachedRecords[record]  
   
   
   # move to record #, returns 1=New record loaded, 0=invalid #  
   def setRecord(self, record):  
   
     while (record > len(self._cachedRecords) -1) and self._cacheNextRecord():  
       pass  
   
     if record >= len(self._cachedRecords):  
       return None  
     else:  
       self._currentRecord = record  
       self.current = self._cachedRecords[self._currentRecord]  
       self.notifyDetailObjects()  
       return self.current  
   
   # returns 1=New record loaded, 0=No more records  
   def nextRecord(self):  
     if self._currentRecord + 1 == len(self._cachedRecords):  
       if not self._cacheNextRecord():  
         return None  
   
     self._currentRecord += 1  
     self.current = self._cachedRecords[self._currentRecord]  
     self.notifyDetailObjects()  
     return self.current  
   
   
   # returns 1=New record loaded, 0=At first record  
   def prevRecord(self):  
     if self._currentRecord < 1:  
       return None  
     else:  
       self._currentRecord -= 1  
       self.current = self._cachedRecords[self._currentRecord]  
       self.notifyDetailObjects()  
       return self.current  
   
   
   # returns 1=at first record, 0=No records loaded  
   def firstRecord(self):  
     if self._currentRecord < 0:  
       if not self._cacheNextRecord():  
         return None  
   
     self._currentRecord = 0  
     self.current = self._cachedRecords[0]  
     self.notifyDetailObjects()  
     return self.current  
   
   
   
   # returns 1=at last record, 0=No records loaded  
   def lastRecord(self):  
     if self._currentRecord == -1:  
       return None  
     else:  
       while self._cacheNextRecord():  
         pass  
       self._currentRecord = len(self._cachedRecords) - 1  
       self.current = self._cachedRecords[self._currentRecord]  
       self.notifyDetailObjects()  
       return self.current  
   
   
   
   # Insert a blank record after the current record  
   def insertRecord(self):  
     if self.isReadOnly():  
       # Provide better feedback??  
       tmsg =  _("Attempted to insert into a read only datasource")  
       raise ReadOnlyError, tmsg  
     else:  
       GDebug.printMesg(7,'Inserting a blank record')  
       self._currentRecord += 1  
       self._cachedRecords.insert(self._currentRecord, self._createEmptyRecord())  
       self._recordCount += 1  
       self.current = self._cachedRecords[self._currentRecord]  
   
       # Set any dataobject-wide default values  
       for field in self._dataObject._defaultValues.keys():  
         self.current.setField(field, self._dataObject._defaultValues[field],0)  
   
       # Set any resultset specific values  
       for field in self._defaultValues.keys():  
         self.current.setField(field, self._defaultValues[field],0)  
   
       # Pull any primary keys from a master record set  
       if self._masterRecordSet != None and hasattr(self._dataObject, '_masterfields'):  
         i = 0  
         for field in self._dataObject._masterfields:  
           self.current.setField(self._dataObject._detailfields[i],self._masterRecordSet.getField(field),0)  
           i += 1  
   
       self.notifyDetailObjects()  
       return self.current  
   
   
   # Returns 1=DataObject, or a detail resultset, has uncommitted changes  
   def isPending(self):  
     for rec in (self._cachedRecords):  
       if rec.isPending():  
         return 1  
       else:  
         for detail in rec._cachedDetailResultSets.values():  
           if detail.isPending():  
             return 1  
     return 0  
   
   
   # Returns 1=DataObject has uncommitted changes  
   def isRecordPending(self):  
     return self.current.isPending()  
   
   
   def getPostingRecordset(self):  
     global postingRecordset  
     print postingRecordset  
     return postingRecordset  
   
   # Post changes to the database  
   def post(self, foreign_keys={}):  
     global postingRecordset  
     # post our changes  
     self._update_cursor = self._dataObject._dataConnection.cursor()  
   
     recordPosition = 0  
     while recordPosition < len(self._cachedRecords):  
       self._postingRecord = self._cachedRecords[recordPosition]  
       postingRecordset = self._postingRecord  
       delete = self._postingRecord._emptyFlag or self._postingRecord._deleteFlag  
       if not delete:  
         # Flip the flag for 'default' values to true so that hidden  
         # default fields are included in insert statements  
         if self._postingRecord.isPending():  
           for field in self._dataObject._defaultValues.keys():  
             self._postingRecord._modifiedFlags[field] = 1  
   
         for field in foreign_keys.keys():  
           self._postingRecord._fields[field] = foreign_keys[field]  
           # Some DBs will throw an exception if you update a Primary Key  
           # (even if you are updating to the same value)  
           if self._postingRecord._insertFlag:  
             self._postingRecord._modifiedFlags[field] = 1  
   
         recordPosition += 1  
       else:  
         # Adjust the current record if a preceding record  
         # or the current record is deleted  
         if recordPosition <= self._currentRecord:  
           self._currentRecord -= 1  
         self._cachedRecords.pop(recordPosition)  
         self._recordCount -= 1  
   
       self._postingRecord.post()  
   
     # Move to record 0 if all preceding records were deleted  
     # (or set to -1 if all records were deleted)  
     if self._currentRecord < 0:  
       if len(self._cachedRecords):  
         self._currentRecord = 0  
       else:  
         self._currentRecord = -1  
 # TODO: I don't think we need this anymore  
 #    if self._currentRecord >= self._recordCount:  
 #      self._currentRecord = self._recordCount - 1  
   
   def notifyDetailObjects(self):  
     GDebug.printMesg(5,'Master record changed; Notifying Detail Objects')  
     for detail in self._dataObject._detailObjects:  
       if detail[1]:  
         detail[1].masterResultSetChanged(self,  
                                          detail[0]._masterRecordChanged(self))  
   
   
   # Returns 1=Field is bound to a database field  
   def isFieldBound(self, fieldName):  
     if self._dataObject._fieldReferences.has_key(fieldName):  
       return 1  
     else:  
       #TODO: the string.lower() line should never be called but is left  
       #TODO: here untill the code is cleaned up  
       return self._dataObject._fieldReferences.has_key(string.lower(fieldName))  
   
   
   # Load cacheCount number of new records  
   def _cacheNextRecord(self):  
     rs = self._loadNextRecord()  
     if rs:  
       self._dataObject._dataSource._onRecordLoaded(self._cachedRecords[-1])  
     return rs  
   
   
   
   ###  
   ### Methods below should be overridden by Vendor Specific functions  
   ### (_createEmptyRecord may not need to be overridden in all cases)  
   ###  
   
   # Load cacheCount number of new records  
   def _loadNextRecord(self):  
     return 0  
   
   # Create an empty recordset  
   def _createEmptyRecord(self):  
     return self._recordSetClass(self)  
   
   # Iterator support (Python 2.2+)  
   def __iter__(self):  
     return _ResultSetIter(self)  
   
   
 ###########################################################  
 #  
 #  
 #  
 ###########################################################  
 class RecordSet:  
   
   def __init__(self, parent, initialData={}, dbIdentifier=None, defaultData={}):  
     self._detailObjects = []  
     self._dbIdentifier = dbIdentifier  
     self._deleteFlag = 0  
     self._updateFlag = 0  
     self._parent = parent  
     self._modifiedFlags = {}      # If field name is present as a key,  
                                   # then field has been modified  
   
     self._cachedDetailResultSets = {}  
   
     self._initialData = initialData  
   
     if self._initialData and len(self._initialData):  
       self._insertFlag = 0  
       self._emptyFlag = 0  
       self._fields = {}  
       self._fields.update(initialData)  
     else:  
       self._insertFlag = 1  
       self._emptyFlag = 1  
       self._fields = {}  
       self._fields.update(defaultData)  
   
   def __setitem__(self, attr, val):  
     self.setField(attr, val)  
   
   def __getitem__(self, attr):  
     return self.getField(attr)  
   
   # Returns 1=Record has uncommitted changes  
   def isPending(self):  
   
     # The _insertFlag and _deleteFlag takes care of records that  
     # were inserted, but then deleted before a save (i.e., nothing to do)  
     if self._emptyFlag or self._insertFlag and self._deleteFlag:  
       return 0  
     else:  
       return self._insertFlag or self._deleteFlag or self._updateFlag  
   
   
   # Returns 1=Record is pending a deletion  
   def isDeleted(self):  
     if self._emptyFlag:  
       return 0  
     else:  
       return self._deleteFlag and not self._insertFlag  
   
   
   # Returns 1=Record is pending an update  
   def isModified(self):  
     if self._emptyFlag or self._insertFlag:  
       return 0  
     else:  
       return self._updateFlag  
   
   
   # Returns 1=Record is pending an insertion  
   def isInserted(self):  
     if self._emptyFlag:  
       return 0  
     else:  
       return self._insertFlag and not self._deleteFlag  
   
   
   # Returns 1=Record is empty (inserted, but no data set)  
   def isEmpty(self):  
     return self._emptyFlag  
   
   
   # Returns current value of "field"  
   def getField(self, field):  
     try:  
       return self._fields[field]  
     except KeyError:  
       try:  
   
         # TODO: When we're confident that  
         # TODO: all field names are lowercase,  
         # TODO: then this can be removed.  
   
         return self._fields[string.lower(field)]  
       except KeyError:  
         # If a field value has yet to be set  
         # (either from a query or via a setField),  
         # then _fields will not contain a key  
         # for the requested field even though  
         # the field name may still be valid.  
         return None  
   
   
   # Sets current value of "field"  
   # If trackMod is set to 0 then the modification flag isn't raised  
   def setField(self, field, value, trackMod = 1):  
     # If this field is bound to a datasource and the datasource is read only,  
     # generate an error.  
     if self._parent.isFieldBound(field) and self._parent.isReadOnly():  
       # Provide better feedback??  
       tmsg = _("Attempted to modify read only field '%s'") % field  
       raise ReadOnlyError, tmsg  
     else:  
       fn = string.lower(field)  
       self._fields[fn] = value  
       if trackMod == 1:  
         if self._parent.isFieldBound(field):  
           self._emptyFlag = 0  
           self._updateFlag = 1  
           self._modifiedFlags[fn] = 1  
   
           try:  
             self._parent._dataObject._dataSource._onModification(self)  
           except AttributeError:  
             pass  
     return value  
   
   # Batch mode of above setField method  
   # If trackMod is set to 0 then the modification flag isn't raised  
   def setFields(self, updateDict, trackMod = 1):  
     # If this field is bound to a datasource and the datasource is read only,  
     # generate an error.  
     for field in updateDict.keys():  
       self.setField(field, updateDict[field], trackMod)  
   
   
   # Returns 1=Field has been modified  
   def isFieldModified(self, fieldName):  
     if self._modifiedFlags.has_key (fieldName):  
       return 1  
     else:  
       #TODO: the string.lower() line should never be called but is left here  
       #TODO: until the code is clean  
       return self._modifiedFlags.has_key (string.lower(fieldName))  
   
   
   # Mark the current record as deleted  
   def delete(self):  
     if self._parent.isReadOnly():  
       # Provide better feedback??  
       tmsg = _("Attempted to delete from a read only datasource")  
       raise ReadOnlyError, tmsg  
     else:  
       self._deleteFlag = 1  
   
   
   # Posts changes to database  
   def post(self):  
     # Should a post() to a read only datasource cause a ReadOnlyError?  
     # It does no harm to attempt to post since nothing will be posted,  
     # But does this allow sloppy programming?  
   
     GDebug.printMesg(5,'Preparing to post datasource %s' %  self._parent._dataObject.name)  
   
     # Save the initial status so we know if any triggers changed us  
     status = (self._insertFlag, self._deleteFlag, self._updateFlag)  
   
     # Call the hooks for commit-level hooks  
     if not self._emptyFlag and hasattr(self._parent._dataObject,'_dataSource'):  
   
       if self._insertFlag and not self._deleteFlag:  
         self._parent._dataObject._dataSource._beforeCommitInsert(self)  
       elif self._deleteFlag and not self._insertFlag:  
         self._parent._dataObject._dataSource._beforeCommitDelete(self)  
       elif self._updateFlag:  
         self._parent._dataObject._dataSource._beforeCommitUpdate(self)  
   
     #  
     # If the record status changed while we were doing the triggers,  
     # start from the beginning and run the triggers again.  
     #  
     if status != (self._insertFlag, self._deleteFlag, self._updateFlag):  
       self.post()  
       return  
   
   
     if self.isPending():  
       GDebug.printMesg(5,'Posting datasource %s' % self._parent._dataObject.name)  
   
       if self.isPending():  
         self._postChanges()  
   
   
     # Post all detail records  
     for child in (self._cachedDetailResultSets.keys()):  
       c = self._cachedDetailResultSets[child]._dataObject  
       # Set the primary key for any new child records  
       fk = {}  
       for i in range(len(c._masterfields)):  
         fk[c._detailfields[i]] = self.getField(c._masterfields[i])  
   
       self._cachedDetailResultSets[child].post(foreign_keys=fk)  
   
   
   # Sets the ResultSet associated with this master record  
   def addDetailResultSet(self, resultSet):  
     self._cachedDetailResultSets[resultSet._dataObject] = resultSet  
   
   
   ###  
   ### Methods below should be over-written by Vendor Specific functions  
   ###  
   
   # Post any changes to database  
   def _postChanges(self):  
     return 1  
35    
36    
37  # Used to store schema data  # Used to store schema data
38    # TODO: Is this used anywhere??
39    import string
40  class Schema:  class Schema:
41    def __init__(self, attrs={}, getChildSchema=None):    def __init__(self, attrs={}, getChildSchema=None):
42      self.name = ''      self.name = ''
# Line 790  class Schema: Line 58  class Schema:
58    
59    
60    
 # A simple resultset iterator  
 # Lets you use ResultSets as:  
 #  
 #   for record in myResultSet:  
 #      blah  
 #  
 # NOTE: Python 2.2+  (but it won't get called in  
 #    Python 2.1 or below, so not a problem)  
 #  
 class _ResultSetIter:  
   def __init__(self, resultset):  
     self.resultset = resultset  
     self.used = 0  
     self.done = 0  
   
   def __iter__(self):  
     return self  
   
   def next(self):  
     if self.done:  
       raise StopIteration  
     if self.used:  
       rs = self.resultset.firstRecord()  
     else:  
       rs = self.resultset.nextRecord()  
   
     if not rs:  
       raise StopIteration  
     else:  
       return rs  
   

Legend:
Removed from v.1.72.2.2  
changed lines
  Added in v.1.72.2.3

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26