home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / hplip / base / kirbybase.py < prev    next >
Encoding:
Python Source  |  2007-04-04  |  63.9 KB  |  1,532 lines

  1. """
  2. KirbyBase v1.8.1 (applied patch introduced in v1.8.2)
  3.  
  4. Contains classes for a plain-text, client-server dbms.
  5.  
  6. NOTE: This copy has been modified from the standard distribution of KirbyBase. 
  7. To get the complete distribution of KirbyBase, please visit: 
  8. http://www.netpromi.com/kirbybase.html
  9.  
  10. Classes:
  11.     KirbyBase - database class
  12.     KBError - exceptions
  13.  
  14. Example:
  15.     from db import *
  16.  
  17.     db = KirbyBase()
  18.     db.create('plane.tbl', ['name:str', 'country:str', 'speed:int',
  19.      'range:int'])
  20.     db.insert('plane.tbl', ['P-51', 'USA', 403, 1201])
  21.     db.insert('plane.tbl', ['P-38', 'USA', 377, 999])
  22.     db.select('plane.tbl', ['country', 'speed'], ['USA', '>400'])
  23.     db.update('plane.tbl', ['country'], ['USA'], ['United States'],
  24.      ['country'])
  25.     db.delete('plane.tbl', ['speed'], ['<400'])
  26.     db.close()
  27.  
  28. Author:
  29.     Jamey Cribbs -- jcribbs@twmi.rr.com
  30.     www.netpromi.com
  31.  
  32. License:
  33.     KirbyBase is licensed under the Python Software Foundation License.
  34.     KirbyBase carries no warranty!  Use at your own risk. 
  35. """
  36.  
  37. from __future__ import generators
  38. import re, os.path, datetime, cStringIO, operator
  39.  
  40. #--------------------------------------------------------------------------
  41. # KirbyBase Class
  42. #--------------------------------------------------------------------------
  43. class KirbyBase:
  44.     """Database Management System.
  45.  
  46.     Public Methods:
  47.         __init__      - Create an instance of database.
  48.         close         - Close database.
  49.         create        - Create a table.
  50.         insert        - Insert a record into a table.
  51.         insertBatch   - Insert a list of records into a table.
  52.         update        - Update a table.
  53.         delete        - Delete record(s) from a table.
  54.         select        - select record(s) from a table.
  55.         pack          - remove deleted records from a table.
  56.         validate      - validate data in table records.
  57.         drop          - Remove a table.
  58.         getFieldNames - Get a list of a table's field names.
  59.         getFieldTypes - Get a list of a table's field types.
  60.         len           - Total number of records in table.
  61.     """
  62.  
  63.     #----------------------------------------------------------------------
  64.     # PUBLIC METHODS
  65.     #----------------------------------------------------------------------
  66.  
  67.     #----------------------------------------------------------------------
  68.     # init
  69.     #----------------------------------------------------------------------
  70.     def __init__(self):
  71.         """Create an instance of the database and return a reference to it.
  72.         """
  73.         self.connect_type = type
  74.  
  75.         # Regular expression used to determine if field needs to be
  76.         # encoded.
  77.         self.encodeRegExp = re.compile(r'\n|\r|\032|\|')
  78.  
  79.         # Regular expression used to determine if field needs to be
  80.         # un-encoded.
  81.         self.unencodeRegExp = re.compile(
  82.          r'&linefeed;|&carriage_return;|&substitute;|&pipe;')
  83.  
  84.         # This will be used to validate the select statements.
  85.         self.cmpFuncs = {"<":operator.lt, "<=":operator.le, 
  86.          ">=":operator.ge, ">":operator.gt, "==":operator.eq,
  87.          "!=":operator.ne, "<>":operator.ne}
  88.  
  89.         # This will be used to validate and convert the field types in
  90.         # the header rec of the table into valid python types.
  91.         self.strToTypes = {'int':int, 'Integer':int, 'float':float, 
  92.          'Float':float, 'datetime.date':datetime.date, 
  93.          'Date':datetime.date, 'datetime.datetime':datetime.datetime,
  94.          'DateTime':datetime.datetime, 'bool':bool, 'Boolean':bool,
  95.          'str':str, 'String':str}
  96.  
  97.     #----------------------------------------------------------------------
  98.     # close
  99.     #----------------------------------------------------------------------
  100.     def close(self):
  101.         """Close connection to database server.
  102.         """
  103.         pass
  104.  
  105.     #----------------------------------------------------------------------
  106.     # create
  107.     #----------------------------------------------------------------------
  108.     def create(self, name, fields):
  109.         """Create a new table and return True on success.
  110.  
  111.         Arguments:
  112.             name   - physical filename, including path, that will hold
  113.                      table.
  114.             fields - list holding strings made up of multiple fieldname,
  115.                      fieldtype pairs (i.e. ['plane:str','speed:int']).
  116.                      Valid fieldtypes are: str, int, float, datetime.date,
  117.                      datetime.datetime, bool or, for compatibility with 
  118.                      the Ruby version of KirbyBase use String, Integer,
  119.                      Float, Date, DateTime, and Boolean.
  120.  
  121.         Returns True if no exceptions are raised.
  122.         """
  123.         # Check to see if file already exists.
  124.         if os.path.exists(name):
  125.             raise KBError(name + ' already exists!')
  126.  
  127.         # Validate field types. Integer, String, Float, Date, DateTime, and
  128.         # Boolean types are compatible between the Ruby and Python versions
  129.         # of KirbyBase.
  130.         for x in [y.split(':')[1] for y in fields]:
  131.             if x not in self.strToTypes:
  132.                 raise KBError('Invalid field type: %s' % x)
  133.  
  134.         # Make copy of fields list so that value passed in is not changed.
  135.         # Add recno counter, delete counter, and recno field definition at
  136.         # beginning.
  137.         header_rec = list(['000000','000000','recno:int'] + fields)
  138.  
  139.         # Open the table in write mode since we are creating it new, write
  140.         # the header record to it and close it.
  141.         fptr = self._openTable(name, 'w')
  142.         fptr.write('|'.join(header_rec) + '\n')
  143.         self._closeTable(fptr)
  144.  
  145.         # Return success.
  146.         return True
  147.  
  148.     #----------------------------------------------------------------------
  149.     # insert
  150.     #----------------------------------------------------------------------
  151.     def insert(self, name, values):
  152.         """Insert a new record into table, return unique record number.
  153.  
  154.         Arguments:
  155.             name   - physical file name, including path, that holds table.
  156.             values - list, dictionary, or object containing field values
  157.                      of new record.
  158.  
  159.         Returns unique record number assigned to new record when it is
  160.         created.
  161.         """
  162.  
  163.         # Open the table.
  164.         fptr = self._openTable(name, 'r+')
  165.  
  166.         # Update the instance variables holding table header info
  167.         self._updateHeaderVars(fptr)
  168.  
  169.         # If values is a dictionary or an object, we are going to convert
  170.         # it into a list.  That way, we can use the same validation and 
  171.         # updating routines regardless of whether the user passed in a 
  172.         # dictionary, an object, or a list.  This returns a copy of 
  173.         # values so that we are not messing with the original values.
  174.         record = self._convertInput(values)
  175.  
  176.         # Check input fields to make sure they are valid.
  177.         self._validateUpdateCriteria(record, self.field_names[1:])
  178.  
  179.         try:
  180.             # Get a new record number.
  181.             rec_no = self._incrRecnoCounter(fptr)
  182.  
  183.             # Add record number to front of record.
  184.             record.insert(0, rec_no)
  185.  
  186.             # Append the new record to the end of the table and close the
  187.             # table.  Run each field through encoder to take care of 
  188.             # special characters.
  189.             self._writeRecord(fptr, 'end', '|'.join(map(self._encodeString,
  190.              [str(item) for item in record])))
  191.         finally:
  192.             self._closeTable(fptr)
  193.  
  194.         # Return the unique record number created for this new record.
  195.         return rec_no
  196.  
  197.     #----------------------------------------------------------------------
  198.     # insertBatch
  199.     #----------------------------------------------------------------------
  200.     def insertBatch(self, name, batchRecords):
  201.         """Insert a batch of records into table, return a list of rec #s.
  202.  
  203.         Arguments:
  204.             name         - physical file name, including path, that holds 
  205.                            table.
  206.             batchRecords - list of records.  Each record can be a list, a 
  207.                            dictionary, or an object containing field values
  208.                            of new record.
  209.  
  210.         Returns list of unique record numbers assigned.
  211.         """
  212.         # Open the table, update the instance variables holding table
  213.         # header info and close table.
  214.         fptr = self._openTable(name, 'r')
  215.         self._updateHeaderVars(fptr)
  216.         self._closeTable(fptr)
  217.  
  218.         # Create an empty list to hold the batch after it has been
  219.         # validated and any records within it that are in dictionary format
  220.         # have been converted to list format.
  221.         records = []
  222.  
  223.         for values in batchRecords:
  224.             # If values is a dictionary or an object, we are going to 
  225.             # convert it into a list.  That way, we can use the same 
  226.             # validation and updating routines regardless of whether the
  227.             # user passed in a dictionary, an object, or a list.  This 
  228.             # returns a copy of values so that we are not messing with the
  229.             # original values.
  230.             record = self._convertInput(values)
  231.             # Check input fields to make sure they are valid.
  232.             self._validateUpdateCriteria(record, self.field_names[1:])
  233.  
  234.             # Add the validated (and possibly converted) record to the
  235.             # records list.
  236.             records.append(record)
  237.  
  238.         # Create empty list to hold new record numbers.
  239.         rec_nos = []
  240.  
  241.         # Open the table again, this time in read-write mode.
  242.         fptr = self._openTable(name, 'r+')
  243.  
  244.         try:
  245.             # Now that the batch has been validated, add it to the database
  246.             # table.
  247.             for record in records:
  248.                 # Get a new record number.
  249.                 rec_no = self._incrRecnoCounter(fptr)
  250.  
  251.                 # Add record number to front of record.
  252.                 record.insert(0, rec_no)
  253.  
  254.                 # Append the new record to the end of the table.  Run each 
  255.                 # field through encoder to take care of special characters.
  256.                 self._writeRecord(fptr, 'end', '|'.join(
  257.                  map(self._encodeString, [str(item) for item in record])))
  258.  
  259.                 # Add the newly create record number to the list of that we
  260.                 # we return back to the user.
  261.                 rec_nos.append(rec_no)
  262.         finally:
  263.             self._closeTable(fptr)
  264.  
  265.         # Return the unique record number created for this new record.
  266.         return rec_nos
  267.  
  268.     #----------------------------------------------------------------------
  269.     # update
  270.     #----------------------------------------------------------------------
  271.     def update(self, name, fields, searchData, updates, filter=None, 
  272.      useRegExp=False):
  273.         """Update record(s) in table, return number of records updated.
  274.  
  275.         Arguments:
  276.             name       - physical file name, including path, that holds
  277.                          table.
  278.             fields     - list containing names of fields to search on. If 
  279.                          any of the items in this list is 'recno', then the
  280.                          table will be searched by the recno field only and
  281.                          will update, at most, one record, since recno is 
  282.                          the system generated primary key.
  283.             searchData - list containing actual data to search on.  Each 
  284.                          item in list corresponds to item in the 'fields' 
  285.                          list.
  286.             updates    - list, dictionary, or object containing actual data
  287.                          to put into table field.  If it is a list and 
  288.                          'filter' list is empty or equal to None, then 
  289.                          updates list must have a value for each field in
  290.                          table record.
  291.             filter     - only used if 'updates' is a list.  This is a
  292.                          list containing names of fields to update.  Each
  293.                          item in list corresponds to item in the 'updates'
  294.                          list.  If 'filter' list is empty or equal to None,
  295.                          and 'updates' is a list, then 'updates' list must 
  296.                          have an item for each field in table record, 
  297.                          excepting the recno field.
  298.             useRegExp  - if true, match string fields using regular 
  299.                          expressions, else match string fields using
  300.                          strict equality (i.e. '==').  Defaults to true.
  301.  
  302.         Returns integer specifying number of records that were updated.
  303.  
  304.         Example:
  305.             db.update('plane.tbl',['country','speed'],['USA','>400'],
  306.              [1230],['range'])
  307.  
  308.             This will search for any plane from the USA with a speed
  309.             greater than 400mph and update it's range to 1230 miles.
  310.         """
  311.  
  312.         # Make copy of searchData list so that value passed in is not 
  313.         # changed if I edit it in validateMatchCriteria.
  314.         patterns = list(searchData)
  315.  
  316.         # Open the table.
  317.         fptr = self._openTable(name, 'r+')
  318.  
  319.         # Update the instance variables holding table header info.
  320.         self._updateHeaderVars(fptr)
  321.  
  322.         # If no update filter fields were specified, that means user wants
  323.         # to update all field in record, so we set the filter list equal
  324.         # to the list of field names of table, excluding the recno field,
  325.         # since user is not allowed to update recno field.
  326.         if filter:
  327.             if isinstance(updates, list):
  328.                 pass
  329.             # If updates is a dictionary, user cannot specify a filter,
  330.             # because the keys of the dictionary will function as the
  331.             # filter.
  332.             elif isinstance(updates, dict):
  333.                 raise KBError('Cannot specify filter when updates is a ' +
  334.                  'dictionary.')
  335.             else:
  336.                 raise KBError('Cannot specify filter when updates is an ' +
  337.                  'object.')
  338.  
  339.         else:
  340.             # If updates is a list and no update filter
  341.             # fields were specified, that means user wants to update
  342.             # all fields in record, so we set the filter list equal
  343.             # to the list of field names of table, excluding the recno
  344.             # field, since user is not allowed to update recno field.
  345.             if isinstance(updates, list): filter = self.field_names[1:]
  346.  
  347.         # If updates is a list, do nothing because it is already in the
  348.         # proper format and filter has either been supplied by the user
  349.         # or populated above.
  350.         if isinstance(updates, list): pass
  351.         # If updates is a dictionary, we are going to convert it into an
  352.         # updates list and a filters list.  This will allow us to use the
  353.         # same routines for validation and updating.
  354.         elif isinstance(updates, dict):
  355.             filter = [k for k in updates.keys() if k in 
  356.              self.field_names[1:]]
  357.             updates = [updates[i] for i in filter]
  358.         # If updates is an object, we are going to convert it into an
  359.         # updates list and a filters list.  This will allow us to use the
  360.         # same routines for validation and updating.
  361.         else:
  362.             filter = [x for x in self.field_names[1:] if hasattr(updates,x)]
  363.             updates = [getattr(updates,x) for x in self.field_names[1:] if 
  364.              hasattr(updates,x)]
  365.  
  366.         try:
  367.             # Check input arguments to make sure they are valid.
  368.             self._validateMatchCriteria(fields, patterns)
  369.             self._validateUpdateCriteria(updates, filter)
  370.         except KBError:
  371.             # If something didn't check out, close the table and re-raise
  372.             # the error.
  373.             fptr.close()
  374.             raise
  375.  
  376.         # Search the table and populate the match list.
  377.         match_list = self._getMatches(fptr, fields, patterns, useRegExp)
  378.  
  379.         # Create a list with each member being a list made up of a
  380.         # fieldname and the corresponding update value, converted to a
  381.         # safe string.
  382.         filter_updates = zip(filter, 
  383.          [self._encodeString(str(u)) for u in updates])
  384.  
  385.         updated = 0
  386.         # Step through the match list.
  387.         for line, fpos in match_list:
  388.             # Create a copy of the current record.
  389.             new_record = line.strip().split('|')
  390.             # For each filter field, apply the updated value to the
  391.             # table record.
  392.             for field, update in filter_updates:
  393.                 new_record[self.field_names.index(field)] = update
  394.  
  395.             # Convert the updated record back into a text line so we
  396.             # can write it back out to the file.
  397.             new_line = '|'.join(new_record)
  398.  
  399.             # Since we are changing the current record, we will first
  400.             # write over it with all blank spaces in the file.
  401.             self._deleteRecord(fptr, fpos, line)
  402.  
  403.             # If the updated copy of the record is not bigger than the
  404.             # old copy, then we can just write it in the same spot in
  405.             # the file.  If it is bigger, then we will have to append
  406.             # it to the end of the file.
  407.             if len(new_line) > len(line):
  408.                 self._writeRecord(fptr, 'end', new_line)
  409.                 # If we didn't overwrite the current record, that means
  410.                 # we have another blank record (i.e. delete record) out
  411.                 # there, so we need to increment the deleted records
  412.                 # counter.
  413.                 self._incrDeleteCounter(fptr)
  414.             else:
  415.                 self._writeRecord(fptr, fpos, new_line)
  416.             updated+=1
  417.  
  418.         # Close the table.
  419.         self._closeTable(fptr)
  420.  
  421.         # Return the number of records updated.
  422.         return updated
  423.  
  424.     #----------------------------------------------------------------------
  425.     # delete
  426.     #----------------------------------------------------------------------
  427.     def delete(self, name, fields, searchData, useRegExp=False):
  428.         """Delete record(s) from table, return number of records deleted.
  429.  
  430.         Arguments:
  431.             name       - physical file name, including path, that holds
  432.                          table.
  433.             fields     - list containing names of fields to search on. if
  434.                          any of the items in this list is 'recno', then the
  435.                          table will be searched by the recno field only and
  436.                          will delete, at most, one record, since recno is 
  437.                          the system generated primary key.
  438.             searchData - list containing actual data to search on.  Each 
  439.                          item in list corresponds to item in the 'fields'
  440.                          list.
  441.             useRegExp  - if true, match string fields using regular 
  442.                          expressions, else match string fields using
  443.                          strict equality (i.e. '==').  Defaults to true.
  444.  
  445.         Returns integer specifying number of records that were deleted.
  446.  
  447.         Example:
  448.             db.delete('plane.tbl',['country','speed'],['USA','>400'])
  449.  
  450.             This will search for any plane from the USA with a speed
  451.             greater than 400mph and delete it.
  452.         """
  453.  
  454.         # Make copy of searchData list so that value passed in is not 
  455.         # changed if I edit it in validateMatchCriteria.
  456.         patterns = list(searchData)
  457.  
  458.         # Open the table.
  459.         fptr = self._openTable(name, 'r+')
  460.  
  461.         # Update the instance variables holding table header info.
  462.         self._updateHeaderVars(fptr)
  463.  
  464.         try:
  465.             # Check input arguments to make sure they are valid.
  466.             self._validateMatchCriteria(fields, patterns)
  467.         except KBError:
  468.             # If something didn't check out, close the table and re-raise
  469.             # the error.
  470.             fptr.close()
  471.             raise
  472.  
  473.         # Search the table and populate the match list.
  474.         match_list = self._getMatches(fptr, fields, patterns, useRegExp)
  475.         deleted = 0
  476.  
  477.         # Delete any matches found.
  478.         for line, fpos in match_list:
  479.             self._deleteRecord(fptr, fpos, line)
  480.             # Increment the delete counter.
  481.             self._incrDeleteCounter(fptr)
  482.             deleted+=1
  483.  
  484.         # Close the table.
  485.         self._closeTable(fptr)
  486.  
  487.         # Return the number of records deleted.
  488.         return deleted
  489.  
  490.     #----------------------------------------------------------------------
  491.     # select
  492.     #----------------------------------------------------------------------
  493.     def select(self, name, fields, searchData, filter=None, 
  494.      useRegExp=False, sortFields=[], sortDesc=[], returnType='list', 
  495.      rptSettings=[0,False]):
  496.         """Select record(s) from table, return list of records selected.
  497.  
  498.         Arguments:
  499.             name          - physical file name, including path, that holds
  500.                             table.
  501.             fields        - list containing names of fields to search on. 
  502.                             If any of the items in this list is 'recno', 
  503.                             then the table will be searched by the recno 
  504.                             field only and will select, at most, one record,
  505.                             since recno is the system generated primary key.
  506.             searchData    - list containing actual data to search on.  Each
  507.                             item in list corresponds to item in the 
  508.                             'fields' list.
  509.             filter        - list containing names of fields to include for
  510.                             selected records.  If 'filter' list is empty or
  511.                             equal to None, then all fields will be included
  512.                             in result set.
  513.             useRegExp     - if true, match string fields using regular 
  514.                             expressions, else match string fields using
  515.                             strict equality (i.e. '==').  Defaults to False.
  516.             sortFields    - list of fieldnames to sort on.  Each must be a
  517.                             valid field name, and, if filter list is not
  518.                             empty, the same fieldname must be in the filter
  519.                             list.  Result set will be sorted in the same
  520.                             order as fields appear in sortFields in 
  521.                             ascending order unless the same field name also 
  522.                             appears in sortDesc, then they will be sorted in
  523.                             descending order.
  524.             sortDesc      - list of fieldnames that you want to sort result
  525.                             set by in descending order.  Each field name
  526.                             must also be in sortFields.
  527.             returnType    - a string specifying the type of the items in the
  528.                             returned list.  Can be 'list' (items are lists
  529.                             of values), 'dict' (items are dictionaries with
  530.                             keys = field names and values = matching
  531.                             values), 'object' (items = instances of the
  532.                             generic Record class) or 'report' (result set
  533.                             is formatted as a table with a header, suitable
  534.                             for printing).  Defaults to list.
  535.             rptSettings   - a list with two elements.  This is only used if
  536.                             returnType is 'report'.  The first element
  537.                             specifies the number of records to print on each
  538.                             page.  The default is 0, which means do not do
  539.                             any page breaks.  The second element is a 
  540.                             boolean specifying whether to print a row 
  541.                             separator (a line of dashes) between each 
  542.                             record.  The default is False.
  543.  
  544.         Returns list of records matching selection criteria.
  545.  
  546.         Example:
  547.             db.select('plane.tbl',['country','speed'],['USA','>400'])
  548.  
  549.             This will search for any plane from the USA with a speed
  550.             greater than 400mph and return it.
  551.         """
  552.  
  553.         # Check returnType argument to make sure it is either 'list',
  554.         # 'dict', or 'object'.
  555.         if returnType not in ['list', 'dict', 'object', 'report']:
  556.             raise KBError('Invalid return type: %s' % returnType)
  557.  
  558.         # Check rptSettings list to make sure it's items are valid.
  559.         if type(rptSettings[0]) != int:
  560.             raise KBError('Invalid report setting: %s' % rptSettings[0])
  561.         if type(rptSettings[1]) != bool:        
  562.             raise KBError('Invalid report setting: %s' % rptSettings[1])
  563.  
  564.         # Make copy of searchData list so that value passed in is not 
  565.         # changed if I edit it in validateMatchCriteria.
  566.         patterns = list(searchData)
  567.  
  568.         # Open the table in read-only mode since we won't be updating it.
  569.         fptr = self._openTable(name, 'r')
  570.  
  571.         # Update the instance variables holding table header info.
  572.         self._updateHeaderVars(fptr)
  573.  
  574.         try:
  575.             # Check input arguments to make sure they are valid.
  576.             self._validateMatchCriteria(fields, patterns)
  577.             if filter: self._validateFilter(filter)
  578.             else: filter = self.field_names
  579.         except KBError:
  580.             # If something didn't check out, close the table and re-raise
  581.             # the error.
  582.             fptr.close()
  583.             raise
  584.  
  585.         # Validate sort field argument.  It needs to be one of the field
  586.         # names included in the filter.
  587.         for field in [sf for sf in sortFields if sf not in filter]:
  588.             raise KBError('Invalid sort field specified: %s' % field)
  589.         # Make sure any fields specified in sort descending list are also
  590.         # in sort fields list.    
  591.         for field in [sf for sf in sortDesc if sf not in sortFields]:
  592.             raise KBError('Cannot specify a field to sort in descending ' +
  593.              'order if you have not specified that field as a sort field')
  594.  
  595.         # Search table and populate match list.
  596.         match_list = self._getMatches(fptr, fields, patterns, useRegExp)
  597.  
  598.         # Initialize result set.
  599.         result_set = []
  600.         # Get a list of filter field indexes (i.e., where in the
  601.         # table record is the field that the filter item is
  602.         # referring to.
  603.         filterIndeces = [self.field_names.index(x) for x in filter]
  604.  
  605.         # For each record in match list, add it to the result set.
  606.         for record, fpos in match_list:
  607.             # Initialize a record to hold the filtered fields of
  608.             # the record.
  609.             result_rec = []
  610.             # Split the record line into it's fields.
  611.             fields = record.split('|')
  612.  
  613.             # Step through each field index in the filter list. Grab the
  614.             # result field at that position, convert it to
  615.             # proper type, and put it in result set.
  616.             for i in filterIndeces:
  617.                 # If the field is empty, just append it to the result rec.
  618.                 # CHANGE TO KIRBYBASE CODE:                
  619.                 #if fields[i] == '':
  620.                     #result_rec.append(None)
  621.                 # Otherwise, convert field to its proper type before 
  622.                 # appending it to the result record.
  623.                 #else:
  624.                 result_rec.append(
  625.                      self.convert_types_functions[i](fields[i]))
  626.  
  627.             # Add the result record to the result set.
  628.             result_set.append(result_rec)
  629.  
  630.         # Close the table.
  631.         self._closeTable(fptr)
  632.  
  633.         # If a sort field was specified...
  634.         # I stole the following code from Steve Lucy.  I got it from the
  635.         # ASPN Python Cookbook webpages.  Thanks Steve!
  636.         if len(sortFields) > 0:
  637.             reversedSortFields = list(sortFields)
  638.             reversedSortFields.reverse()
  639.             for sortField in reversedSortFields:
  640.                 i = filter.index(sortField)
  641.                 result_set.sort( lambda x,y:
  642.                     cmp(*[(x[i], y[i]), (y[i], x[i])]
  643.                      [sortField in sortDesc]))
  644.  
  645.         # If returnType is 'object', then convert each result record
  646.         # to a Record object before returning the result list.
  647.         if returnType == 'object':
  648.             return [Record(filter, rec) for rec in result_set]
  649.         # If returnType is 'dict', then convert each result record to
  650.         # a dictionary with the keys being the field names before returning
  651.         # the result list.
  652.         elif returnType == 'dict':
  653.             return [dict(zip(filter, rec)) for rec in result_set]
  654.         # If returnType is 'report', then return a pretty print version of
  655.         # the result set.
  656.         elif returnType == 'report':
  657.             # How many records before a formfeed.
  658.             numRecsPerPage = rptSettings[0]
  659.             # Put a line of dashes between each record?
  660.             rowSeparator = rptSettings[1]
  661.             delim = ' | '
  662.  
  663.             # columns of physical rows
  664.             columns = apply(zip, [filter] + result_set)
  665.  
  666.             # get the maximum of each column by the string length of its 
  667.             # items
  668.             maxWidths = [max([len(str(item)) for item in column]) 
  669.              for column in columns]
  670.             # Create a string of dashes the width of the print out.
  671.             rowDashes = '-' * (sum(maxWidths) + len(delim)*
  672.              (len(maxWidths)-1))
  673.  
  674.             # select the appropriate justify method
  675.             justifyDict = {str:str.ljust,int:str.rjust,float:str.rjust,
  676.              bool:str.ljust,datetime.date:str.ljust,
  677.              datetime.datetime:str.ljust}
  678.  
  679.             # Create a string that holds the header that will print.
  680.             headerLine = delim.join([justifyDict[fieldType](item,width) 
  681.              for item,width,fieldType in zip(filter,maxWidths,
  682.             self.field_types)])
  683.  
  684.             # Create a StringIO to hold the print out.
  685.             output=cStringIO.StringIO()
  686.  
  687.             # Variable to hold how many records have been printed on the
  688.             # current page.
  689.             recsOnPageCount = 0
  690.  
  691.             # For each row of the result set, print that row.
  692.             for row in result_set:
  693.                 # If top of page, print the header and a dashed line.
  694.                 if recsOnPageCount == 0:
  695.                     print >> output, headerLine
  696.                     print >> output, rowDashes
  697.  
  698.                 # Print a record.
  699.                 print >> output, delim.join([justifyDict[fieldType](
  700.                  str(item),width) for item,width,fieldType in 
  701.                  zip(row,maxWidths,self.field_types)])
  702.  
  703.                 # If rowSeparator is True, print a dashed line.
  704.                 if rowSeparator: print >> output, rowDashes
  705.  
  706.                 # Add one to the number of records printed so far on
  707.                 # the current page.
  708.                 recsOnPageCount += 1
  709.  
  710.                 # If the user wants page breaks and you have printed 
  711.                 # enough records on this page, print a form feed and
  712.                 # reset records printed variable.
  713.                 if numRecsPerPage > 0 and (recsOnPageCount ==
  714.                  numRecsPerPage):
  715.                     print >> output, '\f',
  716.                     recsOnPageCount = 0
  717.             # Return the contents of the StringIO.
  718.             return output.getvalue()
  719.         # Otherwise, just return the list of lists.
  720.         else:
  721.             return result_set
  722.  
  723.  
  724.     #----------------------------------------------------------------------
  725.     # pack
  726.     #----------------------------------------------------------------------
  727.     def pack(self, name):
  728.         """Remove blank records from table and return total removed.
  729.  
  730.         Keyword Arguments:
  731.             name - physical file name, including path, that holds table.
  732.  
  733.         Returns number of blank lines removed from table.
  734.         """
  735.  
  736.         # Open the table in read-only mode since we won't be updating it.
  737.         fptr = self._openTable(name, 'r')
  738.  
  739.         # Read in all records.
  740.         lines = fptr.readlines()
  741.  
  742.         # Close the table so we can re-build it.
  743.         self._closeTable(fptr)
  744.  
  745.         # Reset number of deleted records to zero.
  746.         header_rec = lines[0].split('|')
  747.         header_rec[1] = "000000"
  748.  
  749.         # Set first line of re-built file to the header record.
  750.         lines[0] = '|'.join(header_rec)
  751.  
  752.         # Open the table in write mode since we will be re-building it.
  753.         fptr = self._openTable(name, 'w')
  754.  
  755.         # This is the counter we will use to report back how many blank
  756.         # records were removed.
  757.         lines_deleted = 0
  758.  
  759.         # Step through all records in table, only writing out non-blank
  760.         # records.
  761.         for line in lines:
  762.             # By doing a rstrip instead of a strip, we can remove any
  763.             # extra spaces at the end of line that were a result of
  764.             # updating a record with a shorter one.
  765.             line = line.rstrip()
  766.             if line == "":
  767.                lines_deleted += 1
  768.                continue
  769.             try:
  770.                 fptr.write(line + '\n')
  771.             except:
  772.                 raise KBError('Could not write record in: ' + name)
  773.  
  774.         # Close the table.
  775.         self._closeTable(fptr)
  776.  
  777.         # Return number of records removed from table.
  778.         return lines_deleted
  779.  
  780.     #----------------------------------------------------------------------
  781.     # validate
  782.     #----------------------------------------------------------------------
  783.     def validate(self, name):
  784.         """Validate all records have field values of proper data type.
  785.  
  786.         Keyword Arguments:
  787.             name - physical file name, including path, that holds table.
  788.  
  789.         Returns list of records that have invalid data.
  790.         """
  791.  
  792.         # Open the table in read-only mode since we won't be updating it.
  793.         fptr = self._openTable(name, 'r')
  794.  
  795.         # Update the instance variables holding table header info
  796.         self._updateHeaderVars(fptr)
  797.  
  798.         # Create list to hold any invalid records that are found.
  799.         invalid_list = []
  800.  
  801.         # Loop through all records in the table.
  802.         for line in fptr:
  803.             # Strip off newline character and any trailing spaces.
  804.             line = line[:-1].strip()
  805.  
  806.             # If blank line, skip this record.
  807.             if line == "": continue
  808.  
  809.             # Split the line up into fields.
  810.             record = line.split("|")
  811.  
  812.             # Check the value of recno to see if the value is
  813.             # greater than the last recno assigned.  If it is,
  814.             # add this to the invalid record list.
  815.             if self.last_recno < int(record[0]):
  816.                 invalid_list.append([record[0], 'recno', record[0]])
  817.  
  818.             # For each field in the record check to see if you
  819.             # can convert it to the field type specified in the
  820.             # header record by using the conversion function
  821.             # specified in self.convert_types_functions. 
  822.             # If you can't convert it, add the
  823.             # record number, the field name, and the offending
  824.             # field value to the list of invalid records.
  825.             for i, item in enumerate(record):
  826.                 if item == '': continue
  827.                 try:
  828.                     if self.convert_types_functions[i](item): pass
  829.                 except:
  830.                     invalid_list.append([record[0], self.field_names[i], 
  831.                      item])
  832.  
  833.         # Close the table.
  834.         self._closeTable(fptr)
  835.  
  836.         # Return list of invalid records.
  837.         return invalid_list
  838.  
  839.     #----------------------------------------------------------------------
  840.     # drop
  841.     #----------------------------------------------------------------------
  842.     def drop(self, name):
  843.         """Delete physical file containing table and return True.
  844.  
  845.         Arguments:
  846.             name - physical filename, including path, that holds table.
  847.  
  848.         Returns True if no exceptions are raised.
  849.         """
  850.  
  851.         # Delete physical file.
  852.         os.remove(name)
  853.  
  854.         # Return success.
  855.         return True
  856.  
  857.     #----------------------------------------------------------------------
  858.     # getFieldNames
  859.     #----------------------------------------------------------------------
  860.     def getFieldNames(self, name):
  861.         """Return list of field names for specified table name
  862.  
  863.         Arguments:
  864.             name - physical file name, including path, that holds table.
  865.  
  866.         Returns list of field names for table.
  867.         """
  868.  
  869.         # Open the table in read-only mode since we won't be updating it.
  870.         fptr = self._openTable(name, 'r')
  871.  
  872.         # Update the instance variables holding table header info
  873.         self._updateHeaderVars(fptr)
  874.  
  875.         # Close the table.
  876.         self._closeTable(fptr)
  877.  
  878.         return self.field_names
  879.  
  880.     #----------------------------------------------------------------------
  881.     # getFieldTypes
  882.     #----------------------------------------------------------------------
  883.     def getFieldTypes(self, name):
  884.         """Return list of field types for specified table name
  885.  
  886.         Arguments:
  887.             name - physical file name, including path, that holds table.
  888.  
  889.         Returns list of field types for table.
  890.         """
  891.  
  892.         # Open the table in read-only mode since we won't be updating it.
  893.         fptr = self._openTable(name, 'r')
  894.  
  895.         # Update the instance variables holding table header info
  896.         self._updateHeaderVars(fptr)
  897.  
  898.         # Close the table.
  899.         self._closeTable(fptr)
  900.  
  901.         return self.field_types
  902.  
  903.  
  904.     #----------------------------------------------------------------------
  905.     # len
  906.     #----------------------------------------------------------------------
  907.     def len(self, name):
  908.         '''Return total number of non-deleted records in specified table
  909.  
  910.         Arguments:
  911.             name - physical file name, including path, that holds table.
  912.  
  913.         Returns total number of records in table.
  914.         '''
  915.  
  916.         # Initialize counter.
  917.         rec_count = 0
  918.  
  919.         # Open the table in read-only mode since we won't be updating it.
  920.         fptr = self._openTable(name, 'r')
  921.  
  922.         # Skip header record.
  923.         line = fptr.readline()
  924.  
  925.         # Loop through entire table.
  926.         line = fptr.readline()
  927.         while line:
  928.             # Strip off newline character.
  929.             line = line[0:-1]
  930.  
  931.             # If not blank line, add 1 to record count.
  932.             if line.strip() != "": rec_count += 1
  933.  
  934.             # Read next record.
  935.             line = fptr.readline()
  936.  
  937.         # Close the table.
  938.         self._closeTable(fptr)
  939.  
  940.         return rec_count
  941.  
  942.     #----------------------------------------------------------------------
  943.     # PRIVATE METHODS
  944.     #----------------------------------------------------------------------
  945.  
  946.  
  947.     #----------------------------------------------------------------------
  948.     # _strToBool
  949.     #----------------------------------------------------------------------
  950.     def _strToBool(self, boolString):
  951.         if boolString == 'True': return True
  952.         elif boolString == 'False': return False
  953.         else: raise KBError('Invalid value for boolean: %s' % boolString)
  954.  
  955.  
  956.     #----------------------------------------------------------------------
  957.     # _strToDate
  958.     #----------------------------------------------------------------------
  959.     def _strToDate(self, dateString):
  960.         # Split the date string up into pieces and create a
  961.         # date object.
  962.         return datetime.date(*map(int, dateString.split('-'))) 
  963.  
  964.     #----------------------------------------------------------------------
  965.     # _strToDateTime
  966.     #----------------------------------------------------------------------
  967.     def _strToDateTime(self, dateTimeString):
  968.         # Split datetime string into datetime portion microseconds portion.
  969.         tempDateTime = dateTimeString.split('.')
  970.         # Were there microseconds in the datetime string.
  971.         if len(tempDateTime) > 1: microsec = int(tempDateTime[1])
  972.         else: microsec = 0
  973.  
  974.         # Now, split the datetime portion into a date
  975.         # and a time string.  Take all of the pieces and
  976.         # create a datetime object.
  977.         tempDate, tempTime = tempDateTime[0].split(' ')
  978.         y, m, d = tempDate.split('-')
  979.         h, min, sec = tempTime.split(':')
  980.         return datetime.datetime(int(y),int(m),int(d),int(h),int(min),
  981.          int(sec),microsec)
  982.  
  983.     #----------------------------------------------------------------------
  984.     # _encodeString
  985.     #----------------------------------------------------------------------
  986.     def _encodeString(self, s):
  987.         '''Encode a string.
  988.  
  989.         Translates problem characters like \n, \r, and \032 to benign
  990.         character strings.
  991.  
  992.         Keyword Arguments:
  993.             s - string to encode.
  994.  
  995.         Returns encoded string.
  996.         '''
  997.         if self.encodeRegExp.search(s):
  998.             s = s.replace('\n', '&linefeed;')
  999.             s = s.replace('\r', '&carriage_return;')
  1000.             s = s.replace('\032', '&substitute;')
  1001.             s = s.replace('|', '&pipe;')
  1002.         return s
  1003.  
  1004.     #----------------------------------------------------------------------
  1005.     # _unencodeString
  1006.     #----------------------------------------------------------------------
  1007.     def _unencodeString(self, s):
  1008.         '''Unencode a string.
  1009.  
  1010.         Translates encoded character strings back to special characters
  1011.         like \n, \r, \032.
  1012.  
  1013.         Keyword Arguments:
  1014.             s - string to unencode.
  1015.  
  1016.         Returns unencoded string.
  1017.         '''
  1018.         if self.unencodeRegExp.search(s):
  1019.             s = s.replace('&linefeed;', '\n')
  1020.             s = s.replace('&carriage_return;', '\r')
  1021.             s = s.replace('&substitute;', '\032')
  1022.             s = s.replace('&pipe;', '|')
  1023.         return s
  1024.  
  1025.     #----------------------------------------------------------------------
  1026.     # _updateHeaderVars
  1027.     #----------------------------------------------------------------------
  1028.     def _updateHeaderVars(self, fptr):
  1029.         # Go to the header record and read it in.
  1030.         fptr.seek(0)
  1031.         line = fptr.readline()
  1032.  
  1033.         # Chop off the newline character.
  1034.         line = line[0:-1]
  1035.  
  1036.         # Split the record into fields.
  1037.         header_rec = line.split('|')
  1038.  
  1039.         # Update Last Record Number and Deleted Records counters.
  1040.         self.last_recno = int(header_rec[0])
  1041.         self.del_counter = int(header_rec[1])
  1042.  
  1043.         # Skip the recno counter, and the delete counter.
  1044.         header_fields = header_rec[2:]
  1045.  
  1046.         # Create an instance variable holding the field names.
  1047.         self.field_names = [item.split(':')[0] for item in header_fields]
  1048.         # Create an instance variable holding the field types.
  1049.         self.field_types = [self.strToTypes[x.split(':')[1]] for x in
  1050.          header_fields]
  1051.  
  1052.         # the functions to use to convert values as strings into their type
  1053.         convTypes={ int:int, float:float ,bool:bool, #bool:self._strToBool,
  1054.             str:self._unencodeString,
  1055.             datetime.date:self._strToDate,
  1056.             datetime.datetime:self._strToDateTime }
  1057.         self.convert_types_functions = [convTypes[f] for f in 
  1058.          self.field_types]
  1059.  
  1060.  
  1061.     #----------------------------------------------------------------------
  1062.     # _validateMatchCriteria
  1063.     #----------------------------------------------------------------------
  1064.     def _validateMatchCriteria(self, fields, patterns):
  1065.         """Run various checks against list of fields and patterns to be
  1066.         used as search criteria.  This method is called from all public
  1067.         methods that search the database.
  1068.         """
  1069.         if len(fields) == 0:
  1070.             raise KBError('Length of fields list must be greater ' +
  1071.              'than zero.')
  1072.         if len(fields) != len(patterns):
  1073.             raise KBError('Length of fields list and patterns list ' +
  1074.              'not the same.')
  1075.  
  1076.         # If any of the list of fields to search on do not match a field
  1077.         # in the table, raise an error.
  1078.         for field, pattern in zip(fields, patterns):
  1079.             if field not in self.field_names:
  1080.                 raise KBError('Invalid field name in fields list: %s' 
  1081.                     %field)
  1082.  
  1083.             # If the field is recno, make sure they are trying not to
  1084.             # search on more than one field.  Also, make sure they are
  1085.             # either trying to match all records or that it is an integer.
  1086.             if field == 'recno':
  1087.                 if len(fields) > 1:
  1088.                     raise KBError('If selecting by recno, no other ' +
  1089.                      'selection criteria is allowed')
  1090.                 if pattern != '*':
  1091.                     # check if all specified recnos are integers
  1092.                     if not isinstance(pattern,(tuple,list)):
  1093.                         pattern = [pattern]
  1094.                     for x in pattern:
  1095.                         if not isinstance(x,int):
  1096.                             raise KBError('Recno argument %s has type %s' 
  1097.                                 ', expected an integer' %(x,type(x)))                        
  1098.                 continue
  1099.  
  1100.             # If the field type is not a str or a bool, make sure the
  1101.             # pattern you are searching on has a proper comparion
  1102.             # operator (<,<=,>,>=,==,!=,or <>) in it.
  1103.             if (self.field_types[self.field_names.index(field)] in  
  1104.              [int, float, datetime.date, datetime.datetime]):
  1105.                 r = re.search('[\s]*[\+-]?\d', pattern)
  1106.                 if not self.cmpFuncs.has_key(pattern[:r.start()]):
  1107.                     raise KBError('Invalid comparison syntax: %s'
  1108.                      % pattern[:r.start()])
  1109.  
  1110.  
  1111.     #----------------------------------------------------------------------
  1112.     #_convertInput
  1113.     #----------------------------------------------------------------------
  1114.     def _convertInput(self, values):
  1115.         """If values is a dictionary or an object, we are going to convert 
  1116.         it into a list.  That way, we can use the same validation and 
  1117.         updating routines regardless of whether the user passed in a 
  1118.         dictionary, an object, or a list.
  1119.         """
  1120.         # If values is a list, make a copy of it.
  1121.         if isinstance(values, list): record = list(values)
  1122.         # If values is a dictionary, convert it's values into a list
  1123.         # corresponding to the table's field names.  If there is not
  1124.         # a key in the dictionary corresponding to a field name, place a
  1125.         # '' in the list for that field name's value.
  1126.         elif isinstance(values, dict):
  1127.             record = [values.get(k,'') for k in self.field_names[1:]]        
  1128.         # If values is a record object, then do the same thing for it as
  1129.         # you would do for a dictionary above.
  1130.         else:
  1131.             record = [getattr(values,a,'') for a in self.field_names[1:]]
  1132.         # Return the new list with all items == None replaced by ''.
  1133.         new_rec = []
  1134.         for r in record:
  1135.             if r == None:
  1136.                 new_rec.append('')
  1137.             else:
  1138.                 new_rec.append(r)
  1139.         return new_rec        
  1140.  
  1141.  
  1142.     #----------------------------------------------------------------------
  1143.     # _validateUpdateCriteria
  1144.     #----------------------------------------------------------------------
  1145.     def _validateUpdateCriteria(self, updates, filter):
  1146.         """Run various checks against list of updates and fields to be
  1147.         used as update criteria.  This method is called from all public
  1148.         methods that update the database.
  1149.         """
  1150.         if len(updates) == 0:
  1151.             raise KBError('Length of updates list must be greater ' +
  1152.              'than zero.')
  1153.  
  1154.         if len(updates) != len(filter):
  1155.             raise KBError('Length of updates list and filter list ' +
  1156.              'not the same.')
  1157.         # Since recno is the record's primary key and is system
  1158.         # generated, like an autoincrement field, do not allow user
  1159.         # to update it.
  1160.         if 'recno' in filter:
  1161.             raise KBError("Cannot update value of 'recno' field.")
  1162.  
  1163.         # Validate filter list.
  1164.         self._validateFilter(filter)
  1165.  
  1166.         # Make sure each update is of the proper type.
  1167.         for update, field_name in zip(updates, filter):
  1168.             if update in ['', None]: pass
  1169.             elif type(update) != self.field_types[
  1170.              self.field_names.index(field_name)]:
  1171.                 raise KBError("Invalid update value for %s" % field_name)
  1172.  
  1173.     #----------------------------------------------------------------------
  1174.     # _validateFilter
  1175.     #----------------------------------------------------------------------
  1176.     def _validateFilter(self, filter):
  1177.         # Each field in the filter list must be a valid field in the table.
  1178.         for field in filter:
  1179.             if field not in self.field_names:
  1180.                 raise KBError('Invalid field name: %s' % field)
  1181.  
  1182.     #----------------------------------------------------------------------
  1183.     # _getMatches
  1184.     #----------------------------------------------------------------------
  1185.     def _getMatches(self, fptr, fields, patterns, useRegExp):
  1186.         # Initialize a list to hold all records that match the search
  1187.         # criteria.
  1188.         match_list = []
  1189.  
  1190.         # If one of the fields to search on is 'recno', which is the
  1191.         # table's primary key, then search just on that field and return
  1192.         # at most one record.
  1193.         if 'recno' in fields:
  1194.             return self._getMatchByRecno(fptr,patterns)
  1195.         # Otherwise, search table, using all search fields and patterns
  1196.         # specified in arguments lists.
  1197.         else:
  1198.             new_patterns = [] 
  1199.             fieldNrs = [self.field_names.index(x) for x in fields]
  1200.             for fieldPos, pattern in zip(fieldNrs, patterns):
  1201.                 if self.field_types[fieldPos] == str:
  1202.                     # If useRegExp is True, compile the pattern to a
  1203.                     # regular expression object and add it to the
  1204.                     # new_patterns list.  Otherwise,  just add it to
  1205.                     # the new_patterns list.  This will be used below 
  1206.                     # when matching table records against the patterns.
  1207.                     if useRegExp:
  1208.                         new_patterns.append(re.compile(pattern))
  1209.                         # the pattern can be a tuple with re flags like re.I
  1210.                     else:
  1211.                         new_patterns.append(pattern)
  1212.                 elif self.field_types[fieldPos] == bool:
  1213.                     # If type is boolean, I am going to coerce it to be
  1214.                     # either True or False by applying bool to it.  This
  1215.                     # is because it could be '' or [].  Next, I am going
  1216.                     # to convert it to the string representation: either
  1217.                     # 'True' or 'False'.  The reason I do this is because
  1218.                     # that is how it is stored in each record of the table
  1219.                     # and it is a lot faster to change this one value from
  1220.                     # boolean to string than to change possibly thousands
  1221.                     # of table values from string to boolean.  And, if they
  1222.                     # both are either 'True' or 'False' I can still
  1223.                     # compare them using the equality test and get the same
  1224.                     # result as if they were both booleans.
  1225.                     new_patterns.append(str(bool(pattern)))
  1226.                 else:
  1227.                     # If type is int, float, date, or datetime, this next
  1228.                     # bit of code will split the the comparison string
  1229.                     # into the string representing the comparison
  1230.                     # operator (i.e. ">=" and the actual value we are going
  1231.                     # to compare the table records against from the input
  1232.                     # pattern, (i.e. "5").  So, for example, ">5" would be
  1233.                     # split into ">" and "5".
  1234.                     r = re.search('[\s]*[\+-]?\d', pattern)
  1235.                     if self.field_types[fieldPos] == int:
  1236.                         patternValue = int(pattern[r.start():])
  1237.                     elif self.field_types[fieldPos] == float:
  1238.                         patternValue = float(pattern[r.start():])
  1239.                     else:
  1240.                         patternValue = pattern[r.start():]
  1241.                     new_patterns.append(
  1242.                      [self.cmpFuncs[pattern[:r.start()]], patternValue]
  1243.                     ) 
  1244.  
  1245.             fieldPos_new_patterns = zip(fieldNrs, new_patterns)
  1246.             maxfield = max(fieldNrs)+1
  1247.  
  1248.             # Record current position in table. Then read first detail
  1249.             # record.
  1250.             fpos = fptr.tell()
  1251.             line = fptr.readline()
  1252.  
  1253.             # Loop through entire table.
  1254.             while line:
  1255.                 # Strip off newline character and any trailing spaces.
  1256.                 line = line[:-1].strip()
  1257.                 try:
  1258.                     # If blank line, skip this record.
  1259.                     if line == "": raise 'No Match'
  1260.                     # Split the line up into fields.
  1261.                     record = line.split("|", maxfield)
  1262.  
  1263.                     # Foreach correspond field and pattern, check to see
  1264.                     # if the table record's field matches successfully.
  1265.                     for fieldPos, pattern in fieldPos_new_patterns:
  1266.                         # If the field type is string, it
  1267.                         # must be an exact match or a regular expression,
  1268.                         # so we will compare the table record's field to it
  1269.                         # using either '==' or the regular expression 
  1270.                         # engine.  Since it is a string field, we will need
  1271.                         # to run it through the unencodeString function to
  1272.                         # change any special characters back to their 
  1273.                         # original values.
  1274.                         if self.field_types[fieldPos] == str:
  1275.                             try:
  1276.                                 if useRegExp:
  1277.                                     if not pattern.search(
  1278.                                      self._unencodeString(record[fieldPos])
  1279.                                      ):
  1280.                                         raise 'No Match'
  1281.                                 else:
  1282.                                     if record[fieldPos] != pattern:
  1283.                                         raise 'No Match'        
  1284.                             except Exception:
  1285.                                 raise KBError(
  1286.                                  'Invalid match expression for %s'
  1287.                                  % self.field_names[fieldPos])
  1288.                         # If the field type is boolean, then I will simply
  1289.                         # do an equality comparison.  See comments above
  1290.                         # about why I am actually doing a string compare
  1291.                         # here rather than a boolean compare.
  1292.                         elif self.field_types[fieldPos] == bool:
  1293.                             if record[fieldPos] != pattern:
  1294.                                 raise 'No Match'
  1295.                         # If it is not a string or a boolean, then it must 
  1296.                         # be a number or a date.
  1297.                         else:
  1298.                             # Convert the table's field value, which is a
  1299.                             # string, back into it's native type so that
  1300.                             # we can do the comparison.
  1301.                             if record[fieldPos] == '':
  1302.                                 tableValue = None
  1303.                             elif self.field_types[fieldPos] == int:
  1304.                                 tableValue = int(record[fieldPos])
  1305.                             elif self.field_types[fieldPos] == float:
  1306.                                 tableValue = float(record[fieldPos])
  1307.                             # I don't convert datetime values from strings
  1308.                             # back into their native types because it is
  1309.                             # faster to just leave them as strings and 
  1310.                             # convert the comparison value that the user
  1311.                             # supplied into a string.  Comparing the two
  1312.                             # strings works out the same as comparing two
  1313.                             # datetime values anyway.    
  1314.                             elif self.field_types[fieldPos] in (
  1315.                              datetime.date, datetime.datetime):
  1316.                                 tableValue = record[fieldPos]
  1317.                             else:
  1318.                                 # If it falls through to here, then,
  1319.                                 # somehow, a bad field type got put into
  1320.                                 # the table and we show an error.
  1321.                                 raise KBError('Invalid field type for %s'
  1322.                                  % self.field_names[fieldPos])
  1323.                             # Now we do the actual comparison.  I used to
  1324.                             # just do an eval against the pattern string
  1325.                             # here, but I found that eval's are VERY slow.
  1326.                             # So, now I determine what type of comparison
  1327.                             # they are trying to do and I do it directly.
  1328.                             # This sped up queries by 40%.     
  1329.                             if not pattern[0](tableValue, pattern[1]):
  1330.                                 raise 'No Match' 
  1331.                 # If a 'No Match' exception was raised, then go to the
  1332.                 # next record, otherwise, add it to the list of matches.
  1333.                 except 'No Match':
  1334.                     pass
  1335.                 else:
  1336.                     match_list.append([line, fpos])
  1337.                 # Save the file position BEFORE we read the next record,
  1338.                 # because after a read it is pointing at the END of the
  1339.                 # current record, which, of course, is also the BEGINNING
  1340.                 # of the next record.  That's why we have to save the
  1341.                 # position BEFORE we read the next record.
  1342.                 fpos = fptr.tell()
  1343.                 line = fptr.readline()
  1344.  
  1345.         # After searching, return the list of matched records.
  1346.         return match_list
  1347.  
  1348.     #----------------------------------------------------------------------
  1349.     # _getMatchByRecno
  1350.     #----------------------------------------------------------------------
  1351.     def _getMatchByRecno(self, fptr, recnos):
  1352.         """Search by recnos. recnos is a list, containing '*', an integer, or
  1353.         a list or tuple of integers"""
  1354.  
  1355.         # Initialize table location marker and read in first record
  1356.         # of table.
  1357.         fpos = fptr.tell()
  1358.         line = fptr.readline()
  1359.  
  1360.         if recnos == ['*']:
  1361.             # take all the non blank lines
  1362.             while line:
  1363.                 line = line[:-1] # remove trailing \n (patch ver. 1.8.2)
  1364.                 if line.strip():
  1365.                     yield [line,fpos]
  1366.                 fpos = fptr.tell()
  1367.                 line = fptr.readline()
  1368.         else:
  1369.             # select the records with record number in recnos
  1370.             if isinstance(recnos[0],(tuple,list)):
  1371.                 # must make it a list, to be able to remove items
  1372.                 recnos = list(recnos[0])
  1373.             while line:
  1374.                 # Strip of newline character.
  1375.                 line = line[0:-1]
  1376.  
  1377.                 # If line is not blank, split it up into fields.
  1378.                 if line.strip():
  1379.                     record = line.split("|")
  1380.                     # If record number for current record equals record number
  1381.                     # we are searching for, add it to match list
  1382.                     if int(record[0]) in recnos:
  1383.                         yield [line, fpos]
  1384.                         recnos.remove(int(record[0]))
  1385.                         # if no more recno to search, stop looping
  1386.                         if not recnos: break
  1387.  
  1388.                 # update the table location marker
  1389.                 # and read the next record.
  1390.                 fpos = fptr.tell()
  1391.                 line = fptr.readline()
  1392.  
  1393.         # Stop iteration
  1394.         return
  1395.  
  1396.     #----------------------------------------------------------------------
  1397.     # _incrRecnoCounter
  1398.     #----------------------------------------------------------------------
  1399.     def _incrRecnoCounter(self, fptr):
  1400.         # Save where we are in the table.
  1401.         last_pos = fptr.tell()
  1402.  
  1403.         # Go to header record and grab header fields.
  1404.         fptr.seek(0)
  1405.         line = fptr.readline()
  1406.         header_rec = line[0:-1].split('|')
  1407.  
  1408.         # Increment the recno counter.
  1409.         self.last_recno += 1
  1410.         header_rec[0] = "%06d" %(self.last_recno)
  1411.  
  1412.         # Write the header record back to the file.  Run each field through
  1413.         # encoder to handle special characters.
  1414.         self._writeRecord(fptr, 0, '|'.join(header_rec))
  1415.  
  1416.         # Go back to where you were in the table.
  1417.         fptr.seek(last_pos)
  1418.         # Return the newly incremented recno counter.
  1419.         return self.last_recno
  1420.  
  1421.     #----------------------------------------------------------------------
  1422.     # _incrDeleteCounter
  1423.     #----------------------------------------------------------------------
  1424.     def _incrDeleteCounter(self, fptr):
  1425.         # Save where we are in the table.
  1426.         last_pos = fptr.tell()
  1427.  
  1428.         # Go to header record and grab header fields.
  1429.         fptr.seek(0)
  1430.         line = fptr.readline()
  1431.         header_rec = line[0:-1].split('|')
  1432.  
  1433.         # Increment the delete counter.
  1434.         self.del_counter += 1
  1435.         header_rec[1] = "%06d" %(self.del_counter)
  1436.  
  1437.         # Write the header record back to the file.
  1438.         self._writeRecord(fptr, 0, '|'.join(header_rec))
  1439.  
  1440.         # Go back to where you were in the table.
  1441.         fptr.seek(last_pos)
  1442.  
  1443.     #----------------------------------------------------------------------
  1444.     # _deleteRecord
  1445.     #----------------------------------------------------------------------
  1446.     def _deleteRecord(self, fptr, pos, record):
  1447.         # Move to record position in table.
  1448.         fptr.seek(pos)
  1449.  
  1450.         # Overwrite record with all spaces.
  1451.         self._writeRecord(fptr, pos, " " * len(record))
  1452.  
  1453.     #----------------------------------------------------------------------
  1454.     # _writeRecord
  1455.     #----------------------------------------------------------------------
  1456.     def _writeRecord(self, fptr, pos, record):
  1457.         try:
  1458.             # If record is to be appended, go to end of table and write
  1459.             # record, adding newline character.
  1460.             if pos == 'end':
  1461.                 fptr.seek(0, 2)
  1462.                 fptr.write(record + '\n')
  1463.             else:
  1464.                 # Otherwise, move to record position in table and write
  1465.                 # record.
  1466.                 fptr.seek(pos)
  1467.                 fptr.write(record)
  1468.         except:
  1469.             raise KBError('Could not write record to: ' + fptr.name)
  1470.  
  1471.     #----------------------------------------------------------------------
  1472.     # _openTable
  1473.     #----------------------------------------------------------------------
  1474.     def _openTable(self, name, access):
  1475.         try:
  1476.             # Open physical file holding table.
  1477.             fptr = open(name, access)
  1478.         except:
  1479.             raise KBError('Could not open table: ' + name)
  1480.         # Return handle to physical file.
  1481.         return fptr
  1482.  
  1483.     #----------------------------------------------------------------------
  1484.     # _closeTable
  1485.     #----------------------------------------------------------------------
  1486.     def _closeTable(self, fptr):
  1487.         try:
  1488.             # Close the file containing the table.
  1489.             fptr.close()
  1490.         except:
  1491.             raise KBError('Could not close table: ' + fptr.name)
  1492.  
  1493. #--------------------------------------------------------------------------
  1494. # Generic class for records
  1495. #--------------------------------------------------------------------------
  1496. class Record(object):
  1497.     """Generic class for record objects.
  1498.  
  1499.     Public Methods:
  1500.         __init__ - Create an instance of Record.
  1501.     """
  1502.     #----------------------------------------------------------------------
  1503.     # init
  1504.     #----------------------------------------------------------------------
  1505.     def __init__(self,names,values):
  1506.         self.__dict__ = dict(zip(names, values))
  1507.  
  1508.  
  1509. #--------------------------------------------------------------------------
  1510. # KBError Class
  1511. #--------------------------------------------------------------------------
  1512. class KBError(Exception):
  1513.     """Exception class for Database Management System.
  1514.  
  1515.     Public Methods:
  1516.         __init__ - Create an instance of exception.
  1517.     """
  1518.     #----------------------------------------------------------------------
  1519.     # init
  1520.     #----------------------------------------------------------------------
  1521.     def __init__(self, value):
  1522.         self.value = value
  1523.  
  1524.     def __str__(self):
  1525.         return `self.value`
  1526.  
  1527.     # I overrode repr so I could pass error objects from the server to the
  1528.     # client across the network.
  1529.     def __repr__(self):
  1530.         format = """KBError("%s")"""
  1531.         return format % (self.value)
  1532.