Alteryx IO Discussions

Customize and extend the power of Alteryx with SDKs, APIs, custom tools, and more.

use python Engine SDK to implements 2 inputs and 2 outputs for plugin. Please help me !

flying008
15 - Aurora

Dear @tlarsen7572  and all, hello !

 

I am now trying to write a plugin using the old python Engine SDK. This plugin does not require a GUI for interaction. It has 2 input anchors, S anchor and I anchor, and 2 output anchors, L anchor and R anchor. The purpose of this plugin is to achieve the effect of automatically switching the direction of the workflow. The idea is to decide whether to output the data of I anchor to L anchor or R anchor based on the bool value input by S anchor. In short, it can only be output to one of the anchors, and the other one will not output anything at the same time (Like Detour tool but it is automation).

 

***Config.xml***

    <InputConnections>
      <Connection Name="Switch" AllowMultiple="False" Optional="False" Type="Connection" Label="S"/>
      <Connection Name="Input" AllowMultiple="False" Optional="False" Type="Connection" Label="I"/>
    </InputConnections>
    <OutputConnections>
      <Connection Name="Left" AllowMultiple="False" Optional="False" Type="Connection" Label="L"/>
      <Connection Name="RecordCount" AllowMultiple="False" Optional="True" Type="Connection" Label="o"/>
      <Connection Name="Right" AllowMultiple="False" Optional="False" Type="Connection" Label="R"/>
    </OutputConnections>

 

I am a Python newbie. Could you please help me correct the errors and requirements of Line 58, Line 178, Line 206? Thank you!

Please see the notes in code.

import AlteryxPythonSDK as Sdk
import xml.etree.ElementTree as Et
from collections import deque

class AyxPlugin:
    """
    Implements the plugin interface methods, to be utilized by the Alteryx engine to communicate with a plugin.
    Prefixed with "pi", the Alteryx engine will expect the below five interface methods to be defined.
    """

    def __init__(self, n_tool_id: int, alteryx_engine: object, output_anchor_mgr: object):
        """
        Constructor is called whenever the Alteryx engine wants to instantiate an instance of this plugin.
        :param n_tool_id: The assigned unique identification for a tool instance.
        :param alteryx_engine: Provides an interface into the Alteryx engine.
        :param output_anchor_mgr: A helper that wraps the outgoing connections for a plugin.
        """

        # Default properties
        self.n_tool_id = n_tool_id
        self.alteryx_engine = alteryx_engine
        self.output_anchor_mgr = output_anchor_mgr

        # Custom properties
         self.is_initialized = True
         self.switch_input = None
         self.data_input = None
         self.left_anchor = None
         self.recordcount_anchor = None
         self.right_anchor = None

    def pi_init(self, str_xml: str):
        """
        Handles configuration based on the GUI.
        Called when the Alteryx engine is ready to provide the tool configuration from the GUI.
        :param str_xml: The raw XML from the GUI.
        """

        # Getting the dataName data property from the Gui.html
        # There is no need gui for operation.

        # Getting the output anchor from Config.xml by the output connection name
         self.left_anchor = self.output_anchor_mgr.get_output_anchor('Left')
         self.recordcount_anchor = self.output_anchor_mgr.get_output_anchor('RecordCount')
         self.right_anchor = self.output_anchor_mgr.get_output_anchor('Right')

    def pi_add_incoming_connection(self, str_type: str, str_name: str) -> object:
        """
        The IncomingInterface objects are instantiated here, one object per incoming connection.
        Called when the Alteryx engine is attempting to add an incoming data connection.
        :param str_type: The name of the input connection anchor, defined in the Config.xml file.
        :param str_name: The name of the wire, defined by the workflow author.
        :return: The IncomingInterface object(s).
        """

        # ****** Is the code here correct? ******
        # #######################################
        if str_type == 'Switch':
            self.switch_input = IncomingInterface(self, str_type)
            return self.switch_input
        elif str_type == 'Input':
            self.data_input = IncomingInterface(self, str_type)
            return self.data_input
        else:
            self.display_error_message('Invalid Input Connection')
        # #########################
        # ****** ??? As above******

    def pi_add_outgoing_connection(self, str_name: str) -> bool:
        """
        Called when the Alteryx engine is attempting to add an outgoing data connection.
        :param str_name: The name of the output connection anchor, defined in the Config.xml file.
        :return: True signifies that the connection is accepted.
        """

        return True


    def pi_push_all_records(self, n_record_limit: int) -> bool:
        """
        Handles generating a new field for no incoming connections.
        Called when a tool has no incoming data connection.
        :param n_record_limit: Set it to <0 for no limit, 0 for no records, and >0 to specify the number of records.
        :return: False if there's an error with the field name, otherwise True.
        """
        # Tool is not an input tool, so must have an input to process
        self.alteryx_engine.output_message(self.n_tool_id, Sdk.EngineMessageType.error, "Input connection required.")
        return False
            
    def pi_close(self, b_has_errors: bool):
        """
        Called after all records have been processed.
        :param b_has_errors: Set to true to not do the final processing.
        """

        # Checks whether connections were properly closed.
         self.left_anchor.assert_close()
         self.right_anchor.assert_close()

    def display_error_msg(self, msg_string: str):
        """
        A non-interface method, that is responsible for displaying the relevant error message in Designer.
        :param msg_string: The custom error message.
        """

        self.is_initialized = False
        self.alteryx_engine.output_message(self.n_tool_id, Sdk.EngineMessageType.error, self.xmsg(msg_string))

    def xmsg(self, msg_string: str):
        """
        A non-interface, non-operational placeholder for the eventual localization of predefined user-facing strings.
        :param msg_string: The user-facing string.
        :return: msg_string
        """

        return msg_string


class IncomingInterface:
    """
    This class is returned by pi_add_incoming_connection, and it implements the incoming interface methods, to be\
    utilized by the Alteryx engine to communicate with a plugin when processing an incoming connection.
    Prefixed with "ii", the Alteryx engine will expect the below four interface methods to be defined.
    """

    def __init__(self, parent: object):
        """
        Constructor for IncomingInterface.
        :param parent: AyxPlugin
        """

        # Default properties
        self.parent = parent

        # Custom properties
        self.record_copier = None
        self.record_creator = None
        self.record_count = 0
        self.out_records = deque()
        self.record_info_in = None
        self.record_info_out = None
        self.field_index = None

    def ii_init(self, record_info_in: object) -> bool:
        """
        Handles appending the new field to the incoming data.
        Called to report changes of the incoming connection's record metadata to the Alteryx engine.
        :param record_info_in: A RecordInfo object for the incoming connection's fields.
        :return: False if there's an error with the field name, otherwise True.
        """

        if not self.parent.is_initialized:
            return False

        # Returns a new, empty RecordCreator object that is identical to record_info_in.
        self.record_info_in = record_info_in
        self.record_info_out = record_info_in.clone()

        # Initialize the record copier for the in/out transfer
        self.initialize_copier()

        if self.parent.alteryx_engine.get_init_var(self.parent.n_tool_id,'UpdateOnly') == 'True':
            self.initialize_output()

        return True

    def ii_push_record(self, in_record: object) -> bool:
        """
        Responsible for pushing records out.
        Called when an input record is being sent to the plugin.
        :param in_record: The data for the incoming record.
        :return: False if there's a downstream error, or if there's an error with the field name, otherwise True.
        """

        if not self.parent.is_initialized:
            return False

        # ******The following functions are to be implemented .******
        # #######################################
        # 1- Because self.switch_input will only have one field column, and this field column always has only one row, the data type is bool.
        # 2- so it is necessary to obtain and determine whether this bool value is True or False.
        # 3- If it is False, the data of self.data_input (i.e. Connection Name="Input") is output to self.left_anchor, and self.right_anchor does not output anything.
        # 4- If it is True, the data of self.data_input (i.e. Connection Name="Input") is output to self.right_anchor, and self.left_anchor does not output anything.
        # #########################
        # ****** ??? As above******

        return True

    def ii_update_progress(self, d_percent: float):
        """
        Called by the upstream tool to report what percentage of records have been pushed.
        :param d_percent: Value between 0.0 and 1.0.
        """

        # Inform the Alteryx engine of the tool's progress.
        self.parent.alteryx_engine.output_tool_progress(self.parent.n_tool_id, d_percent)

        # Inform the outgoing connections of the tool's progress.
         self.parent.left_anchor.update_progress(d_percent)
         self.parent.right_anchor.update_progress(d_percent)

    def ii_close(self):
        """
        Called when the incoming connection has finished passing all of its records.
        """
        # if the bool value = True , then:
            self.parent.alteryx_engine.output_message(self.parent.n_tool_id, Sdk.EngineMessageType.info, self.parent.xmsg("「■」Bypass is Action! ---||"))

        # Make sure that the recordcount anchor is closed.
        self.parent.recordcount_anchor.close()
        
        # Close outgoing connections.
         self.parent.left_anchor.close()
         self.parent.right_anchor.close()

    def initialize_copier(self):
        # Creating a new, empty record creator based on record_info_out's record layout.
        self.record_creator = self.record_info_out.construct_record_creator()

        # Instantiate a new instance of the RecordCopier class.
        self.record_copier = Sdk.RecordCopier(self.record_info_out, self.record_info_in)

        # Map each column of the input to where we want in the output.
        for index in range(self.record_info_in.num_fields):
            # Adding a field index mapping.
            self.record_copier.add(index, index)

        # Let record copier know that all field mappings have been added.
        self.record_copier.done_adding()

    def initialize_output(self):
        # Lets the downstream tools know what the outgoing record metadata will look like, based on record_info_out.
         self.parent.left_anchor.init(self.record_info_out)
         self.parent.right_anchor.init(self.record_info_out)


 

0 REPLIES 0