ArcGIS Python Tool - Importation d'un script personnalisé dans la classe ToolValidator

9

La semaine dernière, j'avais posé une question sur la personnalisation d'une classe ToolValidator et obtenu de très bonnes réponses. En travaillant avec les solutions proposées, j'ai créé un module personnalisé qui effectue des requêtes sur une base de données, et sera appelé à la fois par la classe ToolValidator (pour fournir des valeurs pour les listes déroulantes) et plus tard dans le script de géotraitement (pour obtenir d'autres paramètres basés sur les éléments sélectionnés dans les listes déroulantes). Cependant, je n'arrive pas à appeler le module personnalisé dans la classe ToolValidator. J'ai essayé d'ajouter au chemin sans succès. Lorsque j'essaie d'appliquer ces modifications au script, j'obtiens une erreur d'exécution: [Errno 9] Mauvais descripteur de fichier. Si je commente la ligne d'importation, aucune erreur.

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

Beaucoup d'entre vous se demandent peut-être pourquoi je n'implémente pas simplement un outil personnalisé avec ArcObjects. La raison en est que mes utilisateurs finaux ne disposent pas des privilèges nécessaires pour enregistrer des DLL sur leur ordinateur.

MISE À JOUR: Cela m'arrivait dans ArcGIS 10. Assez intéressant, j'étais initialement en train d'ajouter au chemin à l'intérieur de la fonction initialiazeParameters de la classe ToolValidator. Si je fais l'ajout à l'extérieur (c'est-à-dire au-dessus) de la classe ToolValidator, tout fonctionne comme prévu.

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

MISE À JOUR 2: Je pense avoir trouvé la véritable cause de mon problème. Dans les extraits de code de cet article, j'ai ajouté ce qui semble être de vrais chemins (ex C: / Working / SomeFolder) au sys.path. Dans ma classe ToolValidator actuelle, je construisais un chemin relatif en utilisant os.path.dirname(__file__)+ "\ my_special_folder ...". os.path.dirname(__file__)J'anticipais que cela retournerait le chemin de la boîte à outils, car il contient la classe ToolValidator. Je suis venu pour constater que ce n'est pas le cas. Pour autant que je sache, la classe ToolValidator n'est jamais réellement écrite dans un fichier .py, et je spécule que le code est transmis à l'interpréteur python en mémoire, donc __file__inutile, ou qu'un script temporaire est persistant, puis execfile ( path_to_script) est appelé, rendant à nouveau__file__inutile. Je suis sûr qu'il y a d'autres raisons qui me manquent également.

Pour faire court, si j'utilise un chemin codé en dur, sys.append fonctionne n'importe où, les chemins relatifs ne fonctionnent pas si bien dans la classe ToolValidator.

user890
la source
Est-ce en 9.3 ou 10?
Jason Scheirer
Nous avons du mal à reproduire cela ici chez Esri, si nous isolons la cause, nous pouvons rétroporter un correctif pour 10.0 SP3. En attendant, je suppose que vous êtes coincé avec le premier et non le dernier modèle d'utilisation.
Jason Scheirer

Réponses:

3

Pour ce faire, après avoir démarré ArcGIS ou ArcCatalog, exécutez d'abord un outil factice ("Exécuter cette fois") appelant un script dummy.py. Après cela, vous pouvez importer des scripts python dans le validateur en utilisant sys.argv [0]. Cela pointera vers le dossier où se trouvait le premier script. Par la suite, vous pouvez importer le script nécessaire dans de Validator Class.

Le script dummy.py appelé par l'outil "Exécuter cette fois":

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

Désolé, impossible d'obtenir le bon formatage Cordialement, Maarten Tromp

Maarten Tromp
la source
3

Enfin craqué cet horrible bug! Par exemple, lorsque vous essayez d'appliquer des modifications pour importer un module ou un package relatif, vous pouvez voir l'erreur suivante:

entrez la description de l'image ici

Option 1:
pour le développeur uniquement, ajoutez le chemin complet du module au PYTHONPATH . Vous devrez redémarrer ArcMap / ArcCatalog avant qu'il ne prenne effet. Utilisez le code ci-dessous pour importer le module à partir d'un chemin relatif (pour le déploiement). Ne vous inquiétez pas, l'utilisateur final n'a besoin d'aucun ajout à sa variable PYTHONPATH, cela fonctionnera!

Option 2:
ajoutez une ligne supplémentaire dans le code ci-dessous pour ajouter le chemin codé en dur, par exemple: sys.path.append (r "c: \ temp \ test \ scripts")
Lorsque vous êtes prêt à déployer, vous disposez d'un répertoire superflu, mais cela n'a pas d'importance, tout devrait fonctionner sur l'ordinateur de l'utilisateur car le premier chemin que vous avez ajouté était le répertoire relatif (notre objectif était de dépasser la boîte de dialogue d'échec).

Code commun aux deux 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

Mise à jour

Adieu mal couper et coller! J'ai mis à jour l'exemple de code afin que la classe ToolValidator soit importée de la bibliothèque. Je coupe et colle une seule fois lorsque les paramètres de l'outil sont définis pour la première fois. Je stocke cet extrait de code dans la docstring du ToolValidator en cours d'importation.

Dans cet exemple, le nom du répertoire source est basé sur le nom tbx. Cette approche évite les collisions si vous avez deux boîtes à outils avec des répertoires source différents. La norme que j'ai utilisée pour nommer le dossier source est la suivante:
TOOLBOXNAME__anything.tbx -> TOOLBOXNAME.src

Pourquoi le "__anything"? Étant donné que les fichiers binaires ne peuvent pas être fusionnés dans notre DVCS, nous pouvons attribuer des outils à des individus et ne pas craindre de perdre des modifications. Lorsque l'outil est finalisé, il est coupé et collé dans le maître.

J'avais également besoin d'accéder aux fichiers dans le dossier source pour remplir une liste déroulante, utilisez cette méthode pour obtenir le chemin d'accès à la boîte à outils à partir de votre module importé:

import __main__
tbxPath = __main__.__file__.split("#")[0]
Michael Jackson
la source
Se pourrait-il que le code ToolValidator définisse la valeur par défaut du paramètre? Vérifiez le paramètre «Valeur par défaut» du paramètre dans les propriétés de l'outil de script.
blah238
Merci pour la suggestion. J'ai vérifié, et la valeur par défaut n'est pas définie dans la boîte à outils ... mais j'ai copié la boîte à outils et renommé tout, et la valeur persistait toujours dans les deux copies. Par conséquent, je vais abandonner mon idée de cache et suggérer qu'elle pourrait en fait être stockée dans le fichier .tbx, ce qui est toujours un comportement étrange.
MJ
2

Mettre les importations en haut du module de validation, en dehors de la ToolValidatorclasse semble bien fonctionner pour moi - je suis sur 10.0 SP2. Cependant, je ne fais rien avec le module importé ailleurs que dans 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
la source
J'ai essayé d'importer en dehors de la classe ToolValidator mais cela échouait sur l'instruction d'importation. Utilisiez-vous un ArcCatalog fraîchement ouvert, avant l'exécution de scripts? J'imagine que c'est pourquoi ESRI a du mal à reproduire l'erreur ... cela ne se produit que dans une application nouvellement ouverte avant l'exécution de scripts.
MJ
Cela fonctionne pour moi avec un ArcCatalog récemment ouvert. Je me demande si c'est l'importation d'une classe vs une fonction qui est le problème?
blah238
Merci, vous pourriez être sur quelque chose ... Je me souviens vaguement d'un cas où cela a fonctionné lorsque j'ai directement importé une fonction, je ferai plus de tests.
MJ
Comportement très étrange ... cela fonctionnerait jusqu'à ce que je réussisse à le casser. Après l'avoir cassé, il lancerait systématiquement une erreur. L'utilisation de PYTHONPATH sur la machine du développeur ou l'ajout d'un deuxième chemin codé en dur, comme indiqué ci-dessus, a fait l'affaire.
MJ
0

J'ai pu déplacer ma validation dans un fichier py en l'important et en l'appelant depuis la validation d'outil TBX existante. La clé appelait l'importation à l'intérieur du constructeur. Si je l'ai appelé de l'extérieur de la classe ToolValidator, l'importation a échoué. Voici ce que j'avais à l'intérieur de l'onglet de validation du 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

Ma logique de validation a ensuite vécu dans ExportParcelIntersected.ToolValidator (). Où il pourrait être maintenu plus facilement.

TurboGus
la source