├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── index.js ├── package-lock.json └── package.json /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12 18 | - run: npm ci 19 | - run: npm test 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v1 27 | with: 28 | node-version: 12 29 | registry-url: https://registry.npmjs.org/ 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Distribution / packaging 2 | .Python 3 | env/ 4 | build/ 5 | develop-eggs/ 6 | dist/ 7 | downloads/ 8 | eggs/ 9 | .eggs/ 10 | lib/ 11 | lib64/ 12 | parts/ 13 | sdist/ 14 | var/ 15 | *.egg-info/ 16 | .installed.cfg 17 | *.egg 18 | *.pyc 19 | # Serverless directories 20 | .serverless/ 21 | # Other directories 22 | .DS_Store 23 | node_modules/ 24 | npm-debug.log 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 4 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 5 | 6 | ## [0.2.1] - 2017-08-01 7 | ### Changed 8 | - Zip-dir to Zip-local to implement synchronous zipping, while I figure out how to async properly 9 | - Fixed ensureImage to properly filter Docker images 10 | 11 | ## [0.2.0] - 2017-07-23 12 | ### Added 13 | - Support for compiling platform-dependent packages using Docker. Even Santa isn't this nice. 14 | 15 | ### Changed 16 | - Included Docker integration instructions in README.md. If you build it and tell them how to use it, they will come. 17 | 18 | 19 | ### Removed 20 | 21 | 22 | ## [0.2.4] - 2017-10-24 23 | ### Added 24 | - Normalized path for building on windows courtesy @wsteimel77 25 | 26 | ## [0.2.5] - 2017-11-11 27 | - Bug fix for #21 courtesy @JFox 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2017, Ubani Balogun 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-package-python-functions 2 | 3 | [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) 4 | [![npm version](https://badge.fury.io/js/serverless-package-python-functions.svg)](https://badge.fury.io/js/serverless-package-python-functions) 5 | 6 | - [Installation](#install) 7 | - [What's it?](#what) 8 | - [Why do I need it?](#why) 9 | - [How does it work?](#how) 10 | 11 | ## Installation 12 | 13 | ``` 14 | $ npm install --save serverless-package-python-functions 15 | ``` 16 | 17 | ``` 18 | # serverless.yml 19 | plugins: 20 | - serverless-package-python-functions 21 | ``` 22 | 23 | ## What is it? 24 | A Serverless Framework plugin for packaging Python Lambda functions with only the dependencies they need. 25 | 26 | ## Why do I need it? 27 | 28 | This plugin makes it easy to manage function-level and service-level dependencies for your awesome Python Serverless project 29 | 30 | Let's consider the following project structure 31 | 32 | ``` 33 | your-awesome-project/ 34 | ├── common_files 35 | │ ├── common1.py 36 | │ └── common2.py 37 | ├── function1 38 | │ ├── lambda.py 39 | │ └── requirements.txt # with simplejson library 40 | ├── function2 41 | │ ├── lambda.py 42 | │ └── requirements.txt 43 | ├── requirements.txt # with requests library 44 | └── serverless.yml 45 | ``` 46 | 47 | This project has: 48 | - two functions, `function1` and `function2`, each with their own `requirements.txt` files. function1's requirements.txt lists the simplejson pip package 49 | - Code common to both `function1` and `function2` in a directory named `common_files` 50 | - A top-level `requirements.txt` file with pip dependencies common to both functions, e.g requests library 51 | 52 | This plugin will package your functions into individual zip files that look like: 53 | 54 | ``` 55 | ├── lambda.py # function-level code 56 | ├── requirements.txt 57 | ├── common1.py # service-level code 58 | ├── common2.py 59 | ├── simplejson # function-level dependencies 60 | ├── simplejson-3.10.0.dist-info 61 | ├── requests # service-level dependencies 62 | └── requests-2.13.0.dist-info 63 | ``` 64 | 65 | So that the below code 66 | ``` 67 | import common1, common2, requests, simplejson 68 | ``` 69 | in function1/lambda.py works like works like a charm! 70 | 71 | The plugin also supports packaging your dependencies using a Docker Image that replicates your cloud providers environment, allowing you easily work with platform-dependent libraries like numpy. 72 | 73 | 74 | ## How does it work? 75 | The plugin handles the creation of the [artifact zip files](https://serverless.com/framework/docs/providers/aws/guide/packaging#artifact) for your Serverless functions. 76 | 77 | When `serverless deploy` is run, the plugin will: 78 | 1. create a build directory for each function 79 | 2. copy the appropriate function-level and service-level code you specify into each function's build directory 80 | 3. Download the appropriate function-level and service-level pip dependencies into each function's build directory 81 | 4. Create zip files of each functions build directory 82 | 83 | The Serverless framework will then pickup each zip file and upload it to your provider. 84 | 85 | Here's a simple `serverless.yml` configuration for this plugin, assuming the project structure above 86 | one of the functions we add `-${opt:stage}` to the name in order to append the stage to the function name 87 | 88 | ``` 89 | service: your-awesome-project 90 | 91 | package: 92 | individually: true 93 | 94 | plugins: 95 | - serverless-package-python-functions 96 | 97 | custom: 98 | pkgPyFuncs: # plugin configuration 99 | buildDir: _build 100 | requirementsFile: 'requirements.txt' 101 | globalRequirements: 102 | - ./requirements.txt 103 | globalIncludes: 104 | - ./common_files 105 | cleanup: true 106 | 107 | functions: 108 | function1: 109 | name: function1-${opt:stage} 110 | handler: lambda.handler 111 | package: 112 | include: 113 | - function1 114 | artifact: ${self:custom.pkgPyFuncs.buildDir}/function1.zip 115 | 116 | function2: 117 | name: function2 118 | handler: lambda.handler 119 | package: 120 | include: 121 | - function2 122 | artifact: ${self:custom.pkgPyFuncs.buildDir}/function2.zip 123 | ``` 124 | 125 | The plugin configurations are simple: 126 | 127 | | Configuration | Description | Optional? | 128 | |--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| 129 | | buildDir | Path to a build directory relative to project root, e.g. build | No | 130 | | requirementsFile | The name of the requirements file used for function-level requirements. All function-level requirements files must use the name specified here. | Yes. Defaults to `requirements.txt` | 131 | | globalRequirements | A list of paths to files containing service-level pip requirements. | Yes | 132 | | globalIncludes | A list of paths to folders containing service-level code files (i.e. code common to all functions). Only the folders contents will be packaged, not the folder itself. Paths to files are not currently supported. | Yes | 133 | | useDocker | Boolean indicating whether to package pip dependencies using Docker. Set this to true if your project uses platform-specific compiled libraries like numpy. Requires a [Docker installation](https://www.docker.com/get-docker). | Yes. Defaults to `false` | 134 | | dockerImage | The Docker image to use to compile functions if `useDocker` is set to `true`. Must be specified as `repository:tag`. If the image doesn't exist on the system, it will be downloaded. The initial download may take some time. | Yes. Defaults to `lambci/lambda:build-${provider.runtime}` | 135 | | containerName | The desired name for the Docker container. | Yes. Defaults to `serverless-package-python-functions` | 136 | | abortOnPackagingErrors | Boolean indicating whether you want to stop deployment when packaging errors are detected. Examples of scenarios that will cause packaging errors include: `useDocker` is enabled but the Docker service is not running, pip finds dependency mismatches, virtual environment errrors, etc.. When an error is detected, this will prompt via commandline to continue or abort deploy. | Yes. Defaults to `false` | 137 | 138 | At the function level, you: 139 | - Specify `name` to give your function a name. The plugin uses the function's name as the name of the zip artifact 140 | - Use `include` to specify what function-level files you want to include in your artifact. Simply specifying the path to the function's folder will include every file in the folder in the function's zip artifact 141 | - Use `artifact` to tell Serverless where to find the zip artifact. The plugin creates the zip artifact for the function at `buildDir`/`name`.zip, so using `${self:custom.pkgPyFuncs.buildDir}/[function-name-here].zip` is advised. 142 | 143 | At the package level, you may need to: 144 | - Specify the `individually` parameter as `true` to ensure that zip artifacts are generated properly. You may need this if you are getting file not found errors about your zip artifact. 145 | 146 | Now, you may be wondering, doesn't the [Serverless documentation say](https://serverless.com/framework/docs/providers/aws/guide/packaging#artifact): 147 | > Serverless won't zip your service if [artifact] is configured and therefore exclude and include will be ignored. Either you use artifact or include / exclude. 148 | 149 | Yes, that is correct and is actually awesome! Since Serverless ignores `include`/`exclude` silently when `artifact` is specified, it allows this plugin take advantage of the `include` property to provide you with a familiar interface for specifying function-level dependencies. So while this plugin uses `include` to determine what goes in your artifact, all Serverless cares about is the artifact that this plugin creates when it executes. 150 | 151 | The last thing that your keen eye may have noticed from the example `serverless.yml` above is that `handler` is specified simply as `lambda.handler` not `${self:custom.pkgPyFuncs.buildDir}/function/lambda.hadler` or `function/lambda.handler`. This is because the plugin zips your artifacts such that /path/to/function is the root of the zip file. Combined with the fact that it uses `pip install -t` to download pip dependencies directly to the top level of the zip file, this makes imports significantly simpler for your project. 152 | Furthermore, since `pip install -t` downloads the actual pip package files into a folder, this plugin works without the need for `virtualenv` 153 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BbPromise = require('bluebird'); 4 | const _ = require('lodash'); 5 | const Fse = require('fs-extra'); 6 | const Path = require('path'); 7 | const ChildProcess = require('child_process'); 8 | const zipper = require('zip-local'); 9 | const upath = require('upath'); 10 | const readlineSync = require('readline-sync'); 11 | 12 | BbPromise.promisifyAll(Fse); 13 | 14 | 15 | class PkgPyFuncs { 16 | 17 | fetchConfig(){ 18 | if (!this.serverless.service.custom){ 19 | this.error("No serverless custom configurations are defined") 20 | } 21 | 22 | const config = this.serverless.service.custom.pkgPyFuncs 23 | 24 | if ( !config ) { 25 | this.error("No serverless-package-python-functions configuration detected. Please see documentation") 26 | } 27 | this.requirementsFile = config.requirementsFile || 'requirements.txt' 28 | config.buildDir ? this.buildDir = config.buildDir : this.error("No buildDir configuration specified") 29 | this.globalRequirements = config.globalRequirements || [] 30 | this.globalIncludes = config.globalIncludes || [] 31 | config.cleanup === undefined ? this.cleanup = true : this.cleanup = config.cleanup 32 | this.useDocker = config.useDocker || false 33 | this.dockerImage = config.dockerImage || `lambci/lambda:build-${this.serverless.service.provider.runtime}` 34 | this.containerName = config.containerName || 'serverless-package-python-functions' 35 | this.mountSSH = config.mountSSH || false 36 | this.abortOnPackagingErrors = config.abortOnPackagingErrors || false 37 | this.dockerServicePath = '/var/task' 38 | } 39 | 40 | autoconfigArtifacts() { 41 | _.map(this.serverless.service.functions, (func_config, func_name) => { 42 | let autoArtifact = `${this.buildDir}/${func_config.name}.zip` 43 | func_config.package.artifact = func_config.package.artifact || autoArtifact 44 | this.serverless.service.functions[func_name] = func_config 45 | }) 46 | } 47 | 48 | clean(){ 49 | if (!this.cleanup) { 50 | this.log('Cleanup is set to "false". Build directory and Docker container (if used) will be retained') 51 | return false 52 | } 53 | this.log("Cleaning build directory...") 54 | Fse.removeAsync(this.buildDir) 55 | .catch( err => { this.log(err) } ) 56 | 57 | if (this.useDocker){ 58 | this.log("Removing Docker container...") 59 | this.runProcess('docker', ['stop',this.containerName,'-t','0']) 60 | } 61 | return true 62 | } 63 | 64 | selectAll() { 65 | const functions = _.reject(this.serverless.service.functions, (target) => { 66 | return target.runtime && !(target.runtime + '').match(/python/i); 67 | }); 68 | 69 | const info = _.map(functions, (target) => { 70 | return { 71 | name: target.name, 72 | includes: target.package.include, 73 | artifact: target.package.artifact 74 | } 75 | }) 76 | return info 77 | } 78 | 79 | installRequirements(buildPath,requirementsPath){ 80 | 81 | if ( !Fse.pathExistsSync(requirementsPath) ) { 82 | return 83 | } 84 | const size = Fse.statSync(requirementsPath).size 85 | 86 | if (size === 0){ 87 | this.log(`WARNING: requirements file at ${requirementsPath} is empty. Skiping.`) 88 | return 89 | } 90 | 91 | let cmd = 'pip' 92 | let args = ['install', '--upgrade', '-t', upath.normalize(buildPath), '-r'] 93 | if ( this.useDocker === true ){ 94 | cmd = 'docker' 95 | args = ['exec', this.containerName, 'pip', ...args] 96 | requirementsPath = `${this.dockerServicePath}/${requirementsPath}` 97 | } 98 | 99 | args = [...args, upath.normalize(requirementsPath)] 100 | return this.runProcess(cmd, args) 101 | } 102 | 103 | checkDocker(){ 104 | const out = this.runProcess('docker', ['version', '-f', 'Server Version {{.Server.Version}} & Client Version {{.Client.Version}}']) 105 | this.log(`Using Docker ${out}`) 106 | } 107 | 108 | runProcess(cmd,args){ 109 | const ret = ChildProcess.spawnSync(cmd,args) 110 | if (ret.error){ 111 | throw new this.serverless.classes.Error(`[serverless-package-python-functions] ${ret.error.message}`) 112 | } 113 | 114 | const out = ret.stdout.toString() 115 | 116 | if (ret.stderr.length != 0){ 117 | const errorText = ret.stderr.toString().trim() 118 | this.log(errorText) // prints stderr 119 | 120 | if (this.abortOnPackagingErrors){ 121 | const countErrorNewLines = errorText.split('\n').length 122 | 123 | 124 | if(!errorText.includes("ERROR:") && countErrorNewLines < 2 && errorText.toLowerCase().includes('git clone')){ 125 | // Ignore false positive due to pip git clone printing to stderr 126 | } else if(errorText.toLowerCase().includes('warning') && !errorText.toLowerCase().includes('error')){ 127 | // Ignore warnings 128 | } else if(errorText.toLowerCase().includes('docker')){ 129 | console.log('stdout:', out) 130 | this.error("Docker Error Detected") 131 | } else { 132 | // Error is not false positive, 133 | console.log('___ERROR DETECTED, BEGIN STDOUT____\n', out) 134 | this.requestUserConfirmation() 135 | } 136 | } 137 | 138 | } 139 | 140 | return out 141 | } 142 | 143 | requestUserConfirmation(prompt="\n\n??? Do you wish to continue deployment with the stated errors? \n", 144 | yesText="Continuing Deployment!", 145 | noText='ABORTING DEPLOYMENT' 146 | ){ 147 | const response = readlineSync.question(prompt); 148 | if(response.toLowerCase().includes('y')) { 149 | console.log(yesText); 150 | return 151 | } else { 152 | console.log(noText) 153 | this.error('Aborting') 154 | return 155 | } 156 | } 157 | 158 | setupContainer(){ 159 | let out = this.runProcess('docker',['ps', '-a', '--filter',`name=${this.containerName}`,'--format','{{.Names}}']) 160 | out = out.replace(/^\s+|\s+$/g, '') 161 | 162 | if ( out === this.containerName ){ 163 | this.log('Container already exists. Reusing.') 164 | let out = this.runProcess('docker', ['kill', `${this.containerName}`]) 165 | this.log(out) 166 | } 167 | 168 | let args = ['run', '--rm', '-dt', '-v', `${process.cwd()}:${this.dockerServicePath}`] 169 | 170 | if (this.mountSSH) { 171 | args = args.concat(['-v', `${process.env.HOME}/.ssh:/root/.ssh`]) 172 | } 173 | 174 | args = args.concat(['--name',this.containerName, this.dockerImage, 'bash']) 175 | this.runProcess('docker', args) 176 | this.log('Container created') 177 | } 178 | 179 | ensureImage(){ 180 | const out = this.runProcess('docker', ['images', '--format','{{.Repository}}:{{.Tag}}','--filter',`reference=${this.dockerImage}`]).replace(/^\s+|\s+$/g, '') 181 | if ( out != this.dockerImage ){ 182 | this.log(`Docker Image ${this.dockerImage} is not already installed on your system. Downloading. This might take a while. Subsequent deploys will be faster...`) 183 | this.runProcess('docker', ['pull', this.dockerImage]) 184 | } 185 | } 186 | 187 | setupDocker(){ 188 | if (!this.useDocker){ 189 | return 190 | } 191 | this.log('Packaging using Docker container...') 192 | this.checkDocker() 193 | this.ensureImage() 194 | this.log(`Creating Docker container "${this.containerName}"...`) 195 | this.setupContainer() 196 | this.log('Docker setup completed') 197 | } 198 | 199 | makePackage(target){ 200 | this.log(`Packaging ${target.name}...`) 201 | const buildPath = Path.join(this.buildDir, target.name) 202 | const requirementsPath = Path.join(buildPath,this.requirementsFile) 203 | // Create package directory and package files 204 | Fse.ensureDirSync(buildPath) 205 | // Copy includes 206 | let includes = target.includes || [] 207 | if (this.globalIncludes){ 208 | includes = _.concat(includes, this.globalIncludes) 209 | } 210 | _.forEach(includes, (item) => { Fse.copySync(item, buildPath) } ) 211 | 212 | // Install requirements 213 | let requirements = [requirementsPath] 214 | if (this.globalRequirements){ 215 | requirements = _.concat(requirements, this.globalRequirements) 216 | } 217 | _.forEach(requirements, (req) => { this.installRequirements(buildPath,req) }) 218 | zipper.sync.zip(buildPath).compress().save(`${buildPath}.zip`) 219 | } 220 | 221 | 222 | 223 | constructor(serverless, options) { 224 | this.serverless = serverless; 225 | this.options = options; 226 | this.log = (msg) => { this.serverless.cli.log(`[serverless-package-python-functions] ${msg}`) } 227 | this.error = (msg) => { throw new Error(`[serverless-package-python-functions] ${msg}`) } 228 | 229 | 230 | 231 | this.hooks = { 232 | 'before:package:createDeploymentArtifacts': () => BbPromise.bind(this) 233 | .then(this.fetchConfig) 234 | .then(this.autoconfigArtifacts) 235 | .then( () => { Fse.ensureDirAsync(this.buildDir) }) 236 | .then(this.setupDocker) 237 | .then(this.selectAll) 238 | .map(this.makePackage), 239 | 240 | 'after:deploy:deploy': () => BbPromise.bind(this) 241 | .then(this.clean) 242 | }; 243 | 244 | } 245 | } 246 | 247 | module.exports = PkgPyFuncs; 248 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-package-python-functions", 3 | "version": "0.5.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "1.5.2", 9 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 10 | "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" 11 | }, 12 | "bluebird": { 13 | "version": "3.5.0", 14 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 15 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 16 | }, 17 | "fs-extra": { 18 | "version": "3.0.1", 19 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", 20 | "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", 21 | "requires": { 22 | "graceful-fs": "^4.1.2", 23 | "jsonfile": "^3.0.0", 24 | "universalify": "^0.1.0" 25 | } 26 | }, 27 | "graceful-fs": { 28 | "version": "4.1.11", 29 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 30 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" 31 | }, 32 | "jsonfile": { 33 | "version": "3.0.1", 34 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", 35 | "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", 36 | "requires": { 37 | "graceful-fs": "^4.1.6" 38 | } 39 | }, 40 | "jszip": { 41 | "version": "2.6.1", 42 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", 43 | "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", 44 | "requires": { 45 | "pako": "~1.0.2" 46 | } 47 | }, 48 | "lodash": { 49 | "version": "4.17.19", 50 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", 51 | "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" 52 | }, 53 | "pako": { 54 | "version": "1.0.5", 55 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.5.tgz", 56 | "integrity": "sha1-0iBd/ludqK95fnwWPbTR+E5GALw=" 57 | }, 58 | "q": { 59 | "version": "1.5.0", 60 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz", 61 | "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=" 62 | }, 63 | "readline-sync": { 64 | "version": "1.4.10", 65 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", 66 | "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==" 67 | }, 68 | "universalify": { 69 | "version": "0.1.1", 70 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", 71 | "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" 72 | }, 73 | "upath": { 74 | "version": "1.1.0", 75 | "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", 76 | "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" 77 | }, 78 | "zip-local": { 79 | "version": "0.3.4", 80 | "resolved": "https://registry.npmjs.org/zip-local/-/zip-local-0.3.4.tgz", 81 | "integrity": "sha1-4pMZByV6lGR56lvQ0OIK3+srWgc=", 82 | "requires": { 83 | "async": "^1.4.2", 84 | "graceful-fs": "^4.1.3", 85 | "jszip": "^2.5.0", 86 | "q": "^1.4.1" 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-package-python-functions", 3 | "version": "0.7.0", 4 | "description": "Serverless Framework plugin to package python functions and their requirements", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "author": "Ubani Balogun", 8 | "license": "ISC", 9 | "dependencies": { 10 | "bluebird": "^3.5.0", 11 | "fs-extra": "^3.0.0", 12 | "lodash": "^4.17.11", 13 | "upath": "^1.1.0", 14 | "readline-sync": "^1.4.10", 15 | "zip-local": "^0.3.4" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ubaniabalogun/serverless-package-python-functions.git" 20 | }, 21 | "keywords": [ 22 | "serverless", 23 | "serverless", 24 | "framework", 25 | "plugin", 26 | "serverless", 27 | "applications", 28 | "aws", 29 | "lambda", 30 | "amazon", 31 | "amazon", 32 | "web", 33 | "services", 34 | "python", 35 | "pip", 36 | "requirements" 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/ubaniabalogun/serverless-package-python-functions/issues" 40 | }, 41 | "homepage": "https://github.com/ubaniabalogun/serverless-package-python-functions#readme" 42 | } 43 | --------------------------------------------------------------------------------