Debugging a ToolValidator class

As illustrated below, to program a ToolValidator class, right-click your tool, click Properties, click the Validation tab, then click Edit. This opens your installed Python editor, such as IDLE or PythonWin. Add your code, save your changes, exit the editor, then click either Apply or OK on the Validation panel.

Entering ToolValidator code

When you click Apply or OK, your ToolValidator code is checked for syntax errors. If there are syntax errors in any of the class methods, a message box containing a description of the syntax error appears, and you must fix the error before you can apply your edits. In addition to checking for syntax errors, the ToolValidator class is initialized and the initializeParameters method is called and checked for runtime errors. An example of a runtime error is invoking a method that doesn't exist, such as the following:

fc = self.params[0].Value  # Should be "value"

Runtime errors in updateParameters and updateMessages methods are found when the tool dialog box is opened and these methods are called. Runtime errors show as errors in the first parameter. This is illustrated below; the updateParameters method contains a typo (for example, Value versus value):

Display of runtime errors

To execute the tool, you need to edit your ToolValidator code and fix the error (in this case, changing Value to value).

Using the Describe function

A subtle but important issue is the use of the Describe function.

CautionCaution:

When using the Describe function, never use the string representation of the value.

Incorrect (str function used)

  desc = arcpy.Describe(str(self.params[0].value))

Correct

  desc = arcpy.Describe(self.params[0].value)

You shouldn't use the string representation for datasets (which yields the catalog path to the dataset) because the dataset may not exist—it may be a model-derived variable, and the model has to be run before the dataset exists on disk. (You can check whether a parameter contains a derived variable with the parameter.isInputValueDerived method.) If you use the string representation for the dataset, Describe may fail, since the dataset may not yet exist on disk.

Make sure your parameter has a value before testing

A common ToolValidator coding mistake is not testing for uninitialized values.

Incorrect (The value is not set, and arcpy.Describe fails.)

  fc = self.params[0].value
  shapetype = arcpy.Describe(fc).shapeType.lower()

Correct (Test for value first.)

  fc = self.params[0].value
  if fc:
    shapetype = arcpy.Describe(fc).shapeType.lower()

Advanced debugging

Even if your ToolValidator class is free of syntax and runtime errors, you may still have logic errors, such as parameters not enabling or disabling properly, default values not being calculated properly, or the output description not being populated correctly. Traditionally, there are two techniques to find logic errors in a script tool:

Because ToolValidator code is stored with the tool and is executed only when the dialog or command line is being used, these two techniques require special handling in ToolValidator.

Displaying debug messages

Unfortunately, you cannot print messages in ToolValidator—the Python print directive has no effect, since there is nowhere to write the message. Using the parameter object methods setErrorMessage and setWarningMessage to display debugging messages is problematic—you can only use these in the updateMessages method, but the debugging message you want to display was probably generated in updateParameters, and there's no way to pass your debug message across class methods in ToolValidator.

You can, however, use this trick:

  • Add a new string parameter to your tool. Make it the last parameter.
  • In updateParameters, write your debugging message to this new parameter.
  • Open the tool dialog box and provide values. The debugging message appears in the new string parameter.
  • After finding and fixing the issue, remove the last parameter from the parameter list and any use of it in your ToolValidator code.

The following code demonstrates this:

  def updateParameters(self):
    if self.params[0].value and not self.params[1].altered:
      desc = arcpy.Describe(self.params[0].value)
      fields = desc.fields
      for field in fields:
        fType = field.type.lower()
        if fType == "smallinteger" or \
           fType == "integer":
          self.params[1].value = field.name

          # Update our "debug" parameter to show
          #  the field type
          #
          self.params[2].value = fType
          break

        # No field, update "debug" parameter
        #
        self.params[1].value = ""
        self.params[2].value = "No field found"
    return

Debugging in a Python IDE

In some cases, printing out debug messages isn't enough, and you need to debug your code using a Python IDE (such as IDLE or PythonWin), setting breakpoints, stepping through your code, examining values, and fixing logic errors.

To debug, create a stand-alone script and debug it within your editor. At the top of the script, load the toolbox, create the parameter array, then set the necessary parameter values, as follows:

import arcpy

# Load the toolbox and get the tool's parameters, using the tool
#  name (not the tool label).
#
arcpy.ImportToolbox("E:/Documents/Tool Validation Examples.tbx")
params = arcpy.GetParameterInfo("HotSpots_stats")

# Set required parameters
#
params[0].value = "D:/st_johns/city.mdb/roads"

Note that arcpy.GetParameterInfo contains the name of your tool—not the label. This allows geoprocessing to create the parameter array for you. You then set one or more parameters in the array.

Now add your ToolValidator code. (You can copy/paste the code directly from the Properties dialog box).

At the bottom of your script, call ToolValidator as follows:

# Create the ToolValidator class and call updateParameters 
#   and/or updateMessages
#
validator = ToolValidator()
validator.updateParameters()

To review, the basic structure of your stand-alone script is as follows (actual ToolValidator code has been removed to keep the example brief):

# Create the parameter array and values
#
import arcpy

# Add the toolbox and fetch the parameter list
#
arcpy.ImportToolbox("E:/Documents/Tool Validation Examples.tbx")
params = arcpy.GetParameterInfo("HotSpots_stats")
params[0].value = "D:/st_johns/city.mdb/roads"

# ToolValidator class block
#
class ToolValidator:

  def __init__(self):
    import arcpy 
    self.params = arcpy.GetParameterInfo()

  def initializeParameters(self):
    # (initializeParameters code here)
    return

  def updateParameters(self):
    # (updateParameters code here)
    return

  def updateMessages(self):
    # (updateMessages code here)
    return

# Call routine(s) to debug
#
validator = ToolValidator()
validator.updateParameters()
validator.updateMessages()

4/14/2011