17

When using a python toolbox (.pyt) in ArcMap, I'll typically follow a pattern where the .pyt file itself is simply a wrapper to collect input arguments and define the tools themselves. Supporting code is kept in separate unit-testable modules.

Example:

import supporting_module

class MyTool(object):

    ...

    def execute(self, parameters, messages):
        """The source code of the tool."""
        some_input = parameters[0].valueAsText
        some_output = parameters[1].valueAsText

        supporting_module.do_something(some_input, some_output)
        arcpy.SetParameter(2, some_output)

This works out really well but I've run into one frustrating issue during development. Right-click -> Refresh on the .pyt in ArcMap only refreshes the .pyt file's code. It does not refresh the imported modules, so I have to close and re-open ArcMap whenever I change something there. Fortunately, since I'm testing the code independently, I don't have to do this a ton, but it's still a major hassle. Is there any way around this? Somewhat related - is there any way to fully refresh the Python console (i have a custom site-package that I must also close/reopen ArcMap to pull in changes from as well)?

I'm using ArcMap 10.2.1.

PolyGeo
  • 65,136
  • 29
  • 109
  • 338
Josh Werts
  • 816
  • 7
  • 17

4 Answers4

17

I found this possibility, https://stackoverflow.com/questions/1517038/python-refresh-reload

The one caveat is that if you have any variables assigned to the module, they will need to be assigned again.

But as you have it written above, you could do this:

import supporting_module
def execute()
  reload(supporting_module)
  ...

This way every time you run the tool you'll be sure to have the updated module. Once development is done, this can be taken out.

cndnflyr
  • 3,392
  • 2
  • 25
  • 43
  • 1
    Extremely helpful and this does solve the simple example I posted. However, if supporting_module also imports other helper modules, then they would need to be called with reload as well. I'm thinking this is the best we'll get as it makes sense with the way Python loads modules - I'll mark yours as the answer if something more robust doesn't come along in the next day or so. – Josh Werts Mar 27 '14 at 19:12
  • 1
    Very helpful. I have found that it is sufficient to call reload() right after importing the supporting module, at the top of the toolbox's PYT file. Thus, if the toolbox contains several tools, I do not have to duplicate the reload in each tool's execute() function. – Mike Finch Aug 31 '17 at 15:19
  • I don't really understand how this anwser solves the issue. I have my Tools stored in .py files and I import them using from pyfile import Tool, adding reload(tool) returns typeError: reload() argument must be module. Any ideas? – Ratnanil Feb 06 '18 at 22:29
  • 3
    I found the solution! import the py-File as a Module first, reload and then import the class within the module. See: https://stackoverflow.com/a/6946467/4139249 – Ratnanil Feb 14 '18 at 22:42
5

Here is a different and more robust way than I suggested before.

I haven't used this module myself, but I think it would solve your problem:

Python Module Reloader

This library implements a dependency-based module reloader for Python. Unlike the builtin reload() function, this reloader will reload the requested module and all other modules that are dependent on that module.

Given the previous example, this should load all the dependancies with one call:

import reloader
reloader.enable()

import supporting_module

def execute()
  reloader.reload(supporting_module)
  ...

This is the first time I've noticed this module, so if you implement it in your tools, comment back on how well it works for you.

cndnflyr
  • 3,392
  • 2
  • 25
  • 43
  • Good find! This worked if added exactly as you show. I only have to right-click -> refresh on the toolbox if the .pyt file itself changes. Supporting modules and their sub modules are being reloaded successfully. One caveat though - I did get a couple unexplained exceptions on occasion and an ArcMap crash (though ArcMap crashes really aren't that abnormal, unfortunately). Note - I've only tested this with the supporting module and one dependent sub-module. May report back on stability in the future. – Josh Werts Mar 28 '14 at 12:55
  • 1
    Super, thanks for reporting back. Yeah, when driving ArcMap, I sometimes feel like a Crash Test Dummy. – cndnflyr Mar 28 '14 at 16:51
  • Unfortunately, I've run into way too many stability issues with this reloader, so I've changed the selected answer to the original reload() built-in function. Sometimes simpler is better! – Josh Werts Mar 31 '14 at 20:35
0

Here's an example of refreshing a tool imported from a different folder in ArcGIS Pro using Python 3.7

# Import the module to support refreshing
import tools.MyNewTool as MyNewToolModule
# Import the tool
from tools.MyNewTool import MyNewTool

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 = "toolbox"

    # Reload the MyNewToolModule to ensure all properties are updated
    importlib.reload(MyNewToolModule)

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

# Rest of the code ...

Deejers
  • 101
  • 2
0

Here's how I accomplished it, but it should be noted that while this may make development easy, it creates a slew of complications after development:

  1. You'll need to remove importlib.reload() in order to successfully stage the tool when publishing to ArcGIS Server
  2. Even after successfully publishing to ArcGIS Server, the tool will fail with ERROR 000816: The tool is not valid because the imported module is unrecognized by the server: https://support.esri.com/en-us/knowledge-base/error-error-000816-the-tool-is-not-valid-000023081

Frankly I'm not sure the best way to build a large toolbox and isolate functionality into other modules if the goal is to publish to ArcGIS Server, so I've now resorted to leaving all functionality within the .pyt (welcome any solutions). I'm using ArcGIS Pro 3.1.3 and ArcGIS Server 11.0

import importlib
import SupportingModule
importlib.reload(SupportingModule)

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 = "Toolbox"

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

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

def getParameterInfo(self):
    return

def isLicensed(self):
    return True

def updateParameters(self, parameters):
    return

def updateMessages(self, parameters):
    return

def execute(self, parameters, messages):
    SupportingModule.some_function(parameters)
    return

def postExecute(self, parameters):
    return