community
cancel
Showing results for 
Search instead for 
Did you mean: 

Dev Space

Customize & extend the power of Alteryx. SDKs, APIs, custom tools, and more!
SOLVED

Python SDK: Strange behavior with multiple instances of the tool

Asteroid

Hi there,

 

I've run into a super strange error with my Python SDK tool: I get errors and strange behaviors when the tool is present multiple times in the workflow.

 

My tool has a GUI option to select different modes: List Files or Download Files. This is stored as a DataItem named ToolMode and read in the AyxPlugin.pi_init() method:

class ToolMode(Enum):
    NONE_MODE = 0
    LIST_FILES = 1
    DOWNLOAD_TO_PATH = 2
    DOWNLOAD_TO_BLOB = 3

class AyxPlugin:
    def __init__(self, n_tool_id: int, alteryx_engine: object, output_anchor_mgr: object):
        # Default properties
        self.n_tool_id = n_tool_id
        self.alteryx_engine = alteryx_engine
        self.output_anchor_mgr = output_anchor_mgr

        # Tool mode: What should we do?
        self.tool_mode = ToolMode.NONE_MODE

    def pi_init(self, str_xml: str):
        setting_tree = Et.fromstring(str_xml)
        self.tool_mode = {
            'list': ToolMode.LIST_FILES,
            'download_file': ToolMode.DOWNLOAD_TO_PATH,
            'download_blob': ToolMode.DOWNLOAD_TO_BLOB
        }.get(setting_tree.find('ToolMode').text, ToolMode.NONE_MODE)

 

At a different method I check which self.tool_mode I have and decide what to do:

 

    def pi_push_all_records(self, n_record_limit: int) -> bool:

            self.output_message("Debug: {}".format(self.tool_mode))
            if self.tool_mode == ToolMode.LIST_FILES:
                # Fields only present for LIST_FILES mode
                self.output_recordinfo[field_dict['UID']].set_from_string(record_creator, f['uid'])
            else:
                # Download files if not in LIST_FILES mode
                if self.tool_mode == ToolMode.DOWNLOAD_TO_BLOB:
                    # Generate temporary filename
                    out_fname = self.alteryx_engine.create_temp_file_name('tmp')

                # Download file
                try:
                    # Download file to temporary folder
                    sftp_conn.get(f['filename'], localpath=out_fname)
                except IOError as e:
                    self.output_message('Error transferring file "{}": {}'.format(f['filename'], e))

 

Everything works as expected as long as I have the tool only once on the canvas. As soon as I have two or more instances of the tool, strange behavior starts. In particular, in pi_push_all_records() I get an error that out_fname is not defined -- despite the fact that tool should not even run this chunk of code. The output correctly shows "Debug: ToolMode.LST_FILES", but the subsequent "if" does not catch this, but runs into the "else" branch.

 

This behavior does not make any sense. It seems like Alteryx is messing up the objects and properties are shared between instances. But I have checked very carefully to use "self.tool_mode" everywhere. So, there should be absolutely no global objects present.

 

Did anyone ever epexierence something  similar?

 

Best

Christopher

Asteroid

Problem solved...

 

In my post I claimed

 


But I have checked very carefully to use "self.tool_mode" everywhere. So, there should be absolutely no global objects present.


but this isn't actually true! ToolMode as a class is defined as a global object. As soon as a second instance of the tool is placed on the canvas, a second class of the same name is defined. It looks the same, it actually is the same, but the comparisons in the first instance will fail -- as it is, in fact, not the same class anymore.

 

Remember: If you develop a Python SDK tool, do not ever define global objects or classes.

 

Solution was to define ToolMode as "part" of AyxPlugin:

 

class AyxPlugin:

    class ToolMode(Enum):
        # ...

    # ...
    self.tool_mode = self.ToolMode.NONE_MODE
    # etc.

 

Best

Christopher 

Highlighted

Thanks for sharing this, @chrisha!  I was quite stumped with your problem.  I assumed (like you must have), that enums were static or singleton values like they are in every other language I've used.  Very strange that it is not the case, and very good to keep in mind.