Skip to content

Commit 304da70

Browse files
authored
Merge pull request #282 from extrabacon/test_pr
v5 release
2 parents 56f8d1f + 91f1097 commit 304da70

File tree

6 files changed

+79
-91
lines changed

6 files changed

+79
-91
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## [5.0.0] - 2023-02-10
2+
### BREAKING CHANGES
3+
- run and runString now return a promise instead of a using a callback.
4+
- You will need to 1) check the return value, 2) remove the callback argument, and 3) change to a promise
5+
- see readme for usage examples
6+
7+
### Other notes
8+
- I confirmed that python-shell works with python 3.11 and node v18.
9+
110
## [4.0.0] - 2023-02-10
211
### Changed
312
- run and runString now return a promise instead of a using a callback.

README.md

+11-18
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ npm install python-shell
2727
```typescript
2828
import {PythonShell} from 'python-shell';
2929

30-
PythonShell.runString('x=1+1;print(x)', null, function (err) {
31-
if (err) throw err;
30+
PythonShell.runString('x=1+1;print(x)', null).then(messages=>{
3231
console.log('finished');
3332
});
3433
```
@@ -47,8 +46,7 @@ let {PythonShell} = require('python-shell')
4746
```typescript
4847
import {PythonShell} from 'python-shell';
4948

50-
PythonShell.run('my_script.py', null, function (err) {
51-
if (err) throw err;
49+
PythonShell.run('my_script.py', null).then(messages=>{
5250
console.log('finished');
5351
});
5452
```
@@ -68,8 +66,7 @@ let options = {
6866
args: ['value1', 'value2', 'value3']
6967
};
7068

71-
PythonShell.run('my_script.py', options, function (err, results) {
72-
if (err) throw err;
69+
PythonShell.run('my_script.py', options).then(messages=>{
7370
// results is an array consisting of messages collected during execution
7471
console.log('results: %j', results);
7572
});
@@ -205,32 +202,28 @@ Example:
205202
PythonShell.defaultOptions = { scriptPath: '../scripts' };
206203
```
207204

208-
#### `#run(script, options, callback)`
209-
210-
Runs the Python script and invokes `callback` with the results. The callback contains the execution error (if any) as well as an array of messages emitted from the Python script.
205+
#### `#run(script, options)`
211206

212-
This method is also returning the `PythonShell` instance.
207+
Runs 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.
213208

214209
Example:
215210

216211
```typescript
217212
// run a simple script
218-
PythonShell.run('script.py', null, function (err, results) {
213+
PythonShell.run('script.py', null).then(results => {
219214
// script finished
220215
});
221216
```
222217

223-
#### `#runString(code, options, callback)`
218+
#### `#runString(code, options)`
224219

225-
Runs the Python code and invokes `callback` with the results. The callback contains the execution error (if any) as well as an array of messages emitted from the Python script.
226-
227-
This method is also returning the `PythonShell` instance.
220+
Runs 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.
228221

229222
Example:
230223

231224
```typescript
232-
// run a simple script
233-
PythonShell.runString('x=1;print(x)', null, function (err, results) {
225+
// run some simple code
226+
PythonShell.runString('x=1;print(x)', null).then(messages=>{
234227
// script finished
235228
});
236229
```
@@ -247,7 +240,7 @@ Promise is rejected if there is a syntax error.
247240

248241
#### `#getVersion(pythonPath?:string)`
249242

250-
Returns the python version. Optional pythonPath param to get the version
243+
Returns the python version as a promise. Optional pythonPath param to get the version
251244
of a specific python interpreter.
252245

253246
#### `#getVersionSync(pythonPath?:string)`

index.ts

+27-41
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ export class PythonShellError extends Error {
6767
exitCode?: number;
6868
}
6969

70+
export class PythonShellErrorWithLogs extends PythonShellError {
71+
logs: any[]
72+
}
73+
7074
/**
7175
* Takes in a string stream and emits batches seperated by newlines
7276
*/
@@ -306,62 +310,44 @@ export class PythonShell extends EventEmitter {
306310
}
307311

308312
/**
309-
* Runs a Python script and returns collected messages
310-
* @param {string} scriptPath The path to the script to execute
311-
* @param {Options} options The execution options
312-
* @param {Function} (deprecated argument) callback The callback function to invoke with the script results
313-
* @return {Promise<string[]> | PythonShell} the output from the python script
313+
* Runs a Python script and returns collected messages as a promise.
314+
* If the promise is rejected, the err will probably be of type PythonShellErrorWithLogs
315+
* @param scriptPath The path to the script to execute
316+
* @param options The execution options
314317
*/
315-
static run(scriptPath: string, options?: Options, callback?: (err?: PythonShellError, output?: any[]) => any) {
316-
317-
if(callback) {
318-
console.warn('PythonShell.run() callback is deprecated. Use PythonShell.run() promise instead.')
319-
320-
return this.runLegacy(scriptPath, options, callback);
321-
}
322-
else {
323-
return new Promise((resolve, reject) => {
324-
let pyshell = new PythonShell(scriptPath, options);
325-
let output = [];
326-
327-
pyshell.on('message', function (message) {
328-
output.push(message);
329-
}).end(function (err) {
330-
if(err) reject(err);
331-
else resolve(output);
332-
});
318+
static run(scriptPath: string, options?: Options): Promise<any[]> {
319+
return new Promise((resolve, reject) => {
320+
let pyshell = new PythonShell(scriptPath, options);
321+
let output = [];
322+
323+
pyshell.on('message', function (message) {
324+
output.push(message);
325+
}).end(function (err) {
326+
if(err){
327+
(err as PythonShellErrorWithLogs).logs = output
328+
reject(err);
329+
}
330+
else resolve(output);
333331
});
334-
}
335-
};
336-
337-
private static runLegacy(scriptPath: string, options?: Options, callback?: (err?: PythonShellError, output?: any[]) => any) {
338-
let pyshell = new PythonShell(scriptPath, options);
339-
let output = [];
340-
341-
return pyshell.on('message', function (message) {
342-
output.push(message);
343-
}).end(function (err) {
344-
return callback(err ? err : null, output.length ? output : null);
345332
});
346333
};
347334

348335

349336

350337
/**
351-
* Runs the inputted string of python code and returns collected messages. DO NOT ALLOW UNTRUSTED USER INPUT HERE!
352-
* @param {string} code The python code to execute
353-
* @param {Options} options The execution options
354-
* @param {Function} callback The callback function to invoke with the script results
355-
* @return {PythonShell} The PythonShell instance
338+
* Runs the inputted string of python code and returns collected messages as a promise. DO NOT ALLOW UNTRUSTED USER INPUT HERE!
339+
* @param code The python code to execute
340+
* @param options The execution options
341+
* @return a promise with the output from the python script
356342
*/
357-
static runString(code: string, options?: Options, callback?: (err: PythonShellError, output?: any[]) => any) {
343+
static runString(code: string, options?: Options) {
358344

359345
// put code in temp file
360346
const randomInt = getRandomInt();
361347
const filePath = tmpdir + sep + `pythonShellFile${randomInt}.py`
362348
writeFileSync(filePath, code);
363349

364-
return PythonShell.run(filePath, options, callback);
350+
return PythonShell.run(filePath, options);
365351
};
366352

367353
static getVersion(pythonPath?: string) {

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"name": "python-shell",
3-
"version": "4.0.0",
3+
"version": "5.0.0",
44
"description": "Run Python scripts from Node.js with simple (but efficient) inter-process communication through stdio",
55
"keywords": [
66
"python"
77
],
88
"scripts": {
99
"test": "tsc -p ./ && mocha -r ts-node/register",
1010
"appveyorTest": "tsc -p ./ && nyc mocha --reporter mocha-appveyor-reporter test/*.js",
11-
"compile": "tsc -watch -p ./"
11+
"compile": "tsc -watch -p ./",
12+
"compileOnce": "tsc -p ./"
1213
},
1314
"devDependencies": {
1415
"@types/mocha": "^8.2.1",

test/test-python-shell.ts

+27-28
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,23 @@ describe('PythonShell', function () {
117117
before(() => {
118118
PythonShell.defaultOptions = {};
119119
})
120-
it('should be able to execute a string of python code using callbacks', function (done) {
121-
let pythonshell = PythonShell.runString('print("hello");print("world")', null, function (err, results) {
122-
if (err) return done(err);
120+
it('should be able to execute a string of python code', function (done) {
121+
PythonShell.runString('print("hello");print("world")', null).then((results) => {
123122
results.should.be.an.Array().and.have.lengthOf(2);
124123
results.should.eql(['hello', 'world']);
125124
done();
126125
});
127-
128-
pythonshell.should.be.an.instanceOf(PythonShell);
129126
});
130127
it('should be able to execute a string of python code using promises', async function () {
131128
let results = await PythonShell.runString('print("hello");print("world")');
132129
results.should.be.an.Array().and.have.lengthOf(2);
133130
results.should.eql(['hello', 'world']);
134131
});
132+
it('should be able to execute a string of python code async', async function () {
133+
let results = await PythonShell.runString('print("hello");print("world")');
134+
results.should.be.an.Array().and.have.lengthOf(2);
135+
results.should.eql(['hello', 'world']);
136+
});
135137
after(() => {
136138
PythonShell.defaultOptions = {
137139
// reset to match initial value
@@ -144,28 +146,27 @@ describe('PythonShell', function () {
144146
it('should run the script and return output data using callbacks', function (done) {
145147
PythonShell.run('echo_args.py', {
146148
args: ['hello', 'world']
147-
}, function (err, results) {
148-
if (err) return done(err);
149+
}).then((results) => {
149150
results.should.be.an.Array().and.have.lengthOf(2);
150151
results.should.eql(['hello', 'world']);
151152
done();
152153
});
153154
});
154-
it('should run the script and return output data using promise', async function () {
155+
it('should run the script and return output data async', async function () {
155156
let results = await PythonShell.run('echo_args.py', {
156157
args: ['hello', 'world']
157158
});
158159
results.should.be.an.Array().and.have.lengthOf(2);
159160
results.should.eql(['hello', 'world']);
160161
});
161162
it('should try to run the script and fail appropriately', function (done) {
162-
PythonShell.run('unknown_script.py', null, function (err, results) {
163+
PythonShell.run('unknown_script.py', null).catch((err) => {
163164
err.should.be.an.Error;
164165
err.exitCode.should.be.exactly(2);
165166
done();
166167
});
167168
});
168-
it('should try to run the script and fail appropriately', async function () {
169+
it('should try to run the script and fail appropriately - async', async function () {
169170
try {
170171
let results = await PythonShell.run('unknown_script.py');
171172
throw new Error(`should not get here because the script should fail` + results);
@@ -175,22 +176,24 @@ describe('PythonShell', function () {
175176
}
176177
});
177178
it('should include both output and error', function (done) {
178-
PythonShell.run('echo_hi_then_error.py', null, function (err, results) {
179-
err.should.be.an.Error;
180-
results.should.eql(['hi'])
181-
done();
179+
PythonShell.run('echo_hi_then_error.py', null).then((results) => {
180+
done("Error: This promise should never successfully resolve");
181+
}).catch((err)=>{
182+
err.logs.should.eql(['hi'])
183+
err.should.be.an.Error
184+
done()
182185
});
183186
});
184187
it('should run the script and fail with an extended stack trace', function (done) {
185-
PythonShell.run('error.py', null, function (err, results) {
188+
PythonShell.run('error.py', null).catch((err) => {
186189
err.should.be.an.Error;
187190
err.exitCode.should.be.exactly(1);
188191
err.stack.should.containEql('----- Python Traceback -----');
189192
done();
190193
});
191194
});
192195
it('should run the script and fail with an extended stack trace even when mode is binary', function (done) {
193-
PythonShell.run('error.py', { mode: "binary" }, function (err, results) {
196+
PythonShell.run('error.py', { mode: "binary" }).catch((err) => {
194197
err.should.be.an.Error;
195198
err.exitCode.should.be.exactly(1);
196199
err.stack.should.containEql('----- Python Traceback -----');
@@ -210,7 +213,7 @@ describe('PythonShell', function () {
210213
}
211214
}
212215
function runSingleErrorScript(callback) {
213-
PythonShell.run('error.py', null, function (err, results) {
216+
PythonShell.run('error.py', null).catch((err) => {
214217
err.should.be.an.Error;
215218
err.exitCode.should.be.exactly(1);
216219
err.stack.should.containEql('----- Python Traceback -----');
@@ -234,8 +237,7 @@ describe('PythonShell', function () {
234237
function runSingleScript(callback) {
235238
PythonShell.run('echo_args.py', {
236239
args: ['hello', 'world']
237-
}, function (err, results) {
238-
if (err) return done(err);
240+
}).then((results)=> {
239241
results.should.be.an.Array().and.have.lengthOf(2);
240242
results.should.eql(['hello', 'world']);
241243
callback();
@@ -249,14 +251,11 @@ describe('PythonShell', function () {
249251

250252
PythonShell.run('-m', {
251253
args: ['timeit', '-n 1', `'x=5'`]
252-
}, function (err, results) {
253-
254+
}).then((results)=> {
254255
PythonShell.defaultOptions = {
255256
// reset to match initial value
256257
scriptPath: pythonFolder
257258
};
258-
259-
if (err) return done(err);
260259
results.should.be.an.Array();
261260
results[0].should.be.an.String();
262261
results[0].slice(0, 6).should.eql('1 loop');
@@ -522,17 +521,17 @@ describe('PythonShell', function () {
522521
let pyshell = new PythonShell('error.py');
523522
pyshell.on('pythonError', function (err) {
524523
err.stack.should.containEql('----- Python Traceback -----');
525-
err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 4');
526-
err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 6');
524+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 4');
525+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 6');
527526
done();
528527
});
529528
});
530529
it('should work in json mode', function (done) {
531530
let pyshell = new PythonShell('error.py', { mode: 'json' });
532531
pyshell.on('pythonError', function (err) {
533532
err.stack.should.containEql('----- Python Traceback -----');
534-
err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 4');
535-
err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 6');
533+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 4');
534+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 6');
536535
done();
537536
});
538537
});
@@ -563,4 +562,4 @@ describe('PythonShell', function () {
563562
}, 500);
564563
});
565564
});
566-
});
565+
});

0 commit comments

Comments
 (0)