Want to run my Python script as a script tool in a custom toolbox

Jump to solution
04-29-2014 06:30 PM
New Contributor II
Hi there,

I wrote a Python script which allows me to select a layer out of my TOC and change the source to a new data source.
The script I wrote works fine and does the job.

Now I'd like to create a script tool with a user interface working out of a custom tool box.
I cannot get my head around with rewriting the script with variables and use it in conjunction with parameters as a script tool.

When I run my script tool it does not spill out any errors nor the script runs as I want it to. (It saves a new *.mxd but without any changes to the file).:confused:

There must be something wrong the way I set up the variables in my Python script and/or the way I setup the parameter in the script tool properties.

- Please find attached my original Python script which runs perfectly fine.
- Also a revised script for the script tool running within a custom tool box.
- ... and a screenshot of the parameter setup. (I did not make any changes to the parameter properties)

Any help would be terrific.
0 Kudos
1 Solution

Accepted Solutions
Frequent Contributor
I duplicated your error - the cause is my ListLayers line in the ToolValidator code.  Long story short, when layers are embedded within group layers, the returned names take on a 'longName' string, so I substituted this original line that works for non-grouped layers:

lyrList = [str(lyr) for lyr in arcpy.mapping.ListLayers(mxd)]

...with this line that works for layers, grouped or not:

lyrList = sorted(set([os.path.basename(str(lyr)) for lyr in arcpy.mapping.ListLayers(mxd) if not lyr.isGroupLayer]))

Also the main script has been corrected to loop on all the layers by the 'short' name, in other words, the layer parameter (text list generated from the validator code) are unique values passed to the main scripts ListLayers function which in turn returns all layer objects (in a group layer or not) by that name to change the datasource.

Here's the modified main script (also in the newly attached toolbox folder [remember to delete any other toolbox versions to avoid confusion]):
# Script Name: Change_datasource # Description: This script allows you to select a specific layer #              in your table of content and change the data source. # Created By:  Pierre # Date:        29/04/2014   import arcpy import os  # Read the parameter values: #  1: Input workspace #  2: Input layer #  3: Set new server connection #  4: Set new source dataset #  mxdLocation = arcpy.GetParameterAsText(0) layer = arcpy.GetParameterAsText(1) serverConnection = arcpy.GetParameterAsText(2) newSource = arcpy.GetParameterAsText(3) newMXDname = arcpy.GetParameterAsText(4)  def gdbType(connPath):         connType = os.path.splitext(connPath)[1]         if connType == '.sde':           arcpy.AddMessage('\nsde source.')           wsType = 'SDE_WORKSPACE'         elif connType == '.gdb':           arcpy.AddMessage('\nfile gdb source:')           wsType = 'FILEGDB_WORKSPACE'         else:           arcpy.AddMessage('\nunhandled source type')           wsType = ''         return wsType  arcpy.env.workspace = serverConnection if arcpy.Exists(newSource):         wsType = gdbType(serverConnection)         arcpy.AddMessage('the workspace for the new source is {0}\n'.format(serverConnection))          MXDdir = os.path.dirname(mxdLocation) newMXDname = os.path.join(MXDdir, newMXDname + '.mxd') mxd = arcpy.mapping.MapDocument(mxdLocation)  # single-layer datasource replacement, 1st one listed from TOC. #lyr = arcpy.mapping.ListLayers(mxd, layer)[0]  # multiple-layer datasource replacement... for lyr in arcpy.mapping.ListLayers(mxd, layer):         if lyr.supports("DATASOURCE") and wsType != '':                 arcpy.AddMessage('DATASOURCE property is supported for {0}.\n'.format(lyr.longName))                 lyr.replaceDataSource(serverConnection, wsType, newSource)         else:                 arcpy.AddMessage('DATASOURCE property is either not supported or handling by this script is not provided.\n')  if not os.path.exists(newMXDname):         arcpy.AddMessage('Please check the layer properties for ALL layers named \'{0}\', checking the source change in your new map doc:'.format(layer))         arcpy.AddMessage('{0}.\n'.format(newMXDname))         mxd.saveACopy(newMXDname) else:         arcpy.AddWarning('mxd name is not unique and will not be saved...\n')  arcpy.AddMessage('The End.')

Enjoy - let me know if you have any further problems....it's very interesting working with the ToolValidator class and interacting/debugging with it.


PS- Another thing I didn't mention that you may need depending on how you've named your layers in the TOC.  You have the option to use a wildcard for the layer name you're searching for....for example, not that the following is practical for you to do, but you could form this valid wildcard combination:

arcpy.mapping.ListLayers(mxd, 'BIOTA_*_ABORIGINAL_LAND*')

View solution in original post

0 Kudos
16 Replies
by Anonymous User
Not applicable
Original User: Wayne_Whitley

I just took a brief look... it appears you're setting the 1st param to the workspace but you have to feed in the full pathname to your map doc.  Maybe that's why it doesn't save any changes.  That's where I'd start.

Also, if you want to set the workspace separately, you can do something like this:

arcpy.env.workspace = r'C:\thisIsMyDesktop\andThisIsWhereMyMxdLives'
mxd = arcpy.mapping.MapDocument("myMapDoc.mxd")

...then change the workspace just before saving to the directory you you want to save to (if different than the original workspace).
0 Kudos
New Contributor II
Thank you Wayne for coming back to me so quickly.

I am even more confused now.

As I mentioned the script runs without any errors and it also saves a new *.mxd no problem but it just does not want to change any data sources for the selected layers in my TOC.

I must have done something wrong the way I setup variables in my revised Pyhton script or the way I setup the parameters for my script tool.

0 Kudos
by Anonymous User
Not applicable
Original User: Wayne_Whitley

I did see another likely error - the 2nd parameter for the layer name should not be a layer parameter but a string value.
In other words, when you list layers, that command in your script lists by a name (which is a string).  Try fixing that.
All I was saying earlier was that your 1st param should be a map doc pathname,...looks like you may have taken care of that, but I'd also change that to string too although it may run as ArcMap Document parameter.

So, since you got to the end of the script with a new mxd written, ignore for now what I said about the workspace - your main concern at the moment is to get your tool parameter types defined.
0 Kudos
New Contributor II
Thank you for the tip.

I changed my second and fourth parameter to a String.
The script is now working.

To make it more elegant I wanted to have those drop down menus where the user only have to select
the layer(2nd parameter) and the new dataset(4th parameter) rather than typing them in.
That is why I choose layer (for second parameter) and dataset for my 4th parameter.

Any thoughts how I can make that happen?

Thanks heaps already.
0 Kudos
by Anonymous User
Not applicable
Original User: Wayne_Whitley

Okay, so you'd do that with the ToolValidator class...you'd access that on the script tool properties window from the Validation tab - there is Python code there that executes prior to actually running the script file itself.  This could be made much more elegant as you say, but basically all I did below was generate the lists for the 2nd and 4th parameters from the 1st and 2nd parameters (respectively).  I included a zip of the tool box containing this script tool code on that tab (click 'Edit...' to open it in a familiar editor in order to further modify it).
import arcpy
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.params = arcpy.GetParameterInfo()

  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."""
    stringFilterTargetLyrs = self.params[1].filter
    stringFilterSourceLyrs = self.params[3].filter
    theMap = self.params[0].value
    theGDB = self.params[2].value
    if theMap:
      mxd = arcpy.mapping.MapDocument(str(theMap))
      lyrList = [str(lyr) for lyr in arcpy.mapping.ListLayers(mxd)]
      stringFilterTargetLyrs.list = lyrList
    if theGDB:
      arcpy.env.workspace = str(theGDB)
      fcList = arcpy.ListFeatureClasses()
      stringFilterSourceLyrs.list = fcList

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

Your script file (included) is basically untouched so you should be able to unzip and copy the entire toolbox to run the tool from your directory (it is relative-pathed).  It should give you the general idea, even limited to only generating the 2 lists, although further refined code would additionally filter for geom data types, check schema, and include feature datasets, etc.  Also, you may want to add a param for the path to the saved mxd copy.  You can readily see that TargetLyrs refers to a listing of target layers to change the datasource for in the target mxd and SourceLyrs refers to a listing of the available SDE feature classes from the source gdb.

Further info here at the webhelp:

Anyway, the above code is a quick intro to at least the capability you requested, adding value lists based on your other params.

0 Kudos
New Contributor II
Thank you. Now it gets serious. As you probably noticed I am not a coder and I must admit this is my first time I have touched any Python scripting.

Anyway I really like to way the user can now select certain parameters via a dropdown list.

I did exactly what you did and made changes to the ToolValidator class.

The script runs fine and saves a new file but unfortunately it does not change any source. 😞
Also is does not even come back with an error message.

Maybe it does not like it when I have inputs as string parameter because I guess the parameter we set with the ToolValidator are now objects? I do not really know this is just me guessing.
Could you please have a look at it again for me?

I attached my current tool and the script this time with relative paths. 🙂

I also added a new field which allows the user to define the name of the output file.

Thank you
0 Kudos
by Anonymous User
Not applicable
Original User: Wayne_Whitley

The attached zip at the last post was already geared to go, if you will try that 1st - in other words, it has the ToolValidator code already embedded and is also sourced to your script (and the script tool param datatypes are also set.  Download, unzip to a directory you have a Catalog connection to (or make a new connection), open the script tool and try it out.

Tell me if the layer you target successfully changes datasource.  It could be the target layer you're trying to modify the datasource for is not compatible with the chosen source layer - that was what I was saying about further refining the tool to assure filtering for viable candidate source substitutions.  For simplicity (and convenience for me) I didn't bother with the further coding and in doing so, left it up to your judgement to make good choice --- unfortunately, we oftentimes have data layers we aren't altogether familiar with....so if you intend to use this type of tool often you'd likely want to beef up both the initial list filtering and the main script's error trapping.

Let me know as soon as that as-is copy runs, and what results you get, and then we'll discuss troubleshooting options further; it's a simple script and the point of failure is probably these lines below - in fact, you could probably insert a simple msg afterwards as shown to make sure the 'if' statement evaluated true and executed your 'replaceDataSource' line:
if lyr.supports("DATASOURCE"):
  lyr.replaceDataSource(serverConnection, "SDE_WORKSPACE", newSource)
                arcpy.AddMessage('replace executed...')

...if you're curious about the param datatypes, check the properties of the attached...I think they're Map Document, String, Workspace, and String for the 4 original parameters (in that order).


PS- This is obvious I guess, but make sure you're checking the datasource in the mxd copy you saved to, not the original you started with.
0 Kudos
New Contributor II

I gave it another go and ran your original Toolbox and script. I ended up with the same results like before

The script runs and it saves a new *.mxd but no changes were made to the target.
I also added the msg string as you suggested and it did not come up with a message. I guess that means the script struggles at this point?

What else could I try?

There must be something not right the way I select the layer and/or the new targeted via the drop down.

I am stuck and do not know where else to look at.

0 Kudos
by Anonymous User
Not applicable
Original User: Wayne_Whitley

Try this:

Do you know how to enter commands in the Python window within ArcMap?

1- Open the mxd you are targeting (of make a copy for experimentation and open that).
2- Run the following lines below one by one in the Python window of the open mxd - I'm not testing these lines so let me know if I make a mistake and an error is returned in the window.  Where I have "enter this" in quotes, enter what I ask you.

import arcpy
mxd = arcpy.mapping.MapDocument('CURRENT')
targetLyrName = "enter name of layer in TOC"
targetLyr = arcpy.mapping.ListLayers(mxd, targetLyrName)[0]
if targetLyr is not None:  print "your selected layer is {0}.format(targetLyr.longName)

3- Did a layer name for the selected layer print?  If so, continue on:

serverConnection = r"enter an SDE connection file pathname"
newSource = "enter the full name of the feature class to use for replacement"
arcpy.env.workspace = serverConnection
if arcpy.Exists(newSource):
     print 'good to go'
          if targetLyr.supports("DATASOURCE"):
               print "DATASOURCE property is supported for this layer."
               targetLyr.replaceDataSource(serverConnection, "SDE_WORKSPACE", newSource)
               print "DATASOURCE property is not supported for this layer."

Now tell me what messages you get reported back to you and tell me if the datasource has changed in the current mxd.
0 Kudos