├── .gitignore
├── README.md
├── example
└── pythonShellNode.js
├── package-lock.json
├── package.json
├── src
├── PythonShellNode.js
├── pythonshell.html
└── pythonshell.js
├── test
├── PythonShellNode_Spec.js
├── sample-file-read.py
├── sample-loop.py
├── sample-need-venv-file-read.py
├── sample-need-venv.py
├── sample-python3.py
├── sample-with-arg.py
├── sample.py
├── stdin-data.py
└── test.txt
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_store
2 | .config.json
3 | .dist
4 | .jshintignore
5 | .npm
6 | .project
7 | .sessions.json
8 | .settings
9 | .tern-project
10 | *.backup
11 | *_cred*
12 | coverage
13 | credentials.json
14 | flows*.json
15 | nodes/node-red-nodes/
16 | node_modules
17 | public
18 | locales/zz-ZZ
19 | nodes/core/locales/zz-ZZ
20 |
21 | test/venv
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Executing a python script from node-red. Input to the node will become the argument for the python script, output of the script will be sent to output of the node.
2 |
3 | * Now supporting continuous std in data as the input to the script. If the node is configured to receive continuous data from its input, clicking on the trigger will terminate the script.
4 |
5 | * Now supporting executing within a virtual environment. Specify the path to the virtualenv folder in node configuration.
6 |
7 | Example flow:
8 |
9 | ```
10 | [{"id":"a1b2b31b.65fe7","type":"tab","label":"Flow 1"},{"id":"3df34b3a.b6bb8c","type":"pythonshell in","z":"a1b2b31b.65fe7","name":"","pyfile":"/Users/namtrang/main.py","x":341.5,"y":154,"wires":[["f811cd5c.e9dfe8"]]},{"id":"f4dcbeae.1da998","type":"inject","z":"a1b2b31b.65fe7","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":140.5,"y":76,"wires":[["3df34b3a.b6bb8c"]]},{"id":"f811cd5c.e9dfe8","type":"debug","z":"a1b2b31b.65fe7","name":"","active":true,"console":"false","complete":"false","x":537.5,"y":233,"wires":[]}]
11 | ```
12 |
13 | And this is the content of the python script:
14 |
15 | ```
16 | import sys
17 | print "Got arguments: ", sys.argv
18 | ```
19 |
--------------------------------------------------------------------------------
/example/pythonShellNode.js:
--------------------------------------------------------------------------------
1 | var PythonshellNode = require('../src/PythonShellNode');
2 |
3 | var pyNode = new PythonshellNode({
4 | pyfile: "./test/sample.py",
5 | virtualEnv: "./test/venv",
6 | });
7 |
8 | pyNode.onInput({payload: ""}, console.log, console.log);
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-pythonshell",
3 | "version": "1.4.3",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "balanced-match": {
8 | "version": "1.0.0",
9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
11 | "dev": true
12 | },
13 | "brace-expansion": {
14 | "version": "1.1.11",
15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
17 | "dev": true,
18 | "requires": {
19 | "balanced-match": "1.0.0",
20 | "concat-map": "0.0.1"
21 | }
22 | },
23 | "browser-stdout": {
24 | "version": "1.3.1",
25 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
26 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
27 | "dev": true
28 | },
29 | "commander": {
30 | "version": "2.15.1",
31 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
32 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
33 | "dev": true
34 | },
35 | "concat-map": {
36 | "version": "0.0.1",
37 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
38 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
39 | "dev": true
40 | },
41 | "debug": {
42 | "version": "3.1.0",
43 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
44 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
45 | "dev": true,
46 | "requires": {
47 | "ms": "2.0.0"
48 | }
49 | },
50 | "diff": {
51 | "version": "3.5.0",
52 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
53 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
54 | "dev": true
55 | },
56 | "escape-string-regexp": {
57 | "version": "1.0.5",
58 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
59 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
60 | "dev": true
61 | },
62 | "fs.realpath": {
63 | "version": "1.0.0",
64 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
65 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
66 | "dev": true
67 | },
68 | "glob": {
69 | "version": "7.1.2",
70 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
71 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
72 | "dev": true,
73 | "requires": {
74 | "fs.realpath": "1.0.0",
75 | "inflight": "1.0.6",
76 | "inherits": "2.0.3",
77 | "minimatch": "3.0.4",
78 | "once": "1.4.0",
79 | "path-is-absolute": "1.0.1"
80 | }
81 | },
82 | "growl": {
83 | "version": "1.10.5",
84 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
85 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
86 | "dev": true
87 | },
88 | "has-flag": {
89 | "version": "3.0.0",
90 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
91 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
92 | "dev": true
93 | },
94 | "he": {
95 | "version": "1.1.1",
96 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
97 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
98 | "dev": true
99 | },
100 | "inflight": {
101 | "version": "1.0.6",
102 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
103 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
104 | "dev": true,
105 | "requires": {
106 | "once": "1.4.0",
107 | "wrappy": "1.0.2"
108 | }
109 | },
110 | "inherits": {
111 | "version": "2.0.3",
112 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
113 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
114 | "dev": true
115 | },
116 | "minimatch": {
117 | "version": "3.0.4",
118 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
119 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
120 | "dev": true,
121 | "requires": {
122 | "brace-expansion": "1.1.11"
123 | }
124 | },
125 | "minimist": {
126 | "version": "0.0.8",
127 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
128 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
129 | "dev": true
130 | },
131 | "mkdirp": {
132 | "version": "0.5.1",
133 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
134 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
135 | "dev": true,
136 | "requires": {
137 | "minimist": "0.0.8"
138 | }
139 | },
140 | "mocha": {
141 | "version": "5.2.0",
142 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
143 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
144 | "dev": true,
145 | "requires": {
146 | "browser-stdout": "1.3.1",
147 | "commander": "2.15.1",
148 | "debug": "3.1.0",
149 | "diff": "3.5.0",
150 | "escape-string-regexp": "1.0.5",
151 | "glob": "7.1.2",
152 | "growl": "1.10.5",
153 | "he": "1.1.1",
154 | "minimatch": "3.0.4",
155 | "mkdirp": "0.5.1",
156 | "supports-color": "5.4.0"
157 | }
158 | },
159 | "ms": {
160 | "version": "2.0.0",
161 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
162 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
163 | "dev": true
164 | },
165 | "once": {
166 | "version": "1.4.0",
167 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
168 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
169 | "dev": true,
170 | "requires": {
171 | "wrappy": "1.0.2"
172 | }
173 | },
174 | "path-is-absolute": {
175 | "version": "1.0.1",
176 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
177 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
178 | "dev": true
179 | },
180 | "supports-color": {
181 | "version": "5.4.0",
182 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
183 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
184 | "dev": true,
185 | "requires": {
186 | "has-flag": "3.0.0"
187 | }
188 | },
189 | "wrappy": {
190 | "version": "1.0.2",
191 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
192 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
193 | "dev": true
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-red-contrib-pythonshell",
3 | "version": "1.5.4",
4 | "description": "nodes used to interact with python processes",
5 | "scripts": {
6 | "test": "./node_modules/mocha/bin/mocha"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/namgk/node-red-contrib-pythonshell.git"
11 | },
12 | "keywords": [
13 | "distributed",
14 | "python",
15 | "node",
16 | "node-red"
17 | ],
18 | "author": "Nam Giang",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/namgk/node-red-contrib-pythonshell/issues"
22 | },
23 | "homepage": "https://github.com/namgk/node-red-contrib-pythonshell#readme",
24 | "dependencies": {},
25 | "node-red": {
26 | "nodes": {
27 | "pythonshell": "src/pythonshell.js"
28 | }
29 | },
30 | "devDependencies": {
31 | "mocha": "^5.0.4"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/PythonShellNode.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs");
2 |
3 | function PythonshellInNode(config) {
4 | if (!config.pyfile){
5 | throw 'pyfile not present';
6 | }
7 | this.pythonExec = config.python3 ? "python3" : "python";
8 | this.pyfile = config.pyfile;
9 | this.virtualenv = config.virtualenv;
10 |
11 | if (!fs.existsSync(this.pyfile)) {
12 | throw 'pyfile not exist';
13 | }
14 |
15 | if (this.virtualenv && !fs.existsSync(this.virtualenv)){
16 | throw 'configured virtualenv not exist, consider remove or change';
17 | }
18 |
19 | this.stdInData = config.stdInData;
20 | this.continuous = this.stdInData ? true : config.continuous;
21 | this.pydir = this.pyfile.substring(0, this.pyfile.lastIndexOf('/'));
22 | this.pyfile = this.pyfile.substring(this.pyfile.lastIndexOf('/') + 1, this.pyfile.length);
23 | this.spawn = require('child_process').spawn;
24 | this.onStatus = ()=>{}
25 | }
26 |
27 | PythonshellInNode.prototype.onInput = function(msg, out, err) {
28 | payload = msg.payload || '';
29 | if (typeof payload === 'object'){
30 | payload = JSON.stringify(payload);
31 | } else if (typeof payload !== 'string'){
32 | payload = payload.toString();
33 | }
34 |
35 | if (payload === 'pythonshell@close'){
36 | if (this.py != null){
37 | this.onClose()
38 | return
39 | } else {
40 | // trigger new execution
41 | payload = ''
42 | }
43 | }
44 |
45 | if (this.continuous && !this.stdInData && this.py != null){
46 | this.onStatus({fill:"yellow",shape:"dot",text:"Not accepting input"})
47 | return
48 | }
49 |
50 | var spawnCmd = (this.virtualenv ? this.virtualenv + '/bin/' : '') + this.pythonExec
51 |
52 | if (this.stdInData){
53 | if (!this.py){
54 | this.py = this.spawn(spawnCmd, ['-u', this.pyfile], {
55 | cwd: this.pydir,
56 | detached: true
57 | });
58 | this.firstExecution = true
59 | } else {
60 | this.firstExecution = false
61 | }
62 | } else {
63 | this.py = this.spawn(spawnCmd, ['-u', this.pyfile, payload], {
64 | cwd: this.pydir
65 | });
66 | }
67 |
68 | this.onStatus({fill:"green",shape:"dot",text:"Standby"})
69 |
70 | // subsequence message, no need to setup callbacks
71 | if (this.stdInData && !this.firstExecution){
72 | this.py.stdin.write(payload + '\n')
73 | return
74 | }
75 |
76 | var py = this.py;
77 | var dataString = '';
78 | var errString = '';
79 |
80 | py.stdout.on('data', data => {
81 | clearTimeout(this.standbyTimer)
82 |
83 | this.onStatus({fill:"green",shape:"dot",text:"Processing data"})
84 |
85 | let dataStr = data.toString();
86 |
87 | dataString += dataStr;
88 |
89 | if (dataString.endsWith("\n")){
90 | if (this.continuous){
91 | msg.payload = dataString;
92 | out(msg);
93 | dataString = ''
94 | }
95 | }
96 |
97 | this.standbyTimer = setTimeout(()=>{
98 | this.onStatus({fill:"green",shape:"dot",text:"Standby"})
99 | }, 2000)
100 |
101 | });
102 |
103 | py.stderr.on('data', data => {
104 | errString += String(data);// just a different way to do it
105 | this.onStatus({fill:"red",shape:"dot",text:"Error: " + errString})
106 | });
107 |
108 | py.stderr.on('error', console.log)
109 | py.stdout.on('error', console.log)
110 | py.stdin.on('error', console.log)
111 | py.on('error', console.log)
112 |
113 | py.on('close', code =>{
114 | if (code){
115 | err('exit code: ' + code + ', ' + errString);
116 | this.onStatus({fill:"red",shape:"dot",text:"Exited: " + code})
117 | } else if (!this.continuous){
118 | msg.payload = dataString.trim();
119 | out(msg);
120 | this.onStatus({fill:"green",shape:"dot",text:"Done"})
121 | } else {
122 | this.onStatus({fill:"yellow",shape:"dot",text:"Script Closed"})
123 | }
124 | this.py = null
125 | setTimeout(()=>{
126 | this.onStatus({})
127 | }, 2000)
128 | });
129 |
130 | if (this.stdInData){
131 | py.stdin.write(payload + '\n')
132 | }
133 | };
134 |
135 | PythonshellInNode.prototype.onClose = function() {
136 | if (this.py){
137 | this.py.kill()
138 | this.py = null
139 | }
140 | };
141 |
142 | PythonshellInNode.prototype.setStatusCallback = function(callback) {
143 | this.onStatus = callback
144 | };
145 |
146 |
147 | module.exports = PythonshellInNode
--------------------------------------------------------------------------------
/src/pythonshell.html:
--------------------------------------------------------------------------------
1 | f
16 |
54 |
55 |
69 |
70 |
83 |
84 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/src/pythonshell.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Sense Tecnic Systems, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | **/
16 |
17 | var util = require("util");
18 | var httpclient;
19 | var PythonshellNode = require('./PythonShellNode');
20 |
21 | module.exports = function(RED) {
22 | "use strict";
23 |
24 | function PythonshellInNode(n) {
25 | RED.nodes.createNode(this,n);
26 |
27 | var node = this;
28 | node.config = n; // copy config to the backend so that down bellow we can have a reference
29 |
30 | var pyNode = new PythonshellNode(n);
31 |
32 | pyNode.setStatusCallback(node.status.bind(node))
33 |
34 | node.on("input",function(msg) {
35 | pyNode.onInput(msg, function(result){
36 | node.send(result);
37 | }, function(err){
38 | node.error(err);
39 | });
40 | });
41 |
42 | node.on('close', ()=>pyNode.onClose());
43 | }
44 |
45 | RED.nodes.registerType("pythonshell in", PythonshellInNode);
46 |
47 | RED.httpAdmin.post("/pythonshell/:id", RED.auth.needsPermission("pythonshell.query"), function(req,res) {
48 | var node = RED.nodes.getNode(req.params.id);
49 | if (node != null) {
50 | try {
51 | if (node.config.continuous){// see above comment
52 | node.receive({payload: 'pythonshell@close'})
53 | } else {
54 | node.receive();
55 | }
56 | res.sendStatus(200);
57 | } catch(err) {
58 | res.sendStatus(500);
59 | node.error(RED._("pythonshell.failed",{error:err.toString()}));
60 | }
61 | } else {
62 | res.sendStatus(404);
63 | }
64 | });
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/test/PythonShellNode_Spec.js:
--------------------------------------------------------------------------------
1 | let fs = require('fs')
2 | let assert = require('assert');
3 | let spawn = require('child_process').spawn
4 | let net = require("net");
5 |
6 | let PythonshellNode = require('../src/PythonShellNode');
7 |
8 | describe('Pythonshell Node', function() {
9 | let venv = "/venv";
10 |
11 | before(function(done){
12 | this.timeout(10000);
13 |
14 | if (fs.existsSync(__dirname + venv)) {
15 | done();
16 | return;
17 | }
18 |
19 | console.log('creating virtual environment for testing')
20 |
21 | let spawn = require('child_process').spawn;
22 | let ve;
23 | try {
24 | ve = spawn('virtualenv', [__dirname + venv]);
25 | } catch (e){
26 | done(e);
27 | }
28 |
29 | ve.stdout.on('data', d=>console.log(d.toString()));
30 | ve.stderr.on('data', d=>console.log(d.toString()));
31 |
32 | ve.on('close', function(code) {
33 | if (code){
34 | done(code);
35 | } else{
36 | try {
37 | let pipInstall = spawn(__dirname + venv + '/bin/pip', ['install', 'lxml']);
38 | pipInstall.stdout.on('data', d=>console.log(d.toString()));
39 | pipInstall.stderr.on('data', d=>console.log(d.toString()));
40 | pipInstall.on('close', done)
41 | } catch (e){
42 | done(e);
43 | }
44 | }
45 | });
46 | });
47 |
48 | describe('Failing cases', function(){
49 | it('should throw an error for empty config', function(done) {
50 | try {
51 | let pyNode = new PythonshellNode();
52 | done(1)
53 | } catch (e){
54 | done()
55 | }
56 | });
57 |
58 | it('should throw an error for empty config', function(done) {
59 | try {
60 | let pyNode = new PythonshellNode({});
61 | done(1)
62 | } catch (e){
63 | done()
64 | }
65 | });
66 |
67 | it('should throw an error for config without python file', function(done) {
68 | try {
69 | let pyNode = new PythonshellNode({virtualenv: __dirname + venv});
70 | done(1)
71 | } catch (e){
72 | done()
73 | }
74 | });
75 |
76 | it('should throw an error for non existing python file', function(done) {
77 | try {
78 | let pyNode = new PythonshellNode({pyfile: __dirname + "/sample.p"});
79 | done(1)
80 | } catch (e){
81 | done()
82 | }
83 | });
84 |
85 | it('should throw an error for non existing python virtualenv', function(done) {
86 | try {
87 | let pyNode = new PythonshellNode({
88 | pyfile: __dirname + "/sample.py",
89 | virtualenv: __dirname + "/awefaewaf"
90 | });
91 | done(1)
92 | } catch (e){
93 | done()
94 | }
95 | });
96 |
97 | it('should throw an error when importing external libraries without venv', function(done) {
98 | let pyNode = new PythonshellNode({pyfile: __dirname + "/sample-need-venv.py"});
99 |
100 | pyNode.onInput({payload: ""}, function(result){
101 | done(1)
102 | }, function(err){
103 | done()
104 | });
105 | });
106 | })
107 |
108 |
109 | describe('Run Python script', function() {
110 | it('should return the script result', function(done) {
111 | let pyNode = new PythonshellNode({
112 | pyfile: __dirname + "/sample.py"
113 | });
114 |
115 | pyNode.onInput({payload: ""}, function(result){
116 | assert.notEqual(result.payload, null);
117 | assert.equal(result.payload, 'hi');
118 | done()
119 | }, function(err){
120 | done(err)
121 | });
122 | });
123 |
124 | it('should forward the whole message', function(done) {
125 | let pyNode = new PythonshellNode({
126 | pyfile: __dirname + "/sample.py"
127 | });
128 |
129 | pyNode.onInput({payload: "", otherPayload: "testval"}, function(result){
130 | assert.notEqual(result.payload, null);
131 | assert.equal(result.payload, 'hi');
132 | assert.equal(result.otherPayload, "testval")
133 | done()
134 | }, function(err){
135 | done(err)
136 | });
137 | })
138 |
139 | it('should output script ongoing result', function(done) {
140 | this.timeout(10000);
141 |
142 | let runs = 0;
143 |
144 | let pyNode = new PythonshellNode({
145 | pyfile: __dirname + "/sample-loop.py",
146 | continuous: true
147 | });
148 |
149 | pyNode.onInput({payload: ""}, function(result){
150 | assert.notEqual(result.payload, null);
151 | assert.equal(result.payload.trim(), 'on going')
152 | runs++;
153 |
154 | if (runs >= 3){
155 | pyNode.onClose()
156 | done();
157 | }
158 | }, function(err){
159 | done(err)
160 | });
161 | });
162 |
163 | it('should not accepting input when is producing result', function(done) {
164 | this.timeout(10000);
165 |
166 | let ins = 0;
167 | let runner;
168 |
169 | let pyNode = new PythonshellNode({
170 | pyfile: __dirname + "/sample-loop.py",
171 | continuous: true
172 | });
173 |
174 | pyNode.setStatusCallback(status=>{
175 | if (ins === 2 && status.text === "Not accepting input"){
176 | clearInterval(runner)
177 | pyNode.onClose()
178 | done()
179 | }
180 | })
181 |
182 | runner = setInterval(()=>{
183 | ins++
184 | pyNode.onInput({payload: "arg"},(result)=>{}, (err)=>{done(err)})
185 | }, 500)
186 |
187 | // TODO: to double check, look at ps aux | grep python
188 | });
189 |
190 | it('should pass arguments to script', function(done) {
191 | let pyNode = new PythonshellNode({
192 | pyfile: __dirname + "/sample-with-arg.py"
193 | });
194 |
195 | pyNode.onInput({payload: "firstArg secondArg"}, function(result){
196 | assert.notEqual(result.payload, null);
197 | assert.equal(result.payload, 'firstArg secondArg');
198 | done()
199 | }, function(err){
200 | done(err)
201 | });
202 | });
203 |
204 | it('should support file read', function(done) {
205 | let pyNode = new PythonshellNode({
206 | pyfile: __dirname + "/sample-file-read.py"
207 | });
208 |
209 | pyNode.onInput({payload: ""}, function(result){
210 | assert.notEqual(result.payload, null);
211 | assert.equal(result.payload, fs.readFileSync(__dirname + '/test.txt', 'utf8'));
212 | done()
213 | }, function(err){
214 | done(err)
215 | });
216 | });
217 |
218 | it('should support virtual env', function(done) {
219 | let pyNode = new PythonshellNode({
220 | pyfile: __dirname + "/sample-need-venv.py",
221 | virtualenv: __dirname + venv
222 | });
223 |
224 | pyNode.onInput({payload: ""}, function(result){
225 | assert.notEqual(result.payload, null);
226 | assert.equal(result.payload, 'hi from venv');
227 | done()
228 | }, function(err){
229 | done(err)
230 | });
231 | });
232 |
233 | it('should support python3', function(done) {
234 | let pyNode = new PythonshellNode({
235 | pyfile: __dirname + "/sample-python3.py",
236 | python3: true
237 | });
238 | pyNode.onInput({payload: ""}, function(result){
239 | assert.notEqual(result.payload, null);
240 | assert.equal(result.payload, '0 1 2 3 4 5 6 7 8 9');
241 | done()
242 | }, function(err){
243 | done(err)
244 | });
245 | });
246 |
247 | it('should support virtual env and file read', function(done) {
248 | let pyNode = new PythonshellNode({
249 | pyfile: __dirname + "/sample-python3.py",
250 | virtualenv: __dirname + venv,
251 | python3: true
252 | });
253 | pyNode.onInput({payload: ""}, function(result){
254 | assert.notEqual(result.payload, null);
255 | assert.equal(result.payload, '0 1 2 3 4 5 6 7 8 9');
256 | done()
257 | }, function(err){
258 | done(err)
259 | });
260 | });
261 |
262 | it('should support virtual env and file read', function(done) {
263 | let pyNode = new PythonshellNode({
264 | pyfile: __dirname + "/sample-need-venv-file-read.py",
265 | virtualenv: __dirname + venv
266 | });
267 |
268 | pyNode.onInput({payload: ""}, function(result){
269 | assert.notEqual(result.payload, null);
270 | assert.equal(result.payload, fs.readFileSync(__dirname + '/test.txt', 'utf8'));
271 | done()
272 | }, function(err){
273 | done(err)
274 | });
275 | });
276 | });
277 |
278 | describe('piping using unix socket', () => {
279 |
280 | it.skip('pipe', function(done) {
281 | let client
282 | let spawnCmd = 'python'//__dirname + '/' + venv + '/bin/' + 'python'
283 | let py1File = __dirname + "/sample-loop.py"
284 | let py2File = __dirname + "/stdin-data.py"
285 |
286 | let py1 = spawn(spawnCmd, ['-u', py1File])
287 | let py2 = spawn(spawnCmd, ['-u', py2File])
288 |
289 | py2.stdout.pipe(process.stdout)
290 |
291 | py1.stdout.on('data', d => {
292 | if (client)
293 | client.write(d)
294 | })
295 |
296 | let pipeServer = net.createServer(stream => {
297 | stream.on('data', d => {
298 | py2.stdin.write(d)
299 | })
300 | })
301 | pipeServer.listen('./abc')
302 |
303 | client = net.connect('./abc', console.log)
304 | })
305 |
306 | it.skip('work stdin-data', function(done) {
307 | this.timeout(10000);
308 |
309 | let spawnCmd = __dirname + '/' + venv + '/bin/' + 'python'
310 | let stdinDataFile = __dirname + "/stdin-data.py"
311 |
312 | let child = spawn(spawnCmd, ['-u', stdinDataFile])
313 |
314 | setInterval(()=>{
315 | child.stdin.write("abc\n")
316 | },1000)
317 |
318 | child.stdout.pipe(process.stdout);
319 | });
320 |
321 | it('send data to python script stdin', function(done) {
322 | // TODO: here test just one input
323 |
324 | let pyNode = new PythonshellNode({
325 | pyfile: __dirname + "/stdin-data.py",
326 | stdInData: true
327 | });
328 |
329 | pyNode.onInput({payload: "abc\n"}, function(result){
330 | assert.equal(result.payload.trim(), "abc");
331 | done()
332 | }, function(err){
333 | done(err)
334 | });
335 |
336 | setTimeout(()=>{
337 | pyNode.onClose()
338 | }, 1000)
339 | });
340 | })
341 | });
--------------------------------------------------------------------------------
/test/sample-file-read.py:
--------------------------------------------------------------------------------
1 | f = open("test.txt", "r")
2 | print(f.read())
3 |
--------------------------------------------------------------------------------
/test/sample-loop.py:
--------------------------------------------------------------------------------
1 | import time
2 | import sys
3 |
4 | start = time.time()
5 |
6 | i = 0
7 | while True:
8 | now = time.time()
9 | lapsed = now - start
10 | if lapsed > 4:
11 | print( "loop ended")
12 | break
13 | else:
14 | print("on going")
15 | i += 1
16 | time.sleep(1)
17 |
--------------------------------------------------------------------------------
/test/sample-need-venv-file-read.py:
--------------------------------------------------------------------------------
1 | import lxml
2 | f = open("test.txt", "r")
3 | print(f.read())
4 |
--------------------------------------------------------------------------------
/test/sample-need-venv.py:
--------------------------------------------------------------------------------
1 | import lxml
2 |
3 | print 'hi from venv'
--------------------------------------------------------------------------------
/test/sample-python3.py:
--------------------------------------------------------------------------------
1 | # test by running an operation that does not work on python2
2 | a, b, *rest = range(10)
3 | print(a,b,*rest)
--------------------------------------------------------------------------------
/test/sample-with-arg.py:
--------------------------------------------------------------------------------
1 | import sys
2 | print sys.argv[1]
--------------------------------------------------------------------------------
/test/sample.py:
--------------------------------------------------------------------------------
1 | print 'hi'
--------------------------------------------------------------------------------
/test/stdin-data.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | while True:
4 | line = sys.stdin.readline()
5 | print line
--------------------------------------------------------------------------------
/test/test.txt:
--------------------------------------------------------------------------------
1 | python file read content
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | balanced-match@^1.0.0:
6 | version "1.0.0"
7 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
8 |
9 | brace-expansion@^1.1.7:
10 | version "1.1.11"
11 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
12 | dependencies:
13 | balanced-match "^1.0.0"
14 | concat-map "0.0.1"
15 |
16 | browser-stdout@1.3.1:
17 | version "1.3.1"
18 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
19 |
20 | commander@2.11.0:
21 | version "2.11.0"
22 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
23 |
24 | concat-map@0.0.1:
25 | version "0.0.1"
26 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
27 |
28 | debug@3.1.0:
29 | version "3.1.0"
30 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
31 | dependencies:
32 | ms "2.0.0"
33 |
34 | diff@3.5.0:
35 | version "3.5.0"
36 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
37 |
38 | escape-string-regexp@1.0.5:
39 | version "1.0.5"
40 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
41 |
42 | fs.realpath@^1.0.0:
43 | version "1.0.0"
44 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
45 |
46 | glob@7.1.2:
47 | version "7.1.2"
48 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
49 | dependencies:
50 | fs.realpath "^1.0.0"
51 | inflight "^1.0.4"
52 | inherits "2"
53 | minimatch "^3.0.4"
54 | once "^1.3.0"
55 | path-is-absolute "^1.0.0"
56 |
57 | growl@1.10.3:
58 | version "1.10.3"
59 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
60 |
61 | has-flag@^2.0.0:
62 | version "2.0.0"
63 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
64 |
65 | he@1.1.1:
66 | version "1.1.1"
67 | resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
68 |
69 | inflight@^1.0.4:
70 | version "1.0.6"
71 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
72 | dependencies:
73 | once "^1.3.0"
74 | wrappy "1"
75 |
76 | inherits@2:
77 | version "2.0.3"
78 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
79 |
80 | minimatch@^3.0.4:
81 | version "3.0.4"
82 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
83 | dependencies:
84 | brace-expansion "^1.1.7"
85 |
86 | minimist@0.0.8:
87 | version "0.0.8"
88 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
89 |
90 | mkdirp@0.5.1:
91 | version "0.5.1"
92 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
93 | dependencies:
94 | minimist "0.0.8"
95 |
96 | mocha@^5.0.4:
97 | version "5.0.4"
98 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.4.tgz#6b7aa328472da1088e69d47e75925fd3a3bb63c6"
99 | dependencies:
100 | browser-stdout "1.3.1"
101 | commander "2.11.0"
102 | debug "3.1.0"
103 | diff "3.5.0"
104 | escape-string-regexp "1.0.5"
105 | glob "7.1.2"
106 | growl "1.10.3"
107 | he "1.1.1"
108 | mkdirp "0.5.1"
109 | supports-color "4.4.0"
110 |
111 | ms@2.0.0:
112 | version "2.0.0"
113 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
114 |
115 | once@^1.3.0:
116 | version "1.4.0"
117 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
118 | dependencies:
119 | wrappy "1"
120 |
121 | path-is-absolute@^1.0.0:
122 | version "1.0.1"
123 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
124 |
125 | supports-color@4.4.0:
126 | version "4.4.0"
127 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
128 | dependencies:
129 | has-flag "^2.0.0"
130 |
131 | wrappy@1:
132 | version "1.0.2"
133 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
134 |
--------------------------------------------------------------------------------