Skip to content

Commit ebd12cb

Browse files
feat: Support individual packaging with poetry (#682)
1 parent 915bcad commit ebd12cb

File tree

14 files changed

+116
-40
lines changed

14 files changed

+116
-40
lines changed

Diff for: .gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ dist/
5959
downloads/
6060
eggs/
6161
.eggs/
62-
lib/
6362
lib64/
6463
parts/
6564
sdist/

Diff for: index.js

-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const { injectAllRequirements } = require('./lib/inject');
1313
const { layerRequirements } = require('./lib/layer');
1414
const { installAllRequirements } = require('./lib/pip');
1515
const { pipfileToRequirements } = require('./lib/pipenv');
16-
const { pyprojectTomlToRequirements } = require('./lib/poetry');
1716
const { cleanup, cleanupCache } = require('./lib/clean');
1817
BbPromise.promisifyAll(fse);
1918

@@ -203,7 +202,6 @@ class ServerlessPythonRequirements {
203202
}
204203
return BbPromise.bind(this)
205204
.then(pipfileToRequirements)
206-
.then(pyprojectTomlToRequirements)
207205
.then(addVendorHelper)
208206
.then(installAllRequirements)
209207
.then(packRequirements)

Diff for: lib/pip.js

+7-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const spawn = require('child-process-ext/spawn');
77
const { quote } = require('shell-quote');
88
const { buildImage, getBindPath, getDockerUid } = require('./docker');
99
const { getStripCommand, getStripMode, deleteFiles } = require('./slim');
10-
const { isPoetryProject } = require('./poetry');
10+
const { isPoetryProject, pyprojectTomlToRequirements } = require('./poetry');
1111
const {
1212
checkForAndDeleteMaxCacheVersions,
1313
sha256Path,
@@ -60,16 +60,9 @@ function generateRequirementsFile(
6060
pluginInstance
6161
) {
6262
const { serverless, servicePath, options, log } = pluginInstance;
63-
if (
64-
options.usePoetry &&
65-
fse.existsSync(path.join(servicePath, 'pyproject.toml')) &&
66-
isPoetryProject(servicePath)
67-
) {
68-
filterRequirementsFile(
69-
path.join(servicePath, '.serverless/requirements.txt'),
70-
targetFile,
71-
pluginInstance
72-
);
63+
const modulePath = path.dirname(requirementsPath);
64+
if (options.usePoetry && isPoetryProject(modulePath)) {
65+
filterRequirementsFile(targetFile, targetFile, pluginInstance);
7366
if (log) {
7467
log.info(`Parsed requirements.txt from pyproject.toml in ${targetFile}`);
7568
} else {
@@ -570,11 +563,7 @@ function copyVendors(vendorFolder, targetFolder, { serverless, log }) {
570563
* @param {string} fileName
571564
*/
572565
function requirementsFileExists(servicePath, options, fileName) {
573-
if (
574-
options.usePoetry &&
575-
fse.existsSync(path.join(servicePath, 'pyproject.toml')) &&
576-
isPoetryProject(servicePath)
577-
) {
566+
if (options.usePoetry && isPoetryProject(path.dirname(fileName))) {
578567
return true;
579568
}
580569

@@ -609,6 +598,8 @@ async function installRequirementsIfNeeded(
609598
// Our source requirements, under our service path, and our module path (if specified)
610599
const fileName = path.join(servicePath, modulePath, options.fileName);
611600

601+
await pyprojectTomlToRequirements(modulePath, pluginInstance);
602+
612603
// Skip requirements generation, if requirements file doesn't exist
613604
if (!requirementsFileExists(servicePath, options, fileName)) {
614605
return false;

Diff for: lib/poetry.js

+17-16
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@ const tomlParse = require('@iarna/toml/parse-string');
88
/**
99
* poetry install
1010
*/
11-
async function pyprojectTomlToRequirements() {
12-
if (!this.options.usePoetry || !isPoetryProject(this.servicePath)) {
11+
async function pyprojectTomlToRequirements(modulePath, pluginInstance) {
12+
const { serverless, servicePath, options, log, progress } = pluginInstance;
13+
14+
const moduleProjectPath = path.join(servicePath, modulePath);
15+
if (!options.usePoetry || !isPoetryProject(moduleProjectPath)) {
1316
return;
1417
}
1518

1619
let generateRequirementsProgress;
17-
if (this.progress && this.log) {
18-
generateRequirementsProgress = this.progress.get(
20+
if (progress && log) {
21+
generateRequirementsProgress = progress.get(
1922
'python-generate-requirements-toml'
2023
);
2124
generateRequirementsProgress.update(
2225
'Generating requirements.txt from "pyproject.toml"'
2326
);
24-
this.log.info('Generating requirements.txt from "pyproject.toml"');
27+
log.info('Generating requirements.txt from "pyproject.toml"');
2528
} else {
26-
this.serverless.cli.log(
27-
'Generating requirements.txt from pyproject.toml...'
28-
);
29+
serverless.cli.log('Generating requirements.txt from pyproject.toml...');
2930
}
3031

3132
try {
@@ -42,15 +43,15 @@ async function pyprojectTomlToRequirements() {
4243
'--with-credentials',
4344
],
4445
{
45-
cwd: this.servicePath,
46+
cwd: moduleProjectPath,
4647
}
4748
);
4849
} catch (e) {
4950
if (
5051
e.stderrBuffer &&
5152
e.stderrBuffer.toString().includes('command not found')
5253
) {
53-
throw new this.serverless.classes.Error(
54+
throw new serverless.classes.Error(
5455
`poetry not found! Install it according to the poetry docs.`,
5556
'PYTHON_REQUIREMENTS_POETRY_NOT_FOUND'
5657
);
@@ -59,16 +60,16 @@ async function pyprojectTomlToRequirements() {
5960
}
6061

6162
const editableFlag = new RegExp(/^-e /gm);
62-
const sourceRequirements = path.join(this.servicePath, 'requirements.txt');
63+
const sourceRequirements = path.join(moduleProjectPath, 'requirements.txt');
6364
const requirementsContents = fse.readFileSync(sourceRequirements, {
6465
encoding: 'utf-8',
6566
});
6667

6768
if (requirementsContents.match(editableFlag)) {
68-
if (this.log) {
69-
this.log.info('The generated file contains -e flags, removing them');
69+
if (log) {
70+
log.info('The generated file contains -e flags, removing them');
7071
} else {
71-
this.serverless.cli.log(
72+
serverless.cli.log(
7273
'The generated file contains -e flags, removing them...'
7374
);
7475
}
@@ -78,10 +79,10 @@ async function pyprojectTomlToRequirements() {
7879
);
7980
}
8081

81-
fse.ensureDirSync(path.join(this.servicePath, '.serverless'));
82+
fse.ensureDirSync(path.join(servicePath, '.serverless'));
8283
fse.moveSync(
8384
sourceRequirements,
84-
path.join(this.servicePath, '.serverless', 'requirements.txt'),
85+
path.join(servicePath, '.serverless', modulePath, 'requirements.txt'),
8586
{ overwrite: true }
8687
);
8788
} finally {

Diff for: test.js

+19
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,25 @@ test(
14791479
{ skip: !hasPython(3.6) }
14801480
);
14811481

1482+
test(
1483+
'poetry py3.6 can package flask with package individually option',
1484+
async (t) => {
1485+
process.chdir('tests/poetry_individually');
1486+
const path = npm(['pack', '../..']);
1487+
npm(['i', path]);
1488+
1489+
sls(['package'], { env: {} });
1490+
const zipfiles = await listZipFiles(
1491+
'.serverless/module1-sls-py-req-test-dev-hello.zip'
1492+
);
1493+
t.true(zipfiles.includes(`flask${sep}__init__.py`), 'flask is packaged');
1494+
t.true(zipfiles.includes(`bottle.py`), 'bottle is packaged');
1495+
t.true(zipfiles.includes(`boto3${sep}__init__.py`), 'boto3 is packaged');
1496+
t.end();
1497+
},
1498+
{ skip: !hasPython(3.6) }
1499+
);
1500+
14821501
test(
14831502
'py3.6 can package flask with package individually option',
14841503
async (t) => {

Diff for: tests/base/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12-
"serverless-python-requirements": "file:serverless-python-requirements-5.1.1.tgz"
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
1313
}
1414
}

Diff for: tests/non_build_pyproject/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12-
"serverless-python-requirements": "file:serverless-python-requirements-5.1.1.tgz"
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
1313
}
1414
}

Diff for: tests/non_poetry_pyproject/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12-
"serverless-python-requirements": "file:serverless-python-requirements-5.1.1.tgz"
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
1313
}
1414
}

Diff for: tests/pipenv/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12-
"serverless-python-requirements": "file:serverless-python-requirements-5.1.1.tgz"
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
1313
}
1414
}

Diff for: tests/poetry/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
"author": "",
1010
"license": "ISC",
1111
"dependencies": {
12-
"serverless-python-requirements": "file:serverless-python-requirements-5.1.1.tgz"
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
1313
}
1414
}

Diff for: tests/poetry_individually/module1/handler.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import requests
2+
3+
4+
def hello(event, context):
5+
return requests.get('https://httpbin.org/get').json()

Diff for: tests/poetry_individually/module1/pyproject.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[tool.poetry]
2+
name = "poetry"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Your Name <[email protected]>"]
6+
7+
[tool.poetry.dependencies]
8+
python = "^3.6"
9+
Flask = "^1.0"
10+
bottle = {git = "https://[email protected]/bottlepy/bottle.git", tag = "0.12.16"}
11+
boto3 = "^1.9"
12+
13+
[tool.poetry.dev-dependencies]
14+
15+
[build-system]
16+
requires = ["poetry>=0.12"]
17+
build-backend = "poetry.masonry.api"

Diff for: tests/poetry_individually/package.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "example",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"dependencies": {
12+
"serverless-python-requirements": "file:serverless-python-requirements-5.3.1.tgz"
13+
}
14+
}

Diff for: tests/poetry_individually/serverless.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
service: sls-py-req-test
2+
3+
provider:
4+
name: aws
5+
runtime: python3.6
6+
7+
plugins:
8+
- serverless-python-requirements
9+
custom:
10+
pythonRequirements:
11+
zip: ${env:zip, self:custom.defaults.zip}
12+
slim: ${env:slim, self:custom.defaults.slim}
13+
slimPatterns: ${file(./slimPatterns.yml):slimPatterns, self:custom.defaults.slimPatterns}
14+
slimPatternsAppendDefaults: ${env:slimPatternsAppendDefaults, self:custom.defaults.slimPatternsAppendDefaults}
15+
dockerizePip: ${env:dockerizePip, self:custom.defaults.dockerizePip}
16+
defaults:
17+
zip: false
18+
slimPatterns: false
19+
slimPatternsAppendDefaults: true
20+
slim: false
21+
dockerizePip: false
22+
23+
package:
24+
individually: true
25+
26+
functions:
27+
hello:
28+
handler: handler.hello
29+
module: module1
30+
package:
31+
patterns:
32+
- 'module1/**'

0 commit comments

Comments
 (0)