├── .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 | [](http://www.serverless.com)
4 | [](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 |
--------------------------------------------------------------------------------