Skip to content

Commit ef76ab7

Browse files
committed
US26805: Python SDK - output tool sample plugin
US26805: Python SDK - output tool sample plugin
1 parent be897b8 commit ef76ab7

File tree

5 files changed

+1752
-0
lines changed

5 files changed

+1752
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
The name of this file must be the name of the plugin folder + "Config"
4+
-->
5+
<AlteryxJavaScriptPlugin>
6+
<EngineSettings EngineDll="Python" EngineDllEntryPoint="engine.py" SDKVersion="10.1" />
7+
<GuiSettings Html="PyOutputToolExampleGui.html" Icon="PyOutputToolExampleIcon.png" SDKVersion="10.1">
8+
<InputConnections>
9+
<Connection Name="Input" AllowMultiple="False" Optional="False" Type="Connection" Label=""/>
10+
</InputConnections>
11+
<OutputConnections>
12+
</OutputConnections>
13+
</GuiSettings>
14+
<Properties>
15+
<MetaInfo>
16+
<Name>PyOutputToolExample</Name>
17+
<Description>An example Python output tool by Ozzie</Description>
18+
<CategoryName>Laboratory</CategoryName>
19+
<SearchTags>python, sdk, python sdk, output</SearchTags>
20+
</MetaInfo>
21+
</Properties>
22+
</AlteryxJavaScriptPlugin>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html style="padding:20px">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>PyOutputTool</title>
6+
7+
<script type="text/javascript">
8+
document.write('<link rel="import" href="' + window.Alteryx.LibDir + '2/lib/includes.html">');
9+
</script>
10+
</head>
11+
<body>
12+
<form>
13+
<fieldset>
14+
<legend class='blueTitle'>XMSG("Python Output Tool Example")</legend>
15+
<label>XMSG("File Output Path")</label>
16+
<ayx data-props="{type: 'TextBox', dataName: 'TextBox1'}"></ayx>
17+
</fieldset>
18+
</form>
19+
<script type="text/javascript"></script>
20+
<style>
21+
/*This styling needs to be here. Is due to file loading. Otherwise, will not get proper styling on dropdowns*/
22+
.Select, .Select div, .Select input, .Select span {
23+
box-sizing: content-box;
24+
-webkit-box-sizing: content-box;
25+
}
26+
</style>
27+
</body>
28+
</html>
6.77 KB
Loading

PyOutputToolExample/engine.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import AlteryxPythonSDK
2+
import xml.etree.ElementTree as ET
3+
import os
4+
5+
6+
class AyxPlugin:
7+
def __init__(self, n_tool_id, engine_interface, generic_engine, output_anchor_mgr):
8+
# initialize *all* members that will be used (for PEP8 compliance)
9+
10+
# miscellaneous variables
11+
self.n_tool_id = n_tool_id
12+
self.name = str('PyOutputTool_') + str(self.n_tool_id)
13+
self.closed = False
14+
self.initialized = False
15+
16+
# engine handles
17+
self.alteryx_engine = engine_interface
18+
self.generic_engine = generic_engine
19+
20+
# record management
21+
self.record_info_in = None
22+
23+
#
24+
# TODO: create your custom members here and give them default values
25+
#
26+
# self.str_file_path = 'testing'
27+
#
28+
# END custom members
29+
#
30+
31+
return
32+
33+
def output_message(self, method, status, message):
34+
# helper for printing messages out to the engine
35+
self.alteryx_engine.output_message(self.n_tool_id, status, method + ': ' + str(message))
36+
37+
#
38+
# pi_init will be called when the Engine is ready to give us the tool configuration from the GUI
39+
#
40+
def pi_init(self, str_xml):
41+
try:
42+
root = ET.fromstring(str_xml)
43+
#
44+
# TODO: parse the XML from Designer here
45+
#
46+
self.str_file_path = root.find('TextBox1').text
47+
#
48+
# END XML parsing
49+
#
50+
except AttributeError:
51+
self.output_message('pi_init', AlteryxPythonSDK.EngineMessageType.error, 'Invalid XML: ' + str_xml)
52+
raise
53+
54+
self.initialized = True
55+
56+
return
57+
58+
#
59+
# pi_close will be called after all the records have been processed
60+
#
61+
def pi_close(self, b_has_errors):
62+
return
63+
64+
#
65+
# pi_add_incoming_connection will be called when a new input is connected to this tool
66+
#
67+
def pi_add_incoming_connection(self, str_type, str_name):
68+
return self
69+
70+
#
71+
# pi_add_outgoing_connection will be called when a new output is connected to this tool
72+
#
73+
def pi_add_outgoing_connection(self, str_name):
74+
self.output_message(
75+
'ii_push_record'
76+
, AlteryxPythonSDK.EngineMessageType.error
77+
, 'This tool does not accept an Outgoing Connection'
78+
)
79+
return True
80+
81+
#
82+
# pi_push_all_records will be called if there are no inputs connected to this tool
83+
#
84+
def pi_push_all_records(self, n_record_limit):
85+
self.output_message(
86+
'pi_push_all_records'
87+
, AlteryxPythonSDK.EngineMessageType.error
88+
, 'Missing Incoming Connection'
89+
)
90+
91+
return False
92+
93+
#
94+
# ii_init will be called when an incoming connection has been initalized and has told the Engine
95+
# what its output will look like. record_info_in represents what the incoming record will look like
96+
#
97+
def ii_init(self, record_info_in):
98+
self.record_info_in = record_info_in
99+
100+
def get_field_names(record_info_in):
101+
ret = []
102+
for idx in range(len(record_info_in)):
103+
ret.append(record_info_in[idx].name) # record_info_in metadata about a field record_info_in[0].name
104+
return ret
105+
106+
field_names = get_field_names(record_info_in)
107+
field_names = str(field_names)
108+
field_names = field_names.replace('[','')
109+
field_names = field_names.replace(']','')
110+
field_names = field_names.replace('\'','')
111+
112+
self.field_names = field_names
113+
self.all_records = ''
114+
115+
self.initialized = True
116+
117+
return True
118+
119+
#
120+
# ii_push_record will be called every time we get a new record from the upstream tool
121+
#
122+
def ii_push_record(self, in_record):
123+
if self.initialized is not True:
124+
return False
125+
126+
def null_to_str(field, in_record):
127+
ret = field.get_as_string(in_record)
128+
if ret is None:
129+
return '[Null]'
130+
return ret
131+
132+
all_records = str([null_to_str(field, in_record) for field in self.record_info_in])
133+
all_records = all_records.replace('[','')
134+
all_records = all_records.replace(']','')
135+
all_records = all_records.replace('\'','')
136+
137+
self.all_records += all_records + ' \n'
138+
139+
return True
140+
141+
#
142+
# ii_update_progress will be called periodically from the upstream tools, where they will tell us how far along
143+
# they are in processing their data. If our tool needs to do any custom logic about how much work it has left to
144+
# do, that logic should happen in here
145+
#
146+
def ii_update_progress(self, d_percent):
147+
self.alteryx_engine.output_tool_progress(self.n_tool_id, d_percent)
148+
return
149+
150+
#
151+
# ii_close will be called when the upstream tool is finished
152+
#
153+
def ii_close(self):
154+
# self.output_message('Error ',AlteryxPythonSDK.EngineMessageType.info, self.str_file_path + ' already exists. Please enter a different path.')
155+
if os.access(self.str_file_path, os.F_OK):
156+
self.output_message('Error ', AlteryxPythonSDK.EngineMessageType.error, self.str_file_path + ' already exists. Please enter a different path.')
157+
else:
158+
with open(self.str_file_path, 'a') as testfile:
159+
testfile.write(self.field_names + '\n' + self.all_records)
160+
testfile.close
161+
message = self.str_file_path + ' was written.'
162+
self.output_message('Output ', AlteryxPythonSDK.EngineMessageType.info, message)
163+
return

0 commit comments

Comments
 (0)