I had posted a question last week about customizing a ToolValidator class and got some very good answers. In working with the proposed solutions, I have created a custom module that performs queries on a db, and will be called by both the ToolValidator class (to provide values for the drop-down lists) and later in the geoprocessing script (to get other parameters based on items selected in drop-down lists). However, I can't seem to actually call the custom module in the ToolValidator class. I have been trying to append to path with no luck. When I try to apply these changes to the script, I get a runtime error: [Errno 9] Bad file descriptor. If I comment out import line, no errors.

import my_custom_module

Many of you may be asking why don't I just implement a custom tool with ArcObjects. The reason is that my end users don't have the privleges necessary to register ANY dlls on their computer.

UPDATE: This was happening to me in ArcGIS 10. Interestingly enough, I was initially appending to the path inside of the initialiazeParameters function of the ToolValidator class. If I do the append outside (i.e., on top of) of the ToolValidator class, everything works as expected.

import somescript -------->THIS WORKS

class ToolValidator:
  """Class for validating a tool's parameter values and controlling
  the behavior of the tool's dialog."""

  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    import arcpy
    import somescript -------> THIS DOESNT WORK
    self.params = arcpy.GetParameterInfo()

UPDATE 2: I think I found the true cause of my problem. In the code snippets in this post, I have been appending what appear to be real paths (ex C:/Working/SomeFolder) to the sys.path. In my actual ToolValidator class, I was constructing a relative path using os.path.dirname(__file__) +"\my_special_folder...". I was anticipating that os.path.dirname(__file__) would return the path of the toolbox, since that contains the ToolValidator class. I have come to find this is not the case. As far as I can tell, the ToolValidator class is never actually written to a .py file, and I speculate the the this code is passed to the python interpreter in memory, so __file__ is useless, or some temp script is persisted and then execfile(path_to_script) is called, again rendering __file__ useless. I'm sure there are other reasons I am missing as well.

Long story short, if I use a hardcoded path, the sys.append works anywhere, relative paths don't work so well in the ToolValidator class.

  • Is this in 9.3 or 10? – Jason Scheirer Apr 18 '11 at 22:50
  • We're having trouble reproducing this here at Esri, if we isolate the cause we can backport a fix for 10.0 SP3. In the meantime, I suppose you're stuck with the former and not the latter use pattern. – Jason Scheirer Apr 19 '11 at 23:14

The way I do this is, after starting ArcGIS or ArcCatalog, first run a dummy tool ("Run this once") calling a dummy.py script. After doing that you can import python scripts in the validator by using sys.argv[0]. This will point to the folder where the first script was located. Thereafter you can import the needed script in de Validator Class.

The dummy.py script called by "Run this once" tool:

import arcgisscripting, sys, os
gp = arcgisscripting.create(9.3)

# set up paths to Toolshare, scripts en Tooldata
scriptPath = sys.path[0]  
toolSharePath = os.path.dirname(scriptPath)
toolDataPath = toolSharePath + os.sep + "ToolData"
gp.addmessage("scriptPath: " + scriptPath)
gp.addmessage("toolSharePath: " + toolSharePath)
gp.addmessage("toolDataPath: " + toolDataPath)

# Use this to read properties, VERY handy!!
import ConfigParser
config = ConfigParser.SafeConfigParser()
config.readfp(open(scriptPath + os.sep + 'properties.ini'))
activeOTAP = config.get('DEFAULT', 'activeOTAP')
activeprojectspace = config.get('DEFAULT', 'activeprojectspace')
activeproject = config.get('DEFAULT', 'activeproject')
activesdeconnection = config.get('DEFAULT', 'activesdeconnection')

Sorry, can't get the formatting right Regards, Maarten Tromp

| improve this answer | |

Finally cracked this horrible bug! For example when you try to apply changes to import a relative module or package, you might see the following error:

enter image description here

Option 1:
For the developer only, add the full path to the module to the PYTHONPATH. You will need to restart ArcMap/ArcCatalog before it takes effect. Use the code below to import the module from a relative path (for deployment). Don't worry, the end user does not need any additions to their PYTHONPATH variable, it will work!

Option 2:
Add an additional line into the code below to append the hard-coded path, for example: sys.path.append(r"c:\temp\test\scripts")
When you are ready to deploy, you have an extraneous directory, but it doesn't matter, all should work on the enduser's computer because the first path you added was the relative directory (our goal was to just get past the failure dialog).

Code common to both options:

import os
import sys

tbxPath = __file__.split("#")[0]
srcDirName = os.path.basename(tbxPath).rstrip(".tbx").split("__")[0] + ".src" 
tbxParentDirPath =  os.path.dirname(tbxPath)
srcDirPath = os.path.join(tbxParentDirPath, srcDirName)

# sys.path.append(r"c:\temp\test\scripts")  #option2

from esdlepy.metrics.validation.LandCoverProportions import ToolValidator


Farewell evil cutting and pasting! I updated the code sample so the ToolValidator class is imported from the library. I cut and paste only once when the tool parameters are first set. I store this code snippit in the docstring of the ToolValidator being imported.

In this example, the source directory name is based of the tbx name. This approach avoids collisions if you have two toolboxes with different source directories. The standard I used for source folder naming is as follows:
TOOLBOXNAME__anything.tbx -> TOOLBOXNAME.src

Why the "__anything"? Since binary files can't be merged in our DVCS, we can assign tools to individuals and not worry about losing changes. When the tool is finalized, it is cut and pasted into the master.

I also needed to access files in the source folder to populate a dropdown, use this method to get the path to the toolbox from within your imported module:

import __main__
tbxPath = __main__.__file__.split("#")[0]
  • Could it be that the ToolValidator code is setting the default value of the parameter? Check the parameter's 'Default Value' setting in the script tool properties. – blah238 Oct 13 '11 at 6:27
  • Thanks for the suggestion. I checked, and the default value is not set in the toolbox...but I did copy the toolbox and rename everything, and the value still persisted in both copies. Therefore I'll abandon my cache idea and suggest that it might actually be stored in the .tbx file, which is still odd behavior. – majgis Oct 13 '11 at 15:05

Putting the imports at the top of the validation module, outside of the ToolValidator class seems to work fine for me -- I am on 10.0 SP2. However I am not doing anything with the imported module anywhere but in updateParameters.

import os
import sys
scriptDir = os.path.join(os.path.dirname(__file__.split("#")[0]), "Scripts") 
from someModuleInScriptDir import someFunction

class ToolValidator:
| improve this answer | |
  • I tried importing outside the ToolValidator class but it would fail on the import statement. Were you using a freshly opened ArcCatalog, before any scripts were run? I would imagine this is why ESRI is having difficulty reproducing the error...it only occurs in a newly opened application before any scripts are run. – majgis Nov 10 '11 at 7:12
  • It works for me with a newly opened ArcCatalog. I wonder if it is importing a class vs a function that is the problem? – blah238 Nov 10 '11 at 18:08
  • Thanks, you might be on to something....I vaguely remember a case where it worked when I directly imported a function, I'll do some more testing. – majgis Nov 15 '11 at 6:59
  • Very odd behavior...it would work until I managed to break it. After breaking it, it would consistently throw an error. Using PYTHONPATH on the developer machine, or appending a second hard coded path, as outlined above, did the trick. – majgis Nov 17 '11 at 4:47

I was able to move my validation into a py file by importing it and calling it from within the existing TBX's tool validation. The key was calling the import inside the constructor. If I called it from outside the ToolValidator class the import failed. Here is what I had inside the TBX's validation tab.

import arcpy
import os
import sys

class ToolValidator(object):
   """Class for validating a tool's parameter values and controlling
   the behavior of the tool's dialog."""

def __init__(self):
   """Setup arcpy and the list of tool parameters."""
   self.scriptDir = os.path.dirname(__file__.split("#")[0])
   import ExportParcelIntersected
   self.validator = ExportParcelIntersected.ToolValidator()
   self.params = self.validator.params

 def initializeParameters(self):
   """Refine the properties of a tool's parameters.  This method is
   called when the tool is opened."""

 def updateParameters(self):
   """Modify the values and properties of parameters before internal
   validation is performed.  This method is called whenever a parameter
   has been changed."""

 def updateMessages(self):
   """Modify the messages created by internal validation for each tool
   parameter.  This method is called after internal validation."""

My validation logic then lived in ExportParcelIntersected.ToolValidator(). Where it could be maintained easier.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.