Skip to content

Commit bea01af

Browse files
committed
remove callbacks
1 parent 56f8d1f commit bea01af

File tree

4 files changed

+73
-85
lines changed

4 files changed

+73
-85
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
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

Lines changed: 10 additions & 18 deletions
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
});
@@ -91,7 +88,6 @@ pyshell.on('message', function (message) {
9188

9289
// end the input stream and allow the process to exit
9390
pyshell.end(function (err,code,signal) {
94-
if (err) throw err;
9591
console.log('The exit code was: ' + code);
9692
console.log('The exit signal was: ' + signal);
9793
console.log('finished');
@@ -205,32 +201,28 @@ Example:
205201
PythonShell.defaultOptions = { scriptPath: '../scripts' };
206202
```
207203

208-
#### `#run(script, options, callback)`
204+
#### `#run(script, options)`
209205

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.
211-
212-
This method is also returning the `PythonShell` instance.
206+
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.
213207

214208
Example:
215209

216210
```typescript
217211
// run a simple script
218-
PythonShell.run('script.py', null, function (err, results) {
212+
PythonShell.run('script.py', null).then(results => {
219213
// script finished
220214
});
221215
```
222216

223217
#### `#runString(code, options, callback)`
224218

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.
219+
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.
228220

229221
Example:
230222

231223
```typescript
232-
// run a simple script
233-
PythonShell.runString('x=1;print(x)', null, function (err, results) {
224+
// run some simple code
225+
PythonShell.runString('x=1;print(x)', null).then(messages=>{
234226
// script finished
235227
});
236228
```
@@ -247,7 +239,7 @@ Promise is rejected if there is a syntax error.
247239

248240
#### `#getVersion(pythonPath?:string)`
249241

250-
Returns the python version. Optional pythonPath param to get the version
242+
Returns the python version as a promise. Optional pythonPath param to get the version
251243
of a specific python interpreter.
252244

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

index.ts

Lines changed: 27 additions & 41 deletions
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+
* @param scriptPath The path to the script to execute
315+
* @param options The execution options
316+
* @return a promise with the output from the python script
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) {
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) {

test/test-python-shell.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ 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();
@@ -132,6 +131,11 @@ describe('PythonShell', function () {
132131
results.should.be.an.Array().and.have.lengthOf(2);
133132
results.should.eql(['hello', 'world']);
134133
});
134+
it('should be able to execute a string of python code async', async function () {
135+
let results = await PythonShell.runString('print("hello");print("world")');
136+
results.should.be.an.Array().and.have.lengthOf(2);
137+
results.should.eql(['hello', 'world']);
138+
});
135139
after(() => {
136140
PythonShell.defaultOptions = {
137141
// reset to match initial value
@@ -144,28 +148,27 @@ describe('PythonShell', function () {
144148
it('should run the script and return output data using callbacks', function (done) {
145149
PythonShell.run('echo_args.py', {
146150
args: ['hello', 'world']
147-
}, function (err, results) {
148-
if (err) return done(err);
151+
}).then((results) => {
149152
results.should.be.an.Array().and.have.lengthOf(2);
150153
results.should.eql(['hello', 'world']);
151154
done();
152155
});
153156
});
154-
it('should run the script and return output data using promise', async function () {
157+
it('should run the script and return output data async', async function () {
155158
let results = await PythonShell.run('echo_args.py', {
156159
args: ['hello', 'world']
157160
});
158161
results.should.be.an.Array().and.have.lengthOf(2);
159162
results.should.eql(['hello', 'world']);
160163
});
161164
it('should try to run the script and fail appropriately', function (done) {
162-
PythonShell.run('unknown_script.py', null, function (err, results) {
165+
PythonShell.run('unknown_script.py', null).catch((err) => {
163166
err.should.be.an.Error;
164167
err.exitCode.should.be.exactly(2);
165168
done();
166169
});
167170
});
168-
it('should try to run the script and fail appropriately', async function () {
171+
it('should try to run the script and fail appropriately - async', async function () {
169172
try {
170173
let results = await PythonShell.run('unknown_script.py');
171174
throw new Error(`should not get here because the script should fail` + results);
@@ -175,22 +178,24 @@ describe('PythonShell', function () {
175178
}
176179
});
177180
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();
181+
PythonShell.run('echo_hi_then_error.py', null).then((results) => {
182+
done("Error: This promise should never successfully resolve");
183+
}).catch((err)=>{
184+
err.logs.should.eql(['hi'])
185+
err.should.be.an.Error
186+
done()
182187
});
183188
});
184189
it('should run the script and fail with an extended stack trace', function (done) {
185-
PythonShell.run('error.py', null, function (err, results) {
190+
PythonShell.run('error.py', null).catch((err) => {
186191
err.should.be.an.Error;
187192
err.exitCode.should.be.exactly(1);
188193
err.stack.should.containEql('----- Python Traceback -----');
189194
done();
190195
});
191196
});
192197
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) {
198+
PythonShell.run('error.py', { mode: "binary" }).catch((err) => {
194199
err.should.be.an.Error;
195200
err.exitCode.should.be.exactly(1);
196201
err.stack.should.containEql('----- Python Traceback -----');
@@ -210,7 +215,7 @@ describe('PythonShell', function () {
210215
}
211216
}
212217
function runSingleErrorScript(callback) {
213-
PythonShell.run('error.py', null, function (err, results) {
218+
PythonShell.run('error.py', null).catch((err) => {
214219
err.should.be.an.Error;
215220
err.exitCode.should.be.exactly(1);
216221
err.stack.should.containEql('----- Python Traceback -----');
@@ -234,8 +239,7 @@ describe('PythonShell', function () {
234239
function runSingleScript(callback) {
235240
PythonShell.run('echo_args.py', {
236241
args: ['hello', 'world']
237-
}, function (err, results) {
238-
if (err) return done(err);
242+
}).then((results)=> {
239243
results.should.be.an.Array().and.have.lengthOf(2);
240244
results.should.eql(['hello', 'world']);
241245
callback();
@@ -249,14 +253,11 @@ describe('PythonShell', function () {
249253

250254
PythonShell.run('-m', {
251255
args: ['timeit', '-n 1', `'x=5'`]
252-
}, function (err, results) {
253-
256+
}).then((results)=> {
254257
PythonShell.defaultOptions = {
255258
// reset to match initial value
256259
scriptPath: pythonFolder
257260
};
258-
259-
if (err) return done(err);
260261
results.should.be.an.Array();
261262
results[0].should.be.an.String();
262263
results[0].slice(0, 6).should.eql('1 loop');
@@ -522,17 +523,17 @@ describe('PythonShell', function () {
522523
let pyshell = new PythonShell('error.py');
523524
pyshell.on('pythonError', function (err) {
524525
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');
526+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 4');
527+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 6');
527528
done();
528529
});
529530
});
530531
it('should work in json mode', function (done) {
531532
let pyshell = new PythonShell('error.py', { mode: 'json' });
532533
pyshell.on('pythonError', function (err) {
533534
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');
535+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 4');
536+
err.stack.should.containEql('test' + sep + 'python' + sep + 'error.py", line 6');
536537
done();
537538
});
538539
});
@@ -563,4 +564,4 @@ describe('PythonShell', function () {
563564
}, 500);
564565
});
565566
});
566-
});
567+
});

0 commit comments

Comments
 (0)