serverless / serverless-python-requirements

⚡️🐍📦 Serverless plugin to bundle Python packages
MIT License
1.11k stars 290 forks source link

Support PEP-621 pyproject.toml #634

Open KerberosMorphy opened 2 years ago

KerberosMorphy commented 2 years ago

I know you already support poetry and pipenv, but I would like to request the following feature to support PDM to. Supporting official PEP-621 pyproject.toml and their extra.

I've try to create this addition since I wanted to contribute, but my javascript isn't really up to date and I have some difficulties to implements tests and understand some part of your code.

What I would like to:

custom:
  pythonRequirements:
    usePyproject: true  # don't know if it's necessary
    extra:
      - rich

so for this pyproject:

[project]
name = "pyproject"
version = "0.1.0"
description = ""
requires-python = ">=3.6"
authors = [
  {name = "Your Name"},
  {email = "you@example.com"},
]
dependencies = [
  "Flask>=1.0",
  "bottle @ git+https://git@github.com/bottlepy/bottle.git@v21.8.0",
  "boto3>=1.9",
]

[project.optional-dependencies]
test = [
  "tox",
]
rich = [
  "rich",
]

It would generate:

requirements.txt

Flask>=1.0
bottle @ git+https://git@github.com/bottlepy/bottle.git@v21.8.0
boto3>=1.9
rich

What I have tried to do:

const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const { spawnSync } = require('child_process');
const tomlParse = require('@iarna/toml/parse-string');

/**
 * pyproject install
 */
function pyprojectTomlToRequirements() {
  if (!this.options.usePyproject) {
    return;
  }

  this.serverless.cli.log('Generating requirements.txt from pyproject.toml...');

  const destination = path.join(this.servicePath, 'requirements.txt');
  const requirementsContents = extractDependencies(this.servicePath);

  const editableFlag = new RegExp(/^-e /gm);
  const sourceRequirements = path.join(this.servicePath, 'requirements.txt');

  if (requirementsContents.match(editableFlag)) {
    this.serverless.cli.log(
      'The generated file contains -e flags, removing them...'
    );
    fse.writeFileSync(
      sourceRequirements,
      requirementsContents.replace(editableFlag, '')
    );
  }

  fse.ensureDirSync(path.join(this.servicePath, '.serverless'));
  fse.moveSync(
    sourceRequirements,
    path.join(this.servicePath, '.serverless', 'requirements.txt'),
    { overwrite: true }
  );
}

/**
 * Extract dependencies and extra from pyproject.toml
 */
function extractDependencies(servicePath, extra) {
  const pyprojectPath = path.join(servicePath, 'pyproject.toml');

  if (!fse.existsSync(pyprojectPath)) {
    return false;
  }

  const pyprojectToml = fs.readFileSync(pyprojectPath);
  const pyproject = tomlParse(pyprojectToml);

  const dependencies =
    (pyproject['project'] && pyproject['project']['dependencies']) || [];

  const extraDependencies =
    (pyproject['project'] && pyproject['project']['optional-dependencies']) || {};

  for (const [extraName, dependencies] of Object.entries(extraDependencies)) {
    if (extra.includes(extraName)) {
      dependencies = dependencies.concat(dependencies);
    }
  }
  return dependencies.join("\n");
}

module.exports = { pyprojectTomlToRequirements, isPyprojectProject };

Hope that what I've done could help, sorry if I couldn't do more.

spookyuser commented 1 year ago

This looks amazing, and would be so nice, I love PDM ❤️