UNPKG

13.8 kBMarkdownView Raw
1# [python-shell](https://www.npmjs.com/package/python-shell) [![Build status](https://ci.appveyor.com/api/projects/status/m8e3h53vvxg5wb2q/branch/master?svg=true)](https://ci.appveyor.com/project/Almenon/python-shell/branch/master) [![codecov](https://codecov.io/gh/extrabacon/python-shell/branch/master/graph/badge.svg)](https://codecov.io/gh/extrabacon/python-shell)
2
3<!-- change above url according to repo -->
4A simple way to run Python scripts from Node.js with basic but efficient inter-process communication and better error handling.
5
6## Features
7
8+ Reliably spawn Python scripts in a child process
9+ Built-in text, JSON and binary modes
10+ Custom parsers and formatters
11+ Simple and efficient data transfers through stdin and stdout streams
12+ Extended stack traces when an error is thrown
13
14## Requirements
15First make sure you are able to run `python3` (Mac/Linux) or `python` (Windows) from the terminal. If you are not then you might need to add it to the PATH. If you want to use a version of python not in the PATH you should specify `options.pythonPath`.
16
17## Installation
18
19```bash
20npm install python-shell
21```
22
23## Documentation
24
25### Running python code:
26
27```typescript
28import {PythonShell} from 'python-shell';
29
30PythonShell.runString('x=1+1;print(x)', null).then(messages=>{
31 console.log('finished');
32});
33```
34
35If the script exits with a non-zero code, an error will be thrown.
36
37Note the use of imports! If you're not using typescript ಠ_ಠ you can [still get imports to work with this guide](https://github.com/extrabacon/python-shell/issues/148#issuecomment-419120209).
38
39Or you can use require like so:
40```javascript
41let {PythonShell} = require('python-shell')
42```
43
44### Running a Python script:
45
46```typescript
47import {PythonShell} from 'python-shell';
48
49PythonShell.run('my_script.py', null).then(messages=>{
50 console.log('finished');
51});
52```
53
54If the script exits with a non-zero code, an error will be thrown.
55
56### Running a Python script with arguments and options:
57
58```typescript
59import {PythonShell} from 'python-shell';
60
61let options = {
62 mode: 'text',
63 pythonPath: 'path/to/python',
64 pythonOptions: ['-u'], // get print results in real-time
65 scriptPath: 'path/to/my/scripts',
66 args: ['value1', 'value2', 'value3']
67};
68
69PythonShell.run('my_script.py', options).then(messages=>{
70 // results is an array consisting of messages collected during execution
71 console.log('results: %j', results);
72});
73```
74
75### Exchanging data between Node and Python:
76
77```typescript
78import {PythonShell} from 'python-shell';
79let pyshell = new PythonShell('my_script.py');
80
81// sends a message to the Python script via stdin
82pyshell.send('hello');
83
84pyshell.on('message', function (message) {
85 // received a message sent from the Python script (a simple "print" statement)
86 console.log(message);
87});
88
89// end the input stream and allow the process to exit
90pyshell.end(function (err,code,signal) {
91 if (err) throw err;
92 console.log('The exit code was: ' + code);
93 console.log('The exit signal was: ' + signal);
94 console.log('finished');
95});
96```
97
98Use `.send(message)` to send a message to the Python script. Attach the `message` event to listen to messages emitted from the Python script.
99
100Use `options.mode` to quickly setup how data is sent and received between your Node and Python applications.
101
102 * use `text` mode for exchanging lines of text ending with a [newline character](http://hayne.net/MacDev/Notes/unixFAQ.html#endOfLine).
103 * use `json` mode for exchanging JSON fragments
104 * use `binary` mode for anything else (data is sent and received as-is)
105
106Stderr always uses text mode.
107
108For more details and examples including Python source code, take a look at the tests.
109
110### Error Handling and extended stack traces
111
112An error will be thrown if the process exits with a non-zero exit code. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace.
113
114Sample error with traceback (from test/python/error.py):
115
116```
117Traceback (most recent call last):
118 File "test/python/error.py", line 6, in <module>
119 divide_by_zero()
120 File "test/python/error.py", line 4, in divide_by_zero
121 print 1/0
122ZeroDivisionError: integer division or modulo by zero
123```
124
125would result into the following error:
126
127```typescript
128{ [Error: ZeroDivisionError: integer division or modulo by zero]
129 traceback: 'Traceback (most recent call last):\n File "test/python/error.py", line 6, in <module>\n divide_by_zero()\n File "test/python/error.py", line 4, in divide_by_zero\n print 1/0\nZeroDivisionError: integer division or modulo by zero\n',
130 executable: 'python',
131 options: null,
132 script: 'test/python/error.py',
133 args: null,
134 exitCode: 1 }
135```
136
137and `err.stack` would look like this:
138
139```
140Error: ZeroDivisionError: integer division or modulo by zero
141 at PythonShell.parseError (python-shell/index.js:131:17)
142 at ChildProcess.<anonymous> (python-shell/index.js:67:28)
143 at ChildProcess.EventEmitter.emit (events.js:98:17)
144 at Process.ChildProcess._handle.onexit (child_process.js:797:12)
145 ----- Python Traceback -----
146 File "test/python/error.py", line 6, in <module>
147 divide_by_zero()
148 File "test/python/error.py", line 4, in divide_by_zero
149 print 1/0
150```
151
152## API Reference
153
154#### `PythonShell(script, options)` constructor
155
156Creates an instance of `PythonShell` and starts the Python process
157
158* `script`: the path of the script to execute
159* `options`: the execution options, consisting of:
160 * `mode`: Configures how data is exchanged when data flows through stdin and stdout. The possible values are:
161 * `text`: each line of data is emitted as a message (default)
162 * `json`: each line of data is parsed as JSON and emitted as a message
163 * `binary`: data is streamed as-is through `stdout` and `stdin`
164 * `formatter`: each message to send is transformed using this method, then appended with a newline
165 * `parser`: each line of data is parsed with this function and its result is emitted as a message
166 * `stderrParser`: each line of logs is parsed with this function and its result is emitted as a message
167 * `encoding`: the text encoding to apply on the child process streams (default: "utf8")
168 * `pythonPath`: The path where to locate the "python" executable. Default: "python3" ("python" for Windows)
169 * `pythonOptions`: Array of option switches to pass to "python"
170 * `scriptPath`: The default path where to look for scripts. Default is the current working directory.
171 * `args`: Array of arguments to pass to the script
172* `stdoutSplitter`: splits stdout into chunks, defaulting to splitting into newline-seperated lines
173* `stderrSplitter`: splits stderr into chunks, defaulting to splitting into newline-seperated lines
174
175Other options are forwarded to `child_process.spawn`.
176
177PythonShell instances have the following properties:
178* `script`: the path of the script to execute
179* `command`: the full command arguments passed to the Python executable
180* `stdin`: the Python stdin stream, used to send data to the child process
181* `stdout`: the Python stdout stream, used for receiving data from the child process
182* `stderr`: the Python stderr stream, used for communicating logs & errors
183* `childProcess`: the process instance created via `child_process.spawn`
184* `terminated`: boolean indicating whether the process has exited
185* `exitCode`: the process exit code, available after the process has ended
186
187Example:
188
189```typescript
190// create a new instance
191let shell = new PythonShell('script.py', options);
192```
193
194#### `#defaultOptions`
195
196Configures default options for all new instances of PythonShell.
197
198Example:
199
200```typescript
201// setup a default "scriptPath"
202PythonShell.defaultOptions = { scriptPath: '../scripts' };
203```
204
205#### `#run(script, options)`
206
207Runs the Python script and returns a promise. When you handle the promise the argument will be an array of messages emitted from the Python script.
208
209Example:
210
211```typescript
212// run a simple script
213PythonShell.run('script.py', null).then(results => {
214 // script finished
215});
216```
217
218#### `#runString(code, options)`
219
220Runs the Python script and returns a promise. When you handle the promise the argument will be an array of messages emitted from the Python script.
221
222Example:
223
224```typescript
225// run some simple code
226PythonShell.runString('x=1;print(x)', null).then(messages=>{
227 // script finished
228});
229```
230
231#### `#checkSyntax(code:string)`
232
233Checks the syntax of the code and returns a promise.
234Promise is rejected if there is a syntax error.
235
236#### `#checkSyntaxFile(filePath:string)`
237
238Checks the syntax of the file and returns a promise.
239Promise is rejected if there is a syntax error.
240
241#### `#getVersion(pythonPath?:string)`
242
243Returns the python version as a promise. Optional pythonPath param to get the version
244of a specific python interpreter.
245
246#### `#getVersionSync(pythonPath?:string)`
247
248Returns the python version. Optional pythonPath param to get the version
249of a specific python interpreter.
250
251#### `.send(message)`
252
253Sends a message to the Python script via stdin. The data is formatted according to the selected mode (text or JSON), or through a custom function when `formatter` is specified.
254
255Example:
256
257```typescript
258// send a message in text mode
259let shell = new PythonShell('script.py', { mode: 'text'});
260shell.send('hello world!');
261
262// send a message in JSON mode
263let shell = new PythonShell('script.py', { mode: 'json'});
264shell.send({ command: "do_stuff", args: [1, 2, 3] });
265```
266
267#### `.end(callback)`
268
269Closes the stdin stream, allowing the Python script to finish and exit. The optional callback is invoked when the process is terminated.
270
271#### `.kill(signal)`
272
273Terminates the python script. A kill signal may be provided by `signal`, if `signal` is not specified SIGTERM is sent.
274
275#### event: `message`
276
277After the stdout stream is split into chunks by stdoutSplitter the chunks are parsed by the parser and a message event is emitted for each parsed chunk. This event is not emitted in binary mode.
278
279Example:
280
281```typescript
282// receive a message in text mode
283let shell = new PythonShell('script.py', { mode: 'text'});
284shell.on('message', function (message) {
285 // handle message (a line of text from stdout)
286});
287
288// receive a message in JSON mode
289let shell = new PythonShell('script.py', { mode: 'json'});
290shell.on('message', function (message) {
291 // handle message (a line of text from stdout, parsed as JSON)
292});
293```
294
295#### event: `stderr`
296
297After the stderr stream is split into chunks by stderrSplitter the chunks are parsed by the parser and a message event is emitted for each parsed chunk. This event is not emitted in binary mode.
298
299Example:
300
301```typescript
302// receive a message in text mode
303let shell = new PythonShell('script.py', { mode: 'text'});
304shell.on('stderr', function (stderr) {
305 // handle stderr (a line of text from stderr)
306});
307```
308
309#### event: `close`
310
311Fires when the process has been terminated, with an error or not.
312
313#### event: `pythonError`
314
315Fires when the process terminates with a non-zero exit code.
316
317#### event: `error`
318
319Fires when:
320* The process could not be spawned, or
321* The process could not be killed, or
322* Sending a message to the child process failed.
323
324If the process could not be spawned please double-check that python can be launched from the terminal.
325
326### NewlineTransformer
327
328A utility class for splitting stream data into newlines. Used as the default for stdoutSplitter and stderrSplitter if they are unspecified. You can use this class for any extra python streams if you'd like. For example:
329
330```python
331# foo.py
332print('hello world', file=open(3, "w"))
333```
334
335```typescript
336import { PythonShell, NewlineTransformer, Options } from 'python-shell'
337
338const options: Options = {
339 'stdio':
340 ['pipe', 'pipe', 'pipe', 'pipe'] // stdin, stdout, stderr, custom
341}
342const pyshell = new PythonShell('foo.py', options)
343
344const customPipe = pyshell.childProcess.stdio[3]
345customPipe.pipe(new NewlineTransformer()).on('data', (customResult: Buffer) => {
346 console.log(customResult.toString())
347})
348```
349
350## Used By:
351
352Python-Shell is used by [arepl-vscode](https://github.com/almenon/arepl-vscode), [gitinspector](https://github.com/ejwa/gitinspector), [pyspreadsheet](https://github.com/extrabacon/pyspreadsheet), [AtlantOS Ocean Data QC](https://github.com/ocean-data-qc/ocean-data-qc) and more!
353
354## License
355
356The MIT License (MIT)
357
358Copyright (c) 2014 Nicolas Mercier
359
360Permission is hereby granted, free of charge, to any person obtaining a copy
361of this software and associated documentation files (the "Software"), to deal
362in the Software without restriction, including without limitation the rights
363to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
364copies of the Software, and to permit persons to whom the Software is
365furnished to do so, subject to the following conditions:
366
367The above copyright notice and this permission notice shall be included in
368all copies or substantial portions of the Software.
369
370THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
371IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
372FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
373AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
374LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
375OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
376THE SOFTWARE.