Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

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.

sys.path.append('my_custom_module_directory')
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.

sys.path.append('C:/Working/SomeFolder')
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
    sys.path.append('C:/Working/SomeFolder')
    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.

share|improve this question
    
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
add comment

3 Answers

up vote 3 down vote accepted

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

share|improve this answer
add comment

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") 
sys.path.append(scriptDir)
from someModuleInScriptDir import someFunction

class ToolValidator:
    ...
share|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
add comment

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(srcDirPath)
# sys.path.append(r"c:\temp\test\scripts")  #option2

from esdlepy.metrics.validation.LandCoverProportions import ToolValidator

Update

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]
share|improve this answer
    
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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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