ArcGIS Python Tool - Importar script personalizado en la clase ToolValidator

9

Había publicado una pregunta la semana pasada sobre la personalización de una clase de ToolValidator y obtuve algunas muy buenas respuestas. Al trabajar con las soluciones propuestas, he creado un módulo personalizado que realiza consultas en una base de datos, y será llamado por la clase ToolValidator (para proporcionar valores para las listas desplegables) y más tarde en el script de geoprocesamiento (para obtener otros parámetros basados ​​en elementos seleccionados en listas desplegables). Sin embargo, parece que no puedo llamar al módulo personalizado en la clase ToolValidator. He estado tratando de unirme al camino sin suerte. Cuando intento aplicar estos cambios al script, aparece un error de tiempo de ejecución: [Errno 9] Descriptor de archivo incorrecto. Si comento una línea de importación, no hay errores.

sys.path.append('my_custom_module_directory')
import my_custom_module

Muchos de ustedes se preguntarán por qué no solo implemento una herramienta personalizada con ArcObjects. La razón es que mis usuarios finales no tienen los privilegios necesarios para registrar CUALQUIER dlls en su computadora.

ACTUALIZACIÓN: Esto me estaba sucediendo en ArcGIS 10. Curiosamente, inicialmente estaba agregando a la ruta dentro de la función initialiazeParameters de la clase ToolValidator. Si anexo fuera (es decir, encima) de la clase ToolValidator, todo funciona como se esperaba.

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()

ACTUALIZACIÓN 2: Creo que encontré la verdadera causa de mi problema. En los fragmentos de código de esta publicación, he estado agregando lo que parecen ser rutas reales (por ejemplo, C: / Working / SomeFolder) al sys.path. En mi clase real de ToolValidator, estaba construyendo una ruta relativa usando os.path.dirname(__file__)+ "\ my_special_folder ...". Estaba anticipando que os.path.dirname(__file__)devolvería la ruta de la caja de herramientas, ya que contiene la clase ToolValidator. He llegado a encontrar que este no es el caso. Por lo que puedo decir, la clase ToolValidator nunca se escribe realmente en un archivo .py, y especulo que este código se pasa al intérprete de Python en la memoria, por lo que __file__es inútil, o se conserva algún script temporal y luego execfile ( path_to_script) se llama, nuevamente renderizando__file__inútil. Estoy seguro de que también faltan otras razones.

Para resumir, si uso una ruta codificada, sys.append funciona en cualquier lugar, las rutas relativas no funcionan tan bien en la clase ToolValidator.

usuario890
fuente
¿Es esto en 9.3 o 10?
Jason Scheirer
Tenemos problemas para reproducir esto aquí en Esri, si aislamos la causa podemos respaldar una solución para 10.0 SP3. Mientras tanto, supongo que estás atrapado con el primero y no con el último patrón de uso.
Jason Scheirer

Respuestas:

3

La forma en que hago esto es, después de iniciar ArcGIS o ArcCatalog, primero ejecute una herramienta ficticia ("Ejecutar esto una vez") llamando a un script dummy.py. Después de hacerlo, puede importar scripts de python en el validador utilizando sys.argv [0]. Esto apuntará a la carpeta donde se ubicó el primer script. A partir de entonces, puede importar el script necesario en la clase de validación

El script dummy.py llamado por la herramienta "Ejecutar esto una vez":

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')

Lo siento, no puedo obtener el formato correcto Saludos, Maarten Tromp

Maarten Tromp
fuente
3

¡Finalmente resolvió este horrible error! Por ejemplo, cuando intenta aplicar cambios para importar un módulo o paquete relativo, puede ver el siguiente error:

ingrese la descripción de la imagen aquí

Opción 1: solo
para el desarrollador, agregue la ruta completa al módulo a PYTHONPATH . Deberá reiniciar ArcMap / ArcCatalog antes de que surta efecto. Use el siguiente código para importar el módulo desde una ruta relativa (para la implementación). No se preocupe, el usuario final no necesita ninguna adición a su variable PYTHONPATH, ¡funcionará!

Opción 2:
agregue una línea adicional en el código a continuación para agregar la ruta codificada, por ejemplo: sys.path.append (r "c: \ temp \ test \ scripts")
Cuando esté listo para implementar, tiene un directorio extraño, pero no importa, todo debería funcionar en la computadora del usuario final porque la primera ruta que agregó fue el directorio relativo (nuestro objetivo era superar el cuadro de diálogo de falla).

Código común a ambas opciones:

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

Actualizar

¡Adiós mal corte y pegado! Actualicé el ejemplo de código para que la clase ToolValidator se importe desde la biblioteca. Corto y pego solo una vez cuando los parámetros de la herramienta se establecen por primera vez. Guardo este fragmento de código en la cadena de documentos del ToolValidator que se está importando.

En este ejemplo, el nombre del directorio fuente se basa en el nombre tbx. Este enfoque evita colisiones si tiene dos cajas de herramientas con diferentes directorios de origen. El estándar que utilicé para nombrar la carpeta de origen es el siguiente:
TOOLBOXNAME__anything.tbx -> TOOLBOXNAME.src

¿Por qué el "__ cualquier cosa"? Dado que los archivos binarios no pueden fusionarse en nuestro DVCS, podemos asignar herramientas a individuos y no preocuparnos por perder los cambios. Cuando se finaliza la herramienta, se corta y se pega en el maestro.

También necesitaba acceder a los archivos en la carpeta de origen para completar un menú desplegable, use este método para obtener la ruta a la caja de herramientas desde su módulo importado:

import __main__
tbxPath = __main__.__file__.split("#")[0]
Michael Jackson
fuente
¿Podría ser que el código ToolValidator esté configurando el valor predeterminado del parámetro? Verifique la configuración del 'Valor predeterminado' del parámetro en las propiedades de la herramienta de script.
blah238
Gracias por la sugerencia. Lo comprobé y el valor predeterminado no está establecido en la caja de herramientas ... pero copié la caja de herramientas y cambié el nombre de todo, y el valor aún persistió en ambas copias. Por lo tanto, abandonaré mi idea de caché y sugeriré que en realidad podría almacenarse en el archivo .tbx, que sigue siendo un comportamiento extraño.
MJ
2

Poner las importaciones en la parte superior del módulo de validación, fuera de la ToolValidatorclase, parece funcionar bien para mí: estoy en 10.0 SP2. Sin embargo, no estoy haciendo nada con el módulo importado en ninguna parte excepto en 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:
    ...
blah238
fuente
Intenté importar fuera de la clase ToolValidator pero falló en la declaración de importación. ¿Estaba utilizando un ArcCatalog recién abierto, antes de que se ejecutaran los scripts? Me imagino que es por eso que ESRI está teniendo dificultades para reproducir el error ... solo ocurre en una aplicación recién abierta antes de que se ejecuten los scripts.
MJ
A mí me funciona con un ArcCatalog recién abierto. Me pregunto si está importando una clase frente a una función que es el problema.
blah238
Gracias, es posible que tenga algo ... Recuerdo vagamente un caso en el que funcionó cuando importé directamente una función, haré algunas pruebas más.
MJ
Comportamiento muy extraño ... funcionaría hasta que logre romperlo. Después de romperlo, siempre arrojaría un error. El uso del PYTHONPATH en la máquina del desarrollador, o la adición de una segunda ruta codificada, como se describe anteriormente, hizo el truco.
MJ
0

Pude mover mi validación a un archivo py importándola y llamándola desde la validación de la herramienta TBX existente. La clave estaba llamando a la importación dentro del constructor. Si lo llamé desde fuera de la clase ToolValidator, la importación falló. Esto es lo que tenía dentro de la pestaña de validación de TBX.

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])
   sys.path.append(self.scriptDir)
   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."""
   self.validator.initializeParameters()
   return

 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."""
   self.validator.updateParameters()
   return

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

Mi lógica de validación luego vivía en ExportParcelIntersected.ToolValidator (). Donde podría mantenerse más fácil.

TurboGus
fuente