Using ArcGIS Pro 3.2.0 and ArcGIS Pro 3.1.4
I hope the following is understandable. I’ve been working with ArcPy and am encountering a potential issue with importing custom toolboxes using the ImportToolbox function, specifically with .tbx and .atbx files. The problem does not occur with .pyt files.
According to the documentation, the recommended usage of ImportToolbox is as follows:
ImportToolbox(input_file, {module_name})
def ImportToolbox(input_file, module_name=None😞
The toolbox alias is crucial for accessing tools, and if not defined, a temporary alias can be set with the second parameter (module_name). However, in our tests, we’ve observed that setting the module_name parameter can overwrite the predefined alias in .tbx and .atbx files, leading to execution failures for certain tools.
Test Results:
.tbx:
ImportToolbox(tbx_path)
arcpy.<alias>.<toolname> → Success
ImportToolbox(tbx_path, module_name)
arcpy.<module_name>.<toolname> → Success
ImportToolbox(tbx_path)
arcpy.<alias>.<toolname> → fail:
- without module_name, but referencing tools using the predefined alias, worked fine.
- The issue arises when the module_name is introduced, which overwrites the alias. It is this alias overwrite that leads to process failures, not the predefined alias in ArcGIS Pro itself.
.pyt: No issues at all. The alias can be redefined without the problems observed with .tbx and .atbx files.
We’re trying to determine if this is a bug or expected behavior. The .pyt files work as expected, but .tbx and .atbx exhibit problematic behavior when the module_name parameter is used, likely due to the alias being overwritten.
Has anyone else encountered this? Any advice or insights would be appreciated! If this is indeed a bug, we’d like to report it.
Here's the actual import code from arcpy:
def import_toolbox(tbxfile, module_name=None):
r"""Loads a file toolbox(.tbx) and creates a Python module for it.
Sample usage:
arcpy.import_toolbox(r"C:\Data\ArcToolbox\Sample Toolbox\demo.tbx",
'toolboxsample')
This will then generate a python module for demo.tbx and make it
available for import as arcpy.toolboxes.toolboxsample
If the second argument is not provided, the function will use the alias
of the toolbox, if it is set and a valid Python identifier."""
use_alt_alias = True
if isinstance(tbxfile, str):
toolbox = gp.createObject("Toolbox", tbxfile)
if tbxfile.lower().startswith('http://'):
use_alt_alias = False
else: # Assume non-strings are instances of Toolbox
toolbox = tbxfile
mymodule = generate_toolbox_module(toolbox, None, False, False, False, module_name, use_alt_alias)
import arcpy
for tool in mymodule.__all__:
setattr(arcpy, tool + ('_' + mymodule.__alias__
if mymodule.__alias__ else ''),
getattr(mymodule, tool, None))
# Module already exists in arcpy?
if hasattr(arcpy, mymodule.__alias__):
existing_module = getattr(arcpy, mymodule.__alias__)
# Is it a module?
if not hasattr(existing_module, '__all__'):
existing_module.__all__ = []
# Add to the __all__
existing_module.__all__ += mymodule.__all__
# Then add the tool functions
for tool in mymodule.__all__:
setattr(existing_module, tool,
getattr(mymodule, tool, None))
# arcpy.X = mymodule
else:
setattr(arcpy, mymodule.__alias__, mymodule)
return mymodule
This code lives in the 'toolbox_code.py' file in your arcpy source directory. There's a lot of stuff in there that could cause issues it seems. Like the generate_toolbox_module initializing a mutable list in the function header. It also does the module re-writes in a confusing way that seems to persist the aliases across runs. Honestly it does seem like this import_toolbox file is in dire need of some re-writing as I'm getting weird results when I test it with a toolbox:
...
import arcpy
# There should be no aliased tools here
print(f"Default: {[m for m in dir(arcpy) if 'my_module' in m.lower()]}")
# There should be no aliased tools here
import_toolbox(r"C:\Users\hwelch\Desktop\Test.atbx")
print(f"Raw Import: {[m for m in dir(arcpy) if 'my_module' in m.lower()]}")
# There should be one set of aliased tools here
import_toolbox(r"C:\Users\hwelch\Desktop\Test.atbx", 'my_module')
print(f"Aliased Import: {[m for m in dir(arcpy) if 'my_module' in m.lower()]}")
# There shoud be two sets of aliased tools here
import_toolbox(r"C:\Users\hwelch\Desktop\Test.atbx", 'my_module2')
print(f"Re-Aliased Import: {[m for m in dir(arcpy) if 'my_module' in m.lower()]}")
>>> Default: []
>>> Raw Import: ['Tool_my_module2', 'my_module2']
>>> Aliased Import: ['Tool_my_module', 'Tool_my_module2', 'my_module', 'my_module2']
>>> Re-Aliased Import: ['Tool_my_module', 'Tool_my_module2', 'my_module', 'my_module2']
I have no clue why the first run of the import_toolbox function is somehow applying the alias from the last run...