<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Re: Debugging ArcGIS Python Script Tools in Python Questions</title>
    <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1504120#M71049</link>
    <description>&lt;P&gt;EDIT: I wasn't happy with this code and have since totally re-written it in the pinked repo. It worked so I just hadn't really looked at it in a while and tried to apply some new knowledge to it. I'll leave the old code here for posterity, but check the repo for up to date code.&lt;/P&gt;&lt;P&gt;I've shared it before under the pytframe/pytframe2 framework I'm trying to develop as an open standard, but here's the reloading code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;from importlib import reload, import_module
from traceback import format_exc
from typing import Dict
from utils.tool import Tool

def build_dev_error(label: str, desc: str):
    class Development(object):
        def __init__(self):
            """Placeholder tool for development tools"""

            self.category = "Tools in Development"
            self.label = label
            self.alias = self.label.replace(" ", "")
            self.description = desc
            return
    return Development

def import_tools(tool_dict: dict[str, list[str]]) -&amp;gt; list[object]:
    imports = []
    for project, tools in tool_dict.items():
        for tool in tools:
            try:
                module = import_module(f"{project}.{tool}")
                reload(module)
                tool_class = getattr(module, tool)
                globals()[tool] = tool_class
                imports.append(tool)
            except ImportError:
                dev_error = build_dev_error(tool, format_exc())
                globals()[tool] = dev_error
                imports.append(tool)
    return [v for k, v in globals().items() if k in imports]&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Currently it uses a higher order function as a factory for the DevError tool and writes the stack trace to the description and places it in the "Tools in Development" toolset/category. The implementation in a toolbox is done like this:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;# Reloading of modules preformed here to make sure a toolbox refresh also
# reloads all associated modules. A refresh in ArcGIS Pro only reloads the code
# in this .pyt file, not the associated modules

from importlib import reload, invalidate_caches
invalidate_caches()

import utils.reloader
reload(utils.reloader)
from utils.reloader import import_tools

import utils.archelp
reload(utils.archelp)

import utils.tool
reload(utils.tool)

TOOLS = \
{
    "tools.development":
        [
            "DevTool",
        ],
    "tools.utility":
        [
            "VersionControl",
        ],
}

IMPORTS = import_tools(TOOLS)
globals().update({tool.__name__: tool for tool in IMPORTS})

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        
        self.label = "Dev Toolbox"
        self.alias = "DevToolbox"
        
        # List of tool classes associated with this toolbox
        self.tools = IMPORTS&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Where the TOOLS global stores the module paths to your tool files. The reloading is done beacuse a toolbox "Refresh" in Pro only reloads the .pyt file and not its imports meaning any changes to imported tools won't be reflected until a total restart of ArcPro/clearing of the gloabal interpreter namespace. This does slow things down a bit as the __init__ functions of all loaded tools have to run on a refresh, but you can disable those if you move the toolbox out of active development. To simplify and disambiguate what you're importing from a tool file, it requires the name of the .py file the tool is in to exactly match the name of the tool class contained within so "DevTool.py" contains this:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy

from utils.tool import Tool
import utils.archelp as archelp
import utils.models as models

class DevTool(Tool):
    def __init__(self) -&amp;gt; None:
        # Call the super init method to inherit from the base Tool class
        super().__init__()
        
        # Set local tool properties
        self.label = "Dev Tool"
        self.description = "This is a development tool."
        self.category = "Development"
        return&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;I've also implemented a Tool base class that allows all your tools to share a single "config" so if you have certain parameters that you always need for a toolbox, you can either define them in that Tool base class or extend that baseclass with different configs and inherit from that in your &amp;lt;ToolName&amp;gt; implementation:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy
import os

class Tool(object):
    """
    Base class for all tools that use python objects to build parameters
    """
    def __init__(self) -&amp;gt; None:
        """
        Tool Description
        """
        # Tool parameters
        self.label = "Tool"
        self.description = "Base class for all tools that use python objects to build parameters"
        self.canRunInBackground = False
        self.category = "Unassigned"
        
        # Project variables
        self.project = arcpy.mp.ArcGISProject("CURRENT")
        self.project_location = self.project.homeFolder
        self.project_name = os.path.basename(self.project_location)
        
        # Database variables
        self.default_gdb = self.project.defaultGeodatabase
        self.databases = self.project.databases
        
        return
    
    def getParameterInfo(self) -&amp;gt; list: ...
    def updateParameters(self, parameters: list) -&amp;gt; None: ...
    def updateMessages(self, parameters: list) -&amp;gt; None: ...
    def execute(self, parameters: dict, messages: list) -&amp;gt; None: ...

class ToolType(Tool):
    def __init__(self) -&amp;gt; None:
        super().__init__()
        self.my_var: int = 42&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;So if i wanted the DevTool to have access to the self.my_var parameter, all I'd need to do is:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy

from utils.tool import Tool, ToolType
import utils.archelp as archelp
import utils.models as models

class DevTool(ToolType):
    def __init__(self) -&amp;gt; None:
        # Call the super init method to inherit from the base Tool class
        super().__init__()
        
        # Set local tool properties
        self.label = "Dev Tool"
        self.description = "This is a development tool."
        self.category = "Development"
        return
    
    def execute(self, parameters: list, messages: list) -&amp;gt; None:
        arcpy.AddMessage(f"{self.my_var=}")
        return&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;This method can incur some overhead and slow down the toolbox, but the amount of time saved during development absolutely makes up for it. I've also included a VersionControl tool in the framework that allows the toolbox framework to be stored in a git repo and synced to users no matter where the actual toolfile is located as long as they have read access to the repo.&lt;/P&gt;&lt;P data-unlink="true"&gt;If you want to take a peek at the code it's &lt;A href="https://github.com/hwelch-fle/pytframe2" target="_self"&gt;here&lt;/A&gt; I still need to write documentation for everything, but it's still heavily in development as I want to clean up the hacked together code I've been using for a couple years to be more readable and efficient applying all the lessons I've learned managing toolboxes this way for the past 2 years.&lt;/P&gt;</description>
    <pubDate>Fri, 12 Jul 2024 13:46:46 GMT</pubDate>
    <dc:creator>HaydenWelch</dc:creator>
    <dc:date>2024-07-12T13:46:46Z</dc:date>
    <item>
      <title>Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390587#M70007</link>
      <description>&lt;P&gt;I've made a handful of Python Script Tools successfully.&amp;nbsp; My current project is a bit trickier.&amp;nbsp; In the past, I'd get the below red exclamation point and traceback my steps manually and find my error in the .py&lt;STRONG&gt;t&lt;/STRONG&gt; file.&amp;nbsp; However, I can't track it down this time and there are no Tracebacks, or obvious ways in the GUI to determine the source of the error.&amp;nbsp; I should note, this script tool was working prior to my recent attempted update, and still works when I manually comment out the new edits.&amp;nbsp; I'm happy to post the script, but I'm primarily in need of a debugging tool or workflow.&amp;nbsp; I should mention, I'm developing in PyCharm which has built-in syntax highlighting, so these errors are not Python-related necessarily.&amp;nbsp; I think they are related to my validation and logic within the toolbox framework, so I need debugging from a tool, log, etc. in Pro.&lt;/P&gt;&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="01dacc69-73af-48ae-acf2-fb202b830d13.png" style="width: 400px;"&gt;&lt;img src="https://community.esri.com/t5/image/serverpage/image-id/96779iECE7A81971ADCD7E/image-size/medium?v=v2&amp;amp;px=400" role="button" title="01dacc69-73af-48ae-acf2-fb202b830d13.png" alt="01dacc69-73af-48ae-acf2-fb202b830d13.png" /&gt;&lt;/span&gt;&lt;/P&gt;&lt;P&gt; &lt;/P&gt;&lt;P&gt;Help is MUCH appreciated.&lt;/P&gt;&lt;P&gt;Zach&lt;/P&gt;&lt;P&gt;Pro 3.0.2&lt;/P&gt;</description>
      <pubDate>Mon, 04 Mar 2024 18:06:00 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390587#M70007</guid>
      <dc:creator>ZacharyUhlmann1</dc:creator>
      <dc:date>2024-03-04T18:06:00Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390591#M70009</link>
      <description>&lt;P&gt;Generally you get that from a Syntax error. Give that a shot?&lt;/P&gt;&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="AlfredBaldenweck_0-1709575737076.png" style="width: 400px;"&gt;&lt;img src="https://community.esri.com/t5/image/serverpage/image-id/96780iC6D91D4EA1F17498/image-size/medium?v=v2&amp;amp;px=400" role="button" title="AlfredBaldenweck_0-1709575737076.png" alt="AlfredBaldenweck_0-1709575737076.png" /&gt;&lt;/span&gt;&lt;/P&gt;&lt;P&gt;Especially if it goes away when you comment out the edits, that sounds like a syntax error.&lt;/P&gt;&lt;P&gt;Check syntax, make the edits needed, check syntax again, refresh the PYT&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 04 Mar 2024 18:09:41 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390591#M70009</guid>
      <dc:creator>AlfredBaldenweck</dc:creator>
      <dc:date>2024-03-04T18:09:41Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390597#M70010</link>
      <description>&lt;P&gt;Aaaand I read the rest of your post now lol.&lt;/P&gt;&lt;P&gt;I'd still check for the syntax stuff to be safe.&lt;/P&gt;&lt;P&gt;Generally if it's a problem with validation or whatever, the tool can still be opened, but the parameters themselves will have errors next to them.&lt;/P&gt;</description>
      <pubDate>Mon, 04 Mar 2024 18:13:26 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390597#M70010</guid>
      <dc:creator>AlfredBaldenweck</dc:creator>
      <dc:date>2024-03-04T18:13:26Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390606#M70011</link>
      <description>&lt;P&gt;Didn't the right-click.&amp;nbsp; Did it!&amp;nbsp; Thanks for the subsequent post too.&amp;nbsp; I've been doing it all wrong.&amp;nbsp; Never knew about they "check syntax".&amp;nbsp; I did have an issue that my eyes weren't trained to (a string with extra quotation mark).&amp;nbsp; Now time to discover all the validation logic I messed up.&lt;/P&gt;</description>
      <pubDate>Mon, 04 Mar 2024 18:19:59 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1390606#M70011</guid>
      <dc:creator>ZacharyUhlmann1</dc:creator>
      <dc:date>2024-03-04T18:19:59Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1503946#M71041</link>
      <description>&lt;P&gt;I'm a bit late to this one, but you can lazy load tools into a toolbox if you have them in separate files and do module imports.&lt;/P&gt;&lt;P&gt;My setup is a try except block for tool loading that writes the traceback to the tool description (it initializes an empty tool class on a failed import).&lt;/P&gt;&lt;P&gt;This means a single failing tool won't break the whole toolbox and will give you as a developer a way to pinpoint exactly which tools are failing.&lt;/P&gt;</description>
      <pubDate>Thu, 11 Jul 2024 02:37:34 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1503946#M71041</guid>
      <dc:creator>HaydenWelch</dc:creator>
      <dc:date>2024-07-11T02:37:34Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1504096#M71048</link>
      <description>&lt;P&gt;Pretty fancy! Would you be able to share a sample?&lt;/P&gt;</description>
      <pubDate>Thu, 11 Jul 2024 13:04:51 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1504096#M71048</guid>
      <dc:creator>AlfredBaldenweck</dc:creator>
      <dc:date>2024-07-11T13:04:51Z</dc:date>
    </item>
    <item>
      <title>Re: Debugging ArcGIS Python Script Tools</title>
      <link>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1504120#M71049</link>
      <description>&lt;P&gt;EDIT: I wasn't happy with this code and have since totally re-written it in the pinked repo. It worked so I just hadn't really looked at it in a while and tried to apply some new knowledge to it. I'll leave the old code here for posterity, but check the repo for up to date code.&lt;/P&gt;&lt;P&gt;I've shared it before under the pytframe/pytframe2 framework I'm trying to develop as an open standard, but here's the reloading code:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;from importlib import reload, import_module
from traceback import format_exc
from typing import Dict
from utils.tool import Tool

def build_dev_error(label: str, desc: str):
    class Development(object):
        def __init__(self):
            """Placeholder tool for development tools"""

            self.category = "Tools in Development"
            self.label = label
            self.alias = self.label.replace(" ", "")
            self.description = desc
            return
    return Development

def import_tools(tool_dict: dict[str, list[str]]) -&amp;gt; list[object]:
    imports = []
    for project, tools in tool_dict.items():
        for tool in tools:
            try:
                module = import_module(f"{project}.{tool}")
                reload(module)
                tool_class = getattr(module, tool)
                globals()[tool] = tool_class
                imports.append(tool)
            except ImportError:
                dev_error = build_dev_error(tool, format_exc())
                globals()[tool] = dev_error
                imports.append(tool)
    return [v for k, v in globals().items() if k in imports]&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Currently it uses a higher order function as a factory for the DevError tool and writes the stack trace to the description and places it in the "Tools in Development" toolset/category. The implementation in a toolbox is done like this:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;# Reloading of modules preformed here to make sure a toolbox refresh also
# reloads all associated modules. A refresh in ArcGIS Pro only reloads the code
# in this .pyt file, not the associated modules

from importlib import reload, invalidate_caches
invalidate_caches()

import utils.reloader
reload(utils.reloader)
from utils.reloader import import_tools

import utils.archelp
reload(utils.archelp)

import utils.tool
reload(utils.tool)

TOOLS = \
{
    "tools.development":
        [
            "DevTool",
        ],
    "tools.utility":
        [
            "VersionControl",
        ],
}

IMPORTS = import_tools(TOOLS)
globals().update({tool.__name__: tool for tool in IMPORTS})

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        
        self.label = "Dev Toolbox"
        self.alias = "DevToolbox"
        
        # List of tool classes associated with this toolbox
        self.tools = IMPORTS&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;Where the TOOLS global stores the module paths to your tool files. The reloading is done beacuse a toolbox "Refresh" in Pro only reloads the .pyt file and not its imports meaning any changes to imported tools won't be reflected until a total restart of ArcPro/clearing of the gloabal interpreter namespace. This does slow things down a bit as the __init__ functions of all loaded tools have to run on a refresh, but you can disable those if you move the toolbox out of active development. To simplify and disambiguate what you're importing from a tool file, it requires the name of the .py file the tool is in to exactly match the name of the tool class contained within so "DevTool.py" contains this:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy

from utils.tool import Tool
import utils.archelp as archelp
import utils.models as models

class DevTool(Tool):
    def __init__(self) -&amp;gt; None:
        # Call the super init method to inherit from the base Tool class
        super().__init__()
        
        # Set local tool properties
        self.label = "Dev Tool"
        self.description = "This is a development tool."
        self.category = "Development"
        return&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;I've also implemented a Tool base class that allows all your tools to share a single "config" so if you have certain parameters that you always need for a toolbox, you can either define them in that Tool base class or extend that baseclass with different configs and inherit from that in your &amp;lt;ToolName&amp;gt; implementation:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy
import os

class Tool(object):
    """
    Base class for all tools that use python objects to build parameters
    """
    def __init__(self) -&amp;gt; None:
        """
        Tool Description
        """
        # Tool parameters
        self.label = "Tool"
        self.description = "Base class for all tools that use python objects to build parameters"
        self.canRunInBackground = False
        self.category = "Unassigned"
        
        # Project variables
        self.project = arcpy.mp.ArcGISProject("CURRENT")
        self.project_location = self.project.homeFolder
        self.project_name = os.path.basename(self.project_location)
        
        # Database variables
        self.default_gdb = self.project.defaultGeodatabase
        self.databases = self.project.databases
        
        return
    
    def getParameterInfo(self) -&amp;gt; list: ...
    def updateParameters(self, parameters: list) -&amp;gt; None: ...
    def updateMessages(self, parameters: list) -&amp;gt; None: ...
    def execute(self, parameters: dict, messages: list) -&amp;gt; None: ...

class ToolType(Tool):
    def __init__(self) -&amp;gt; None:
        super().__init__()
        self.my_var: int = 42&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;So if i wanted the DevTool to have access to the self.my_var parameter, all I'd need to do is:&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;import arcpy

from utils.tool import Tool, ToolType
import utils.archelp as archelp
import utils.models as models

class DevTool(ToolType):
    def __init__(self) -&amp;gt; None:
        # Call the super init method to inherit from the base Tool class
        super().__init__()
        
        # Set local tool properties
        self.label = "Dev Tool"
        self.description = "This is a development tool."
        self.category = "Development"
        return
    
    def execute(self, parameters: list, messages: list) -&amp;gt; None:
        arcpy.AddMessage(f"{self.my_var=}")
        return&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;This method can incur some overhead and slow down the toolbox, but the amount of time saved during development absolutely makes up for it. I've also included a VersionControl tool in the framework that allows the toolbox framework to be stored in a git repo and synced to users no matter where the actual toolfile is located as long as they have read access to the repo.&lt;/P&gt;&lt;P data-unlink="true"&gt;If you want to take a peek at the code it's &lt;A href="https://github.com/hwelch-fle/pytframe2" target="_self"&gt;here&lt;/A&gt; I still need to write documentation for everything, but it's still heavily in development as I want to clean up the hacked together code I've been using for a couple years to be more readable and efficient applying all the lessons I've learned managing toolboxes this way for the past 2 years.&lt;/P&gt;</description>
      <pubDate>Fri, 12 Jul 2024 13:46:46 GMT</pubDate>
      <guid>https://community.esri.com/t5/python-questions/debugging-arcgis-python-script-tools/m-p/1504120#M71049</guid>
      <dc:creator>HaydenWelch</dc:creator>
      <dc:date>2024-07-12T13:46:46Z</dc:date>
    </item>
  </channel>
</rss>

