5

I am writing a Python tool that perform heavy tasks in a loop. When the user clicks "Close" I want to break the loop, perform some cleanup, and then finish.

Consider this example tool that counts down from ten:

import arcpy
import time

class Tool(object):

   def __init__(self):
      self.label = "Cancel Test"
      self.description = "Tests how the cancel function works."
      self.canRunInBackground = False

   def execute(self, parameters, messages):
      for i in range(10, -1, -1):
         arcpy.AddMessage(i)
         time.sleep(1)
      arcpy.AddMessage("We have lift-off!")
      return

When I click "Close" in the beginning of the execution it does not brake the loop or give me an opportunity to perform any clean up. Instead it loops to the very end and adds the message "We have lift-off!" before it outputs this:

Completed script CancelTest...
Cancelled function
(CancelTest) aborted by User.
Failed at [TIME] (Elapsed Time: 10,01 seconds)

I have found documentation for ArcGIS Pro (that uses Python 3.4) that describes how you can do this there using isCancelled:

import arcpy
import time

#Make sure it does not auto cancel.
arcpy.env.autoCancelling = False

class Tool(object):

   def __init__(self):
      self.label = "Cancel Test"
      self.description = "Tests how the cancel function works."
      self.canRunInBackground = False

   def execute(self, parameters, messages):
      for i in range(10, -1, -1):
         arcpy.AddMessage(i)
         time.sleep(1)
         #Check if the user clicked "Close"
         if arcpy.env.isCancelled:
            arcpy.AddMessage("Launch aborted!")
            return
      arcpy.AddMessage("We have lift-off!")
      return

However, when I run this from ArcCatalog 10.3 (i.e. not using ArcGIS Pro) it gives me the following error:

Traceback (most recent call last):
  File "H:\Mina Dokument\DGD\Python\CancelTest.py", line 19, in execute
    if arcpy.env.isCancelled:
AttributeError: 'GPEnvironment' object has no attribute 'isCancelled'

Is there anyway to mimic the behavior that is available in ArcGIS Pro in an ordinary Python toolbox using Python 2.7?

8
+50

Farid Cher's answer is partially correct.

You can use the Progressor to 'catch' a cancel event from within a Python script, and you can also do whatever cleanup necessary as well, if you enclose the call within a try ... except clause.

For example, a python toolbox:

import arcpy
import time

class Toolbox(object):
    def __init__(self):
        self.label = "Toolbox"
        self.alias = ""
        self.tools = [Tool]


class Tool(object):
    def __init__(self):
        self.label = "Tool"
        self.description = ""
        self.canRunInBackground = False

    def execute(self, parameters, messages):
        some_stuff = [] # a variable to cleanup later
        max = 1000
        arcpy.SetProgressor('step','Testing ArcPy script cancel',0,max,1)

        # You don't have to be in a for loop, but this is the
        # easiest way to show an example
        for i in range(max):
            try: 
                # only put SetProgressorPosition in this clause
                # otherwise you can catch other errors that aren't
                # the user cancelling
                arcpy.SetProgressorPosition()
            except:
                arcpy.AddWarning('User canceled at i = {0}'.format(i))
                del some_stuff # cleanup
                return
            time.sleep(0.1)
            some_stuff.append(i)
        arcpy.AddMessage('User did not cancel')
        del some_stuff

This allows the user to run the tool: before cancel

And then cancel, which is handled gracefully (and still counted as a failed run): after cancel

edit: This doesn't appear to work with 10.3 or ArcGIS Pro.

To catch the cancels (tested in ArcGIS Pro), we bring back our good friend arcgisscripting. This is similar to catching the cancel from the progressor, but the entire execute function, or at least the majority of it, needs to be inside the try:

import arcpy
import arcgisscripting
import time

class Toolbox(object):
    def __init__(self):
        self.label = "Toolbox"
        self.alias = ""
        self.tools = [Tool]


class Tool(object):
    def __init__(self):
        self.label = "Tool"
        self.description = ""
        self.canRunInBackground = False

    def execute(self, parameters, messages):
        try:
            some_stuff = []
            max = 1000
            arcpy.SetProgressor('step','Testing ArcPy script cancel',0,max,1)

            for i in range(max):
                arcpy.SetProgressorPosition()
                time.sleep(0.1)
                some_stuff.append(i)
        except arcgisscripting.ExecuteAbort:
            arcpy.AddWarning('User cancelled at i={0}'.format(i))
            del some_stuff
            return
        arcpy.AddMessage('User did not cancel')
        del some_stuff

before cancel in pro

after cancel in pro

  • Indeed, this is a good alternative to bypass if arcpy.env.isCancelled. – GeoStoneMarten Dec 7 '15 at 14:06
  • +1 @Evil Genius, I didn't think of a try...catch. Good workaround. Thanks for expansion. – Farid Cheraghi Dec 7 '15 at 14:14
  • 3
    Thanks for the answer! Strangely enough, your code does not behave the same way when I run it as when you run it. If I press "Cancel" it does break the loop, but it does not print User canceled at i =. Also tested creating a file (in case there was some strange problem with the message printing) but it looks like the except clasue do not run. I am using ArcGIS 10.3 and running the tool from ArcCatalog, no ArcGIS Pro involved. What version are you running? – Anders Dec 7 '15 at 15:13
  • 2
    @EvilGenius I confirm that it doens't behave as you have showed in your snapshots! Just like Andres mentions.(I use 10.2.2) – Farid Cheraghi Dec 7 '15 at 15:24
  • 1
    I've tested it more thouroughly now, and it does indeed work on 10.3! Thanks! – Anders Dec 7 '15 at 16:46
3

Your message is clear:

AttributeError: 'GPEnvironment' object has no attribute 'isCancelled'

In fact arcpy.env.autoCancelling and arcpy.env.isCancelled are added in Arcgis Pro 1.1 (planned for ArcGIS 10.4)

Furthermore, if you use Arcgis 10.3, these features are not available.

  • Thanks for the answer! Do you know if they will be added in only ArcGIS Pro? Or will they be added "everywhere" in ArcGIS 10.4, so just eventually upgrading to 10.4 but still not use Pro would solve it? And do you have any reference for this information? Thanks. – Anders Dec 7 '15 at 12:32
  • yep. You can look at arpy env object isCancelled doent exist in last version desktop (10.3.1) but arcpy env object exist in last version pro (1.1.1) . You can look this at Whats news in Arc GIS Pro . There is also this project who speak about it – GeoStoneMarten Dec 7 '15 at 13:53
  • @Anders If it's necessary for you to use isCancel you need to bypass python env and use an interop solution in desktop. Alternatively, you can use it now if you use ArcGIS Pro – GeoStoneMarten Dec 7 '15 at 14:00
2

The closest way that I can think of is the use of Progressor in Arcpy. Using progressor, once the user press the cancel button the loop breaks and the execution terminates. However you can not do any cleanup! Here is a sample code:

def execute(self, parameters, messages):
    import time
    n=10
    p=1
    arcpy.SetProgressor("step",
                        "Step progressor: Counting from 0 to {0}".format(n),
                        0, n, p)
    loopTime=.3
    for i in range(n):
        if (i % p) == 0:
            ## put your custom code here
            ## it will break the loop if the user cancels. and the tool execution terminates
            arcpy.SetProgressorLabel("Iteration: {0}".format(i))
            arcpy.SetProgressorPosition(i)
            time.sleep(loopTime)

    arcpy.AddMessage("You will not get here to do cleanup if the loop terminates prematurelly")
    arcpy.SetProgressorLabel("Iteration finished: {0}".format(i + 1))
    arcpy.SetProgressorPosition(i + 1)

    return

Update (To answer the questions in the comment):

why using a progressor changes how the program behaves, other than adding a nice looking progress bar?

The progressor is a kind of multi-thread processing. each time the progressor steps forward, the GUI (a separate thread) must be updated. While using a progressor, you are running your code in another inline thread. However it is not definitely like Background geo-processing in which you can run multiple thread simultaneously.

Does using a progressor make ArcGIS brake the code when the user press "Cancel"?

Yes, note that the breaking happens in the loop, where you are updating your progress bar (e.g. like the example above)

where does it break? Is it directly after whatever line it happens to be at when the button is pressed?

The breaking happens between any two progress steps that the user clicks the cancel button. In the above code example its:

if (i % p) == 0:
            ## put your custom code here
            ## it will break the loop if the user cancels. and the tool execution terminates
            arcpy.SetProgressorLabel("Iteration: {0}".format(i))

If the user click cancel, your custom code will be executed until it reaches arcpy.SetProgressorLabel("Iteration: {0}".format(i)). This is where the gp tool will get notified of the cancel event (e.g. kill the separate inline thread)

  • Thanks for the answer! Not sure I understand how and why using a progressor changes how the program behaves, other than adding a nice looking progress bar? Does using a progressor make ArcGIS brake the code when the user press "Close"? In that case, where does it brake? Is it directly after whatever line it happens to be at when the button is pressed? – Anders Dec 7 '15 at 12:30
  • You're welcome. @Anders, see the updated answer. – Farid Cheraghi Dec 7 '15 at 13:03
  • @FaridCher I explain why it can't work withisCancelled but indeed, SetProgressor is a good alternative to bypassarcpy.env.isCancelled – GeoStoneMarten Dec 7 '15 at 14:09
  • When I run your code, it behaves as designed. The counter stops, and no cleanup is done. However, if I add arcpy.AddMessage(i) between SetProgressorPosition and sleep in the loop, suddenly it runs through the whole loop and the cleanup. Any idea to why that may be? It makes me feel a bit confused... – Anders Dec 7 '15 at 14:53
  • @Anders, huh, very funny behavior. I can't figure out how the ESRI enginer has written the Progressor Class!! – Farid Cheraghi Dec 7 '15 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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