vangelo-esristaff

Command-line utilization of Python toolboxes

Blog Post created by vangelo-esristaff Employee on Nov 15, 2017

I had a project where I needed to publish data at a service provider location, with the publishing to be done by a novice Desktop user.  My solution was to make a Python toolbox to simplify the data import and ArcGIS Server service publishing steps, and then I just went ahead and made the export and cleanup steps tools in that same toolbox as well. This was looking like a great solution until network issues at the end-user site caused the data import step to take seven hours (instead of the usual 20 minutes).  Since this was an Amazon solution, the utility could have been run on a VM from inside the mission, but there were issues accessing the license server from the VM, and from there, well, let's just say it didn't work out.

 

Since my ArcGIS Server node was actually on a Linux box, I didn't have the option of running a graphical utility like a toolbox tool, but the code didn't really need graphical access (just a working ArcPy, a path to the source file geodatabase, and an enterprise connection file (.sde)). I could have ported the app to a command-line utility, then invoked the command-line from the toolbox UI, but this additional development would take time and, as I recently discovered, it wasn't strictly necessary, because Python toolbox ( .pyt ) files can be invoked from Python!

 

For example, lets say we have this trivial toolbox:

import arcpy

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""

        self.label = "Toolbox"
        self.alias = ""

        # List of tool classes associated with this toolbox
        self.tools = [BlogTool]

class BlogTool(object):
    def __init__(self):
        """Define the tool (tool name is the name of the class)."""
        self.label = "BlogTool"
        self.description = "Trivial tool example"
        self.canRunInBackground = False

    def getParameterInfo(self):
        """Define parameter definitions"""
        param0 = arcpy.Parameter(
               displayName="Options",
               name="in_options",
               datatype="GPString",
               parameterType="Required",
               direction="Input")
        return [param0]

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return True

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

        return

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

        return

    def execute(self, parameters, messages):
        # invoke helper
        return doExecute(parameters[0].valueAsText,messages)

def doExecute(param1,messages):
    messages.addMessage("Tool: Executing with parameter '{:s}'".format(param1))
    return None

 

It works like you'd expect:

 

toolbox

 

tool

 

results

 

Now, if you run it from the command line, despite it not having a .py suffix, you get no error, but it doesn't do anything either:

toolbox execution

Ahh, but if you tweak the toolbox to add an extra two lines of code to the end:

if (__name__ == '__main__'):
    arcpy.AddMessage("Whoo, hoo! Command-line enabled!")

 

Then you can use the toolbox from a console:

 invocation

 

"Still, they're not connected, and what about messaging?" you ask.  Well, this is pretty cool:  You can fake the messages parameter in the tool's execute method with a stub class that does everything you need messages to do, and you can grab parameters from the sys.argv array, which makes for a command-line capable toolbox:

if (__name__ == '__main__'):
    arcpy.AddMessage("Whoo, hoo! Command-line enabled!")

    class msgStub:
        def addMessage(self,text):
            arcpy.AddMessage(text)
        def addErrorMessage(self,text):
            arcpy.AddErrorMessage(text)
        def addWarningMessage(self,text):
            arcpy.AddWarningMessage(text)

    import sys
    doExecute(str(sys.argv[1]),msgStub())

 

command-line

 

Obviously, you'd want to do the sort of validation/verification for arguments that Desktop provides before invoking the helper with parameters, but that is, as they say, "A small matter of coding."

 

- V

Outcomes