├── .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 | --------------------------------------------------------------------------------