Skip to content

Commit

Permalink
Added pythonBin option to set python executable, defaulting to curr…
Browse files Browse the repository at this point in the history
…ent runtime version
  • Loading branch information
logandk committed Sep 29, 2017
1 parent c9fc82f commit 2fdcbcd
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 21 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Package requirements into service root directory in order to avoid munging
sys.path to load requirements (#30).
* Package requirements when deploying individual non-WSGI functions (#30).
* Added `pythonBin` option to set python executable, defaulting to current runtime version (#29).


# 1.3.1
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ custom:
For a more advanced approach to packaging requirements, consider using https://github.com/UnitedIncome/serverless-python-requirements.
### Python version
Python is used for packaging requirements and serving the app when invoking `sls wsgi serve`. By
default, the current runtime setting is expected to be the name of the Python binary in `PATH`,
for instance `python3.6`. If this is not the name of your Python binary, override it using the
`pythonBin` option:

```yaml
custom:
wsgi:
app: api.app
pythonBin: python3
```

### Local server

For convenience, a `sls wsgi serve` command is provided to run your WSGI application
Expand Down
7 changes: 5 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ BbPromise.promisifyAll(fse);
class ServerlessWSGI {
validate() {
this.enableRequirements = true;
this.pythonBin = this.serverless.service.provider.runtime;

if (this.serverless.service.custom && this.serverless.service.custom.wsgi) {
this.pythonBin = this.serverless.service.custom.wsgi.pythonBin || this.pythonBin;

if (this.serverless.service.custom.wsgi.app) {
this.wsgiApp = this.serverless.service.custom.wsgi.app;
this.appPath = path.dirname(path.join(this.serverless.config.servicePath, this.wsgiApp));
Expand Down Expand Up @@ -83,7 +86,7 @@ class ServerlessWSGI {
this.serverless.cli.log('Packaging required Python packages...');

return new BbPromise((resolve, reject) => {
const res = child_process.spawnSync('python', args);
const res = child_process.spawnSync(this.pythonBin, args);
if (res.error) {
return reject(res.error);
}
Expand Down Expand Up @@ -179,7 +182,7 @@ class ServerlessWSGI {
const port = this.options.port || 5000;

return new BbPromise((resolve, reject) => {
var status = child_process.spawnSync('python', [
var status = child_process.spawnSync(this.pythonBin, [
path.resolve(__dirname, 'serve.py'),
this.serverless.config.servicePath,
this.wsgiApp,
Expand Down
88 changes: 69 additions & 19 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('serverless-wsgi', () => {
it('skips packaging for non-wsgi app', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -54,6 +54,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } }
},
classes: { Error: Error },
Expand All @@ -73,7 +74,7 @@ describe('serverless-wsgi', () => {
'/tmp/.wsgi_app', 'api.app'
)).to.be.true;
expect(procStub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'requirements.py'),
path.resolve(__dirname, 'requirements.txt'),
Expand All @@ -90,6 +91,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } }
},
classes: { Error: Error },
Expand All @@ -112,6 +114,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } },
package: { include: ['sample.txt'] }
},
Expand All @@ -131,7 +134,45 @@ describe('serverless-wsgi', () => {
expect(writeStub.called).to.be.true;
expect(symlinkStub.called).to.be.true;
expect(procStub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'requirements.py'),
path.resolve(__dirname, 'requirements.txt'),
'/tmp/requirements.txt',
'/tmp/.requirements'
]
)).to.be.true;
expect(plugin.serverless.service.package.include).to.have.members(['sample.txt', 'wsgi.py', '.wsgi_app', 'flask', 'flask/**']);
expect(plugin.serverless.service.package.exclude).to.have.members(['.requirements/**']);
sandbox.restore();
});
});

it('allows setting the python binary', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app', pythonBin: 'my-python' } },
package: { include: ['sample.txt'] }
},
classes: { Error: Error },
cli: { log: () => {} }
}, {});

var sandbox = sinon.sandbox.create();
var copyStub = sandbox.stub(fse, 'copyAsync');
var writeStub = sandbox.stub(fse, 'writeFileAsync');
var symlinkStub = sandbox.stub(fse, 'symlinkSync');
sandbox.stub(fse, 'readdirSync').returns(['flask']);
sandbox.stub(fse, 'existsSync').returns(true);
var procStub = sandbox.stub(child_process, 'spawnSync').returns({ status: 0 });
plugin.hooks['before:deploy:createDeploymentArtifacts']().then(() => {
expect(copyStub.called).to.be.true;
expect(writeStub.called).to.be.true;
expect(symlinkStub.called).to.be.true;
expect(procStub.calledWith(
'my-python',
[
path.resolve(__dirname, 'requirements.py'),
path.resolve(__dirname, 'requirements.txt'),
Expand All @@ -149,6 +190,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api/api.app' } }
},
classes: { Error: Error },
Expand All @@ -165,7 +207,7 @@ describe('serverless-wsgi', () => {
expect(copyStub.called).to.be.true;
expect(writeStub.called).to.be.true;
expect(procStub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'requirements.py'),
path.resolve(__dirname, 'requirements.txt'),
Expand All @@ -180,7 +222,7 @@ describe('serverless-wsgi', () => {
it('throws an error when a file already exists in the service root', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -201,7 +243,7 @@ describe('serverless-wsgi', () => {
it('throws an error when a conflicting symlink already exists in the service root', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -222,7 +264,7 @@ describe('serverless-wsgi', () => {
it('packages user requirements for non-wsgi app', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -239,7 +281,7 @@ describe('serverless-wsgi', () => {
expect(copyStub.called).to.be.false;
expect(writeStub.called).to.be.false;
expect(procStub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'requirements.py'),
'/tmp/requirements.txt',
Expand All @@ -253,7 +295,7 @@ describe('serverless-wsgi', () => {
it('skips packaging for non-wsgi app without user requirements', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -275,7 +317,7 @@ describe('serverless-wsgi', () => {
it('rejects with non successful exit code', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -292,7 +334,7 @@ describe('serverless-wsgi', () => {
it('rejects with stderr output', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {},
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error },
cli: { log: () => {} }
}, {});
Expand All @@ -310,6 +352,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app', packRequirements: false } }
},
classes: { Error: Error },
Expand All @@ -335,6 +378,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app', packRequirements: false } }
},
classes: { Error: Error },
Expand All @@ -361,6 +405,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } },
functions: functions
},
Expand All @@ -387,6 +432,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } },
functions: functions
},
Expand All @@ -409,7 +455,7 @@ describe('serverless-wsgi', () => {
'/tmp/.wsgi_app', 'api.app'
)).to.be.true;
expect(procStub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'requirements.py'),
path.resolve(__dirname, 'requirements.txt'),
Expand All @@ -428,6 +474,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } },
functions: functions
},
Expand Down Expand Up @@ -455,7 +502,7 @@ describe('serverless-wsgi', () => {
it('fails for non-wsgi app', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: { provider: {} },
service: { provider: { runtime: 'python2.7' } },
classes: { Error: Error }
});

Expand All @@ -466,7 +513,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: {},
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } }
},
classes: { Error: Error }
Expand All @@ -475,7 +522,7 @@ describe('serverless-wsgi', () => {
var stub = sinon.stub(child_process, 'spawnSync').returns({});
plugin.hooks['wsgi:serve:serve']().then(() => {
expect(stub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'serve.py'),
'/tmp',
Expand All @@ -492,7 +539,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: {},
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } }
},
classes: { Error: Error }
Expand All @@ -501,7 +548,7 @@ describe('serverless-wsgi', () => {
var stub = sinon.stub(child_process, 'spawnSync').returns({ error: 'Something failed' });
expect(plugin.hooks['wsgi:serve:serve']()).to.eventually.be.rejected.and.notify(() => {
expect(stub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'serve.py'),
'/tmp',
Expand All @@ -518,7 +565,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: {},
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } }
},
classes: { Error: Error }
Expand All @@ -527,7 +574,7 @@ describe('serverless-wsgi', () => {
var stub = sinon.stub(child_process, 'spawnSync').returns({});
plugin.hooks['wsgi:serve:serve']().then(() => {
expect(stub.calledWith(
'python',
'python2.7',
[
path.resolve(__dirname, 'serve.py'),
'/tmp',
Expand All @@ -545,6 +592,7 @@ describe('serverless-wsgi', () => {
config: { servicePath: '/tmp' },
service: {
provider: {
runtime: 'python2.7',
environment: { SOME_ENV_VAR: 42 }
},
functions: {
Expand Down Expand Up @@ -577,6 +625,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app' } },
functions: functions
},
Expand Down Expand Up @@ -606,6 +655,7 @@ describe('serverless-wsgi', () => {
var plugin = new Plugin({
config: { servicePath: '/tmp' },
service: {
provider: { runtime: 'python2.7' },
custom: { wsgi: { app: 'api.app', packRequirements: false } },
functions: functions
},
Expand Down

0 comments on commit 2fdcbcd

Please sign in to comment.