├── .gitignore
├── LICENSE
├── README.md
├── lib
├── python3-function.html
└── python3-function.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Arnau Orriols
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Python 3 Function NodeRED Node
2 | =============================
3 |
4 | Extension of original package from Arrnau Orriols
5 | https://github.com/arnauorriols/node-red-contrib-python-function
6 |
7 |
8 | Install
9 | -------
10 | Python 2.7 and Python 3.x compatible
11 |
12 | `npm install -g node-red-contrib-python3-function`
13 |
14 | Usage
15 | -----
16 |
17 | Just like the plain-old function node, but writing Python instead of Javascript.
18 | The msg is a dictionary, (almost) all the Node-RED helper functions are available, and its behavior
19 | is expected to be exactly the same (at some point in the future at least).
20 |
--------------------------------------------------------------------------------
/lib/python3-function.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
79 |
80 |
99 |
100 |
137 |
--------------------------------------------------------------------------------
/lib/python3-function.js:
--------------------------------------------------------------------------------
1 | module.exports = function (RED) {
2 | var spawn = require('child_process').spawn;
3 | var util = require('util');
4 |
5 | function indentLines(fnCode, depth) {
6 | return fnCode.split('\n').map((line) => Array(depth).join(' ') + line).join('\n')
7 | }
8 |
9 | function spawnFn(self) {
10 | self.child = spawn('python', ['-uc', self.func.code], {
11 | stdio: ['pipe', 'pipe', 'pipe', 'ipc']
12 | });
13 | self.child.stdout.on('data', function (data) {
14 | self.log(data.toString());
15 | });
16 | self.child.stderr.on('data', function (data) {
17 | self.error(data.toString());
18 | });
19 | self.child.on('close', function (exitCode) {
20 | if (exitCode) {
21 | self.error(`Python Function process exited with code ${exitCode}`);
22 | if (self.func.attempts) {
23 | spawnFn(self);
24 | self.func.attempts--;
25 | } else {
26 | self.error(`Function '${self.name}' has failed more than 10 times. Fix it and deploy again`)
27 | self.status({
28 | fill: 'red',
29 | shape: 'dot',
30 | text: 'Stopped, see debug panel'
31 | });
32 | }
33 | }
34 | });
35 | self.child.on('message', function (response) {
36 | switch (response.ctx) {
37 | case 'send':
38 | sendResults(self, response.msgid, response.value);
39 | break;
40 | case 'log':
41 | case 'warn':
42 | case 'error':
43 | case 'status':
44 | self[response.ctx].apply(self, response.value);
45 | break;
46 | default:
47 | throw new Error(`Don't know what to do with ${response.ctx}`);
48 | }
49 | });
50 | self.log(`Python function '${self.name}' running on PID ${self.child.pid}`);
51 | self.status({
52 | fill: 'green',
53 | shape: 'dot',
54 | text: 'Running'
55 | });
56 | }
57 |
58 | function sendResults(self, _msgid, msgs) {
59 | if (msgs == null) {
60 | return;
61 | } else if (!util.isArray(msgs)) {
62 | msgs = [msgs];
63 | }
64 | var msgCount = 0;
65 | for (var m = 0; m < msgs.length; m++) {
66 | if (msgs[m]) {
67 | if (util.isArray(msgs[m])) {
68 | for (var n = 0; n < msgs[m].length; n++) {
69 | msgs[m][n]._msgid = _msgid;
70 | msgCount++;
71 | }
72 | } else {
73 | msgs[m]._msgid = _msgid;
74 | msgCount++;
75 | }
76 | }
77 | }
78 | if (msgCount > 0) {
79 | self.send(msgs);
80 | }
81 | }
82 |
83 | function PythonFunction(config) {
84 | var self = this;
85 | RED.nodes.createNode(self, config);
86 | self.name = config.name;
87 | self.func = {
88 | code: `
89 | import os
90 | import json
91 | import sys
92 |
93 | channel = None
94 | if sys.version_info[0]<3:
95 | channel = os.fdopen(3, "r+")
96 | else:
97 | channel = os.fdopen(3, "r+b", buffering=0)
98 |
99 | class Msg(object):
100 | SEND = 'send'
101 | LOG = 'log'
102 | WARN = 'warn'
103 | ERROR = 'error'
104 | STATUS = 'status'
105 |
106 | def __init__(self, ctx, value, msgid):
107 | self.ctx = ctx
108 | self.value = value
109 | self.msgid = msgid
110 |
111 | def dumps(self):
112 | return json.dumps(vars(self)) + "\\n"
113 |
114 | @classmethod
115 | def loads(cls, json_string):
116 | return cls(**json.loads(json_string))
117 |
118 |
119 | class Node(object):
120 | def __init__(self, msgid, channel):
121 | self.__msgid = msgid
122 | self.__channel = channel
123 |
124 | def send(self, msg):
125 | msg = Msg(Msg.SEND, msg, self.__msgid)
126 | self.send_to_node(msg)
127 |
128 | def log(self, *args):
129 | msg = Msg(Msg.LOG, args, self.__msgid)
130 | self.send_to_node(msg)
131 |
132 | def warn(self, *args):
133 | msg = Msg(Msg.WARN, args, self.__msgid)
134 | self.send_to_node(msg)
135 |
136 | def error(self, *args):
137 | msg = Msg(Msg.ERROR, args, self.__msgid)
138 | self.send_to_node(msg)
139 |
140 | def status(self, *args):
141 | msg = Msg(Msg.STATUS, args, self.__msgid)
142 | self.send_to_node(msg)
143 |
144 | def send_to_node(self, msg):
145 | m = msg.dumps()
146 | if sys.version_info[0]>2:
147 | m = m.encode("utf-8")
148 | self.__channel.write(m)
149 |
150 |
151 | def python_function(msg):
152 | ` + indentLines(config.func, 4) +
153 | `
154 | while True:
155 | raw_msg = channel.readline()
156 | if not raw_msg:
157 | raise RuntimeError('Received EOF!')
158 | msg = json.loads(raw_msg)
159 | msgid = msg["_msgid"]
160 | node = Node(msgid, channel)
161 | res_msgs = python_function(msg)
162 | node.send(res_msgs)
163 | `,
164 | attempts: 10
165 | };
166 | spawnFn(self);
167 | self.on('input', function (msg) {
168 | var cache = [];
169 | jsonMsg = JSON.stringify(msg, function (key, value) {
170 | if (typeof value === 'object' && value !== null) {
171 | if (cache.indexOf(value) !== -1) {
172 | // Circular reference found, discard key
173 | return;
174 | }
175 | // Store value in our collection
176 | cache.push(value);
177 | }
178 | return value;
179 | });
180 | cache = null; // Enable garbage collection
181 | self.child.send(JSON.parse(jsonMsg));
182 | });
183 | self.on('close', function () {
184 | self.child.kill();
185 | });
186 | }
187 | RED.nodes.registerType('python3-function', PythonFunction);
188 | };
189 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-python3-function",
3 | "version": "0.0.4",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/dejanrizvan/node-red-contrib-python3-function"
7 | },
8 | "description": "Define a function with Python instead of Javascript",
9 | "keywords": [
10 | "node-red",
11 | "function",
12 | "Python",
13 | "exec",
14 | "polyglot",
15 | "hack"
16 | ],
17 | "author": "Arnau Orriols",
18 | "license": "MIT",
19 | "node-red": {
20 | "nodes": {
21 | "python-function": "lib/python3-function.js"
22 | }
23 | },
24 | "dependencies": {
25 | },
26 | "contributors": [
27 | {
28 | "name": "Dejan Rizvan",
29 | "email": "dejan.rizvan@gmail.com",
30 | "url": "https://github.com/dejanrizvan",
31 | "contributions": 1,
32 | "hireable": true
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------