def __init__(self, scisheets_api, is_logging=False, debug=False):
   """
   :param ApiFormula scisheets_api:
   :param bool is_logging: creates a log file
   """
   self.debug = debug
   self._api = scisheets_api
   self._block_linenumber = None  # Where exception occurred in block
   self._block_name = None
   self._block_start_linenumber = None  # Start of block in source
   self._caller_filename = None
   self._exception = None
   self._exception_filename = None
   if is_logging:
     self._logger = Logger(settings.SCISHEETS_LOG,
         "controller")
   else:
     self._logger = None
   self._iterations = 0
   self._is_first = True
   self._table = None
   if self._api is not None:
     self._table = self._api.getTable()
class BlockExecutionController(object):

  """
  Assists with:
    1. Controlling the execution of a code block, such as
       making exceptions precise by identifying the 
       code block and line number at which an exception occurs.
       See: startBlock, endBlock, exceptionForBlock
    2. Managing loop iterations.
       See: initializeLoop, startIteration, endIteration
  """

  def __init__(self, scisheets_api, is_logging=False, debug=False):
    """
    :param ApiFormula scisheets_api:
    :param bool is_logging: creates a log file
    """
    self.debug = debug
    self._api = scisheets_api
    self._block_linenumber = None  # Where exception occurred in block
    self._block_name = None
    self._block_start_linenumber = None  # Start of block in source
    self._caller_filename = None
    self._exception = None
    self._exception_filename = None
    if is_logging:
      self._logger = Logger(settings.SCISHEETS_LOG,
          "controller")
    else:
      self._logger = None
    self._iterations = 0
    self._is_first = True
    self._table = None
    if self._api is not None:
      self._table = self._api.getTable()

  def _log(self, name, details):
    if self._logger is not None:
      self._logger.log(name, details=details)

  # TODO: Handle different file for caller
  def startBlock(self, name):
    """
    Called at the start of a block that is being evaluated.
    :param str name: User oriented identifier of the code block
    """
    if self.debug:
      if name == 'V_MAX':
        import pdb; pdb.set_trace()
    self._block_name = name
    context = inspect.getouterframes(inspect.currentframe())[1]
    linenumber = context[2]
    self._caller_filename = context[1]
    self._block_start_linenumber = linenumber + 1
    self._exception_filename = None
    self._log("start/%s" % self._block_name, "")

  def endBlock(self):
    """
    Called at the end of a block
    """
    self._log("end/%s" % self._block_name, "")
    self._block_start_linenumber = None
    self._caller_filename = None
    self._exception_filename = None
    self._block_name = None

  def exceptionForBlock(self, exception):
    """
    Called when an exception has occurred.
    :param Exception exception:
    :return str, int: block name, line number in the block
    :raises RuntimeError: if not within a block
    """
    if self.debug:
      import pdb; pdb.set_trace()
    if self._block_name is None:
      self._block_name = "Unknown"
    self._exception = exception
    _, _, exc_tb = sys.exc_info()
    self._exception_filename = exc_tb.tb_frame.f_code.co_filename
    # Check for compile error
    if 'lineno' in dir(self._exception):
      abs_linenumber = self._exception.lineno
      is_runtime_error = False
    # Must be runtime error
    else:
      abs_linenumber = exc_tb.tb_lineno
      is_runtime_error = True
    # Compute the line number of the exception
    if is_runtime_error and   \
        self._exception_filename == self._caller_filename:
      self._block_linenumber = abs_linenumber  \
          - self._block_start_linenumber + 1
    else:
      self._block_linenumber = abs_linenumber
    self._log("exception/%s" % self._block_name, self.formatError())

  def formatError(self, 
                  is_absolute_linenumber=False,
                  is_use_block_name=True):
    """
    Formats the exception to include the block and line number.
    :param bool is_absolute_linenumber: Forces message to be
                                      an absolute line number
    :param bool is_use_block_name: Use the block name in the message
    :return str/None:
    """
    if self._exception is None:
      return None
    if is_use_block_name:
      if (not is_absolute_linenumber)  \
          and self._caller_filename == self._exception_filename:
        if not "Computing" in str(self._exception):
          msg = "Computing %s near line %d: %s" % (self._block_name, 
              self._block_linenumber, str(self._exception))
        else:
          msg = str(self._exception)
      else:
        msg = "In %s near line %d: %s" % (self._exception_filename,
            self._block_linenumber, str(self._exception))
    else:
      msg = "near line %d: %s" % (self._block_linenumber, str(self._exception))
    return msg

  def initializeLoop(self):
    """
    Initializes variables before loop begins
    """
    self._iterations = 0
    self._log("initializeLoop", "")

  def startAnIteration(self):
    """
    Beginning of a loop iteration
    """
    self._iterations += 1
    self._exception = None
    for cv in self._api.getColumnVariables():
      try:
        cv.setIterationStartValue()
      except Exception as err:
        import pdb; pdb.set_trace()
        pass
    self._log("startAnIteration", "iterations=%d" % self._iterations)

  def endAnIteration(self):
    """
    End of a loop iteration
    """
    self._log("endAnIteration", "iterations=%d" % self._iterations)

  def endProgram(self, details=""):
    """
    End of a loop iteration
    """
    self._log("endProgram", details)

  def _isEquivalentValues(self):
    """
    Checks if not namespace variable has changed since the start of the iteration.
    :return bool, cv/None: True if no change; cv of first ColumnVariable that failed
    """
    for cv in self._api.getColumnVariables():
      if not cv.isNamespaceValueEquivalentToIterationStartValue():
        return False, cv
    return True, None

  def isTerminateLoop(self):
    """
    Determines if the loop should terminate
    :return bool: terminate loop if True
    """
    num_formula_columns = len(self._table.getFormulaColumns())
    outcome = ""
    done = None
    is_first = self._is_first
    if is_first:
      self._is_first = False
      done = False
      is_not_evaluate = None
      is_not_except= None
      is_equiv = None
      is_large = None
      cv_bad = None
    else:
      is_not_evaluate = not self._table.getIsEvaluateFormulas()
      is_not_except= self._exception is None
      is_equiv, cv_bad = self._isEquivalentValues()
      is_large = self._iterations >= num_formula_columns
      if is_not_evaluate:
        outcome = "True - not isEvaluateFormulas"
        done = True
      elif is_not_except and is_equiv:
        outcome = "True - not exception & equivalent values"
        done = True
      elif is_large:
        outcome = "True - iterations >= num_formula_columns"
        done = True
      else:
        outcome = "False"
        done = False
    details = "%s: not_evaluate: %s; not_except: %s;"  \
        % (outcome, is_not_evaluate, is_not_except)
    cv_msg = str(is_equiv)
    if cv_bad is not None:
        cv_msg = "%s,col=%s" % (is_equiv, cv_bad.getColumn().getName())
    details = "%s equiv: %s; first: %s; large: %s."  \
        % (details, cv_msg, is_first, is_large)
    self._log("isTerminateLoop", details)
    return done
    
  def getException(self):
    return self._exception

  def getExceptionLineNumber(self):
    return self._block_linenumber

  def setTable(self, table):
    self._table = table