Source code for schrodinger.infra.table

"""
A pythonic wrapper to an mmlibs mmtable object.

Note that column and row indices start at 1.

Copyright Schrodinger, LLC. All rights reserved.

"""

#Contributors: Scott MacHaffie

# to support: from schrodinger.infra.table import *
from schrodinger.infra import mm
from schrodinger.infra import mmbitset
from schrodinger.infra import mmobject

__all__ = ['Table', 'TableRow']

_module_name = "table"  # Is this really necessary?

INVALID_TABLE_HANDLE = mm.MMTABLE_INVALID_TABLE

# ***************************************************************************
# ***************************************************************************


def _is_data_name(name):
    """ Returns true if the given name is an m2io data name """
    if len(name) < 2:
        return False
    return (name[:2] in ['b_', 'i_', 'r_', 's_'])


[docs]class TableRow: """ This class represents a table row--what you get by indexing a Table. Note that a TableRow should not outlive its corresponding Table. """ # *********************************************************************** # Constructor # ***********************************************************************
[docs] def __init__(self, table, row): """Construct a Table Row for the given table handle and row number""" self._table = table self._row = row # Check validity of the table: if not mm.mmtable_table_is_valid(self._table): raise ValueError("Invalid table handle: %d" % self._table) num_rows = mm.mmtable_get_row_total(self._table) if row < 1: raise IndexError("Row must be >= 1") if row > num_rows: raise IndexError("Row must be <= %d." % num_rows)
def __repr__(self): """ Return representation suitable for use as a Python expression """ return "TableRow(%d, %d)" % (self._table, self._row) def __getitem__(self, prop): """ Return Table Row cell value or None if the cell doesn't have a value prop is the name of the property (column). This can either be a data name or a user name. prop can also be the index of a column """ # Get column index num_columns = mm.mmtable_get_column_total(self._table) if type(prop) is int: # Integer column, use directly column = prop if column < 1: raise IndexError("Column must be >= 1") if column > num_columns: raise IndexError("Column must be <= %d" % num_columns) elif isinstance(prop, str): column = -1 try: column = mm.mmtable_get_column_index(self._table, prop) except mm.MmException: # Try finding a matching user name for i in range(1, num_columns + 1): name = mm.mmtable_column_get_name(self._table, i) if name == prop: column = i break if column < 1: Table._printDebug("TableRow::__getitem__ - %s '%s'" % \ ("Could not find property", prop)) raise KeyError("Couldn't find property: %s" % prop) else: raise TypeError("Column must be either a string or an integer") # Get column's property type try: data_type = mm.mmtable_column_get_data_type(self._table, column) except mm.MmException: Table._printDebug("TableRow::__getitem__ - %s '%s'" % \ ("Could not get type for property", prop)) raise TypeError("Couldn't get type for property: %s" % prop) # Get the value of the correct type. If we cannot, return None if data_type == mm.M2IO_REAL_TYPE: Table._printDebug('real', self._row, prop) try: val = mm.mmtable_cell_get_real(self._table, self._row, column) except mm.MmException: val = None elif data_type == mm.M2IO_BOOLEAN_TYPE: Table._printDebug('Boolean') try: val = mm.mmtable_cell_get_boolean(self._table, self._row, \ column) except mm.MmException: val = None elif data_type == mm.M2IO_INT_TYPE: Table._printDebug('int') try: val = mm.mmtable_cell_get_integer(self._table, self._row, \ column) except mm.MmException: val = None elif data_type == mm.M2IO_STRING_TYPE: Table._printDebug('string') try: val = mm.mmtable_cell_get_string(self._table, self._row, column) except mm.MmException: val = None else: raise TypeError("Property %s does not have a valid data type" \ % prop) return val def __setitem__(self, prop, value): """ Set Table Row cell value prop is the name of the property (column), i.e. the key. This can either be the data name or the user name prop can also be the index of a column """ # Try to get the column index. If neither user nor data name exists, # then we have to create a new one. num_columns = mm.mmtable_get_column_total(self._table) if type(prop) is int: # Integer column index column = prop if column < 1: raise IndexError("Column must be >= 1") if column > num_columns: raise IndexError("Column must be <= %d" % num_columns) elif isinstance(prop, str): column = -1 try: # Look for matching m2io data name column = mm.mmtable_get_column_index(self._table, prop) except mm.MmException: # Try finding a matching user name for i in range(1, num_columns + 1): name = mm.mmtable_column_get_name(self._table, i) if name == prop: column = i break if column < 1: # Construct a new property # If the property is a user name, convert to a data name if _is_data_name(prop): index = prop.find("_", 2) if index < 0: raise KeyError("%s is a badly formed m2io name" % prop) name = prop[index + 1:] else: name = prop prop = "s_user_" + name mm.mmtable_insert_column(self._table, mm.MMTABLE_END, prop) column = mm.mmtable_get_column_index(self._table, prop) mm.mmtable_column_set_name(self._table, column, name) else: raise TypeError("Column must be either an integer or a string") # Get column's property type try: data_type = mm.mmtable_column_get_data_type(self._table, column) except mm.MmException: Table._printDebug("TableRow::__setitem__ - %s '%s'" % \ ("Could not get type for property", prop)) return self._setValue(column, data_type, value) def _setValue(self, column, data_type, value): """Private function to set a cell value""" # Set the value of the correct type if data_type == mm.M2IO_REAL_TYPE: Table._printDebug('set real') mm.mmtable_cell_set_real(self._table, self._row, column, value) elif data_type == mm.M2IO_BOOLEAN_TYPE: Table._printDebug('set Boolean') mm.mmtable_cell_set_boolean(self._table, self._row, column, value) elif data_type == mm.M2IO_INT_TYPE: Table._printDebug('set int') mm.mmtable_cell_set_integer(self._table, self._row, column, value) elif data_type == mm.M2IO_STRING_TYPE: Table._printDebug('set string, row: %d, column: %d, value: %s' \ % (self._row, column, value)) mm.mmtable_cell_set_string(self._table, self._row, column, value)
# *************************************************************************** # ***************************************************************************
[docs]class Table(mmobject.MmObject): """ Class to handle mmtables. This is largely a wrapper to the underlying C library which stores all the state information. """ # *************** # Class variables # *************** # This dict will keep track of the number of objects for each handle, so # we know when to call mmtable_delete. This allows someone to use # foo = Table(1) as a way of gaining access to a garbage # collected project handle. This is all handled in the # MmObject base class. # Required by MmObject base class _instances = {} # Controls whether debugging output is generated or not # On by default. Class level, not instance. _debug = False # *********************************************************************** # Static method. Enable or disable debug output # ***********************************************************************
[docs] def setDebug(state): """ Enable or disable debug output. Use False or True. Sets this for the class, i.e. affects all instances, current and future. """ # Set at the class level, not for a given instance Table._debug = state
setDebug = staticmethod(setDebug) # *********************************************************************** # Static printing method. Print out debug if enabled # ******************************************************************* def _printDebug(*args): """Print debugging output if enabled""" if Table._debug: print(args) _printDebug = staticmethod(_printDebug) # *********************************************************************** # Static initialization method. Used to initialize various mmlibs # ***********************************************************************
[docs] def initialize(error_handler=None): """ Initialize necessary mmlibs (which will also implicitly initialize all the dependent mmlibs) """ if error_handler is None: error_handler = mm.error_handler Table._printDebug("initialize(): Initializing mmlibs") mm.mmtable_initialize(error_handler)
initialize = staticmethod(initialize) # *********************************************************************** # Static termination method. Used to terminate mmlibs libraries # ***********************************************************************
[docs] def terminate(): """ Terminate various mmlibs (which also implicitly terminates all the libraries they are dependent upon) """ mm.mmtable_terminate()
terminate = staticmethod(terminate) # *********************************************************************** # Constructor # ***********************************************************************
[docs] def __init__(self, table_file="", table_handle=None, manage=True): """ Construct a Table class object either by reading a table file or using a handle to an existing mmtable. If a table file is given via table_file, then we read the given mmtable file. If a table handle for an existing table is passed in (via the table_handle argument), then we use that. If neither are given, then we create a new table object owned by this table. Specifying both is an error. Passing in a table file causes manage=True. When a managed instance goes out of scope or is deleted, garbage collection is performed. One side effect is that the table is deleted. Passing in a table handle causes manage=False. When a non-managed instance goes out of scope or is deleted the table is left around. """ Table._printDebug("In __init__()") if table_file and table_handle is not None: raise ValueError("Cannot specify both table file and handle") if table_file: Table._printDebug("__init__(): reading table file") manage = True self.table_file = table_file # Read the table file and get a table handle Table._printDebug("__init()__: table file exists... :%s:" % \ table_file) import os if not os.path.exists(table_file): raise OSError("Table file '%s' does not exist" % table_file) # Have to initialize here because this is before # the base class initialization. mm.mmtable_initialize(mm.error_handler) try: tmp_handle = mm.mmtable_m2io_read(table_file) except mm.MmException: raise OSError("Table file '%s' cannot be read" % table_file) Table._printDebug("Successfully read table") # Call base class initalization super().__init__(tmp_handle, manage, mm.error_handler) # Cancel out the initialize call made above. mm.mmtable_terminate() else: if table_handle is None: Table._printDebug("__init__(): creating a new table") table_handle = mm.mmtable_new() manage = True else: Table._printDebug("__init__(): using existing table") # Nothing to do as it has already been initialized and # we have a handle # Call base class initalization super().__init__(table_handle, manage, mm.error_handler) if not mm.mmtable_table_is_valid(self.handle): print("There is no active table for the handle %d." % \ self.handle) return self.table_name = "" # *** Initialize other instance variables # Set currently selected row to "no row" # Used for supporting iterator self._sel_bs_position = -1
# *********************************************************************** # Private deletion method. Required by MmObject base class # *********************************************************************** def _delete(self): """ A function to delete this object. Required for MmObject interface. """ mm.mmtable_delete(self.handle) # *********************************************************************** # Full string representation # *********************************************************************** def __repr__(self): """ Return representation suitable for use as a Python expression """ return "Table(%d)" % self.handle # *********************************************************************** # Human readable string # *********************************************************************** def __str__(self): """Return string representation""" return "Table file (%s), handle (%d)" % (self.table_file, self.handle) # *********************************************************************** # MMlibs mmtable handle # *********************************************************************** def __index__(self): """ Return mmtable handle. Can be used with mmtable functions. """ return int(self.handle) # *********************************************************************** # # ***********************************************************************
[docs] def __len__(self): """ Return the number of rows in the table """ num_rows = mm.mmtable_get_row_total(self.handle) return num_rows
# *********************************************************************** # Provide sequence key style access # *********************************************************************** def __getitem__(self, item): """ Returns Table Row (if item is an integer) or table property (if item is a string) """ if type(item) is int: # Treat as row number return TableRow(self.handle, item) elif isinstance(item, str): # Treat as table-level property if item[0] == 'b': # Boolean return mm.mmtable_get_boolean(self.handle, item) elif item[0] == 'i': # Integer return mm.mmtable_get_integer(self.handle, item) elif item[0] == 'r': # Real return mm.mmtable_get_real(self.handle, item) elif item[0] == 's': # String return mm.mmtable_get_string(self.handle, item) raise TypeError("Property %s is not an integer or string" % str(item)) def __setitem__(self, item, value): """This function sets a table-level property""" if isinstance(item, str): if item[0] == 'b': # Boolean mm.mmtable_set_boolean(self.handle, item, value) elif item[0] == 'i': # Integer mm.mmtable_set_integer(self.handle, item, value) elif item[0] == 'r': # Real mm.mmtable_set_real(self.handle, item, value) elif item[0] == 's': # String mm.mmtable_set_string(self.handle, item, value) else: raise KeyError("Property %s is not a valid data name" % item) else: raise TypeError("Property %s is not a string" % str(item)) def __iter__(self): """ Return an iterator object Allows iteration over the selected rows. To iterate over all rows use: \'for row in range(1, len(table)+1)\' """ bitset = mmbitset.Bitset(mm.mmtable_get_selected_rows(self.handle)) yield from bitset
[docs] def cellClearData(self, row, column): """ Clears any data in the given cell """ mm.mmtable_cell_clear_data(self.handle, row, column)
[docs] def cellHasData(self, row, column): """ Indicates whether or not the given cell has data """ return mm.mmtable_cell_has_data(self.handle, row, column)
[docs] def columnGetDataName(self, column): """ Returns the data name for the given column """ return mm.mmtable_column_get_data_name(self.handle, column)
[docs] def columnGetDataType(self, column): """ Returns the data type for the given column Possibilities are: mm.M2IO_BOOLEAN_TYPE, mm.M2IO_INT_TYPE, mm.M2IO_REAL_TYPE, or mm.M2IO_STRING_TYPE """ return mm.mmtable_column_get_data_type(self.handle, column)
[docs] def columnGetName(self, column): """ Returns the user name for the given column """ return mm.mmtable_column_get_name(self.handle, column)
[docs] def columnGetPersistent(self, column): """ Returns whether or not the given column is persistent. Only persistent columns will be written to disk. """ return mm.mmtable_column_get_persistent(self, column)
[docs] def columnGetProperty(self, column, prop): """ Returns the value of the given property for the column """ if isinstance(prop, str): if prop[0] == 'b': return mm.mmtable_column_get_boolean(self.handle, column, prop) elif prop[0] == 'i': return mm.mmtable_column_get_integer(self.handle, column, prop) elif prop[0] == 'r': return mm.mmtable_column_get_real(self.handle, column, prop) elif prop[0] == 's': return mm.mmtable_column_get_string(self.handle, column, prop) raise TypeError("Property %s is not a string" % str(prop))
[docs] def columnGetVisible(self, column): """ Returns the visible property for the given column """ return mm.mmtable_column_get_visible(self.handle, column)
[docs] def columnGetWidth(self, column): """ Returns the width in characters of the given column """ return mm.mmtable_column_get_width(self.handle, column)
[docs] def columnSetName(self, column, name): """ Sets the user name for the given column """ mm.mmtable_column_set_name(self.handle, column, name)
[docs] def columnSetPersistent(self, column, persistent): """ Sets whether or not the given row is persistent. Only persistent columns will be written to disk. """ mm.mmtable_column_set_persistent(self.handle, column, persistent)
[docs] def columnSetProperty(self, column, prop, value): """ Sets the value of the given property for the column """ if isinstance(prop, str): if prop[0] == 'b': mm.mmtable_column_set_boolean(self.handle, column, prop, value) elif prop[0] == 'i': mm.mmtable_column_set_integer(self.handle, column, prop, value) elif prop[0] == 'r': mm.mmtable_column_set_real(self.handle, column, prop, value) elif prop[0] == 's': mm.mmtable_column_set_string(self.handle, column, prop, value) else: raise KeyError("Property %s is not a valid data name" % prop) else: raise TypeError("Property %s is not a string" % str(prop))
[docs] def columnSetVisible(self, column, visible): """ Sets the visible property for the given column """ mm.mmtable_column_set_visible(self.handle, column, visible)
[docs] def columnSetWidth(self, column, width): """ Sets the width in characters for the given column """ mm.mmtable_column_set_width(self.handle, column, width)
[docs] def deleteAllRows(self): """ Deletes all rows from the table """ mm.mmtable_delete_all_rows(self.handle)
[docs] def deleteColumn(self, column): """ Delets the given column """ mm.mmtable_delete_column(self.handle, column)
[docs] def deleteRow(self, row): """ Deletes the given row """ mm.mmtable_delete_row(self.handle, row)
[docs] def getColumnIndex(self, name): """ Returns the column index for the given user or data name """ column = -1 try: # Check m2io data name column = mm.mmtable_get_column_index(self.handle, name) except mm.MmException: # Look for user name instead num_columns = self.getColumnTotal() for i in range(1, num_columns + 1): n = mm.mmtable_column_get_name(self.handle, i) if n == name: column = i break if column < 1: raise KeyError("Property %s was not found" % name) return column
[docs] def getColumnNames(self): """ Return a list of the column data names in this table """ ret_list = [] num_columns = mm.mmtable_get_column_total(self.handle) for i in range(1, num_columns + 1): data_name = mm.mmtable_column_get_data_name(self.handle) ret_list.append(data_name) return ret_list
[docs] def getColumnTotal(self): """ Returns the number of columns in the table """ return mm.mmtable_get_column_total(self.handle)
[docs] def getFirstSelectedRow(self): """ Returns the first selected row--throws an error if no rows are selected. """ return mm.mmtable_get_first_selected_row(self.handle)
[docs] def getOrAddColumn(self, name, visible): """ Finds or adds the given column via data name. visible indicates whether or not the column should be visible if it is added. """ return mm.mmtable_get_or_add_column(self.handle, name, visible)
[docs] def getSelectedRows(self): """ Returns a bitset containing the selected rows """ bitset = mm.mmtable_get_selected_rows(self.handle) return mmbitset.Bitset(bitset)
[docs] def getSelectedRowTotal(self): """ Return the total number of selected rows """ return mm.mmtable_get_num_selected_rows(self.handle)
[docs] def getVisibleColumnTotal(self): """ Returns the number of visible columns """ return mm.mmtable_get_visible_column_total(self.handle)
[docs] def getVisibleColumnIndex(self, column): """ Converts an index from the visible columns into the full set of columns """ return mm.mmtable_get_visible_column_index(self.handle, column)
[docs] def getVisibleRowIndex(self, row): """Converts an index from the visible rows into the full set of rows""" return mm.mmtable_get_visible_row_index(self.handle, row)
[docs] def getVisibleRowTotal(self): """ Returns the number of visible rows """ return mm.mmtable_get_visible_row_total(self.handle)
[docs] def insertColumn(self, column, name): """ Adds the given column before the specified column. Specify column 1 to insert at the far left of the table (first column) Specify mm.MMTABLE_END to add to the right of the table """ prefix = name[:2] if prefix != "b_" and prefix != "i_" and prefix != "r_" \ and prefix != "s_": user_name = name prop = "s_user_" + user_name else: prop = name index = prop.find("_", 2) user_name = prop[index + 1:] mm.mmtable_insert_column(self.handle, column, prop) new_column = self.getColumnIndex(prop) self.columnSetName(new_column, user_name)
[docs] def insertRow(self, row, file_index): """ Adds a row. Set row to 1 to insert at the top of the table or mm.MMTABLE_END to add to the bottom of the table. If the table has an M2IO_DATA_FILE_INDEX column, the cell's value will be set to file_index (if file_index != 0), or to a unique value if file_index == 0. """ mm.mmtable_insert_row(self.handle, row, file_index)
[docs] def isRowSelected(self, row): """ Returns True if the given row is selected, or False otherwise """ return bool(mm.mmtable_row_get_selection(self.handle, row))
[docs] def keys(self): """ Returns the column data names """ num_columns = self.getColumnTotal() return [self.columnGetDataName(c) for c in range(1, num_columns + 1)]
[docs] def makeNestedRowVisible(self, row): """ Expands and makes visible all parents of the given row. Also makes the given row visible, but does not expand it. """ mm.mmtable_make_nested_row_visible(self.handle, row)
[docs] def moveColumn(self, from_column, to_column): """ Moves the given column to the destination column """ mm.mmtable_move_column(self.handle, from_column, to_column)
[docs] def moveRows(self, from_rows, to_row): """ Moves the given rows to the destination row """ mm.mmtable_move_rows(self.handle, from_rows, len(from_rows), to_row)
[docs] def rowGetExpanded(self, row): """ Indicates whether or not the given row is expanded """ return mm.mmtable_row_get_expanded(self.handle, row)
[docs] def rowGetVisible(self, row): """ Returns the visible property for the given row """ return mm.mmtable_row_get_visible(self.handle, row)
[docs] def rowSetExpanded(self, row, expanded): """ Sets the expanded property for the given row. The row can also be set to mm.MMTABLE_ALL_ROWS. """ mm.mmtable_row_set_expanded(self.handle, row, expanded)
[docs] def rowSetVisible(self, row, visible): """ Sets the visible property for the given row. The row can also be set to mm.MMTABLE_ALL_ROWS. """ mm.mmtable_row_set_visible(self.handle, row, visible)
[docs] def selectAddRows(self, rows): """ This function selects the given rows without deselecting any other rows. """ if type(rows) is int: mm.mmtable_row_set_selection(self.handle, rows, True) else: for row in rows: mm.mmtable_row_set_selection(self.handle, row, True)
[docs] def selectRows(self, rows): """ This function selects only the given rows. """ mm.mmtable_row_set_selection(self.handle, mm.MMTABLE_ALL_ROWS, False) self.selectAddRows(rows)
[docs] def sortRows(self, column, ascending): """ Sorts the rows by the given column in either ascending or descending order """ mm.mmtable_sort_rows(self.handle, column, ascending)
[docs] def unselectRows(self, rows): """ This function unselects the given rows """ if type(rows) is int: mm.mmtable_row_set_selection(self.handle, rows, False) else: for row in rows: mm.mmtable_row_set_selection(self.handle, row, False)