diff --git a/lib/pip.js b/lib/pip.js index 29e21cfc..538ad55a 100644 --- a/lib/pip.js +++ b/lib/pip.js @@ -53,7 +53,7 @@ function mergeCommands(commands) { * @param {Object} options * @return {undefined} */ -function installRequirementsFile( +function generateRequirementsFile( requirementsPath, targetFile, serverless, @@ -61,7 +61,7 @@ function installRequirementsFile( options ) { if (options.usePipenv && fse.existsSync(path.join(servicePath, 'Pipfile'))) { - generateRequirementsFile( + filterRequirementsFile( path.join(servicePath, '.serverless/requirements.txt'), targetFile, options @@ -70,7 +70,7 @@ function installRequirementsFile( `Parsed requirements.txt from Pipfile in ${targetFile}...` ); } else { - generateRequirementsFile(requirementsPath, targetFile, options); + filterRequirementsFile(requirementsPath, targetFile, options); serverless.cli.log( `Generated requirements from ${requirementsPath} in ${targetFile}...` ); @@ -306,7 +306,6 @@ function dockerPathForWin(path) { return path; } } - /** create a filtered requirements.txt without anything from noDeploy * then remove all comments and empty lines, and sort the list which * assist with matching the static cache. The sorting will skip any @@ -318,7 +317,7 @@ function dockerPathForWin(path) { * @param {string} target requirements where results are written * @param {Object} options */ -function generateRequirementsFile(source, target, options) { +function filterRequirementsFile(source, target, options) { const noDeploy = new Set(options.noDeploy || []); const requirements = fse .readFileSync(source, { encoding: 'utf-8' }) @@ -413,11 +412,21 @@ function installRequirementsIfNeeded( } } - // First, generate the requirements file to our local .serverless folder - fse.ensureDirSync(path.join(servicePath, '.serverless')); - const slsReqsTxt = path.join(servicePath, '.serverless', 'requirements.txt'); + let requirementsTxtDirectory; + // Copy our requirements to another path in .serverless (incase of individually packaged) + if (modulePath && modulePath !== '.') { + requirementsTxtDirectory = path.join( + servicePath, + '.serverless', + modulePath + ); + } else { + requirementsTxtDirectory = path.join(servicePath, '.serverless'); + } + fse.ensureDirSync(requirementsTxtDirectory); + const slsReqsTxt = path.join(requirementsTxtDirectory, 'requirements.txt'); - installRequirementsFile( + generateRequirementsFile( fileName, slsReqsTxt, serverless, @@ -433,28 +442,13 @@ function installRequirementsIfNeeded( return false; } - // Copy our requirements to another filename in .serverless (incase of individually packaged) - if (modulePath && modulePath != '.') { - fse.existsSync(path.join(servicePath, '.serverless', modulePath)); - const destinationFile = path.join( - servicePath, - '.serverless', - modulePath, - 'requirements.txt' - ); - serverless.cli.log( - `Copying from ${slsReqsTxt} into ${destinationFile} ...` - ); - fse.copySync(slsReqsTxt, destinationFile); - } - // Then generate our MD5 Sum of this requirements file to determine where it should "go" to and/or pull cache from const reqChecksum = md5Path(slsReqsTxt); // Then figure out where this cache should be, if we're caching, if we're in a module, etc const workingReqsFolder = getRequirementsWorkingPath( reqChecksum, - servicePath, + requirementsTxtDirectory, options ); diff --git a/lib/shared.js b/lib/shared.js index b3a1ffaa..fe48e0e0 100644 --- a/lib/shared.js +++ b/lib/shared.js @@ -57,7 +57,11 @@ function checkForAndDeleteMaxCacheVersions(options, serverless) { * @param {Object} options * @return {string} */ -function getRequirementsWorkingPath(subfolder, servicePath, options) { +function getRequirementsWorkingPath( + subfolder, + requirementsTxtDirectory, + options +) { // If we want to use the static cache if (options && options.useStaticCache) { if (subfolder) { @@ -69,7 +73,7 @@ function getRequirementsWorkingPath(subfolder, servicePath, options) { } // If we don't want to use the static cache, then fallback to the way things used to work - return path.join(servicePath, '.serverless', 'requirements'); + return path.join(requirementsTxtDirectory, 'requirements'); } /** diff --git a/test.js b/test.js index b310e672..64d15221 100644 --- a/test.js +++ b/test.js @@ -1008,35 +1008,79 @@ test('py3.6 can package flask with package individually option', t => { sls(['--individually=true', 'package']); const zipfiles_hello = listZipFiles('.serverless/hello.zip'); + t.false( + zipfiles_hello.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello' + ); t.true( zipfiles_hello.includes('handler.py'), 'handler.py is packaged in function hello' ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); t.true( zipfiles_hello.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello' ); const zipfiles_hello2 = listZipFiles('.serverless/hello2.zip'); + t.false( + zipfiles_hello2.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello2' + ); t.true( zipfiles_hello2.includes('handler.py'), 'handler.py is packaged in function hello2' ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); t.true( zipfiles_hello2.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello2' ); const zipfiles_hello3 = listZipFiles('.serverless/hello3.zip'); + t.false( + zipfiles_hello3.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello3' + ); t.true( zipfiles_hello3.includes('handler.py'), - 'handler.py is packaged in function hello2' + 'handler.py is packaged in function hello3' + ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' ); t.false( zipfiles_hello3.includes(`flask${sep}__init__.py`), 'flask is NOT packaged in function hello3' ); + const zipfiles_hello4 = listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.false( + zipfiles_hello4.includes(`fn2${sep}__init__.py`), + 'fn2 is NOT packaged in function hello4' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); + t.end(); }); @@ -1060,6 +1104,10 @@ test('py3.6 can package flask with package individually & slim option', t => { zipfiles_hello.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello' ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); const zipfiles_hello2 = listZipFiles('.serverless/hello2.zip'); t.true( @@ -1075,6 +1123,10 @@ test('py3.6 can package flask with package individually & slim option', t => { zipfiles_hello2.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello2' ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); const zipfiles_hello3 = listZipFiles('.serverless/hello3.zip'); t.true( @@ -1091,6 +1143,27 @@ test('py3.6 can package flask with package individually & slim option', t => { 'flask is NOT packaged in function hello3' ); + const zipfiles_hello4 = listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); + t.deepEqual( + zipfiles_hello4.filter(filename => filename.endsWith('.pyc')), + [], + 'no pyc files packaged in function hello4' + ); + t.end(); }); @@ -1109,6 +1182,10 @@ test('py2.7 can package flask with package individually option', t => { zipfiles_hello.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello' ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); const zipfiles_hello2 = listZipFiles('.serverless/hello2.zip'); t.true( @@ -1119,6 +1196,10 @@ test('py2.7 can package flask with package individually option', t => { zipfiles_hello2.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello2' ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); const zipfiles_hello3 = listZipFiles('.serverless/hello3.zip'); t.true( @@ -1129,6 +1210,26 @@ test('py2.7 can package flask with package individually option', t => { zipfiles_hello3.includes(`flask${sep}__init__.py`), 'flask is NOT packaged in function hello3' ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' + ); + + const zipfiles_hello4 = listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); t.end(); }); @@ -1153,6 +1254,10 @@ test('py2.7 can package flask with package individually & slim option', t => { zipfiles_hello.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello' ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); const zipfiles_hello2 = listZipFiles('.serverless/hello2.zip'); t.true( @@ -1168,6 +1273,10 @@ test('py2.7 can package flask with package individually & slim option', t => { zipfiles_hello2.includes(`flask${sep}__init__.py`), 'flask is packaged in function hello2' ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); const zipfiles_hello3 = listZipFiles('.serverless/hello3.zip'); t.true( @@ -1183,6 +1292,26 @@ test('py2.7 can package flask with package individually & slim option', t => { zipfiles_hello3.includes(`flask${sep}__init__.py`), 'flask is NOT packaged in function hello3' ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' + ); + + const zipfiles_hello4 = listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); t.end(); }); @@ -1255,6 +1384,10 @@ test('py3.6 can package lambda-decorators using vendor and invidiually option', zipfiles_hello.includes(`lambda_decorators.py`), 'lambda_decorators.py is packaged in function hello' ); + t.false( + zipfiles_hello.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello' + ); const zipfiles_hello2 = listZipFiles('.serverless/hello2.zip'); t.true( @@ -1269,6 +1402,10 @@ test('py3.6 can package lambda-decorators using vendor and invidiually option', zipfiles_hello2.includes(`lambda_decorators.py`), 'lambda_decorators.py is packaged in function hello2' ); + t.false( + zipfiles_hello2.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello2' + ); const zipfiles_hello3 = listZipFiles('.serverless/hello3.zip'); t.true( @@ -1283,7 +1420,26 @@ test('py3.6 can package lambda-decorators using vendor and invidiually option', zipfiles_hello3.includes(`lambda_decorators.py`), 'lambda_decorators.py is NOT packaged in function hello3' ); + t.false( + zipfiles_hello3.includes(`dataclasses.py`), + 'dataclasses is NOT packaged in function hello3' + ); + const zipfiles_hello4 = listZipFiles( + '.serverless/fn2-sls-py-req-test-dev-hello4.zip' + ); + t.true( + zipfiles_hello4.includes('fn2_handler.py'), + 'fn2_handler is packaged in the zip-root in function hello4' + ); + t.true( + zipfiles_hello4.includes(`dataclasses.py`), + 'dataclasses is packaged in function hello4' + ); + t.false( + zipfiles_hello4.includes(`flask${sep}__init__.py`), + 'flask is NOT packaged in function hello4' + ); t.end(); }); diff --git a/tests/base/fn2/__init__.py b/tests/base/fn2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/base/fn2/fn2_handler.py b/tests/base/fn2/fn2_handler.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/base/fn2/requirements.txt b/tests/base/fn2/requirements.txt new file mode 100644 index 00000000..eea18113 --- /dev/null +++ b/tests/base/fn2/requirements.txt @@ -0,0 +1 @@ +dataclasses \ No newline at end of file diff --git a/tests/base/serverless.yml b/tests/base/serverless.yml index a62dbc01..1684ab2f 100644 --- a/tests/base/serverless.yml +++ b/tests/base/serverless.yml @@ -32,7 +32,7 @@ package: individually: ${opt:individually, self:custom.defaults.individually} exclude: - '**/*' - include: + include: - 'handler.py' functions: @@ -43,3 +43,11 @@ functions: hello3: handler: handler.hello runtime: nodejs6.10 + hello4: + handler: fn2_handler.hello + module: fn2 + package: + include: + - 'fn2/**' + + diff --git a/tests/individually/module1/handler1.py b/tests/individually/module1/handler1.py index 970b0c01..369835cd 100644 --- a/tests/individually/module1/handler1.py +++ b/tests/individually/module1/handler1.py @@ -1,6 +1,9 @@ import boto3 + def hello(event, context): - return { - 'status': 200, - } + return {"status": 200} + + +def hello2(event, context): + return {"status": 200}