From b147e958256cbfecf33871ed47860242aee95edd Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 03:03:09 -0500 Subject: [PATCH 01/29] feat: python project --- src/__tests__/python/python-project.test.ts | 19 ++ src/python/index.ts | 9 + src/python/pip.ts | 63 +++++++ src/python/pytest.ts | 30 ++++ src/python/python-deps.ts | 22 +++ src/python/python-env.ts | 1 + src/python/python-packaging.ts | 1 + src/python/python-project.ts | 188 ++++++++++++++++++++ src/python/python-sample.ts | 21 +++ src/python/requirements-file.ts | 61 +++++++ src/python/venv.ts | 11 ++ 11 files changed, 426 insertions(+) create mode 100644 src/__tests__/python/python-project.test.ts create mode 100644 src/python/index.ts create mode 100644 src/python/pip.ts create mode 100644 src/python/pytest.ts create mode 100644 src/python/python-deps.ts create mode 100644 src/python/python-env.ts create mode 100644 src/python/python-packaging.ts create mode 100644 src/python/python-project.ts create mode 100644 src/python/python-sample.ts create mode 100644 src/python/requirements-file.ts create mode 100644 src/python/venv.ts diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts new file mode 100644 index 00000000000..15843b84145 --- /dev/null +++ b/src/__tests__/python/python-project.test.ts @@ -0,0 +1,19 @@ +// import { LogLevel } from '../../logger'; +// import { PythonProject, PythonProjectOptions } from '../../python'; +// import { synthSnapshot } from '../util'; + +// test('defaults', () => { +// const p = new TestPythonProject(); +// expect(synthSnapshot(p)).toMatchSnapshot(); +// }); + +// class TestPythonProject extends PythonProject { +// constructor(options: Partial = { }) { +// super({ +// ...options, +// name: 'test-python-project', +// logging: { level: LogLevel.OFF }, +// jsiiFqn: 'projen.python.PythonProject', +// }); +// } +// } diff --git a/src/python/index.ts b/src/python/index.ts new file mode 100644 index 00000000000..e94f1be8874 --- /dev/null +++ b/src/python/index.ts @@ -0,0 +1,9 @@ +export * from './pip'; +export * from './pytest'; +export * from './python-env'; +export * from './python-deps'; +export * from './python-packaging'; +export * from './python-project'; +export * from './python-sample'; +export * from './requirements-file'; +export * from './venv'; \ No newline at end of file diff --git a/src/python/pip.ts b/src/python/pip.ts new file mode 100644 index 00000000000..25b16900714 --- /dev/null +++ b/src/python/pip.ts @@ -0,0 +1,63 @@ +import { Component } from '../component'; +import { DependencyType } from '../deps'; +import { Project } from '../project'; +import { IPythonDeps } from './python-deps'; +import { RequirementsFile } from './requirements-file'; + +export interface PipOptions {} + +export class Pip extends Component implements IPythonDeps { + constructor(project: Project, _options: PipOptions) { + super(project); + + new RequirementsFile(project, 'requirements.txt', { lazyPackages: () => this.synthDependencies() }); + new RequirementsFile(project, 'requirements-dev.txt', { lazyPackages: () => this.synthDevDependencies() }); + } + + private synthDependencies() { + const dependencies: string[] = []; + for (const pkg of this.project.deps.all) { + if (pkg.type === DependencyType.RUNTIME) { + dependencies.push( `${pkg.name}@${pkg.version}`); + } + } + return dependencies; + } + + private synthDevDependencies() { + const dependencies: string[] = []; + for (const pkg of this.project.deps.all) { + if ([DependencyType.TEST, DependencyType.DEVENV].includes(pkg.type)) { + dependencies.push( `${pkg.name}@${pkg.version}`); + } + } + return dependencies; + } + + /** + * Adds a runtime dependency. + * + * @param spec Format `@` + */ + public addDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.RUNTIME); + } + + /** + * Adds a test dependency. + * + * @param spec Format `@` + */ + public addTestDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.TEST); + } + + /** + * Adds a dev dependency. + * + * @param spec Format `@` + */ + public addDevDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.DEVENV); + } +} \ No newline at end of file diff --git a/src/python/pytest.ts b/src/python/pytest.ts new file mode 100644 index 00000000000..b80a2b36852 --- /dev/null +++ b/src/python/pytest.ts @@ -0,0 +1,30 @@ +import { Component } from '../component'; +import { TaskCategory } from '../tasks'; +import { PythonProject } from './python-project'; + +export interface PytestOptions { + /** + * Pytest version + * + * @default "6.2.1" + */ + readonly version?: string; +} + +export class Pytest extends Component { + constructor(project: PythonProject, options: PytestOptions) { + super(project); + + const version = options.version ?? '6.2.1'; + + project.depsManager?.addTestDependency(`pytest@${version}`); + + project.addTask('test', { + description: 'Runs tests', + category: TaskCategory.TEST, + exec: 'pytest', + }); + + // TODO: add sample tests with `SampleDir` + } +} \ No newline at end of file diff --git a/src/python/python-deps.ts b/src/python/python-deps.ts new file mode 100644 index 00000000000..48639bd2cd4 --- /dev/null +++ b/src/python/python-deps.ts @@ -0,0 +1,22 @@ +export interface IPythonDeps { + /** + * Adds a runtime dependency. + * + * @param spec Format `@` + */ + addDependency(spec: string): void; + + /** + * Adds a test dependency. + * + * @param spec Format `@` + */ + addTestDependency(spec: string): void; + + /** + * Adds a dev dependency. + * + * @param spec Format `@` + */ + addDevDependency(spec: string): void; +} diff --git a/src/python/python-env.ts b/src/python/python-env.ts new file mode 100644 index 00000000000..6f684aaf22c --- /dev/null +++ b/src/python/python-env.ts @@ -0,0 +1 @@ +export interface IPythonEnv {} diff --git a/src/python/python-packaging.ts b/src/python/python-packaging.ts new file mode 100644 index 00000000000..a60ab930862 --- /dev/null +++ b/src/python/python-packaging.ts @@ -0,0 +1 @@ +export interface IPythonPackaging {} diff --git a/src/python/python-project.ts b/src/python/python-project.ts new file mode 100644 index 00000000000..1e0d9a80267 --- /dev/null +++ b/src/python/python-project.ts @@ -0,0 +1,188 @@ +import { Project, ProjectOptions, ProjectType } from '../project'; +import { Pip } from './pip'; +import { Pytest, PytestOptions } from './pytest'; +import { IPythonDeps } from './python-deps'; +import { IPythonEnv } from './python-env'; +import { IPythonPackaging } from './python-packaging'; +import { PythonSample } from './python-sample'; +import { Venv } from './venv'; + +/** + * Options for `PythonProject`. + */ +export interface PythonProjectOptions extends ProjectOptions { + // -- dependencies -- + + /** + * List of runtime dependencies for this project. + * + * Dependencies use the format: `/@` + * + * Additional dependencies can be added via `project.addDependency()`. + * + * @default [] + */ + readonly deps?: string[]; + + /** + * List of test dependencies for this project. + * + * Dependencies use the format: `/@` + * + * Additional dependencies can be added via `project.addTestDependency()`. + * + * @default [] + */ + readonly testDeps?: string[]; + + // -- core components -- + + /** + * Use pip with a requirements.txt file to track project dependencies. + * + * @default true + */ + readonly pip?: boolean; + + /** + * Use venv to manage a virtual environment for installing dependencies inside. + * + * @default true + */ + readonly venv?: boolean; + + // -- optional components -- + + /** + * Include pytest tests. + * @default true + */ + readonly pytest?: boolean; + + /** + * pytest options + * @default - defaults + */ + readonly pytestOptions?: PytestOptions; + + /** + * Include sample code and test if the relevant directories don't exist. + */ + readonly sample?: boolean; +} + +/** + * Python project. + * + * @pjid python + */ +export class PythonProject extends Project { + /** + * API for managing dependencies. + */ + public readonly depsManager?: IPythonDeps; + + /** + * API for mangaging the Python runtime environment. + */ + public readonly envManager?: IPythonEnv; + + /** + * API for managing packaging the project as a library. Only applies when the `projectType` is LIB. + */ + public readonly packagingManager?: IPythonPackaging; + + /** + * Pytest component. + */ + public readonly pytest?: Pytest; + + constructor(options: PythonProjectOptions) { + super(options); + + if (options.pip ?? true) { + this.depsManager = new Pip(this, options); // ? + } + + if (options.venv ?? true) { + this.envManager = new Venv(this, options); // ? + } + + // if (options.setuptools ?? true) { + // this.packagingManager = new SetupTools(this, options); + // } + + // if (options.conda ?? false) { + // this.depsManager = new Conda(this, options); + // this.envManager = this.depsManager; + // } + + // if (options.pipenv ?? false) { + // this.depsManager = new Pipenv(this, options); + // this.envManager = this.depsManager; + // } + + // if (options.poetry ?? false) { + // this.depsManager = new Poetry(this, options); + // this.envManager = this.depsManager; + // this.packagingManager = this.packagingManager; + // } + + if (!this.depsManager) { + throw new Error('At least one tool must be chosen for managing dependencies (pip, conda, pipenv, or poetry).'); + } + + if (!this.envManager) { + throw new Error('At least one tool must be chosen for managing the environment (venv, conda, pipenv, or poetry).'); + } + + if (!this.packagingManager && this.projectType === ProjectType.LIB) { + throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); + } + + if (options.pytest ?? true) { + this.pytest = new Pytest(this, { + ...options.pytestOptions, + }); + } + + if (options.sample ?? true) { + new PythonSample(this, { projectType: this.projectType }); + } + + for (const dep of options.deps ?? []) { + this.addDependency(dep); + } + + for (const dep of options.testDeps ?? []) { + this.addTestDependency(dep); + } + } + + /** + * Adds a runtime dependency. + * + * @param spec Format `@` + */ + public addDependency(spec: string) { + return this.depsManager?.addDependency(spec); + } + + /** + * Adds a test dependency. + * + * @param spec Format `@` + */ + public addTestDependency(spec: string) { + return this.depsManager?.addTestDependency(spec); + } + + /** + * Adds a dev dependency. + * + * @param spec Format `@` + */ + public addDevDependency(spec: string) { + return this.depsManager?.addDevDependency(spec); + } +} diff --git a/src/python/python-sample.ts b/src/python/python-sample.ts new file mode 100644 index 00000000000..6bf9a80047f --- /dev/null +++ b/src/python/python-sample.ts @@ -0,0 +1,21 @@ +import { Component } from '../component'; +import { Project, ProjectType } from '../project'; +import { SampleDir } from '../sample-file'; + +export interface PythonSampleOptions { + /** + * Which type of project this is. + */ + readonly projectType: ProjectType; +} + +/** + * Python code sample. + */ +export class PythonSample extends Component { + constructor(project: Project, _options: PythonSampleOptions) { + super(project); + + new SampleDir(project, 'test', { files: {} }); + } +} \ No newline at end of file diff --git a/src/python/requirements-file.ts b/src/python/requirements-file.ts new file mode 100644 index 00000000000..ee8d974daf4 --- /dev/null +++ b/src/python/requirements-file.ts @@ -0,0 +1,61 @@ +import { EOL } from 'os'; +import { Dependencies } from '../deps'; +import { FileBase, IResolver } from '../file'; +import { Project } from '../project'; +import { toPythonVersionRange } from '../util/semver'; + +export interface RequirementsFileOptions { + /** + * Accepts a function that resolves to an list of packages that should get included. + * @internal + */ + readonly lazyPackages: any; +} + +/** + * Specifies a list of packages to be installed using pip. + * + * @see https://pip.pypa.io/en/stable/reference/pip_install/#requirements-file-format + */ +export class RequirementsFile extends FileBase { + private readonly packages = new Array(); + private readonly lazyPackages: any; + + constructor(project: Project, filePath: string, options: RequirementsFileOptions) { + super(project, filePath); + + this.lazyPackages = options.lazyPackages; + } + + /** + * Adds the specified packages provided in semver format. + * + * Comment lines (start with `#`) are ignored. + * + * @param packages Package version in format `@` + */ + public addPackages(...packages: string[]) { + for (let pkg of packages) { + if (pkg.startsWith('#')) { + this.packages.push(pkg); + } else { + const { name, version } = Dependencies.parseDependency(pkg); + if (version) { + this.packages.push(`${name}${toPythonVersionRange(version)}`); + } else { + this.packages.push(name); + } + } + } + } + + protected synthesizeContent(resolver: IResolver): string | undefined { + const additionalPackages = resolver.resolve(this.lazyPackages); + this.addPackages(additionalPackages); + + return `${resolver.resolve([ + `# ${FileBase.PROJEN_MARKER}`, + ...this.packages, + ]).join(EOL)}${EOL}`; + } +} diff --git a/src/python/venv.ts b/src/python/venv.ts new file mode 100644 index 00000000000..e88ae77d857 --- /dev/null +++ b/src/python/venv.ts @@ -0,0 +1,11 @@ +import { Component } from '../component'; +import { Project } from '../project'; +import { IPythonEnv } from './python-env'; + +export interface VenvOptions {} + +export class Venv extends Component implements IPythonEnv { + constructor(project: Project, _options: VenvOptions) { + super(project); + } +} \ No newline at end of file From 9fefeff13b887c0fc7a790006836d4bd77648bc7 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 03:13:08 -0500 Subject: [PATCH 02/29] add unit test --- .../__snapshots__/python-project.test.ts.snap | 77 +++++++++++++++++++ src/__tests__/python/python-project.test.ts | 35 +++++---- src/python/requirements-file.ts | 2 +- 3 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 src/__tests__/python/__snapshots__/python-project.test.ts.snap diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap new file mode 100644 index 00000000000..c5e500e3399 --- /dev/null +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`defaults 1`] = ` +Object { + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +node_modules/ +!/.projen/deps.json +!/.projen/tasks.json +!/requirements-dev.txt +!/requirements.txt +", + ".projen/deps.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "dependencies": Array [ + Object { + "name": "pytest", + "type": "test", + "version": "6.2.1", + }, + ], + }, + ".projen/tasks.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "tasks": Object { + "clobber": Object { + "category": "30.maintain", + "condition": "git diff --exit-code > /dev/null", + "description": "hard resets to HEAD of origin and cleans the local repo", + "env": Object { + "BRANCH": "$(git branch --show-current)", + }, + "name": "clobber", + "steps": Array [ + Object { + "exec": "git checkout -b scratch", + "name": "save current HEAD in \\"scratch\\" branch", + }, + Object { + "exec": "git checkout $BRANCH", + }, + Object { + "exec": "git fetch origin", + "name": "fetch latest changes from origin", + }, + Object { + "exec": "git reset --hard origin/$BRANCH", + "name": "hard reset to origin commit", + }, + Object { + "exec": "git clean -fdx", + "name": "clean all untracked files", + }, + Object { + "say": "ready to rock! (unpushed commits are under the \\"scratch\\" branch)", + }, + ], + }, + "test": Object { + "category": "10.test", + "description": "Runs tests", + "name": "test", + "steps": Array [ + Object { + "exec": "pytest", + }, + ], + }, + }, + }, + "README.md": "# replace this", + "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +pytest==6.2.1 +", + "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +", +} +`; diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index 15843b84145..cd217017382 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -1,19 +1,20 @@ -// import { LogLevel } from '../../logger'; -// import { PythonProject, PythonProjectOptions } from '../../python'; -// import { synthSnapshot } from '../util'; +import { LogLevel } from '../../logger'; +import { PythonProject, PythonProjectOptions } from '../../python'; +import { mkdtemp, synthSnapshot } from '../util'; -// test('defaults', () => { -// const p = new TestPythonProject(); -// expect(synthSnapshot(p)).toMatchSnapshot(); -// }); +test('defaults', () => { + const p = new TestPythonProject(); + expect(synthSnapshot(p)).toMatchSnapshot(); +}); -// class TestPythonProject extends PythonProject { -// constructor(options: Partial = { }) { -// super({ -// ...options, -// name: 'test-python-project', -// logging: { level: LogLevel.OFF }, -// jsiiFqn: 'projen.python.PythonProject', -// }); -// } -// } +class TestPythonProject extends PythonProject { + constructor(options: Partial = { }) { + super({ + ...options, + name: 'test-python-project', + outdir: mkdtemp(), + logging: { level: LogLevel.OFF }, + jsiiFqn: 'projen.python.PythonProject', + }); + } +} diff --git a/src/python/requirements-file.ts b/src/python/requirements-file.ts index ee8d974daf4..56433807f05 100644 --- a/src/python/requirements-file.ts +++ b/src/python/requirements-file.ts @@ -51,7 +51,7 @@ export class RequirementsFile extends FileBase { protected synthesizeContent(resolver: IResolver): string | undefined { const additionalPackages = resolver.resolve(this.lazyPackages); - this.addPackages(additionalPackages); + this.addPackages(...additionalPackages); return `${resolver.resolve([ `# ${FileBase.PROJEN_MARKER}`, From 145a509281091d5d3fdade3ec44df286601b8c86 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 03:23:00 -0500 Subject: [PATCH 03/29] add more tests --- .../__snapshots__/python-project.test.ts.snap | 158 +++++++++++++++--- src/__tests__/python/python-project.test.ts | 30 ++++ 2 files changed, 162 insertions(+), 26 deletions(-) diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index c5e500e3399..b1b86f7a5b3 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -22,39 +22,125 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { - "clobber": Object { - "category": "30.maintain", - "condition": "git diff --exit-code > /dev/null", - "description": "hard resets to HEAD of origin and cleans the local repo", - "env": Object { - "BRANCH": "$(git branch --show-current)", - }, - "name": "clobber", + "test": Object { + "category": "10.test", + "description": "Runs tests", + "name": "test", "steps": Array [ Object { - "exec": "git checkout -b scratch", - "name": "save current HEAD in \\"scratch\\" branch", - }, - Object { - "exec": "git checkout $BRANCH", - }, - Object { - "exec": "git fetch origin", - "name": "fetch latest changes from origin", - }, - Object { - "exec": "git reset --hard origin/$BRANCH", - "name": "hard reset to origin commit", - }, - Object { - "exec": "git clean -fdx", - "name": "clean all untracked files", + "exec": "pytest", }, + ], + }, + }, + }, + "README.md": "# replace this", + "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +pytest==6.2.1 +", + "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +", +} +`; + +exports[`dependencies 1`] = ` +Object { + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +node_modules/ +!/.projen/deps.json +!/.projen/tasks.json +!/requirements-dev.txt +!/requirements.txt +", + ".projen/deps.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "dependencies": Array [ + Object { + "name": "aws-cdk.core", + "type": "runtime", + "version": "*", + }, + Object { + "name": "Django", + "type": "runtime", + "version": "3.1.5", + }, + Object { + "name": "hypothesis", + "type": "test", + "version": "^6.0.3", + }, + Object { + "name": "pytest", + "type": "test", + "version": "6.2.1", + }, + ], + }, + ".projen/tasks.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "tasks": Object { + "test": Object { + "category": "10.test", + "description": "Runs tests", + "name": "test", + "steps": Array [ Object { - "say": "ready to rock! (unpushed commits are under the \\"scratch\\" branch)", + "exec": "pytest", }, ], }, + }, + }, + "README.md": "# replace this", + "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +hypothesis>=6.0.3, <7.0.0 +pytest==6.2.1 +", + "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +aws-cdk.core>=0.0.0 +Django==3.1.5 +", +} +`; + +exports[`dependencies via ctor 1`] = ` +Object { + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +node_modules/ +!/.projen/deps.json +!/.projen/tasks.json +!/requirements-dev.txt +!/requirements.txt +", + ".projen/deps.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "dependencies": Array [ + Object { + "name": "aws-cdk.core", + "type": "runtime", + "version": "*", + }, + Object { + "name": "Django", + "type": "runtime", + "version": "3.1.5", + }, + Object { + "name": "hypothesis", + "type": "test", + "version": "^6.0.3", + }, + Object { + "name": "pytest", + "type": "test", + "version": "6.2.1", + }, + ], + }, + ".projen/tasks.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "tasks": Object { "test": Object { "category": "10.test", "description": "Runs tests", @@ -69,7 +155,27 @@ node_modules/ }, "README.md": "# replace this", "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +hypothesis>=6.0.3, <7.0.0 pytest==6.2.1 +", + "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +aws-cdk.core>=0.0.0 +Django==3.1.5 +", +} +`; + +exports[`no pytest 1`] = ` +Object { + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +node_modules/ +!/.projen/deps.json +!/.projen/tasks.json +!/requirements-dev.txt +!/requirements.txt +", + "README.md": "# replace this", + "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index cd217017382..a89c0501d88 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -7,10 +7,40 @@ test('defaults', () => { expect(synthSnapshot(p)).toMatchSnapshot(); }); +test('dependencies', () => { + const p = new TestPythonProject(); + p.addDependency('Django@3.1.5'); + p.addDependency('aws-cdk.core@*'); + p.addTestDependency('hypothesis@^6.0.3'); + expect(synthSnapshot(p)).toMatchSnapshot(); +}); + +test('dependencies via ctor', () => { + const p = new TestPythonProject({ + deps: [ + 'Django@3.1.5', + 'aws-cdk.core@*', + ], + testDeps: [ + 'hypothesis@^6.0.3', + ], + }); + expect(synthSnapshot(p)).toMatchSnapshot(); +}); + +test('no pytest', () => { + const p = new TestPythonProject({ + pytest: false, + }); + + expect(synthSnapshot(p)).toMatchSnapshot(); +}); + class TestPythonProject extends PythonProject { constructor(options: Partial = { }) { super({ ...options, + clobber: false, name: 'test-python-project', outdir: mkdtemp(), logging: { level: LogLevel.OFF }, From 85fcc85996177791e35d2c624efc350b26d3ad4d Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 04:28:36 -0500 Subject: [PATCH 04/29] add some tasks --- .../__snapshots__/python-project.test.ts.snap | 125 ++++++++++++++++++ src/__tests__/python/python-project.test.ts | 11 +- src/python/pytest.ts | 2 +- src/python/python-project.ts | 11 +- src/python/venv.ts | 54 +++++++- 5 files changed, 196 insertions(+), 7 deletions(-) diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index b1b86f7a5b3..c74bf241d62 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -22,6 +22,36 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { + "env:activate": Object { + "category": "99.misc", + "description": "Activate the python environment", + "name": "env:activate", + "steps": Array [ + Object { + "exec": "source .env/bin/activate", + }, + ], + }, + "env:create": Object { + "category": "99.misc", + "description": "Setup the project's python environment", + "name": "env:create", + "steps": Array [ + Object { + "exec": "/usr/bin/python -m venv .env", + }, + ], + }, + "env:deactivate": Object { + "category": "99.misc", + "description": "Deactivate the python environment", + "name": "env:deactivate", + "steps": Array [ + Object { + "exec": "deactivate", + }, + ], + }, "test": Object { "category": "10.test", "description": "Runs tests", @@ -80,6 +110,36 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { + "env:activate": Object { + "category": "99.misc", + "description": "Activate the python environment", + "name": "env:activate", + "steps": Array [ + Object { + "exec": "source .env/bin/activate", + }, + ], + }, + "env:create": Object { + "category": "99.misc", + "description": "Setup the project's python environment", + "name": "env:create", + "steps": Array [ + Object { + "exec": "/usr/bin/python -m venv .env", + }, + ], + }, + "env:deactivate": Object { + "category": "99.misc", + "description": "Deactivate the python environment", + "name": "env:deactivate", + "steps": Array [ + Object { + "exec": "deactivate", + }, + ], + }, "test": Object { "category": "10.test", "description": "Runs tests", @@ -141,6 +201,36 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { + "env:activate": Object { + "category": "99.misc", + "description": "Activate the python environment", + "name": "env:activate", + "steps": Array [ + Object { + "exec": "source .env/bin/activate", + }, + ], + }, + "env:create": Object { + "category": "99.misc", + "description": "Setup the project's python environment", + "name": "env:create", + "steps": Array [ + Object { + "exec": "/usr/bin/python -m venv .env", + }, + ], + }, + "env:deactivate": Object { + "category": "99.misc", + "description": "Deactivate the python environment", + "name": "env:deactivate", + "steps": Array [ + Object { + "exec": "deactivate", + }, + ], + }, "test": Object { "category": "10.test", "description": "Runs tests", @@ -174,6 +264,41 @@ node_modules/ !/requirements-dev.txt !/requirements.txt ", + ".projen/tasks.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "tasks": Object { + "env:activate": Object { + "category": "99.misc", + "description": "Activate the python environment", + "name": "env:activate", + "steps": Array [ + Object { + "exec": "source .env/bin/activate", + }, + ], + }, + "env:create": Object { + "category": "99.misc", + "description": "Setup the project's python environment", + "name": "env:create", + "steps": Array [ + Object { + "exec": "/usr/bin/python -m venv .env", + }, + ], + }, + "env:deactivate": Object { + "category": "99.misc", + "description": "Deactivate the python environment", + "name": "env:deactivate", + "steps": Array [ + Object { + "exec": "deactivate", + }, + ], + }, + }, + }, "README.md": "# replace this", "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index a89c0501d88..4079d51a707 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -1,5 +1,6 @@ import { LogLevel } from '../../logger'; import { PythonProject, PythonProjectOptions } from '../../python'; +import { execOrUndefined } from '../../util'; import { mkdtemp, synthSnapshot } from '../util'; test('defaults', () => { @@ -38,11 +39,19 @@ test('no pytest', () => { class TestPythonProject extends PythonProject { constructor(options: Partial = { }) { + const workdir = mkdtemp(); + const pythonPath = execOrUndefined('which python', { cwd: workdir }); + + if (!pythonPath) { + fail('Failed to obtain a valid python executable path for tests.'); + } + super({ ...options, clobber: false, name: 'test-python-project', - outdir: mkdtemp(), + pythonPath: pythonPath, + outdir: workdir, logging: { level: LogLevel.OFF }, jsiiFqn: 'projen.python.PythonProject', }); diff --git a/src/python/pytest.ts b/src/python/pytest.ts index b80a2b36852..363c507b763 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -17,7 +17,7 @@ export class Pytest extends Component { const version = options.version ?? '6.2.1'; - project.depsManager?.addTestDependency(`pytest@${version}`); + project.addTestDependency(`pytest@${version}`); project.addTask('test', { description: 'Runs tests', diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 1e0d9a80267..8a478ec1b75 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -11,6 +11,11 @@ import { Venv } from './venv'; * Options for `PythonProject`. */ export interface PythonProjectOptions extends ProjectOptions { + /** + * Absolute path to the user's python installation. + */ + readonly pythonPath: string; + // -- dependencies -- /** @@ -101,11 +106,13 @@ export class PythonProject extends Project { super(options); if (options.pip ?? true) { - this.depsManager = new Pip(this, options); // ? + this.depsManager = new Pip(this, {}); } if (options.venv ?? true) { - this.envManager = new Venv(this, options); // ? + this.envManager = new Venv(this, { + pythonPath: options.pythonPath, + }); } // if (options.setuptools ?? true) { diff --git a/src/python/venv.ts b/src/python/venv.ts index e88ae77d857..358987ed9b4 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -1,11 +1,59 @@ import { Component } from '../component'; -import { Project } from '../project'; +import { Task, TaskCategory } from '../tasks'; import { IPythonEnv } from './python-env'; +import { PythonProject } from './python-project'; -export interface VenvOptions {} +export interface VenvOptions { + /** + * Absolute path to the user's python installation. + */ + readonly pythonPath: string; + + /** + * Name of directory to store the environment in + * + * @default ".env" + */ + readonly envdir?: string; +} export class Venv extends Component implements IPythonEnv { - constructor(project: Project, _options: VenvOptions) { + /** + * Absolute path to the user's python installation. + */ + private readonly pythonPath: string; + + /** + * Name of directory to store the environment in + */ + private readonly envdir: string; + + public readonly createEnvTask: Task; + public readonly activateTask: Task; + public readonly deactivateTask: Task; + + constructor(project: PythonProject, options: VenvOptions) { super(project); + + this.pythonPath = options.pythonPath; + this.envdir = options.envdir ?? '.env'; + + this.createEnvTask = project.addTask('env:create', { + description: 'Setup the project\'s python environment', + category: TaskCategory.MISC, + exec: `${this.pythonPath} -m venv ${this.envdir}`, + }); + + this.activateTask = project.addTask('env:activate', { + description: 'Activate the python environment', + category: TaskCategory.MISC, + exec: `source ${this.envdir}/bin/activate`, + }); + + this.deactivateTask = project.addTask('env:deactivate', { + description: 'Deactivate the python environment', + category: TaskCategory.MISC, + exec: 'deactivate', + }); } } \ No newline at end of file From a02ae10981fa179e1172d6547965b1a5a53cc002 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 04:53:59 -0500 Subject: [PATCH 05/29] add docs, addEnvTask --- API.md | 488 +++++++++++++++++- .../__snapshots__/python-project.test.ts.snap | 18 + src/__tests__/python/python-project.test.ts | 12 +- src/index.ts | 3 +- src/python/pip.ts | 4 +- src/python/pytest.ts | 2 +- src/python/python-env.ts | 7 +- src/python/python-project.ts | 28 +- src/python/requirements-file.ts | 4 +- 9 files changed, 543 insertions(+), 23 deletions(-) diff --git a/API.md b/API.md index e533666a231..9d5873ae6a6 100644 --- a/API.md +++ b/API.md @@ -54,6 +54,12 @@ Name|Description [java.MavenSample](#projen-java-mavensample)|Java code sample. [java.Pom](#projen-java-pom)|A Project Object Model or POM is the fundamental unit of work in Maven. [java.Projenrc](#projen-java-projenrc)|Allows writing projenrc files in java. +[python.Pip](#projen-python-pip)|*No description* +[python.Pytest](#projen-python-pytest)|*No description* +[python.PythonProject](#projen-python-pythonproject)|Python project. +[python.PythonSample](#projen-python-pythonsample)|Python code sample. +[python.RequirementsFile](#projen-python-requirementsfile)|Specifies a list of packages to be installed using pip. +[python.Venv](#projen-python-venv)|*No description* [tasks.Task](#projen-tasks-task)|A task that can be performed on the project. [tasks.TaskRuntime](#projen-tasks-taskruntime)|The runtime component of the tasks engine. [tasks.Tasks](#projen-tasks-tasks)|Defines project tasks. @@ -150,6 +156,12 @@ Name|Description [java.PomOptions](#projen-java-pomoptions)|Options for `Pom`. [java.ProjenrcCommonOptions](#projen-java-projenrccommonoptions)|Options for `Projenrc`. [java.ProjenrcOptions](#projen-java-projenrcoptions)|*No description* +[python.PipOptions](#projen-python-pipoptions)|*No description* +[python.PytestOptions](#projen-python-pytestoptions)|*No description* +[python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. +[python.PythonSampleOptions](#projen-python-pythonsampleoptions)|*No description* +[python.RequirementsFileOptions](#projen-python-requirementsfileoptions)|*No description* +[python.VenvOptions](#projen-python-venvoptions)|*No description* [tasks.TaskCommonOptions](#projen-tasks-taskcommonoptions)|*No description* [tasks.TaskOptions](#projen-tasks-taskoptions)|*No description* [tasks.TaskSpec](#projen-tasks-taskspec)|Specification of a single task. @@ -183,6 +195,9 @@ Name|Description [IDockerComposeVolumeConfig](#projen-idockercomposevolumeconfig)|Storage for volume configuration. [IMarkableFile](#projen-imarkablefile)|Files that may include the Projen marker. [IResolver](#projen-iresolver)|API for resolving tokens when synthesizing file content. +[python.IPythonDeps](#projen-python-ipythondeps)|*No description* +[python.IPythonEnv](#projen-python-ipythonenv)|*No description* +[python.IPythonPackaging](#projen-python-ipythonpackaging)|*No description* **Enums** @@ -1340,7 +1355,7 @@ addRules(rules: Map): void __Extends__: [Component](#projen-component) -__Implemented by__: [github.PullRequestTemplate](#projen-github-pullrequesttemplate), [web.NextJsTypeDef](#projen-web-nextjstypedef), [web.ReactTypeDef](#projen-web-reacttypedef), [IgnoreFile](#projen-ignorefile), [JsonFile](#projen-jsonfile), [License](#projen-license), [Makefile](#projen-makefile), [TextFile](#projen-textfile), [TomlFile](#projen-tomlfile), [XmlFile](#projen-xmlfile), [YamlFile](#projen-yamlfile) +__Implemented by__: [github.PullRequestTemplate](#projen-github-pullrequesttemplate), [python.RequirementsFile](#projen-python-requirementsfile), [web.NextJsTypeDef](#projen-web-nextjstypedef), [web.ReactTypeDef](#projen-web-reacttypedef), [IgnoreFile](#projen-ignorefile), [JsonFile](#projen-jsonfile), [License](#projen-license), [Makefile](#projen-makefile), [TextFile](#projen-textfile), [TomlFile](#projen-tomlfile), [XmlFile](#projen-xmlfile), [YamlFile](#projen-yamlfile) __Obtainable from__: [Project](#projen-project).[tryFindFile](#projen-project#projen-project-tryfindfile)() ### Initializer @@ -4677,6 +4692,324 @@ Name | Type | Description +## class Pip 🔹 + + + +__Implements__: [python.IPythonDeps](#projen-python-ipythondeps) +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.Pip(project: Project, _options: PipOptions) +``` + +* **project** ([Project](#projen-project)) *No description* +* **_options** ([python.PipOptions](#projen-python-pipoptions)) *No description* + + +### Methods + + +#### addDependency(spec)🔹 + +Adds a runtime dependency. + +```ts +addDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addDevDependency(spec)🔹 + +Adds a dev dependency. + +```ts +addDevDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addTestDependency(spec)🔹 + +Adds a test dependency. + +```ts +addTestDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + + + +## class Pytest 🔹 + + + +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.Pytest(project: PythonProject, options: PytestOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **options** ([python.PytestOptions](#projen-python-pytestoptions)) *No description* + * **version** (string) Pytest version. __*Default*__: "6.2.1" + + + + +## class PythonProject 🔹 + +Python project. + +__Submodule__: python + +__Extends__: [Project](#projen-project) + +### Initializer + + + + +```ts +new python.PythonProject(options: PythonProjectOptions) +``` + +* **options** ([python.PythonProjectOptions](#projen-python-pythonprojectoptions)) *No description* + * **name** (string) This is the name of your project. + * **clobber** (boolean) Add a `clobber` task which resets the repo to origin. __*Default*__: true + * **devContainer** (boolean) Add a VSCode development environment (used for GitHub Codespaces). __*Default*__: false + * **gitpod** (boolean) Add a Gitpod development environment. __*Default*__: false + * **jsiiFqn** (string) The JSII FQN (fully qualified name) of the project class. __*Default*__: undefined + * **logging** ([LoggerOptions](#projen-loggeroptions)) Configure logging options such as verbosity. __*Default*__: {} + * **outdir** (string) The root directory of the project. __*Default*__: "." + * **parent** ([Project](#projen-project)) The parent project, if this project is part of a bigger project. __*Optional*__ + * **projectType** ([ProjectType](#projen-projecttype)) Which type of project this is (library/app). __*Default*__: ProjectType.UNKNOWN + * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } + * **pythonPath** (string) Absolute path to the user's python installation. + * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] + * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true + * **pytest** (boolean) Include pytest tests. __*Default*__: true + * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults + * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Optional*__ + * **testDeps** (Array) List of test dependencies for this project. __*Default*__: [] + * **venv** (boolean) Use venv to manage a virtual environment for installing dependencies inside. __*Default*__: true + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**depsManager**🔹 | [python.IPythonDeps](#projen-python-ipythondeps) | API for managing dependencies. +**envManager**🔹 | [python.IPythonEnv](#projen-python-ipythonenv) | API for mangaging the Python runtime environment. +**packagingManager**🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library. +**pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ + +### Methods + + +#### addDependency(spec)🔹 + +Adds a runtime dependency. + +```ts +addDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addDevDependency(spec)🔹 + +Adds a dev dependency. + +```ts +addDevDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addEnvTask(name, props?)🔹 + +Adds a task that runs in the project's virtual environment. + +```ts +addEnvTask(name: string, props?: TaskOptions): Task +``` + +* **name** (string) The task name to add. +* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) Task properties. + * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC + * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ + * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() + * **description** (string) The description of this build command. __*Default*__: the task name + * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} + * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` + +__Returns__: +* [tasks.Task](#projen-tasks-task) + +#### addTestDependency(spec)🔹 + +Adds a test dependency. + +```ts +addTestDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + + + +## class PythonSample 🔹 + +Python code sample. + +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.PythonSample(project: Project, _options: PythonSampleOptions) +``` + +* **project** ([Project](#projen-project)) *No description* +* **_options** ([python.PythonSampleOptions](#projen-python-pythonsampleoptions)) *No description* + * **projectType** ([ProjectType](#projen-projecttype)) Which type of project this is. + + + + +## class RequirementsFile 🔹 + +Specifies a list of packages to be installed using pip. + +__Submodule__: python + +__Extends__: [FileBase](#projen-filebase) + +### Initializer + + + + +```ts +new python.RequirementsFile(project: Project, filePath: string, options: RequirementsFileOptions) +``` + +* **project** ([Project](#projen-project)) *No description* +* **filePath** (string) *No description* +* **options** ([python.RequirementsFileOptions](#projen-python-requirementsfileoptions)) *No description* + + +### Methods + + +#### addPackages(...packages)🔹 + +Adds the specified packages provided in semver format. + +Comment lines (start with `#`) are ignored. + +```ts +addPackages(...packages: string[]): void +``` + +* **packages** (string) Package version in format `@`. + + + + +#### protected synthesizeContent(resolver)🔹 + +Implemented by derived classes and returns the contents of the file to emit. + +```ts +protected synthesizeContent(resolver: IResolver): string +``` + +* **resolver** ([IResolver](#projen-iresolver)) *No description* + +__Returns__: +* string + + + +## class Venv 🔹 + + + +__Implements__: [python.IPythonEnv](#projen-python-ipythonenv) +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.Venv(project: PythonProject, options: VenvOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **options** ([python.VenvOptions](#projen-python-venvoptions)) *No description* + * **pythonPath** (string) Absolute path to the user's python installation. + * **envdir** (string) Name of directory to store the environment in. __*Default*__: ".env" + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**activateTask**🔹 | [tasks.Task](#projen-tasks-task) | +**createEnvTask**🔹 | [tasks.Task](#projen-tasks-task) | +**deactivateTask**🔹 | [tasks.Task](#projen-tasks-task) | + + + ## class Task 🔹 A task that can be performed on the project. @@ -8147,6 +8480,159 @@ Name | Type | Description +## interface IPythonDeps 🔹 + +__Implemented by__: [python.Pip](#projen-python-pip) + + +### Methods + + +#### addDependency(spec)🔹 + +Adds a runtime dependency. + +```ts +addDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addDevDependency(spec)🔹 + +Adds a dev dependency. + +```ts +addDevDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addTestDependency(spec)🔹 + +Adds a test dependency. + +```ts +addTestDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + + + +## interface IPythonEnv 🔹 + +__Implemented by__: [python.Venv](#projen-python-venv) + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**activateTask**🔹 | [tasks.Task](#projen-tasks-task) | +**deactivateTask**🔹 | [tasks.Task](#projen-tasks-task) | + + + +## interface IPythonPackaging 🔹 + + + + + +## struct PipOptions 🔹 + + + + + +## struct PytestOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**version**?🔹 | string | Pytest version.
__*Default*__: "6.2.1" + + + +## struct PythonProjectOptions 🔹 + + +Options for `PythonProject`. + + + +Name | Type | Description +-----|------|------------- +**name**🔹 | string | This is the name of your project. +**pythonPath**🔹 | string | Absolute path to the user's python installation. +**clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true +**deps**?🔹 | Array | List of runtime dependencies for this project.
__*Default*__: [] +**devContainer**?🔹 | boolean | Add a VSCode development environment (used for GitHub Codespaces).
__*Default*__: false +**gitpod**?🔹 | boolean | Add a Gitpod development environment.
__*Default*__: false +**jsiiFqn**?🔹 | string | The JSII FQN (fully qualified name) of the project class.
__*Default*__: undefined +**logging**?🔹 | [LoggerOptions](#projen-loggeroptions) | Configure logging options such as verbosity.
__*Default*__: {} +**outdir**?🔹 | string | The root directory of the project.
__*Default*__: "." +**parent**?🔹 | [Project](#projen-project) | The parent project, if this project is part of a bigger project.
__*Optional*__ +**pip**?🔹 | boolean | Use pip with a requirements.txt file to track project dependencies.
__*Default*__: true +**projectType**?🔹 | [ProjectType](#projen-projecttype) | Which type of project this is (library/app).
__*Default*__: ProjectType.UNKNOWN +**pytest**?🔹 | boolean | Include pytest tests.
__*Default*__: true +**pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults +**readme**?🔹 | [SampleReadmeProps](#projen-samplereadmeprops) | The README setup.
__*Default*__: { filename: 'README.md', contents: '# replace this' } +**sample**?🔹 | boolean | Include sample code and test if the relevant directories don't exist.
__*Optional*__ +**testDeps**?🔹 | Array | List of test dependencies for this project.
__*Default*__: [] +**venv**?🔹 | boolean | Use venv to manage a virtual environment for installing dependencies inside.
__*Default*__: true + + + +## struct PythonSampleOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**projectType**🔹 | [ProjectType](#projen-projecttype) | Which type of project this is. + + + +## struct RequirementsFileOptions 🔹 + + + + + +## struct VenvOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**pythonPath**🔹 | string | Absolute path to the user's python installation. +**envdir**?🔹 | string | Name of directory to store the environment in.
__*Default*__: ".env" + + + ## struct TaskCommonOptions 🔹 diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index c74bf241d62..4a4289577e6 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -57,9 +57,15 @@ node_modules/ "description": "Runs tests", "name": "test", "steps": Array [ + Object { + "spawn": "env:activate", + }, Object { "exec": "pytest", }, + Object { + "spawn": "env:deactivate", + }, ], }, }, @@ -145,9 +151,15 @@ node_modules/ "description": "Runs tests", "name": "test", "steps": Array [ + Object { + "spawn": "env:activate", + }, Object { "exec": "pytest", }, + Object { + "spawn": "env:deactivate", + }, ], }, }, @@ -236,9 +248,15 @@ node_modules/ "description": "Runs tests", "name": "test", "steps": Array [ + Object { + "spawn": "env:activate", + }, Object { "exec": "pytest", }, + Object { + "spawn": "env:deactivate", + }, ], }, }, diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index 4079d51a707..a7499882e44 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -1,6 +1,5 @@ import { LogLevel } from '../../logger'; import { PythonProject, PythonProjectOptions } from '../../python'; -import { execOrUndefined } from '../../util'; import { mkdtemp, synthSnapshot } from '../util'; test('defaults', () => { @@ -39,19 +38,12 @@ test('no pytest', () => { class TestPythonProject extends PythonProject { constructor(options: Partial = { }) { - const workdir = mkdtemp(); - const pythonPath = execOrUndefined('which python', { cwd: workdir }); - - if (!pythonPath) { - fail('Failed to obtain a valid python executable path for tests.'); - } - super({ ...options, clobber: false, name: 'test-python-project', - pythonPath: pythonPath, - outdir: workdir, + pythonPath: '/usr/bin/python', + outdir: mkdtemp(), logging: { level: LogLevel.OFF }, jsiiFqn: 'projen.python.PythonProject', }); diff --git a/src/index.ts b/src/index.ts index c822543bc4f..cbae440812b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,4 +36,5 @@ export * as web from './web'; export * as github from './github'; export * as vscode from './vscode'; export * as deps from './deps'; -export * as java from './java'; \ No newline at end of file +export * as java from './java'; +export * as python from './python'; diff --git a/src/python/pip.ts b/src/python/pip.ts index 25b16900714..0cf7f20b207 100644 --- a/src/python/pip.ts +++ b/src/python/pip.ts @@ -10,8 +10,8 @@ export class Pip extends Component implements IPythonDeps { constructor(project: Project, _options: PipOptions) { super(project); - new RequirementsFile(project, 'requirements.txt', { lazyPackages: () => this.synthDependencies() }); - new RequirementsFile(project, 'requirements-dev.txt', { lazyPackages: () => this.synthDevDependencies() }); + new RequirementsFile(project, 'requirements.txt', { _lazyPackages: () => this.synthDependencies() }); + new RequirementsFile(project, 'requirements-dev.txt', { _lazyPackages: () => this.synthDevDependencies() }); } private synthDependencies() { diff --git a/src/python/pytest.ts b/src/python/pytest.ts index 363c507b763..d20aa996b6d 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -19,7 +19,7 @@ export class Pytest extends Component { project.addTestDependency(`pytest@${version}`); - project.addTask('test', { + project.addEnvTask('test', { description: 'Runs tests', category: TaskCategory.TEST, exec: 'pytest', diff --git a/src/python/python-env.ts b/src/python/python-env.ts index 6f684aaf22c..3f7a7d57e17 100644 --- a/src/python/python-env.ts +++ b/src/python/python-env.ts @@ -1 +1,6 @@ -export interface IPythonEnv {} +import { Task } from '../tasks'; + +export interface IPythonEnv { + readonly activateTask: Task; + readonly deactivateTask: Task; +} diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 8a478ec1b75..557ef46c020 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -1,4 +1,5 @@ import { Project, ProjectOptions, ProjectType } from '../project'; +import { TaskOptions } from '../tasks'; import { Pip } from './pip'; import { Pytest, PytestOptions } from './pytest'; import { IPythonDeps } from './python-deps'; @@ -85,17 +86,17 @@ export class PythonProject extends Project { /** * API for managing dependencies. */ - public readonly depsManager?: IPythonDeps; + public readonly depsManager!: IPythonDeps; /** * API for mangaging the Python runtime environment. */ - public readonly envManager?: IPythonEnv; + public readonly envManager!: IPythonEnv; /** * API for managing packaging the project as a library. Only applies when the `projectType` is LIB. */ - public readonly packagingManager?: IPythonPackaging; + public readonly packagingManager!: IPythonPackaging; /** * Pytest component. @@ -143,8 +144,12 @@ export class PythonProject extends Project { throw new Error('At least one tool must be chosen for managing the environment (venv, conda, pipenv, or poetry).'); } - if (!this.packagingManager && this.projectType === ProjectType.LIB) { - throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); + if (!this.packagingManager) { + if (this.projectType === ProjectType.LIB) { + throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); + } else { + this.packagingManager = {}; // no-op packaging manager + } } if (options.pytest ?? true) { @@ -166,6 +171,19 @@ export class PythonProject extends Project { } } + /** + * Adds a task that runs in the project's virtual environment. + * + * @param name The task name to add + * @param props Task properties + */ + public addEnvTask(name: string, props: TaskOptions = { }) { + const task = this.tasks.addTask(name, props); + task.prependSpawn(this.envManager?.activateTask); + task.spawn(this.envManager?.deactivateTask); + return task; + } + /** * Adds a runtime dependency. * diff --git a/src/python/requirements-file.ts b/src/python/requirements-file.ts index 56433807f05..2e125c385cb 100644 --- a/src/python/requirements-file.ts +++ b/src/python/requirements-file.ts @@ -9,7 +9,7 @@ export interface RequirementsFileOptions { * Accepts a function that resolves to an list of packages that should get included. * @internal */ - readonly lazyPackages: any; + readonly _lazyPackages: any; } /** @@ -24,7 +24,7 @@ export class RequirementsFile extends FileBase { constructor(project: Project, filePath: string, options: RequirementsFileOptions) { super(project, filePath); - this.lazyPackages = options.lazyPackages; + this.lazyPackages = options._lazyPackages; } /** From 27e76213e33a4bbac1c7140f8f989aef774903f0 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sat, 23 Jan 2021 05:29:21 -0500 Subject: [PATCH 06/29] add sample files --- API.md | 16 +- README.md | 1 + .../__snapshots__/inventory.test.ts.snap | 233 ++++++++++++++++++ src/__tests__/__snapshots__/new.test.ts.snap | 33 +++ .../__snapshots__/python-project.test.ts.snap | 68 +++++ src/python/python-project.ts | 33 ++- src/python/python-sample.ts | 37 ++- src/python/requirements-file.ts | 9 +- 8 files changed, 399 insertions(+), 31 deletions(-) diff --git a/API.md b/API.md index 9d5873ae6a6..ba50e1e0e5f 100644 --- a/API.md +++ b/API.md @@ -159,7 +159,7 @@ Name|Description [python.PipOptions](#projen-python-pipoptions)|*No description* [python.PytestOptions](#projen-python-pytestoptions)|*No description* [python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. -[python.PythonSampleOptions](#projen-python-pythonsampleoptions)|*No description* +[python.PythonSampleOptions](#projen-python-pythonsampleoptions)|Options for python sample code. [python.RequirementsFileOptions](#projen-python-requirementsfileoptions)|*No description* [python.VenvOptions](#projen-python-venvoptions)|*No description* [tasks.TaskCommonOptions](#projen-tasks-taskcommonoptions)|*No description* @@ -4812,6 +4812,7 @@ new python.PythonProject(options: PythonProjectOptions) * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } * **pythonPath** (string) Absolute path to the user's python installation. * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] + * **devDeps** (Array) List of dev dependencies for this project. __*Default*__: [] * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults @@ -4909,12 +4910,11 @@ __Extends__: [Component](#projen-component) ```ts -new python.PythonSample(project: Project, _options: PythonSampleOptions) +new python.PythonSample(project: Project, _options?: PythonSampleOptions) ``` * **project** ([Project](#projen-project)) *No description* * **_options** ([python.PythonSampleOptions](#projen-python-pythonsampleoptions)) *No description* - * **projectType** ([ProjectType](#projen-projecttype)) Which type of project this is. @@ -8584,6 +8584,7 @@ Name | Type | Description **clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true **deps**?🔹 | Array | List of runtime dependencies for this project.
__*Default*__: [] **devContainer**?🔹 | boolean | Add a VSCode development environment (used for GitHub Codespaces).
__*Default*__: false +**devDeps**?🔹 | Array | List of dev dependencies for this project.
__*Default*__: [] **gitpod**?🔹 | boolean | Add a Gitpod development environment.
__*Default*__: false **jsiiFqn**?🔹 | string | The JSII FQN (fully qualified name) of the project class.
__*Default*__: undefined **logging**?🔹 | [LoggerOptions](#projen-loggeroptions) | Configure logging options such as verbosity.
__*Default*__: {} @@ -8603,14 +8604,7 @@ Name | Type | Description ## struct PythonSampleOptions 🔹 - - - - -Name | Type | Description ------|------|------------- -**projectType**🔹 | [ProjectType](#projen-projecttype) | Which type of project this is. - +Options for python sample code. ## struct RequirementsFileOptions 🔹 diff --git a/README.md b/README.md index dc237cd4d31..9781a28b572 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ list): * [nextjs-ts](https://github.com/projen/projen/blob/master/API.md#projen-web.nextjstypescriptproject) - Next.js project with TypeScript. * [node](https://github.com/projen/projen/blob/master/API.md#projen-nodeproject) - Node.js project. * [project](https://github.com/projen/projen/blob/master/API.md#projen-project) - Base project. +* [python](https://github.com/projen/projen/blob/master/API.md#projen-python.pythonproject) - Python project. * [react](https://github.com/projen/projen/blob/master/API.md#projen-web.reactproject) - React project without TypeScript. * [react-ts](https://github.com/projen/projen/blob/master/API.md#projen-web.reacttypescriptproject) - React project with TypeScript. * [typescript](https://github.com/projen/projen/blob/master/API.md#projen-typescriptproject) - TypeScript project. diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 22f53cf9b68..2e0cacec1fd 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -7885,6 +7885,239 @@ Array [ "pjid": "project", "typename": "Project", }, + Object { + "docs": "Python project.", + "docsurl": "https://github.com/projen/projen/blob/master/API.md#projen-python.pythonproject", + "fqn": "projen.python.PythonProject", + "moduleName": "projen", + "options": Array [ + Object { + "default": "true", + "docs": "Add a \`clobber\` task which resets the repo to origin.", + "name": "clobber", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "clobber", + ], + "switch": "clobber", + "type": "boolean", + }, + Object { + "default": "[]", + "docs": "List of runtime dependencies for this project.", + "name": "deps", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "deps", + ], + "switch": "deps", + "type": "unknown", + }, + Object { + "default": "false", + "docs": "Add a VSCode development environment (used for GitHub Codespaces).", + "name": "devContainer", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "devContainer", + ], + "switch": "dev-container", + "type": "boolean", + }, + Object { + "default": "[]", + "docs": "List of dev dependencies for this project.", + "name": "devDeps", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "devDeps", + ], + "switch": "dev-deps", + "type": "unknown", + }, + Object { + "default": "false", + "docs": "Add a Gitpod development environment.", + "name": "gitpod", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "gitpod", + ], + "switch": "gitpod", + "type": "boolean", + }, + Object { + "docs": "The JSII FQN (fully qualified name) of the project class.", + "name": "jsiiFqn", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "jsiiFqn", + ], + "switch": "jsii-fqn", + "type": "string", + }, + Object { + "default": "{}", + "docs": "Configure logging options such as verbosity.", + "name": "logging", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "logging", + ], + "switch": "logging", + "type": "LoggerOptions", + }, + Object { + "default": "$BASEDIR", + "docs": "This is the name of your project.", + "name": "name", + "parent": "ProjectOptions", + "path": Array [ + "name", + ], + "switch": "name", + "type": "string", + }, + Object { + "default": "\\".\\"", + "docs": "The root directory of the project.", + "name": "outdir", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "outdir", + ], + "switch": "outdir", + "type": "string", + }, + Object { + "docs": "The parent project, if this project is part of a bigger project.", + "name": "parent", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "parent", + ], + "switch": "parent", + "type": "Project", + }, + Object { + "default": "true", + "docs": "Use pip with a requirements.txt file to track project dependencies.", + "name": "pip", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "pip", + ], + "switch": "pip", + "type": "boolean", + }, + Object { + "default": "ProjectType.UNKNOWN", + "docs": "Which type of project this is (library/app).", + "name": "projectType", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "projectType", + ], + "switch": "project-type", + "type": "ProjectType", + }, + Object { + "default": "true", + "docs": "Include pytest tests.", + "name": "pytest", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "pytest", + ], + "switch": "pytest", + "type": "boolean", + }, + Object { + "default": "- defaults", + "docs": "pytest options.", + "name": "pytestOptions", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "pytestOptions", + ], + "switch": "pytest-options", + "type": "PytestOptions", + }, + Object { + "default": "\\"/usr/bin/python\\"", + "docs": "Absolute path to the user's python installation.", + "name": "pythonPath", + "parent": "PythonProjectOptions", + "path": Array [ + "pythonPath", + ], + "switch": "python-path", + "type": "string", + }, + Object { + "default": "- { filename: 'README.md', contents: '# replace this' }", + "docs": "The README setup.", + "name": "readme", + "optional": true, + "parent": "ProjectOptions", + "path": Array [ + "readme", + ], + "switch": "readme", + "type": "SampleReadmeProps", + }, + Object { + "docs": "Include sample code and test if the relevant directories don't exist.", + "name": "sample", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "sample", + ], + "switch": "sample", + "type": "boolean", + }, + Object { + "default": "[]", + "docs": "List of test dependencies for this project.", + "name": "testDeps", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "testDeps", + ], + "switch": "test-deps", + "type": "unknown", + }, + Object { + "default": "true", + "docs": "Use venv to manage a virtual environment for installing dependencies inside.", + "name": "venv", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "venv", + ], + "switch": "venv", + "type": "boolean", + }, + ], + "pjid": "python", + "typename": "python.PythonProject", + }, Object { "docs": "React project without TypeScript.", "docsurl": "https://github.com/projen/projen/blob/master/API.md#projen-web.reactproject", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index ef047276f31..2ea16141611 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -813,6 +813,39 @@ project.synth(); " `; +exports[`projen new python 1`] = ` +"const { python } = require('projen'); + +const project = new python.PythonProject({ + jsiiFqn: \\"projen.python.PythonProject\\", + name: 'my-project', + pythonPath: '/usr/bin/python', + + /* ProjectOptions */ + // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ + // devContainer: false, /* Add a VSCode development environment (used for GitHub Codespaces). */ + // gitpod: false, /* Add a Gitpod development environment. */ + // logging: {}, /* Configure logging options such as verbosity. */ + // outdir: '.', /* The root directory of the project. */ + // parent: undefined, /* The parent project, if this project is part of a bigger project. */ + // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ + // readme: undefined, /* The README setup. */ + + /* PythonProjectOptions */ + // deps: [], /* List of runtime dependencies for this project. */ + // devDeps: [], /* List of dev dependencies for this project. */ + // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ + // pytest: true, /* Include pytest tests. */ + // pytestOptions: undefined, /* pytest options. */ + // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ + // testDeps: [], /* List of test dependencies for this project. */ + // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ +}); + +project.synth(); +" +`; + exports[`projen new react 1`] = ` "const { web } = require('projen'); diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index 4a4289577e6..d6d3837e52f 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -75,6 +75,23 @@ node_modules/ pytest==6.2.1 ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +", + "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" +", + "test-python-project/__main__.py": "from .example import hello + +if __name__ == \\"__main__\\": + name = input(\\"What is your name? \\") + print(hello(name)) +", + "test-python-project/example.py": "def hello(name: str) -> str: + \\"\\"\\"A simple greeting. + Args: + name (str): Name to greet. + Returns: + str: greeting message + \\"\\"\\" + return f\\"Hello {name}!\\" ", } `; @@ -172,6 +189,23 @@ pytest==6.2.1 "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". aws-cdk.core>=0.0.0 Django==3.1.5 +", + "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" +", + "test-python-project/__main__.py": "from .example import hello + +if __name__ == \\"__main__\\": + name = input(\\"What is your name? \\") + print(hello(name)) +", + "test-python-project/example.py": "def hello(name: str) -> str: + \\"\\"\\"A simple greeting. + Args: + name (str): Name to greet. + Returns: + str: greeting message + \\"\\"\\" + return f\\"Hello {name}!\\" ", } `; @@ -269,6 +303,23 @@ pytest==6.2.1 "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". aws-cdk.core>=0.0.0 Django==3.1.5 +", + "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" +", + "test-python-project/__main__.py": "from .example import hello + +if __name__ == \\"__main__\\": + name = input(\\"What is your name? \\") + print(hello(name)) +", + "test-python-project/example.py": "def hello(name: str) -> str: + \\"\\"\\"A simple greeting. + Args: + name (str): Name to greet. + Returns: + str: greeting message + \\"\\"\\" + return f\\"Hello {name}!\\" ", } `; @@ -321,6 +372,23 @@ node_modules/ "requirements-dev.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +", + "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" +", + "test-python-project/__main__.py": "from .example import hello + +if __name__ == \\"__main__\\": + name = input(\\"What is your name? \\") + print(hello(name)) +", + "test-python-project/example.py": "def hello(name: str) -> str: + \\"\\"\\"A simple greeting. + Args: + name (str): Name to greet. + Returns: + str: greeting message + \\"\\"\\" + return f\\"Hello {name}!\\" ", } `; diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 557ef46c020..b3490d57cb0 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -14,6 +14,8 @@ import { Venv } from './venv'; export interface PythonProjectOptions extends ProjectOptions { /** * Absolute path to the user's python installation. + * + * @default "/usr/bin/python" */ readonly pythonPath: string; @@ -22,7 +24,7 @@ export interface PythonProjectOptions extends ProjectOptions { /** * List of runtime dependencies for this project. * - * Dependencies use the format: `/@` + * Dependencies use the format: `@` * * Additional dependencies can be added via `project.addDependency()`. * @@ -33,7 +35,7 @@ export interface PythonProjectOptions extends ProjectOptions { /** * List of test dependencies for this project. * - * Dependencies use the format: `/@` + * Dependencies use the format: `@` * * Additional dependencies can be added via `project.addTestDependency()`. * @@ -41,6 +43,17 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly testDeps?: string[]; + /** + * List of dev dependencies for this project. + * + * Dependencies use the format: `@` + * + * Additional dependencies can be added via `project.addDevDependency()`. + * + * @default [] + */ + readonly devDeps?: string[]; + // -- core components -- /** @@ -159,7 +172,7 @@ export class PythonProject extends Project { } if (options.sample ?? true) { - new PythonSample(this, { projectType: this.projectType }); + new PythonSample(this, {}); } for (const dep of options.deps ?? []) { @@ -169,6 +182,10 @@ export class PythonProject extends Project { for (const dep of options.testDeps ?? []) { this.addTestDependency(dep); } + + for (const dep of options.devDeps ?? []) { + this.addDevDependency(dep); + } } /** @@ -179,8 +196,8 @@ export class PythonProject extends Project { */ public addEnvTask(name: string, props: TaskOptions = { }) { const task = this.tasks.addTask(name, props); - task.prependSpawn(this.envManager?.activateTask); - task.spawn(this.envManager?.deactivateTask); + task.prependSpawn(this.envManager.activateTask); + task.spawn(this.envManager.deactivateTask); return task; } @@ -190,7 +207,7 @@ export class PythonProject extends Project { * @param spec Format `@` */ public addDependency(spec: string) { - return this.depsManager?.addDependency(spec); + return this.depsManager.addDependency(spec); } /** @@ -199,7 +216,7 @@ export class PythonProject extends Project { * @param spec Format `@` */ public addTestDependency(spec: string) { - return this.depsManager?.addTestDependency(spec); + return this.depsManager.addTestDependency(spec); } /** @@ -208,6 +225,6 @@ export class PythonProject extends Project { * @param spec Format `@` */ public addDevDependency(spec: string) { - return this.depsManager?.addDevDependency(spec); + return this.depsManager.addDevDependency(spec); } } diff --git a/src/python/python-sample.ts b/src/python/python-sample.ts index 6bf9a80047f..2b601f6ece6 100644 --- a/src/python/python-sample.ts +++ b/src/python/python-sample.ts @@ -1,13 +1,11 @@ import { Component } from '../component'; -import { Project, ProjectType } from '../project'; +import { Project } from '../project'; import { SampleDir } from '../sample-file'; -export interface PythonSampleOptions { - /** - * Which type of project this is. - */ - readonly projectType: ProjectType; -} +/** + * Options for python sample code. + */ +export interface PythonSampleOptions {} /** * Python code sample. @@ -16,6 +14,29 @@ export class PythonSample extends Component { constructor(project: Project, _options: PythonSampleOptions) { super(project); - new SampleDir(project, 'test', { files: {} }); + new SampleDir(project, project.name, { + files: { + '__init__.py': '__version__ = "0.1.0"\n', + '__main__.py': [ + 'from .example import hello', + '', + 'if __name__ == "__main__":', + ' name = input("What is your name? ")', + ' print(hello(name))', + '', + ].join('\n'), + 'example.py': [ + 'def hello(name: str) -> str:', + ' """A simple greeting.', + ' Args:', + ' name (str): Name to greet.', + ' Returns:', + ' str: greeting message', + ' """', + ' return f"Hello {name}!"', + '', + ].join('\n'), + }, + }); } } \ No newline at end of file diff --git a/src/python/requirements-file.ts b/src/python/requirements-file.ts index 2e125c385cb..e3428be5220 100644 --- a/src/python/requirements-file.ts +++ b/src/python/requirements-file.ts @@ -1,4 +1,3 @@ -import { EOL } from 'os'; import { Dependencies } from '../deps'; import { FileBase, IResolver } from '../file'; import { Project } from '../project'; @@ -9,7 +8,7 @@ export interface RequirementsFileOptions { * Accepts a function that resolves to an list of packages that should get included. * @internal */ - readonly _lazyPackages: any; + readonly _lazyPackages?: any; } /** @@ -51,11 +50,13 @@ export class RequirementsFile extends FileBase { protected synthesizeContent(resolver: IResolver): string | undefined { const additionalPackages = resolver.resolve(this.lazyPackages); - this.addPackages(...additionalPackages); + if (additionalPackages) { + this.addPackages(...additionalPackages); + } return `${resolver.resolve([ `# ${FileBase.PROJEN_MARKER}`, ...this.packages, - ]).join(EOL)}${EOL}`; + ]).join('\n')}\n`; } } From d8a57d975312a74aa2238c0126693532f9bb1112 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 24 Jan 2021 05:20:29 -0500 Subject: [PATCH 07/29] Add rest of sample files --- API.md | 89 +++++-- .../__snapshots__/inventory.test.ts.snap | 2 +- src/__tests__/__snapshots__/new.test.ts.snap | 34 +-- .../__snapshots__/python-project.test.ts.snap | 248 +++++++++++------- src/cli/macros.ts | 1 + src/python/pip.ts | 15 +- src/python/pytest.ts | 38 ++- src/python/python-env.ts | 5 +- src/python/python-project.ts | 57 ++-- src/python/python-sample.ts | 6 +- src/python/venv.ts | 56 ++-- 11 files changed, 373 insertions(+), 178 deletions(-) diff --git a/API.md b/API.md index ba50e1e0e5f..e4e0b833ee0 100644 --- a/API.md +++ b/API.md @@ -4707,13 +4707,21 @@ __Extends__: [Component](#projen-component) ```ts -new python.Pip(project: Project, _options: PipOptions) +new python.Pip(project: PythonProject, _options: PipOptions) ``` -* **project** ([Project](#projen-project)) *No description* +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **_options** ([python.PipOptions](#projen-python-pipoptions)) *No description* + +### Properties + + +Name | Type | Description +-----|------|------------- +**installTask**🔹 | [tasks.Task](#projen-tasks-task) | + ### Methods @@ -4777,10 +4785,19 @@ new python.Pytest(project: PythonProject, options: PytestOptions) * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **options** ([python.PytestOptions](#projen-python-pytestoptions)) *No description* + * **testdir** (string) Directory with tests. __*Default*__: 'tests' * **version** (string) Pytest version. __*Default*__: "6.2.1" +### Properties + + +Name | Type | Description +-----|------|------------- +**testTask**🔹 | [tasks.Task](#projen-tasks-task) | + + ## class PythonProject 🔹 @@ -4829,7 +4846,9 @@ Name | Type | Description -----|------|------------- **depsManager**🔹 | [python.IPythonDeps](#projen-python-ipythondeps) | API for managing dependencies. **envManager**🔹 | [python.IPythonEnv](#projen-python-ipythonenv) | API for mangaging the Python runtime environment. +**moduleName**🔹 | string | Python module name (the project name, with any hyphens replaced with underscores). **packagingManager**🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library. +**pythonPath**🔹 | string | Absolute path to the user's python installation. **pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ ### Methods @@ -4861,12 +4880,14 @@ addDevDependency(spec: string): void -#### addEnvTask(name, props?)🔹 +#### addEnvTask(name, props)🔹 -Adds a task that runs in the project's virtual environment. +Adds a single task that runs in the project's virtual environment. + +Additional steps can be added, but they will not be run in the environment. ```ts -addEnvTask(name: string, props?: TaskOptions): Task +addEnvTask(name: string, props: TaskOptions): Task ``` * **name** (string) The task name to add. @@ -4910,10 +4931,10 @@ __Extends__: [Component](#projen-component) ```ts -new python.PythonSample(project: Project, _options?: PythonSampleOptions) +new python.PythonSample(project: PythonProject, _options: PythonSampleOptions) ``` -* **project** ([Project](#projen-project)) *No description* +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **_options** ([python.PythonSampleOptions](#projen-python-pythonsampleoptions)) *No description* @@ -4994,7 +5015,6 @@ new python.Venv(project: PythonProject, options: VenvOptions) * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **options** ([python.VenvOptions](#projen-python-venvoptions)) *No description* - * **pythonPath** (string) Absolute path to the user's python installation. * **envdir** (string) Name of directory to store the environment in. __*Default*__: ".env" @@ -5004,9 +5024,30 @@ new python.Venv(project: PythonProject, options: VenvOptions) Name | Type | Description -----|------|------------- -**activateTask**🔹 | [tasks.Task](#projen-tasks-task) | -**createEnvTask**🔹 | [tasks.Task](#projen-tasks-task) | -**deactivateTask**🔹 | [tasks.Task](#projen-tasks-task) | +**setupEnvTask**🔹 | [tasks.Task](#projen-tasks-task) | + +### Methods + + +#### addEnvTask(name, props)🔹 + +Adds a task that runs in the project's virtual environment. + +```ts +addEnvTask(name: string, props: TaskOptions): Task +``` + +* **name** (string) The task name to add. +* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) Task properties. + * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC + * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ + * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() + * **description** (string) The description of this build command. __*Default*__: the task name + * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} + * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` + +__Returns__: +* [tasks.Task](#projen-tasks-task) @@ -8534,14 +8575,28 @@ addTestDependency(spec: string): void __Implemented by__: [python.Venv](#projen-python-venv) +### Methods + -### Properties +#### addEnvTask(name, props)🔹 -Name | Type | Description ------|------|------------- -**activateTask**🔹 | [tasks.Task](#projen-tasks-task) | -**deactivateTask**🔹 | [tasks.Task](#projen-tasks-task) | + +```ts +addEnvTask(name: string, props: TaskOptions): Task +``` + +* **name** (string) *No description* +* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) *No description* + * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC + * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ + * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() + * **description** (string) The description of this build command. __*Default*__: the task name + * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} + * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` + +__Returns__: +* [tasks.Task](#projen-tasks-task) @@ -8566,6 +8621,7 @@ Name | Type | Description Name | Type | Description -----|------|------------- +**testdir**?🔹 | string | Directory with tests.
__*Default*__: 'tests' **version**?🔹 | string | Pytest version.
__*Default*__: "6.2.1" @@ -8622,7 +8678,6 @@ Options for python sample code. Name | Type | Description -----|------|------------- -**pythonPath**🔹 | string | Absolute path to the user's python installation. **envdir**?🔹 | string | Name of directory to store the environment in.
__*Default*__: ".env" diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 2e0cacec1fd..5297af007c7 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -8057,7 +8057,7 @@ Array [ "type": "PytestOptions", }, Object { - "default": "\\"/usr/bin/python\\"", + "default": "$PYTHON_PATH", "docs": "Absolute path to the user's python installation.", "name": "pythonPath", "parent": "PythonProjectOptions", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 2ea16141611..749a10e572e 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -819,27 +819,27 @@ exports[`projen new python 1`] = ` const project = new python.PythonProject({ jsiiFqn: \\"projen.python.PythonProject\\", name: 'my-project', - pythonPath: '/usr/bin/python', + pythonPath: '/Users/rybickic/.pyenv/shims/python', /* ProjectOptions */ - // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ - // devContainer: false, /* Add a VSCode development environment (used for GitHub Codespaces). */ - // gitpod: false, /* Add a Gitpod development environment. */ - // logging: {}, /* Configure logging options such as verbosity. */ - // outdir: '.', /* The root directory of the project. */ - // parent: undefined, /* The parent project, if this project is part of a bigger project. */ - // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ - // readme: undefined, /* The README setup. */ + // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ + // devContainer: false, /* Add a VSCode development environment (used for GitHub Codespaces). */ + // gitpod: false, /* Add a Gitpod development environment. */ + // logging: {}, /* Configure logging options such as verbosity. */ + // outdir: '.', /* The root directory of the project. */ + // parent: undefined, /* The parent project, if this project is part of a bigger project. */ + // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ + // readme: undefined, /* The README setup. */ /* PythonProjectOptions */ - // deps: [], /* List of runtime dependencies for this project. */ - // devDeps: [], /* List of dev dependencies for this project. */ - // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ - // pytest: true, /* Include pytest tests. */ - // pytestOptions: undefined, /* pytest options. */ - // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ - // testDeps: [], /* List of test dependencies for this project. */ - // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ + // deps: [], /* List of runtime dependencies for this project. */ + // devDeps: [], /* List of dev dependencies for this project. */ + // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ + // pytest: true, /* Include pytest tests. */ + // pytestOptions: undefined, /* pytest options. */ + // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ + // testDeps: [], /* List of test dependencies for this project. */ + // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ }); project.synth(); diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index d6d3837e52f..b47ebbaab14 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -3,6 +3,7 @@ exports[`defaults 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +/.env node_modules/ !/.projen/deps.json !/.projen/tasks.json @@ -22,50 +23,53 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { - "env:activate": Object { - "category": "99.misc", - "description": "Activate the python environment", - "name": "env:activate", + "install": Object { + "category": "00.build", + "description": "Install and upgrade dependencies", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env", + }, + "name": "install", "steps": Array [ Object { - "exec": "source .env/bin/activate", + "exec": "pip install --upgrade pip", + }, + Object { + "exec": "pip install -r requirements.txt", + }, + Object { + "exec": "pip install -r requirements-dev.txt", }, ], }, - "env:create": Object { + "setup-env": Object { "category": "99.misc", "description": "Setup the project's python environment", - "name": "env:create", + "name": "setup-env", "steps": Array [ Object { "exec": "/usr/bin/python -m venv .env", }, - ], - }, - "env:deactivate": Object { - "category": "99.misc", - "description": "Deactivate the python environment", - "name": "env:deactivate", - "steps": Array [ Object { - "exec": "deactivate", + "say": "Environment successfully created.", }, ], }, "test": Object { "category": "10.test", "description": "Runs tests", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env", + }, "name": "test", "steps": Array [ - Object { - "spawn": "env:activate", - }, Object { "exec": "pytest", }, - Object { - "spawn": "env:deactivate", - }, ], }, }, @@ -76,15 +80,15 @@ pytest==6.2.1 ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", - "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test-python-project/__main__.py": "from .example import hello + "test_python-project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test-python-project/example.py": "def hello(name: str) -> str: + "test_python-project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -92,6 +96,23 @@ if __name__ == \\"__main__\\": str: greeting message \\"\\"\\" return f\\"Hello {name}!\\" +", + "tests/__init__.py": "", + "tests/test_example.py": "import pytest + +from test_python-project.example import hello + +@pytest.mark.parametrize( + (\\"name\\", \\"expected\\"), + [ + (\\"A. Musing\\", \\"Hello A. Musing!\\"), + (\\"traveler\\", \\"Hello traveler!\\"), + (\\"projen developer\\", \\"Hello projen developer!\\"), + ], +) +def test_hello(name, expected): + \\"\\"\\"Example test with parametrization.\\"\\"\\" + assert hello(name) == expected ", } `; @@ -99,6 +120,7 @@ if __name__ == \\"__main__\\": exports[`dependencies 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +/.env node_modules/ !/.projen/deps.json !/.projen/tasks.json @@ -133,50 +155,53 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { - "env:activate": Object { - "category": "99.misc", - "description": "Activate the python environment", - "name": "env:activate", + "install": Object { + "category": "00.build", + "description": "Install and upgrade dependencies", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env", + }, + "name": "install", "steps": Array [ Object { - "exec": "source .env/bin/activate", + "exec": "pip install --upgrade pip", + }, + Object { + "exec": "pip install -r requirements.txt", + }, + Object { + "exec": "pip install -r requirements-dev.txt", }, ], }, - "env:create": Object { + "setup-env": Object { "category": "99.misc", "description": "Setup the project's python environment", - "name": "env:create", + "name": "setup-env", "steps": Array [ Object { "exec": "/usr/bin/python -m venv .env", }, - ], - }, - "env:deactivate": Object { - "category": "99.misc", - "description": "Deactivate the python environment", - "name": "env:deactivate", - "steps": Array [ Object { - "exec": "deactivate", + "say": "Environment successfully created.", }, ], }, "test": Object { "category": "10.test", "description": "Runs tests", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env", + }, "name": "test", "steps": Array [ - Object { - "spawn": "env:activate", - }, Object { "exec": "pytest", }, - Object { - "spawn": "env:deactivate", - }, ], }, }, @@ -190,15 +215,15 @@ pytest==6.2.1 aws-cdk.core>=0.0.0 Django==3.1.5 ", - "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test-python-project/__main__.py": "from .example import hello + "test_python-project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test-python-project/example.py": "def hello(name: str) -> str: + "test_python-project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -206,6 +231,23 @@ if __name__ == \\"__main__\\": str: greeting message \\"\\"\\" return f\\"Hello {name}!\\" +", + "tests/__init__.py": "", + "tests/test_example.py": "import pytest + +from test_python-project.example import hello + +@pytest.mark.parametrize( + (\\"name\\", \\"expected\\"), + [ + (\\"A. Musing\\", \\"Hello A. Musing!\\"), + (\\"traveler\\", \\"Hello traveler!\\"), + (\\"projen developer\\", \\"Hello projen developer!\\"), + ], +) +def test_hello(name, expected): + \\"\\"\\"Example test with parametrization.\\"\\"\\" + assert hello(name) == expected ", } `; @@ -213,6 +255,7 @@ if __name__ == \\"__main__\\": exports[`dependencies via ctor 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +/.env node_modules/ !/.projen/deps.json !/.projen/tasks.json @@ -247,50 +290,53 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { - "env:activate": Object { - "category": "99.misc", - "description": "Activate the python environment", - "name": "env:activate", + "install": Object { + "category": "00.build", + "description": "Install and upgrade dependencies", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env", + }, + "name": "install", "steps": Array [ Object { - "exec": "source .env/bin/activate", + "exec": "pip install --upgrade pip", + }, + Object { + "exec": "pip install -r requirements.txt", + }, + Object { + "exec": "pip install -r requirements-dev.txt", }, ], }, - "env:create": Object { + "setup-env": Object { "category": "99.misc", "description": "Setup the project's python environment", - "name": "env:create", + "name": "setup-env", "steps": Array [ Object { "exec": "/usr/bin/python -m venv .env", }, - ], - }, - "env:deactivate": Object { - "category": "99.misc", - "description": "Deactivate the python environment", - "name": "env:deactivate", - "steps": Array [ Object { - "exec": "deactivate", + "say": "Environment successfully created.", }, ], }, "test": Object { "category": "10.test", "description": "Runs tests", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env", + }, "name": "test", "steps": Array [ - Object { - "spawn": "env:activate", - }, Object { "exec": "pytest", }, - Object { - "spawn": "env:deactivate", - }, ], }, }, @@ -304,15 +350,15 @@ pytest==6.2.1 aws-cdk.core>=0.0.0 Django==3.1.5 ", - "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test-python-project/__main__.py": "from .example import hello + "test_python-project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test-python-project/example.py": "def hello(name: str) -> str: + "test_python-project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -320,6 +366,23 @@ if __name__ == \\"__main__\\": str: greeting message \\"\\"\\" return f\\"Hello {name}!\\" +", + "tests/__init__.py": "", + "tests/test_example.py": "import pytest + +from test_python-project.example import hello + +@pytest.mark.parametrize( + (\\"name\\", \\"expected\\"), + [ + (\\"A. Musing\\", \\"Hello A. Musing!\\"), + (\\"traveler\\", \\"Hello traveler!\\"), + (\\"projen developer\\", \\"Hello projen developer!\\"), + ], +) +def test_hello(name, expected): + \\"\\"\\"Example test with parametrization.\\"\\"\\" + assert hello(name) == expected ", } `; @@ -327,6 +390,7 @@ if __name__ == \\"__main__\\": exports[`no pytest 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". +/.env node_modules/ !/.projen/deps.json !/.projen/tasks.json @@ -336,33 +400,37 @@ node_modules/ ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "tasks": Object { - "env:activate": Object { - "category": "99.misc", - "description": "Activate the python environment", - "name": "env:activate", + "install": Object { + "category": "00.build", + "description": "Install and upgrade dependencies", + "env": Object { + "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-QmTuGl/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-QmTuGl/.env", + }, + "name": "install", "steps": Array [ Object { - "exec": "source .env/bin/activate", + "exec": "pip install --upgrade pip", + }, + Object { + "exec": "pip install -r requirements.txt", + }, + Object { + "exec": "pip install -r requirements-dev.txt", }, ], }, - "env:create": Object { + "setup-env": Object { "category": "99.misc", "description": "Setup the project's python environment", - "name": "env:create", + "name": "setup-env", "steps": Array [ Object { "exec": "/usr/bin/python -m venv .env", }, - ], - }, - "env:deactivate": Object { - "category": "99.misc", - "description": "Deactivate the python environment", - "name": "env:deactivate", - "steps": Array [ Object { - "exec": "deactivate", + "say": "Environment successfully created.", }, ], }, @@ -373,15 +441,15 @@ node_modules/ ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", - "test-python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test-python-project/__main__.py": "from .example import hello + "test_python-project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test-python-project/example.py": "def hello(name: str) -> str: + "test_python-project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. diff --git a/src/cli/macros.ts b/src/cli/macros.ts index efb9806e300..1e34a6f7b8e 100644 --- a/src/cli/macros.ts +++ b/src/cli/macros.ts @@ -18,6 +18,7 @@ export function tryProcessMacro(macro: string) { case '$GIT_USER_NAME': return getFromGitConfig('user.name') ?? 'user'; case '$GIT_USER_EMAIL': return resolveEmail(); + case '$PYTHON_PATH': return execOrUndefined('which python') ?? '/usr/bin/python'; } return undefined; diff --git a/src/python/pip.ts b/src/python/pip.ts index 0cf7f20b207..3e61a24c82a 100644 --- a/src/python/pip.ts +++ b/src/python/pip.ts @@ -1,17 +1,28 @@ import { Component } from '../component'; import { DependencyType } from '../deps'; -import { Project } from '../project'; +import { Task, TaskCategory } from '../tasks'; import { IPythonDeps } from './python-deps'; +import { PythonProject } from './python-project'; import { RequirementsFile } from './requirements-file'; export interface PipOptions {} export class Pip extends Component implements IPythonDeps { - constructor(project: Project, _options: PipOptions) { + public readonly installTask: Task; + + constructor(project: PythonProject, _options: PipOptions) { super(project); new RequirementsFile(project, 'requirements.txt', { _lazyPackages: () => this.synthDependencies() }); new RequirementsFile(project, 'requirements-dev.txt', { _lazyPackages: () => this.synthDevDependencies() }); + + this.installTask = project.addEnvTask('install', { + description: 'Install and upgrade dependencies', + category: TaskCategory.BUILD, + }); + this.installTask.exec('pip install --upgrade pip'); + this.installTask.exec('pip install -r requirements.txt'); + this.installTask.exec('pip install -r requirements-dev.txt'); } private synthDependencies() { diff --git a/src/python/pytest.ts b/src/python/pytest.ts index d20aa996b6d..4839dec0e94 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -1,5 +1,6 @@ import { Component } from '../component'; -import { TaskCategory } from '../tasks'; +import { SampleDir } from '../sample-file'; +import { Task, TaskCategory } from '../tasks'; import { PythonProject } from './python-project'; export interface PytestOptions { @@ -9,9 +10,18 @@ export interface PytestOptions { * @default "6.2.1" */ readonly version?: string; + + /** + * Directory with tests + * + * @default 'tests' + */ + readonly testdir?: string; } export class Pytest extends Component { + public readonly testTask: Task; + constructor(project: PythonProject, options: PytestOptions) { super(project); @@ -19,12 +29,34 @@ export class Pytest extends Component { project.addTestDependency(`pytest@${version}`); - project.addEnvTask('test', { + this.testTask = project.addEnvTask('test', { description: 'Runs tests', category: TaskCategory.TEST, exec: 'pytest', }); - // TODO: add sample tests with `SampleDir` + new SampleDir(project, 'tests', { + files: { + '__init__.py': '', + 'test_example.py': [ + 'import pytest', + '', + `from ${project.moduleName}.example import hello`, + '', + '@pytest.mark.parametrize(', + ' ("name", "expected"),', + ' [', + ' ("A. Musing", "Hello A. Musing!"),', + ' ("traveler", "Hello traveler!"),', + ' ("projen developer", "Hello projen developer!"),', + ' ],', + ')', + 'def test_hello(name, expected):', + ' """Example test with parametrization."""', + ' assert hello(name) == expected', + '', + ].join('\n'), + }, + }); } } \ No newline at end of file diff --git a/src/python/python-env.ts b/src/python/python-env.ts index 3f7a7d57e17..9e88ff370dc 100644 --- a/src/python/python-env.ts +++ b/src/python/python-env.ts @@ -1,6 +1,5 @@ -import { Task } from '../tasks'; +import { Task, TaskOptions } from '../tasks'; export interface IPythonEnv { - readonly activateTask: Task; - readonly deactivateTask: Task; + addEnvTask(name: string, props: TaskOptions): Task; } diff --git a/src/python/python-project.ts b/src/python/python-project.ts index b3490d57cb0..2ba298be550 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -8,6 +8,10 @@ import { IPythonPackaging } from './python-packaging'; import { PythonSample } from './python-sample'; import { Venv } from './venv'; + +/** Allowed characters in python project names */ +const PYTHON_PROJECT_NAME_REGEX = /^[A-Za-z0-9-_\.]+$/; + /** * Options for `PythonProject`. */ @@ -15,7 +19,7 @@ export interface PythonProjectOptions extends ProjectOptions { /** * Absolute path to the user's python installation. * - * @default "/usr/bin/python" + * @default $PYTHON_PATH */ readonly pythonPath: string; @@ -96,6 +100,16 @@ export interface PythonProjectOptions extends ProjectOptions { * @pjid python */ export class PythonProject extends Project { + /** + * Absolute path to the user's python installation. + */ + readonly pythonPath: string; + + /** + * Python module name (the project name, with any hyphens replaced with underscores). + */ + readonly moduleName: string; + /** * API for managing dependencies. */ @@ -119,14 +133,19 @@ export class PythonProject extends Project { constructor(options: PythonProjectOptions) { super(options); - if (options.pip ?? true) { - this.depsManager = new Pip(this, {}); + if (!PYTHON_PROJECT_NAME_REGEX.test(options.name)) { + throw new Error('Python projects must only consist of alphanumeric characters (A-Za-z0-9), hyphens (-), and underscores.'); } + this.moduleName = this.safeName(options.name); + this.pythonPath = options.pythonPath; + if (options.venv ?? true) { - this.envManager = new Venv(this, { - pythonPath: options.pythonPath, - }); + this.envManager = new Venv(this, {}); + } + + if (options.pip ?? true) { + this.depsManager = new Pip(this, {}); } // if (options.setuptools ?? true) { @@ -166,9 +185,7 @@ export class PythonProject extends Project { } if (options.pytest ?? true) { - this.pytest = new Pytest(this, { - ...options.pytestOptions, - }); + this.pytest = new Pytest(this, {}); } if (options.sample ?? true) { @@ -189,16 +206,26 @@ export class PythonProject extends Project { } /** - * Adds a task that runs in the project's virtual environment. + * Convert an arbitrary string to a valid module filename. + * + * Replaces hyphens with underscores. + * + * @param name project name + */ + private safeName(name: string) { + return name.replace('-', '_'); + } + + /** + * Adds a single task that runs in the project's virtual environment. + * + * Additional steps can be added, but they will not be run in the environment. * * @param name The task name to add * @param props Task properties */ - public addEnvTask(name: string, props: TaskOptions = { }) { - const task = this.tasks.addTask(name, props); - task.prependSpawn(this.envManager.activateTask); - task.spawn(this.envManager.deactivateTask); - return task; + public addEnvTask(name: string, props: TaskOptions) { + return this.envManager.addEnvTask(name, props); } /** diff --git a/src/python/python-sample.ts b/src/python/python-sample.ts index 2b601f6ece6..9ea6844c081 100644 --- a/src/python/python-sample.ts +++ b/src/python/python-sample.ts @@ -1,6 +1,6 @@ import { Component } from '../component'; -import { Project } from '../project'; import { SampleDir } from '../sample-file'; +import { PythonProject } from './python-project'; /** * Options for python sample code. @@ -11,10 +11,10 @@ export interface PythonSampleOptions {} * Python code sample. */ export class PythonSample extends Component { - constructor(project: Project, _options: PythonSampleOptions) { + constructor(project: PythonProject, _options: PythonSampleOptions) { super(project); - new SampleDir(project, project.name, { + new SampleDir(project, project.moduleName, { files: { '__init__.py': '__version__ = "0.1.0"\n', '__main__.py': [ diff --git a/src/python/venv.ts b/src/python/venv.ts index 358987ed9b4..05594432f3b 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -1,14 +1,10 @@ +import * as path from 'path'; import { Component } from '../component'; -import { Task, TaskCategory } from '../tasks'; +import { Task, TaskCategory, TaskOptions } from '../tasks'; import { IPythonEnv } from './python-env'; import { PythonProject } from './python-project'; export interface VenvOptions { - /** - * Absolute path to the user's python installation. - */ - readonly pythonPath: string; - /** * Name of directory to store the environment in * @@ -18,42 +14,48 @@ export interface VenvOptions { } export class Venv extends Component implements IPythonEnv { - /** - * Absolute path to the user's python installation. - */ - private readonly pythonPath: string; - /** * Name of directory to store the environment in */ private readonly envdir: string; - public readonly createEnvTask: Task; - public readonly activateTask: Task; - public readonly deactivateTask: Task; + public readonly setupEnvTask: Task; constructor(project: PythonProject, options: VenvOptions) { super(project); - this.pythonPath = options.pythonPath; this.envdir = options.envdir ?? '.env'; - this.createEnvTask = project.addTask('env:create', { - description: 'Setup the project\'s python environment', - category: TaskCategory.MISC, - exec: `${this.pythonPath} -m venv ${this.envdir}`, - }); + this.project.gitignore.exclude(`/${this.envdir}`); - this.activateTask = project.addTask('env:activate', { - description: 'Activate the python environment', + this.setupEnvTask = project.addTask('setup-env', { + description: 'Setup the project\'s python environment', category: TaskCategory.MISC, - exec: `source ${this.envdir}/bin/activate`, + exec: `${project.pythonPath} -m venv ${this.envdir}`, }); + this.setupEnvTask.say('Environment successfully created.'); + } - this.deactivateTask = project.addTask('env:deactivate', { - description: 'Deactivate the python environment', - category: TaskCategory.MISC, - exec: 'deactivate', + /** + * Adds a task that runs in the project's virtual environment. + * + * @param name The task name to add + * @param props Task properties + */ + public addEnvTask(name: string, props: TaskOptions) { + const absoluteEnvPath = path.join(this.project.outdir, this.envdir); + + return this.project.tasks.addTask(name, { + ...props, + + // simulate the effect of 'source .env/bin/activate' + // the original script "unsets" PYTHONHOME, but setting to empty string works too + env: { + VIRTUAL_ENV: absoluteEnvPath, + PYTHONHOME: '', + PATH: `$(echo ${absoluteEnvPath}/bin:$PATH)`, + ...props.env, + }, }); } } \ No newline at end of file From 6a3e2274e0fbdcbe046c846e7469bcee74fc12ce Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 24 Jan 2021 05:23:16 -0500 Subject: [PATCH 08/29] simplify error msg --- src/python/python-project.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 2ba298be550..c1ea81e2595 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -134,7 +134,7 @@ export class PythonProject extends Project { super(options); if (!PYTHON_PROJECT_NAME_REGEX.test(options.name)) { - throw new Error('Python projects must only consist of alphanumeric characters (A-Za-z0-9), hyphens (-), and underscores.'); + throw new Error('Python projects must only consist of alphanumeric characters, hyphens, and underscores.'); } this.moduleName = this.safeName(options.name); From 5b9e8206220e0d851f4e82b8fb85d090b961cda8 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 24 Jan 2021 05:30:40 -0500 Subject: [PATCH 09/29] add some docs --- src/python/python-project.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/python/python-project.ts b/src/python/python-project.ts index c1ea81e2595..43ac412c15e 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -97,6 +97,17 @@ export interface PythonProjectOptions extends ProjectOptions { /** * Python project. * + * Every python project must have a component for managing dependencies, a + * component for managing the Python virtual environment, and if it is a + * library, a component for managing packaging the library. Some components + * satisfy multiple requirements. + * + * - pip: dependency manager + * - venv: environment manager + * - pipenv: dependency and environment manager + * - setuptools: packaging manager + * - poetry: dependency, environment, and packaging manager + * * @pjid python */ export class PythonProject extends Project { From af1390b59d079f64feb54a79885106ee7b9d1e0c Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 25 Jan 2021 01:28:28 -0500 Subject: [PATCH 10/29] add docs, refactor a bunch of things --- API.md | 89 +++-------- docs/python.md | 144 +++++++++++++++++ src/__tests__/__snapshots__/new.test.ts.snap | 34 ++-- src/__tests__/new.test.ts | 2 +- .../__snapshots__/python-project.test.ts.snap | 149 +++++------------- src/__tests__/python/python-project.test.ts | 4 +- src/cli/macros.ts | 9 +- src/python/pip.ts | 20 +-- src/python/pytest.ts | 4 +- src/python/python-deps.ts | 10 +- src/python/python-env.ts | 7 +- src/python/python-project.ts | 52 ++---- src/python/venv.ts | 43 ++--- 13 files changed, 281 insertions(+), 286 deletions(-) create mode 100644 docs/python.md diff --git a/API.md b/API.md index e4e0b833ee0..0f5edd5dd55 100644 --- a/API.md +++ b/API.md @@ -4751,15 +4751,14 @@ addDevDependency(spec: string): void -#### addTestDependency(spec)🔹 +#### installDependencies()🔹 -Adds a test dependency. +Installs dependencies (called during post-synthesis). ```ts -addTestDependency(spec: string): void +installDependencies(): void ``` -* **spec** (string) Format `@`. @@ -4846,7 +4845,7 @@ Name | Type | Description -----|------|------------- **depsManager**🔹 | [python.IPythonDeps](#projen-python-ipythondeps) | API for managing dependencies. **envManager**🔹 | [python.IPythonEnv](#projen-python-ipythonenv) | API for mangaging the Python runtime environment. -**moduleName**🔹 | string | Python module name (the project name, with any hyphens replaced with underscores). +**moduleName**🔹 | string | Python module name (the project name, with any hyphens or periods replaced with underscores). **packagingManager**🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library. **pythonPath**🔹 | string | Absolute path to the user's python installation. **pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ @@ -4880,37 +4879,16 @@ addDevDependency(spec: string): void -#### addEnvTask(name, props)🔹 - -Adds a single task that runs in the project's virtual environment. - -Additional steps can be added, but they will not be run in the environment. - -```ts -addEnvTask(name: string, props: TaskOptions): Task -``` +#### postSynthesize()🔹 -* **name** (string) The task name to add. -* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) Task properties. - * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC - * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ - * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() - * **description** (string) The description of this build command. __*Default*__: the task name - * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} - * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` - -__Returns__: -* [tasks.Task](#projen-tasks-task) - -#### addTestDependency(spec)🔹 +Called after all components are synthesized. -Adds a test dependency. +Order is *not* guaranteed. ```ts -addTestDependency(spec: string): void +postSynthesize(): void ``` -* **spec** (string) Format `@`. @@ -5018,36 +4996,20 @@ new python.Venv(project: PythonProject, options: VenvOptions) * **envdir** (string) Name of directory to store the environment in. __*Default*__: ".env" - -### Properties - - -Name | Type | Description ------|------|------------- -**setupEnvTask**🔹 | [tasks.Task](#projen-tasks-task) | - ### Methods -#### addEnvTask(name, props)🔹 +#### setupEnvironment()🔹 -Adds a task that runs in the project's virtual environment. +Initializes the virtual environment if it doesn't exist (called during post-synthesis). ```ts -addEnvTask(name: string, props: TaskOptions): Task +setupEnvironment(): void ``` -* **name** (string) The task name to add. -* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) Task properties. - * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC - * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ - * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() - * **description** (string) The description of this build command. __*Default*__: the task name - * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} - * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` -__Returns__: -* [tasks.Task](#projen-tasks-task) + + @@ -8555,15 +8517,14 @@ addDevDependency(spec: string): void -#### addTestDependency(spec)🔹 +#### installDependencies()🔹 -Adds a test dependency. +Installs dependencies (called during post-synthesis). ```ts -addTestDependency(spec: string): void +installDependencies(): void ``` -* **spec** (string) Format `@`. @@ -8578,25 +8539,17 @@ __Implemented by__: [python.Venv](#projen-python-venv) ### Methods -#### addEnvTask(name, props)🔹 - +#### setupEnvironment()🔹 +Initializes the virtual environment if it doesn't exist (called during post-synthesis). ```ts -addEnvTask(name: string, props: TaskOptions): Task +setupEnvironment(): void ``` -* **name** (string) *No description* -* **props** ([tasks.TaskOptions](#projen-tasks-taskoptions)) *No description* - * **category** ([tasks.TaskCategory](#projen-tasks-taskcategory)) Category for start menu. __*Default*__: TaskCategory.MISC - * **condition** (string) A shell command which determines if the this task should be executed. __*Optional*__ - * **cwd** (string) The working directory for all steps in this task (unless overridden by the step). __*Default*__: process.cwd() - * **description** (string) The description of this build command. __*Default*__: the task name - * **env** (Map) Defines environment variables for the execution of this task. __*Default*__: {} - * **exec** (string) Shell command to execute as the first command of the task. __*Default*__: add steps using `task.exec(command)` or `task.spawn(subtask)` -__Returns__: -* [tasks.Task](#projen-tasks-task) + + diff --git a/docs/python.md b/docs/python.md new file mode 100644 index 00000000000..75efe65591e --- /dev/null +++ b/docs/python.md @@ -0,0 +1,144 @@ +# Python Projects + +Before creating a new project, make sure you have the version of Python you want +to use set up in your terminal. If running `which python` on UNIX/macOS or +`Get-Command python` on Windows does not print the path to the python version +you want to use, you can specify the right path when setting up the project. + +To create a new Python project, use `projen new python`: + +```shell +$ projen new python --name=my-project --python-path=/usr/bin/python +``` + +This will synthesize a standard project directory structure with some sample +code. + +```shell +├── my_project +│   ├── __init__.py +│   ├── __main__.py +│   └── example.py +└── tests + ├── __init__.py + └── test_example.py +``` + +The default options will setup a Python environment using `venv`, and will +create a `requirements.txt` file for installing dependencies via pip. + +The `projen new` command will also generate a `.projenrc.js` file which includes +the definition of your project with any options you specified in the command +line: + +```js +const { python } = require('projen'); + +const project = new python.PythonProject({ + jsiiFqn: "projen.python.PythonProject", + name: 'my-project', + pythonPath: '/usr/bin/python', +}); + +project.synth(); +``` + +> At this point, projenrc is in JavaScript, but in the future we plan to allow +> specifying your project definitions in python. + +To modify your project definitions, edit `.projenrc.js` and run `projen` again +to re-synthesize your project. The following sections describe the various +features of your project. + +The following sections describe the various features of Python projects. + +## Managing environments, dependencies, and packaging + +Every Python project must have a component for managing/installing dependencies, +a component for managing the Python virtual environment, and if it is a library, +a component for managing packaging the library. Some components satisfy multiple +requirements. See the list below: + +- pip: dependency manager +- venv: environment manager +- pipenv (TBD): dependency and environment manager +- setuptools (TBD): packaging manager +- poetry (TBD): dependency, environment, and packaging manager + +By default, pip, venv, and setuptools will be used. But these can be swapped out +as needed by using the provided flags, for example: + +```js +const project = new python.PythonProject({ + pip: false, + venv: false, + setuptools: false, + poetry: true, + poetryOptions: { + ... + }, +}); +``` + +## Dependencies + +Python projects have two types of supported dependencies: + +1. Runtime dependencies (or just "dependencies"). +2. Development dependencies + +You can define dependencies when defining the project itself: + +```ts +const project = new python.PythonProject({ + deps: [ + 'Django@3.1.5', + 'aws-cdk.core@*', + ], + testDeps: [ + 'hypothesis@^6.0.3', + ], +}); +``` + +Or using the APIs: + +```ts +project.addTestDependency('hypothesis@^6.0.3'); +``` + +Notice the syntax for dependencies: + +```text +[@version] +``` + +Where `module` is the module name and `version` is the [semantic version +requirement](https://semver.org) for the dependency. The semver syntax will be +converted to the appropriate syntax in synthesized files. For example, +`lib^3.1.0` will be converted to `lib>=3.1.0, <4.0.0` in `requirements.txt`. + +## Unit Testing with Pytest + +The `Pytest` component adds support for writing Python tests with +[pytest](https://pytest.org/). The component will add the required test +dependencies to your project. + +Test sources are placed under `tests` and can be executed via `projen test`. + +To disable pytest tests, set `pytest: false` when you define the +`PythonProject`. + +## `projenrc.py` + +TBD + +> In the future, it will be possible to write your projenrc file in Python. + +## Packaging + +TBD + +## Publishing + +TBD. diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 749a10e572e..2ea16141611 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -819,27 +819,27 @@ exports[`projen new python 1`] = ` const project = new python.PythonProject({ jsiiFqn: \\"projen.python.PythonProject\\", name: 'my-project', - pythonPath: '/Users/rybickic/.pyenv/shims/python', + pythonPath: '/usr/bin/python', /* ProjectOptions */ - // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ - // devContainer: false, /* Add a VSCode development environment (used for GitHub Codespaces). */ - // gitpod: false, /* Add a Gitpod development environment. */ - // logging: {}, /* Configure logging options such as verbosity. */ - // outdir: '.', /* The root directory of the project. */ - // parent: undefined, /* The parent project, if this project is part of a bigger project. */ - // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ - // readme: undefined, /* The README setup. */ + // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ + // devContainer: false, /* Add a VSCode development environment (used for GitHub Codespaces). */ + // gitpod: false, /* Add a Gitpod development environment. */ + // logging: {}, /* Configure logging options such as verbosity. */ + // outdir: '.', /* The root directory of the project. */ + // parent: undefined, /* The parent project, if this project is part of a bigger project. */ + // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ + // readme: undefined, /* The README setup. */ /* PythonProjectOptions */ - // deps: [], /* List of runtime dependencies for this project. */ - // devDeps: [], /* List of dev dependencies for this project. */ - // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ - // pytest: true, /* Include pytest tests. */ - // pytestOptions: undefined, /* pytest options. */ - // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ - // testDeps: [], /* List of test dependencies for this project. */ - // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ + // deps: [], /* List of runtime dependencies for this project. */ + // devDeps: [], /* List of dev dependencies for this project. */ + // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ + // pytest: true, /* Include pytest tests. */ + // pytestOptions: undefined, /* pytest options. */ + // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ + // testDeps: [], /* List of test dependencies for this project. */ + // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ }); project.synth(); diff --git a/src/__tests__/new.test.ts b/src/__tests__/new.test.ts index 5abd01fc3ea..fac117fb460 100644 --- a/src/__tests__/new.test.ts +++ b/src/__tests__/new.test.ts @@ -14,7 +14,7 @@ for (const type of inventory.discover()) { const projectdir = createProjectDir(outdir); // execute `projen new PJID --no-synth` in the project directory - execProjenCLI(projectdir, ['new', '--no-synth', type.pjid]); + execProjenCLI(projectdir, ['new', '--no-synth', '--python-path=/usr/bin/python', type.pjid]); // compare generated .projenrc.js to the snapshot const projenrc = readFileSync(join(projectdir, PROJEN_RC), 'utf-8'); diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index b47ebbaab14..518d5ade4f6 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -15,22 +15,22 @@ node_modules/ "dependencies": Array [ Object { "name": "pytest", - "type": "test", + "type": "devenv", "version": "6.2.1", }, ], }, ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "env": Object { + "PATH": "$(echo $PWD/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "$(echo $PWD/.env)", + }, "tasks": Object { "install": Object { "category": "00.build", "description": "Install and upgrade dependencies", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env", - }, "name": "install", "steps": Array [ Object { @@ -44,27 +44,9 @@ node_modules/ }, ], }, - "setup-env": Object { - "category": "99.misc", - "description": "Setup the project's python environment", - "name": "setup-env", - "steps": Array [ - Object { - "exec": "/usr/bin/python -m venv .env", - }, - Object { - "say": "Environment successfully created.", - }, - ], - }, "test": Object { "category": "10.test", "description": "Runs tests", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-2Sp0TE/.env", - }, "name": "test", "steps": Array [ Object { @@ -130,6 +112,16 @@ node_modules/ ".projen/deps.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "dependencies": Array [ + Object { + "name": "hypothesis", + "type": "devenv", + "version": "^6.0.3", + }, + Object { + "name": "pytest", + "type": "devenv", + "version": "6.2.1", + }, Object { "name": "aws-cdk.core", "type": "runtime", @@ -140,29 +132,19 @@ node_modules/ "type": "runtime", "version": "3.1.5", }, - Object { - "name": "hypothesis", - "type": "test", - "version": "^6.0.3", - }, - Object { - "name": "pytest", - "type": "test", - "version": "6.2.1", - }, ], }, ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "env": Object { + "PATH": "$(echo $PWD/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "$(echo $PWD/.env)", + }, "tasks": Object { "install": Object { "category": "00.build", "description": "Install and upgrade dependencies", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env", - }, "name": "install", "steps": Array [ Object { @@ -176,27 +158,9 @@ node_modules/ }, ], }, - "setup-env": Object { - "category": "99.misc", - "description": "Setup the project's python environment", - "name": "setup-env", - "steps": Array [ - Object { - "exec": "/usr/bin/python -m venv .env", - }, - Object { - "say": "Environment successfully created.", - }, - ], - }, "test": Object { "category": "10.test", "description": "Runs tests", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-iN3JLq/.env", - }, "name": "test", "steps": Array [ Object { @@ -265,6 +229,16 @@ node_modules/ ".projen/deps.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "dependencies": Array [ + Object { + "name": "hypothesis", + "type": "devenv", + "version": "^6.0.3", + }, + Object { + "name": "pytest", + "type": "devenv", + "version": "6.2.1", + }, Object { "name": "aws-cdk.core", "type": "runtime", @@ -275,29 +249,19 @@ node_modules/ "type": "runtime", "version": "3.1.5", }, - Object { - "name": "hypothesis", - "type": "test", - "version": "^6.0.3", - }, - Object { - "name": "pytest", - "type": "test", - "version": "6.2.1", - }, ], }, ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "env": Object { + "PATH": "$(echo $PWD/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "$(echo $PWD/.env)", + }, "tasks": Object { "install": Object { "category": "00.build", "description": "Install and upgrade dependencies", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env", - }, "name": "install", "steps": Array [ Object { @@ -311,27 +275,9 @@ node_modules/ }, ], }, - "setup-env": Object { - "category": "99.misc", - "description": "Setup the project's python environment", - "name": "setup-env", - "steps": Array [ - Object { - "exec": "/usr/bin/python -m venv .env", - }, - Object { - "say": "Environment successfully created.", - }, - ], - }, "test": Object { "category": "10.test", "description": "Runs tests", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-U2FbJ2/.env", - }, "name": "test", "steps": Array [ Object { @@ -399,15 +345,15 @@ node_modules/ ", ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "env": Object { + "PATH": "$(echo $PWD/.env/bin:$PATH)", + "PYTHONHOME": "", + "VIRTUAL_ENV": "$(echo $PWD/.env)", + }, "tasks": Object { "install": Object { "category": "00.build", "description": "Install and upgrade dependencies", - "env": Object { - "PATH": "$(echo /var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-QmTuGl/.env/bin:$PATH)", - "PYTHONHOME": "", - "VIRTUAL_ENV": "/var/folders/f6/q6wlhk2d6hn_ylbn9rd4m24w0000gn/T/projen-test-QmTuGl/.env", - }, "name": "install", "steps": Array [ Object { @@ -421,19 +367,6 @@ node_modules/ }, ], }, - "setup-env": Object { - "category": "99.misc", - "description": "Setup the project's python environment", - "name": "setup-env", - "steps": Array [ - Object { - "exec": "/usr/bin/python -m venv .env", - }, - Object { - "say": "Environment successfully created.", - }, - ], - }, }, }, "README.md": "# replace this", diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index a7499882e44..7b268ff5808 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -11,7 +11,7 @@ test('dependencies', () => { const p = new TestPythonProject(); p.addDependency('Django@3.1.5'); p.addDependency('aws-cdk.core@*'); - p.addTestDependency('hypothesis@^6.0.3'); + p.addDevDependency('hypothesis@^6.0.3'); expect(synthSnapshot(p)).toMatchSnapshot(); }); @@ -21,7 +21,7 @@ test('dependencies via ctor', () => { 'Django@3.1.5', 'aws-cdk.core@*', ], - testDeps: [ + devDeps: [ 'hypothesis@^6.0.3', ], }); diff --git a/src/cli/macros.ts b/src/cli/macros.ts index 1e34a6f7b8e..a92bbec691c 100644 --- a/src/cli/macros.ts +++ b/src/cli/macros.ts @@ -1,3 +1,4 @@ +import * as os from 'os'; import * as path from 'path'; import { execOrUndefined } from '../util'; @@ -18,7 +19,7 @@ export function tryProcessMacro(macro: string) { case '$GIT_USER_NAME': return getFromGitConfig('user.name') ?? 'user'; case '$GIT_USER_EMAIL': return resolveEmail(); - case '$PYTHON_PATH': return execOrUndefined('which python') ?? '/usr/bin/python'; + case '$PYTHON_PATH': return resolvePython(); } return undefined; @@ -36,3 +37,9 @@ function getFromGitConfig(key: string): string | undefined { function resolveEmail(): string { return getFromGitConfig('user.email') ?? 'user@domain.com'; } + +function resolvePython(): string { + const command = os.platform() === 'win32' ? '(Get-Command python).Path' : 'which python'; + const defaultValue = os.platform() === 'win32' ? 'C:\\Python36\\python.exe' : '/usr/bin/python'; + return execOrUndefined(command) ?? defaultValue; +} diff --git a/src/python/pip.ts b/src/python/pip.ts index 3e61a24c82a..ad7d50b4f2f 100644 --- a/src/python/pip.ts +++ b/src/python/pip.ts @@ -1,6 +1,7 @@ import { Component } from '../component'; import { DependencyType } from '../deps'; import { Task, TaskCategory } from '../tasks'; +import { exec } from '../util'; import { IPythonDeps } from './python-deps'; import { PythonProject } from './python-project'; import { RequirementsFile } from './requirements-file'; @@ -16,7 +17,7 @@ export class Pip extends Component implements IPythonDeps { new RequirementsFile(project, 'requirements.txt', { _lazyPackages: () => this.synthDependencies() }); new RequirementsFile(project, 'requirements-dev.txt', { _lazyPackages: () => this.synthDevDependencies() }); - this.installTask = project.addEnvTask('install', { + this.installTask = project.addTask('install', { description: 'Install and upgrade dependencies', category: TaskCategory.BUILD, }); @@ -38,7 +39,7 @@ export class Pip extends Component implements IPythonDeps { private synthDevDependencies() { const dependencies: string[] = []; for (const pkg of this.project.deps.all) { - if ([DependencyType.TEST, DependencyType.DEVENV].includes(pkg.type)) { + if ([DependencyType.DEVENV].includes(pkg.type)) { dependencies.push( `${pkg.name}@${pkg.version}`); } } @@ -55,20 +56,19 @@ export class Pip extends Component implements IPythonDeps { } /** - * Adds a test dependency. + * Adds a dev dependency. * * @param spec Format `@` */ - public addTestDependency(spec: string) { - this.project.deps.addDependency(spec, DependencyType.TEST); + public addDevDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.DEVENV); } /** - * Adds a dev dependency. - * - * @param spec Format `@` + * Installs dependencies (called during post-synthesis). */ - public addDevDependency(spec: string) { - this.project.deps.addDependency(spec, DependencyType.DEVENV); + public installDependencies() { + this.project.logger.info('Installing dependencies...'); + exec(this.installTask.toShellCommand(), { cwd: this.project.outdir }); } } \ No newline at end of file diff --git a/src/python/pytest.ts b/src/python/pytest.ts index 4839dec0e94..38f8b2188e3 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -27,9 +27,9 @@ export class Pytest extends Component { const version = options.version ?? '6.2.1'; - project.addTestDependency(`pytest@${version}`); + project.addDevDependency(`pytest@${version}`); - this.testTask = project.addEnvTask('test', { + this.testTask = project.addTask('test', { description: 'Runs tests', category: TaskCategory.TEST, exec: 'pytest', diff --git a/src/python/python-deps.ts b/src/python/python-deps.ts index 48639bd2cd4..c70ba5b05e5 100644 --- a/src/python/python-deps.ts +++ b/src/python/python-deps.ts @@ -7,16 +7,14 @@ export interface IPythonDeps { addDependency(spec: string): void; /** - * Adds a test dependency. + * Adds a dev dependency. * * @param spec Format `@` */ - addTestDependency(spec: string): void; + addDevDependency(spec: string): void; /** - * Adds a dev dependency. - * - * @param spec Format `@` + * Installs dependencies (called during post-synthesis). */ - addDevDependency(spec: string): void; + installDependencies(): void; } diff --git a/src/python/python-env.ts b/src/python/python-env.ts index 9e88ff370dc..f4c8a979cf9 100644 --- a/src/python/python-env.ts +++ b/src/python/python-env.ts @@ -1,5 +1,6 @@ -import { Task, TaskOptions } from '../tasks'; - export interface IPythonEnv { - addEnvTask(name: string, props: TaskOptions): Task; + /** + * Initializes the virtual environment if it doesn't exist (called during post-synthesis). + */ + setupEnvironment(): void; } diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 43ac412c15e..6d5a6cd23b5 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -1,5 +1,4 @@ import { Project, ProjectOptions, ProjectType } from '../project'; -import { TaskOptions } from '../tasks'; import { Pip } from './pip'; import { Pytest, PytestOptions } from './pytest'; import { IPythonDeps } from './python-deps'; @@ -97,27 +96,18 @@ export interface PythonProjectOptions extends ProjectOptions { /** * Python project. * - * Every python project must have a component for managing dependencies, a - * component for managing the Python virtual environment, and if it is a - * library, a component for managing packaging the library. Some components - * satisfy multiple requirements. - * - * - pip: dependency manager - * - venv: environment manager - * - pipenv: dependency and environment manager - * - setuptools: packaging manager - * - poetry: dependency, environment, and packaging manager - * * @pjid python */ export class PythonProject extends Project { /** - * Absolute path to the user's python installation. + * Absolute path to the user's python installation. This will be used for + * setting up the virtual environment. */ readonly pythonPath: string; /** - * Python module name (the project name, with any hyphens replaced with underscores). + * Python module name (the project name, with any hyphens or periods replaced + * with underscores). */ readonly moduleName: string; @@ -207,10 +197,6 @@ export class PythonProject extends Project { this.addDependency(dep); } - for (const dep of options.testDeps ?? []) { - this.addTestDependency(dep); - } - for (const dep of options.devDeps ?? []) { this.addDevDependency(dep); } @@ -224,19 +210,7 @@ export class PythonProject extends Project { * @param name project name */ private safeName(name: string) { - return name.replace('-', '_'); - } - - /** - * Adds a single task that runs in the project's virtual environment. - * - * Additional steps can be added, but they will not be run in the environment. - * - * @param name The task name to add - * @param props Task properties - */ - public addEnvTask(name: string, props: TaskOptions) { - return this.envManager.addEnvTask(name, props); + return name.replace('-', '_').replace('.', '_'); } /** @@ -248,15 +222,6 @@ export class PythonProject extends Project { return this.depsManager.addDependency(spec); } - /** - * Adds a test dependency. - * - * @param spec Format `@` - */ - public addTestDependency(spec: string) { - return this.depsManager.addTestDependency(spec); - } - /** * Adds a dev dependency. * @@ -265,4 +230,11 @@ export class PythonProject extends Project { public addDevDependency(spec: string) { return this.depsManager.addDevDependency(spec); } + + public postSynthesize() { + super.postSynthesize(); + + this.envManager.setupEnvironment(); + this.depsManager.installDependencies(); + } } diff --git a/src/python/venv.ts b/src/python/venv.ts index 05594432f3b..b3b1ff4eed7 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -1,6 +1,7 @@ import * as path from 'path'; +import * as fs from 'fs-extra'; import { Component } from '../component'; -import { Task, TaskCategory, TaskOptions } from '../tasks'; +import { exec } from '../util'; import { IPythonEnv } from './python-env'; import { PythonProject } from './python-project'; @@ -18,44 +19,30 @@ export class Venv extends Component implements IPythonEnv { * Name of directory to store the environment in */ private readonly envdir: string; - - public readonly setupEnvTask: Task; + private readonly pythonProject: PythonProject; constructor(project: PythonProject, options: VenvOptions) { super(project); this.envdir = options.envdir ?? '.env'; + this.pythonProject = project; this.project.gitignore.exclude(`/${this.envdir}`); - this.setupEnvTask = project.addTask('setup-env', { - description: 'Setup the project\'s python environment', - category: TaskCategory.MISC, - exec: `${project.pythonPath} -m venv ${this.envdir}`, - }); - this.setupEnvTask.say('Environment successfully created.'); + this.project.tasks.addEnvironment('VIRTUAL_ENV', `$(echo $PWD/${this.envdir})`); + this.project.tasks.addEnvironment('PYTHONHOME', ''); + this.project.tasks.addEnvironment('PATH', `$(echo $PWD/${this.envdir}/bin:$PATH)`); } /** - * Adds a task that runs in the project's virtual environment. - * - * @param name The task name to add - * @param props Task properties + * Initializes the virtual environment if it doesn't exist (called during post-synthesis). */ - public addEnvTask(name: string, props: TaskOptions) { - const absoluteEnvPath = path.join(this.project.outdir, this.envdir); - - return this.project.tasks.addTask(name, { - ...props, - - // simulate the effect of 'source .env/bin/activate' - // the original script "unsets" PYTHONHOME, but setting to empty string works too - env: { - VIRTUAL_ENV: absoluteEnvPath, - PYTHONHOME: '', - PATH: `$(echo ${absoluteEnvPath}/bin:$PATH)`, - ...props.env, - }, - }); + public setupEnvironment() { + const absoluteEnvdir = path.join(this.project.outdir, this.envdir); + if (!fs.pathExistsSync(absoluteEnvdir)) { + this.project.logger.info(`Setting up a virtual environment using the python installation that was found: ${this.pythonProject.pythonPath}.`); + exec(`${this.pythonProject.pythonPath} -m venv ${this.envdir}`, { cwd: this.project.outdir }); + this.project.logger.info(`Environment successfully created (located in /${this.envdir}).`); + } } } \ No newline at end of file From e31274466b1240d021841ce5be68522805f9ecee Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 25 Jan 2021 01:41:03 -0500 Subject: [PATCH 11/29] cleanup --- src/python/pytest.ts | 2 +- src/python/python-sample.ts | 2 +- src/python/venv.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/python/pytest.ts b/src/python/pytest.ts index 38f8b2188e3..34adc1f5f44 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -59,4 +59,4 @@ export class Pytest extends Component { }, }); } -} \ No newline at end of file +} diff --git a/src/python/python-sample.ts b/src/python/python-sample.ts index 9ea6844c081..9c1ad45e94c 100644 --- a/src/python/python-sample.ts +++ b/src/python/python-sample.ts @@ -39,4 +39,4 @@ export class PythonSample extends Component { }, }); } -} \ No newline at end of file +} diff --git a/src/python/venv.ts b/src/python/venv.ts index b3b1ff4eed7..bc809f3cc0c 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -45,4 +45,4 @@ export class Venv extends Component implements IPythonEnv { this.project.logger.info(`Environment successfully created (located in /${this.envdir}).`); } } -} \ No newline at end of file +} From 13dfe3f059b50493171e20a04bc209aa08e87050 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 31 Jan 2021 16:20:32 -0500 Subject: [PATCH 12/29] add initial setuptools support --- API.md | 141 +++++++- .../__snapshots__/inventory.test.ts.snap | 90 +++++ src/__tests__/__snapshots__/new.test.ts.snap | 8 + .../__snapshots__/python-project.test.ts.snap | 308 +++++++++++++++++- src/__tests__/python/setuptools.test.ts | 38 +++ src/python/index.ts | 2 + src/python/python-project.ts | 202 +++++++++++- src/python/setuppy.ts | 127 ++++++++ src/python/setuptools.ts | 31 ++ src/python/venv.ts | 1 - 10 files changed, 939 insertions(+), 9 deletions(-) create mode 100644 src/__tests__/python/setuptools.test.ts create mode 100644 src/python/setuppy.ts create mode 100644 src/python/setuptools.ts diff --git a/API.md b/API.md index a15be2c4bc3..b5534e7e34e 100644 --- a/API.md +++ b/API.md @@ -59,6 +59,8 @@ Name|Description [python.PythonProject](#projen-python-pythonproject)|Python project. [python.PythonSample](#projen-python-pythonsample)|Python code sample. [python.RequirementsFile](#projen-python-requirementsfile)|Specifies a list of packages to be installed using pip. +[python.SetupPy](#projen-python-setuppy)|*No description* +[python.Setuptools](#projen-python-setuptools)|*No description* [python.Venv](#projen-python-venv)|*No description* [tasks.Task](#projen-tasks-task)|A task that can be performed on the project. [tasks.TaskRuntime](#projen-tasks-taskruntime)|The runtime component of the tasks engine. @@ -161,6 +163,9 @@ Name|Description [python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. [python.PythonSampleOptions](#projen-python-pythonsampleoptions)|Options for python sample code. [python.RequirementsFileOptions](#projen-python-requirementsfileoptions)|*No description* +[python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)|Fields to pass in the setup() function of setup.py. +[python.SetupPyOptions](#projen-python-setuppyoptions)|*No description* +[python.SetuptoolsOptions](#projen-python-setuptoolsoptions)|*No description* [python.VenvOptions](#projen-python-venvoptions)|*No description* [tasks.TaskCommonOptions](#projen-tasks-taskcommonoptions)|*No description* [tasks.TaskOptions](#projen-tasks-taskoptions)|*No description* @@ -1355,7 +1360,7 @@ addRules(rules: Map): void __Extends__: [Component](#projen-component) -__Implemented by__: [github.PullRequestTemplate](#projen-github-pullrequesttemplate), [python.RequirementsFile](#projen-python-requirementsfile), [web.NextJsTypeDef](#projen-web-nextjstypedef), [web.ReactTypeDef](#projen-web-reacttypedef), [IgnoreFile](#projen-ignorefile), [JsonFile](#projen-jsonfile), [License](#projen-license), [Makefile](#projen-makefile), [TextFile](#projen-textfile), [TomlFile](#projen-tomlfile), [XmlFile](#projen-xmlfile), [YamlFile](#projen-yamlfile) +__Implemented by__: [github.PullRequestTemplate](#projen-github-pullrequesttemplate), [python.RequirementsFile](#projen-python-requirementsfile), [python.SetupPy](#projen-python-setuppy), [web.NextJsTypeDef](#projen-web-nextjstypedef), [web.ReactTypeDef](#projen-web-reacttypedef), [IgnoreFile](#projen-ignorefile), [JsonFile](#projen-jsonfile), [License](#projen-license), [Makefile](#projen-makefile), [TextFile](#projen-textfile), [TomlFile](#projen-tomlfile), [XmlFile](#projen-xmlfile), [YamlFile](#projen-yamlfile) __Obtainable from__: [Project](#projen-project).[tryFindFile](#projen-project#projen-project-tryfindfile)() ### Initializer @@ -4827,14 +4832,22 @@ new python.PythonProject(options: PythonProjectOptions) * **projectType** ([ProjectType](#projen-projecttype)) Which type of project this is (library/app). __*Default*__: ProjectType.UNKNOWN * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } * **pythonPath** (string) Absolute path to the user's python installation. + * **authorEmail** (string) Author's e-mail. __*Optional*__ + * **authorName** (string) Author's name. __*Optional*__ * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] + * **description** (string) A short project description. __*Optional*__ * **devDeps** (Array) List of dev dependencies for this project. __*Default*__: [] + * **homepage** (string) The project's homepage / website. __*Optional*__ + * **license** (string) The project license. __*Optional*__ * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Optional*__ + * **setuptools** (boolean) Use setuptools with a setup.py script for packaging and distribution. __*Default*__: true if the project type is library + * **setuptoolsOptions** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) Setuptools options. __*Default*__: defaults * **testDeps** (Array) List of test dependencies for this project. __*Default*__: [] * **venv** (boolean) Use venv to manage a virtual environment for installing dependencies inside. __*Default*__: true + * **version** (string) Manually specify package version. __*Optional*__ @@ -4973,6 +4986,79 @@ __Returns__: +## class SetupPy 🔹 + + + +__Submodule__: python + +__Extends__: [FileBase](#projen-filebase) + +### Initializer + + + + +```ts +new python.SetupPy(project: PythonProject, options: SetupPyOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **options** ([python.SetupPyOptions](#projen-python-setuppyoptions)) *No description* + * **setupConfig** ([python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)) Fields to pass in the setup() function. __*Optional*__ + + +### Methods + + +#### protected synthesizeContent(resolver)🔹 + +Implemented by derived classes and returns the contents of the file to emit. + +```ts +protected synthesizeContent(resolver: IResolver): string +``` + +* **resolver** ([IResolver](#projen-iresolver)) *No description* + +__Returns__: +* string + + + +## class Setuptools 🔹 + + + +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.Setuptools(project: PythonProject, options: SetuptoolsOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **options** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) *No description* + * **setupConfig** ([python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)) Fields to pass in the setup() function. __*Optional*__ + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**packageTask**🔹 | [tasks.Task](#projen-tasks-task) | +**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | + + + ## class Venv 🔹 @@ -8590,12 +8676,17 @@ Name | Type | Description -----|------|------------- **name**🔹 | string | This is the name of your project. **pythonPath**🔹 | string | Absolute path to the user's python installation. +**authorEmail**?🔹 | string | Author's e-mail.
__*Optional*__ +**authorName**?🔹 | string | Author's name.
__*Optional*__ **clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true **deps**?🔹 | Array | List of runtime dependencies for this project.
__*Default*__: [] +**description**?🔹 | string | A short project description.
__*Optional*__ **devContainer**?🔹 | boolean | Add a VSCode development environment (used for GitHub Codespaces).
__*Default*__: false **devDeps**?🔹 | Array | List of dev dependencies for this project.
__*Default*__: [] **gitpod**?🔹 | boolean | Add a Gitpod development environment.
__*Default*__: false +**homepage**?🔹 | string | The project's homepage / website.
__*Optional*__ **jsiiFqn**?🔹 | string | The JSII FQN (fully qualified name) of the project class.
__*Default*__: undefined +**license**?🔹 | string | The project license.
__*Optional*__ **logging**?🔹 | [LoggerOptions](#projen-loggeroptions) | Configure logging options such as verbosity.
__*Default*__: {} **outdir**?🔹 | string | The root directory of the project.
__*Default*__: "." **parent**?🔹 | [Project](#projen-project) | The parent project, if this project is part of a bigger project.
__*Optional*__ @@ -8605,8 +8696,11 @@ Name | Type | Description **pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults **readme**?🔹 | [SampleReadmeProps](#projen-samplereadmeprops) | The README setup.
__*Default*__: { filename: 'README.md', contents: '# replace this' } **sample**?🔹 | boolean | Include sample code and test if the relevant directories don't exist.
__*Optional*__ +**setuptools**?🔹 | boolean | Use setuptools with a setup.py script for packaging and distribution.
__*Default*__: true if the project type is library +**setuptoolsOptions**?🔹 | [python.SetuptoolsOptions](#projen-python-setuptoolsoptions) | Setuptools options.
__*Default*__: defaults **testDeps**?🔹 | Array | List of test dependencies for this project.
__*Default*__: [] **venv**?🔹 | boolean | Use venv to manage a virtual environment for installing dependencies inside.
__*Default*__: true +**version**?🔹 | string | Manually specify package version.
__*Optional*__ @@ -8622,6 +8716,51 @@ Options for python sample code. +## struct SetupPyConfigOptions 🔹 + + +Fields to pass in the setup() function of setup.py. + + + +Name | Type | Description +-----|------|------------- +**authorEmail**?🔹 | string | Author's e-mail.
__*Optional*__ +**authorName**?🔹 | string | Author's name.
__*Optional*__ +**description**?🔹 | string | A short project description.
__*Optional*__ +**homepage**?🔹 | string | Package's Homepage / Website.
__*Optional*__ +**license**?🔹 | string | The project license.
__*Optional*__ +**name**?🔹 | string | Name of the package.
__*Optional*__ +**version**?🔹 | string | Manually specify package version.
__*Optional*__ + + + +## struct SetupPyOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**setupConfig**?🔹 | [python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions) | Fields to pass in the setup() function.
__*Optional*__ + + + +## struct SetuptoolsOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**setupConfig**?🔹 | [python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions) | Fields to pass in the setup() function.
__*Optional*__ + + + ## struct VenvOptions 🔹 diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 5297af007c7..722ae9092b5 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -7891,6 +7891,28 @@ Array [ "fqn": "projen.python.PythonProject", "moduleName": "projen", "options": Array [ + Object { + "docs": "Author's e-mail.", + "name": "authorEmail", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "authorEmail", + ], + "switch": "author-email", + "type": "string", + }, + Object { + "docs": "Author's name.", + "name": "authorName", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "authorName", + ], + "switch": "author-name", + "type": "string", + }, Object { "default": "true", "docs": "Add a \`clobber\` task which resets the repo to origin.", @@ -7915,6 +7937,17 @@ Array [ "switch": "deps", "type": "unknown", }, + Object { + "docs": "A short project description.", + "name": "description", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "description", + ], + "switch": "description", + "type": "string", + }, Object { "default": "false", "docs": "Add a VSCode development environment (used for GitHub Codespaces).", @@ -7951,6 +7984,17 @@ Array [ "switch": "gitpod", "type": "boolean", }, + Object { + "docs": "The project's homepage / website.", + "name": "homepage", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "homepage", + ], + "switch": "homepage", + "type": "string", + }, Object { "docs": "The JSII FQN (fully qualified name) of the project class.", "name": "jsiiFqn", @@ -7962,6 +8006,17 @@ Array [ "switch": "jsii-fqn", "type": "string", }, + Object { + "docs": "The project license.", + "name": "license", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "license", + ], + "switch": "license", + "type": "string", + }, Object { "default": "{}", "docs": "Configure logging options such as verbosity.", @@ -8090,6 +8145,30 @@ Array [ "switch": "sample", "type": "boolean", }, + Object { + "default": "- true if the project type is library", + "docs": "Use setuptools with a setup.py script for packaging and distribution.", + "name": "setuptools", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "setuptools", + ], + "switch": "setuptools", + "type": "boolean", + }, + Object { + "default": "- defaults", + "docs": "Setuptools options.", + "name": "setuptoolsOptions", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "setuptoolsOptions", + ], + "switch": "setuptools-options", + "type": "SetuptoolsOptions", + }, Object { "default": "[]", "docs": "List of test dependencies for this project.", @@ -8114,6 +8193,17 @@ Array [ "switch": "venv", "type": "boolean", }, + Object { + "docs": "Manually specify package version.", + "name": "version", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "version", + ], + "switch": "version", + "type": "string", + }, ], "pjid": "python", "typename": "python.PythonProject", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 2ea16141611..283fc0879c5 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -832,14 +832,22 @@ const project = new python.PythonProject({ // readme: undefined, /* The README setup. */ /* PythonProjectOptions */ + // authorEmail: undefined, /* Author's e-mail. */ + // authorName: undefined, /* Author's name. */ // deps: [], /* List of runtime dependencies for this project. */ + // description: undefined, /* A short project description. */ // devDeps: [], /* List of dev dependencies for this project. */ + // homepage: undefined, /* The project's homepage / website. */ + // license: undefined, /* The project license. */ // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ // pytest: true, /* Include pytest tests. */ // pytestOptions: undefined, /* pytest options. */ // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ + // setuptools: undefined, /* Use setuptools with a setup.py script for packaging and distribution. */ + // setuptoolsOptions: undefined, /* Setuptools options. */ // testDeps: [], /* List of test dependencies for this project. */ // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ + // version: undefined, /* Manually specify package version. */ }); project.synth(); diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index 518d5ade4f6..4164442a094 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -3,8 +3,84 @@ exports[`defaults 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +*$py.class +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo +*.pot +*.py,cover +*.py[cod] +*.sage.py +*.so +*.spec +.Python +.cache +.coverage +.coverage.* +.dmypy.json +.eggs/ +.env +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.mypy_cache/ +.nox/ +.pybuilder/ +.pyre/ +.pytest_cache/ +.pytype/ +.ropeproject +.scrapy +.spyderproject +.spyproject +.tox/ +.venv +.webassets-cache /.env +/site +ENV/ +MANIFEST +__pycache__/ +__pypackages__/ +build/ +celerybeat-schedule +celerybeat.pid +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +htmlcov/ +instance/ +ipython_config.py +lib/ +lib64/ +local_settings.py node_modules/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +sdist/ +share/python-wheels/ +target/ +var/ +venv.bak/ +venv/ +wheels/ !/.projen/deps.json !/.projen/tasks.json !/requirements-dev.txt @@ -24,7 +100,6 @@ node_modules/ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "env": Object { "PATH": "$(echo $PWD/.env/bin:$PATH)", - "PYTHONHOME": "", "VIRTUAL_ENV": "$(echo $PWD/.env)", }, "tasks": Object { @@ -102,8 +177,84 @@ def test_hello(name, expected): exports[`dependencies 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +*$py.class +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo +*.pot +*.py,cover +*.py[cod] +*.sage.py +*.so +*.spec +.Python +.cache +.coverage +.coverage.* +.dmypy.json +.eggs/ +.env +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.mypy_cache/ +.nox/ +.pybuilder/ +.pyre/ +.pytest_cache/ +.pytype/ +.ropeproject +.scrapy +.spyderproject +.spyproject +.tox/ +.venv +.webassets-cache /.env +/site +ENV/ +MANIFEST +__pycache__/ +__pypackages__/ +build/ +celerybeat-schedule +celerybeat.pid +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +htmlcov/ +instance/ +ipython_config.py +lib/ +lib64/ +local_settings.py node_modules/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +sdist/ +share/python-wheels/ +target/ +var/ +venv.bak/ +venv/ +wheels/ !/.projen/deps.json !/.projen/tasks.json !/requirements-dev.txt @@ -138,7 +289,6 @@ node_modules/ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "env": Object { "PATH": "$(echo $PWD/.env/bin:$PATH)", - "PYTHONHOME": "", "VIRTUAL_ENV": "$(echo $PWD/.env)", }, "tasks": Object { @@ -219,8 +369,84 @@ def test_hello(name, expected): exports[`dependencies via ctor 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +*$py.class +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo +*.pot +*.py,cover +*.py[cod] +*.sage.py +*.so +*.spec +.Python +.cache +.coverage +.coverage.* +.dmypy.json +.eggs/ +.env +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.mypy_cache/ +.nox/ +.pybuilder/ +.pyre/ +.pytest_cache/ +.pytype/ +.ropeproject +.scrapy +.spyderproject +.spyproject +.tox/ +.venv +.webassets-cache /.env +/site +ENV/ +MANIFEST +__pycache__/ +__pypackages__/ +build/ +celerybeat-schedule +celerybeat.pid +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +htmlcov/ +instance/ +ipython_config.py +lib/ +lib64/ +local_settings.py node_modules/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +sdist/ +share/python-wheels/ +target/ +var/ +venv.bak/ +venv/ +wheels/ !/.projen/deps.json !/.projen/tasks.json !/requirements-dev.txt @@ -255,7 +481,6 @@ node_modules/ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "env": Object { "PATH": "$(echo $PWD/.env/bin:$PATH)", - "PYTHONHOME": "", "VIRTUAL_ENV": "$(echo $PWD/.env)", }, "tasks": Object { @@ -336,8 +561,84 @@ def test_hello(name, expected): exports[`no pytest 1`] = ` Object { ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +*$py.class +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo +*.pot +*.py,cover +*.py[cod] +*.sage.py +*.so +*.spec +.Python +.cache +.coverage +.coverage.* +.dmypy.json +.eggs/ +.env +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.mypy_cache/ +.nox/ +.pybuilder/ +.pyre/ +.pytest_cache/ +.pytype/ +.ropeproject +.scrapy +.spyderproject +.spyproject +.tox/ +.venv +.webassets-cache /.env +/site +ENV/ +MANIFEST +__pycache__/ +__pypackages__/ +build/ +celerybeat-schedule +celerybeat.pid +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +htmlcov/ +instance/ +ipython_config.py +lib/ +lib64/ +local_settings.py node_modules/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +sdist/ +share/python-wheels/ +target/ +var/ +venv.bak/ +venv/ +wheels/ !/.projen/deps.json !/.projen/tasks.json !/requirements-dev.txt @@ -347,7 +648,6 @@ node_modules/ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", "env": Object { "PATH": "$(echo $PWD/.env/bin:$PATH)", - "PYTHONHOME": "", "VIRTUAL_ENV": "$(echo $PWD/.env)", }, "tasks": Object { diff --git a/src/__tests__/python/setuptools.test.ts b/src/__tests__/python/setuptools.test.ts new file mode 100644 index 00000000000..902653d6398 --- /dev/null +++ b/src/__tests__/python/setuptools.test.ts @@ -0,0 +1,38 @@ +import { LogLevel } from '../../logger'; +import { PythonProject, PythonProjectOptions } from '../../python'; +import { mkdtemp, synthSnapshot } from '../util'; + +test('setuptools enabled', () => { + const p = new TestPythonProject({ + setuptools: true, + authorEmail: 'foo@example.com', + authorName: 'Foo Bar', + homepage: 'http://www.example.com', + description: 'a short project description', + license: 'Apache Software License', + setuptoolsOptions: { + setupConfig: { + classifiers: [ + 'Development Status :: 4 - Beta', + ], + }, + }, + }); + + const snapshot = synthSnapshot(p); + expect(snapshot['setup.py']).toContain('Development Status :: 4 - Beta'); +}); + +class TestPythonProject extends PythonProject { + constructor(options: Partial = { }) { + super({ + ...options, + clobber: false, + name: 'test-python-project', + pythonPath: '/usr/bin/python', + outdir: mkdtemp(), + logging: { level: LogLevel.OFF }, + jsiiFqn: 'projen.python.PythonProject', + }); + } +} diff --git a/src/python/index.ts b/src/python/index.ts index e94f1be8874..e3cec9c8209 100644 --- a/src/python/index.ts +++ b/src/python/index.ts @@ -6,4 +6,6 @@ export * from './python-packaging'; export * from './python-project'; export * from './python-sample'; export * from './requirements-file'; +export * from './setuppy'; +export * from './setuptools'; export * from './venv'; \ No newline at end of file diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 6d5a6cd23b5..f01b95d45f8 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -5,6 +5,7 @@ import { IPythonDeps } from './python-deps'; import { IPythonEnv } from './python-env'; import { IPythonPackaging } from './python-packaging'; import { PythonSample } from './python-sample'; +import { Setuptools, SetuptoolsOptions } from './setuptools'; import { Venv } from './venv'; @@ -15,6 +16,8 @@ const PYTHON_PROJECT_NAME_REGEX = /^[A-Za-z0-9-_\.]+$/; * Options for `PythonProject`. */ export interface PythonProjectOptions extends ProjectOptions { + // -- required options -- + /** * Absolute path to the user's python installation. * @@ -22,6 +25,38 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly pythonPath: string; + // -- general information -- + + /** + * Author's name + */ + readonly authorName?: string; + + /** + * Author's e-mail + */ + readonly authorEmail?: string; + + /** + * Manually specify package version + */ + readonly version?: string; + + /** + * A short project description + */ + readonly description?: string; + + /** + * The project license + */ + readonly license?: string; + + /** + * The project's homepage / website + */ + readonly homepage?: string; + // -- dependencies -- /** @@ -73,6 +108,19 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly venv?: boolean; + /** + * Use setuptools with a setup.py script for packaging and distribution. + * + * @default - true if the project type is library + */ + readonly setuptools?: boolean; + + /** + * Setuptools options + * @default - defaults + */ + readonly setuptoolsOptions?: SetuptoolsOptions; + // -- optional components -- /** @@ -149,9 +197,20 @@ export class PythonProject extends Project { this.depsManager = new Pip(this, {}); } - // if (options.setuptools ?? true) { - // this.packagingManager = new SetupTools(this, options); - // } + if (options.setuptools ?? (this.projectType === ProjectType.LIB)) { + this.packagingManager = new Setuptools(this, { + ...options.setuptoolsOptions, + setupConfig: { + authorName: options.authorName, + authorEmail: options.authorEmail, + version: options.version, + description: options.version, + license: options.license, + homepage: options.homepage, + ...options.setuptoolsOptions?.setupConfig, + }, + }); + } // if (options.conda ?? false) { // this.depsManager = new Conda(this, options); @@ -200,6 +259,8 @@ export class PythonProject extends Project { for (const dep of options.devDeps ?? []) { this.addDevDependency(dep); } + + this.addDefaultGitIgnore(); } /** @@ -213,6 +274,141 @@ export class PythonProject extends Project { return name.replace('-', '_').replace('.', '_'); } + /** + * Adds default gitignore options for a Python project based on + * https://github.com/github/gitignore/blob/master/Python.gitignore + */ + private addDefaultGitIgnore() { + this.gitignore.exclude( + '# Byte-compiled / optimized / DLL files', + '__pycache__/', + '*.py[cod]', + '*$py.class', + '', + '# C extensions', + '*.so', + '', + '# Distribution / packaging', + '.Python', + 'build/', + 'develop-eggs/', + 'dist/', + 'downloads/', + 'eggs/', + '.eggs/', + 'lib/', + 'lib64/', + 'parts/', + 'sdist/', + 'var/', + 'wheels/', + 'share/python-wheels/', + '*.egg-info/', + '.installed.cfg', + '*.egg', + 'MANIFEST', + '', + '# PyInstaller', + '# Usually these files are written by a python script from a template', + '# before PyInstaller builds the exe, so as to inject date/other infos into it.', + '*.manifest', + '*.spec', + '', + '# Installer logs', + 'pip-log.txt', + 'pip-delete-this-directory.txt', + '', + '# Unit test / coverage reports', + 'htmlcov/', + '.tox/', + '.nox/', + '.coverage', + '.coverage.*', + '.cache', + 'nosetests.xml', + 'coverage.xml', + '*.cover', + '*.py,cover', + '.hypothesis/', + '.pytest_cache/', + 'cover/', + '', + '# Translations', + '*.mo', + '*.pot', + '', + '# Django stuff:', + '*.log', + 'local_settings.py', + 'db.sqlite3', + 'db.sqlite3-journal', + '', + '# Flask stuff:', + 'instance/', + '.webassets-cache', + '', + '# Scrapy stuff:', + '.scrapy', + '', + '# Sphinx documentation', + 'docs/_build/', + '', + '# PyBuilder', + '.pybuilder/', + 'target/', + '', + '# Jupyter Notebook', + '.ipynb_checkpoints', + '', + '# IPython', + 'profile_default/', + 'ipython_config.py', + '', + '# PEP 582; used by e.g. github.com/David-OConnor/pyflow', + '__pypackages__/', + '', + '# Celery stuff', + 'celerybeat-schedule', + 'celerybeat.pid', + '', + '# SageMath parsed files', + '*.sage.py', + '', + '# Environments', + '.env', + '.venv', + 'env/', + 'venv/', + 'ENV/', + 'env.bak/', + 'venv.bak/', + '', + '# Spyder project settings', + '.spyderproject', + '.spyproject', + '', + '# Rope project settings', + '.ropeproject', + '', + '# mkdocs documentation', + '/site', + '', + '# mypy', + '.mypy_cache/', + '.dmypy.json', + 'dmypy.json', + '', + '# Pyre type checker', + '.pyre/', + '', + '# pytype static type analyzer', + '.pytype/', + '', + '# Cython debug symbols', + 'cython_debug/', + ); + } + /** * Adds a runtime dependency. * diff --git a/src/python/setuppy.ts b/src/python/setuppy.ts new file mode 100644 index 00000000000..2a1a4247625 --- /dev/null +++ b/src/python/setuppy.ts @@ -0,0 +1,127 @@ +import { FileBase, IResolver } from '../file'; +import { PythonProject } from './python-project'; + +/** + * Fields to pass in the setup() function of setup.py + * + * @see https://docs.python.org/3/distutils/setupscript.html + */ +export interface SetupPyConfigOptions { + /** + * Name of the package + */ + readonly name?: string; + + /** + * List of submodules to be packaged + */ + readonly packages?: string[]; + + /** + * Author's name + */ + readonly authorName?: string; + + /** + * Author's e-mail + */ + readonly authorEmail?: string; + + /** + * Manually specify package version + */ + readonly version?: string; + + /** + * A short project description + */ + readonly description?: string; + + /** + * The project license + */ + readonly license?: string; + + /** + * Package's Homepage / Website + */ + readonly homepage?: string; + + /** + * Tags that can be associated with a package. + * + * The full list is available at https://pypi.python.org/pypi?%3Aaction=list_classifiers + */ + readonly classifiers?: string[]; + + /** + * Escape hatch to allow any value + */ + readonly [name: string]: any; +} + +export interface SetupPyOptions { + /** + * Fields to pass in the setup() function + */ + readonly setupConfig?: SetupPyConfigOptions; +} + +export class SetupPy extends FileBase { + private readonly setupConfig: any; + + constructor(project: PythonProject, options: SetupPyOptions) { + super(project, 'setup.py'); + + this.setupConfig = { + name: project.name, + packages: [project.moduleName], + python_requires: '>=3.6', + classifiers: [ + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + ], + ...options.setupConfig ? this.renameFields(options.setupConfig) : [], + }; + } + + protected synthesizeContent(resolver: IResolver): string | undefined { + const lines = [ + `# ${FileBase.PROJEN_MARKER}`, + '', + 'import json', + 'from setuptools import setup', + '', + 'kwargs = json.loads(', + ' """', + JSON.stringify(this.setupConfig, null, 4), + '"""', + ')', + '', + 'setup(**kwargs)', + ]; + + return `${resolver.resolve(lines).join('\n')}\n`; + } + + // modify some key names since JSII interfaces must have camelCase fields + private renameFields(options: SetupPyConfigOptions): any { + const obj: { [key: string]: any } = {}; + for (const [key, value] of Object.entries(options)) { + if (key === 'authorName') { + obj.author = value; + } else if (key === 'authorEmail') { + obj.author_email = value; + } else if (key === 'homepage') { + obj.url = value; + } else { + obj[key] = value; + } + } + return obj; + } +} \ No newline at end of file diff --git a/src/python/setuptools.ts b/src/python/setuptools.ts new file mode 100644 index 00000000000..0080a29e8c4 --- /dev/null +++ b/src/python/setuptools.ts @@ -0,0 +1,31 @@ +import { Component } from '../component'; +import { Task, TaskCategory } from '../tasks'; +import { PythonProject } from './python-project'; +import { SetupPy, SetupPyOptions } from './setuppy'; + +export interface SetuptoolsOptions extends SetupPyOptions {} + +export class Setuptools extends Component { + public readonly packageTask: Task; + public readonly uploadTask: Task; + constructor(project: PythonProject, options: SetuptoolsOptions) { + super(project); + + project.addDevDependency('wheel@0.36.2'); + project.addDevDependency('twine@3.3.0'); + + this.packageTask = project.addTask('package', { + description: 'Creates source archive and wheel for distribution.', + category: TaskCategory.RELEASE, + exec: 'rm -fr dist/* && python setup.py sdist bdist_wheel', + }); + + this.uploadTask = project.addTask('upload:test', { + description: 'Uploads the package against a test PyPI endpoint.', + category: TaskCategory.RELEASE, + exec: 'twine upload --repository-url https://test.pypi.org/legacy/ dist/*', + }); + + new SetupPy(project, options); + } +} \ No newline at end of file diff --git a/src/python/venv.ts b/src/python/venv.ts index bc809f3cc0c..14aade838bc 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -30,7 +30,6 @@ export class Venv extends Component implements IPythonEnv { this.project.gitignore.exclude(`/${this.envdir}`); this.project.tasks.addEnvironment('VIRTUAL_ENV', `$(echo $PWD/${this.envdir})`); - this.project.tasks.addEnvironment('PYTHONHOME', ''); this.project.tasks.addEnvironment('PATH', `$(echo $PWD/${this.envdir}/bin:$PATH)`); } From 266fd6ba9d0f51d6674f2b98ac44afbd393405fa Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 31 Jan 2021 16:24:23 -0500 Subject: [PATCH 13/29] fix bug --- src/__tests__/python/setuptools.test.ts | 7 ++++++- src/python/python-project.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/__tests__/python/setuptools.test.ts b/src/__tests__/python/setuptools.test.ts index 902653d6398..56373ed227e 100644 --- a/src/__tests__/python/setuptools.test.ts +++ b/src/__tests__/python/setuptools.test.ts @@ -6,7 +6,7 @@ test('setuptools enabled', () => { const p = new TestPythonProject({ setuptools: true, authorEmail: 'foo@example.com', - authorName: 'Foo Bar', + authorName: 'Firstname Lastname', homepage: 'http://www.example.com', description: 'a short project description', license: 'Apache Software License', @@ -20,6 +20,11 @@ test('setuptools enabled', () => { }); const snapshot = synthSnapshot(p); + expect(snapshot['setup.py']).toContain('foo@example.com'); + expect(snapshot['setup.py']).toContain('Firstname Lastname'); + expect(snapshot['setup.py']).toContain('http://www.example.com'); + expect(snapshot['setup.py']).toContain('a short project description'); + expect(snapshot['setup.py']).toContain('Apache Software License'); expect(snapshot['setup.py']).toContain('Development Status :: 4 - Beta'); }); diff --git a/src/python/python-project.ts b/src/python/python-project.ts index f01b95d45f8..1771e7c3ade 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -204,7 +204,7 @@ export class PythonProject extends Project { authorName: options.authorName, authorEmail: options.authorEmail, version: options.version, - description: options.version, + description: options.description, license: options.license, homepage: options.homepage, ...options.setuptoolsOptions?.setupConfig, From cb7510e547c0c0ca89fd91b56c56178c4d9826e7 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 31 Jan 2021 18:17:03 -0500 Subject: [PATCH 14/29] building out poetry component --- API.md | 183 +++++++++++++- .../__snapshots__/inventory.test.ts.snap | 12 + src/__tests__/__snapshots__/new.test.ts.snap | 1 + src/__tests__/python/poetry.test.ts | 30 +++ src/python/index.ts | 1 + src/python/poetry.ts | 227 ++++++++++++++++++ src/python/python-project.ts | 20 +- src/python/setuppy.ts | 4 +- src/python/venv.ts | 2 +- 9 files changed, 470 insertions(+), 10 deletions(-) create mode 100644 src/__tests__/python/poetry.test.ts create mode 100644 src/python/poetry.ts diff --git a/API.md b/API.md index b8796e474f2..fd4b0347713 100644 --- a/API.md +++ b/API.md @@ -55,6 +55,8 @@ Name|Description [java.Pom](#projen-java-pom)|A Project Object Model or POM is the fundamental unit of work in Maven. [java.Projenrc](#projen-java-projenrc)|Allows writing projenrc files in java. [python.Pip](#projen-python-pip)|*No description* +[python.Poetry](#projen-python-poetry)|*No description* +[python.PoetryPyproject](#projen-python-poetrypyproject)|Represents configuration of a pyproject.toml file for a Poetry project. [python.Pytest](#projen-python-pytest)|*No description* [python.PythonProject](#projen-python-pythonproject)|Python project. [python.PythonSample](#projen-python-pythonsample)|Python code sample. @@ -159,6 +161,8 @@ Name|Description [java.ProjenrcCommonOptions](#projen-java-projenrccommonoptions)|Options for `Projenrc`. [java.ProjenrcOptions](#projen-java-projenrcoptions)|*No description* [python.PipOptions](#projen-python-pipoptions)|*No description* +[python.PoetryOptions](#projen-python-poetryoptions)|*No description* +[python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)|*No description* [python.PytestOptions](#projen-python-pytestoptions)|*No description* [python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. [python.PythonSampleOptions](#projen-python-pythonsampleoptions)|Options for python sample code. @@ -4770,6 +4774,140 @@ installDependencies(): void +## class Poetry 🔹 + + + +__Implements__: [python.IPythonDeps](#projen-python-ipythondeps), [python.IPythonEnv](#projen-python-ipythonenv), [python.IPythonPackaging](#projen-python-ipythonpackaging) +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.Poetry(project: PythonProject, _options: PoetryOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **_options** ([python.PoetryOptions](#projen-python-poetryoptions)) *No description* + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**installTask**🔹 | [tasks.Task](#projen-tasks-task) | + +### Methods + + +#### addDependency(spec)🔹 + +Adds a runtime dependency. + +```ts +addDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### addDevDependency(spec)🔹 + +Adds a dev dependency. + +```ts +addDevDependency(spec: string): void +``` + +* **spec** (string) Format `@`. + + + + +#### installDependencies()🔹 + +Installs dependencies (called during post-synthesis). + +```ts +installDependencies(): void +``` + + + + + +#### setupEnvironment()🔹 + +Initializes the virtual environment if it doesn't exist (called during post-synthesis). + +```ts +setupEnvironment(): void +``` + + + + + + + +## class PoetryPyproject 🔹 + +Represents configuration of a pyproject.toml file for a Poetry project. + +__Submodule__: python + +__Extends__: [Component](#projen-component) + +### Initializer + + + + +```ts +new python.PoetryPyproject(project: PythonProject, options: PoetryPyprojectOptions) +``` + +* **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* +* **options** ([python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)) *No description* + * **dependencies** (Map) A list of dependencies for the project. + * **description** (string) A short description of the package. + * **devDependencies** (Map) A list of development dependencies for the project. + * **name** (string) Name of the package. + * **scripts** (Map) The scripts or executables that will be installed when installing the package. + * **version** (string) Version of the package. + * **authors** (Array) The authors of the package. __*Optional*__ + * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **documentation** (string) A URL to the documentation of the project. __*Optional*__ + * **exclude** (Array) A list of patterns that will be excluded in the final package. __*Optional*__ + * **homepage** (string) A URL to the website of the project. __*Optional*__ + * **include** (Array) A list of patterns that will be included in the final package. __*Optional*__ + * **keywords** (Array) A list of keywords (max: 5) that the package is related to. __*Optional*__ + * **license** (string) License of this package as an SPDX identifier. __*Optional*__ + * **maintainers** (Array) the maintainers of the package. __*Optional*__ + * **packages** (Array) A list of packages and modules to include in the final distribution. __*Optional*__ + * **readme** (string) The name of the readme file of the package. __*Optional*__ + * **repository** (string) A URL to the repository of the project. __*Optional*__ + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**file**🔹 | [TomlFile](#projen-tomlfile) | + + + ## class Pytest 🔹 @@ -4840,6 +4978,7 @@ new python.PythonProject(options: PythonProjectOptions) * **homepage** (string) The project's homepage / website. __*Optional*__ * **license** (string) The project license. __*Optional*__ * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true + * **poetry** (boolean) Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. __*Default*__: false * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Optional*__ @@ -8577,7 +8716,7 @@ Name | Type | Description ## interface IPythonDeps 🔹 -__Implemented by__: [python.Pip](#projen-python-pip) +__Implemented by__: [python.Pip](#projen-python-pip), [python.Poetry](#projen-python-poetry) ### Methods @@ -8625,7 +8764,7 @@ installDependencies(): void ## interface IPythonEnv 🔹 -__Implemented by__: [python.Venv](#projen-python-venv) +__Implemented by__: [python.Poetry](#projen-python-poetry), [python.Venv](#projen-python-venv) ### Methods @@ -8647,6 +8786,7 @@ setupEnvironment(): void ## interface IPythonPackaging 🔹 +__Implemented by__: [python.Poetry](#projen-python-poetry) @@ -8657,6 +8797,42 @@ setupEnvironment(): void +## struct PoetryOptions 🔹 + + + + + +## struct PoetryPyprojectOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**dependencies**🔹 | Map | A list of dependencies for the project. +**description**🔹 | string | A short description of the package. +**devDependencies**🔹 | Map | A list of development dependencies for the project. +**name**🔹 | string | Name of the package. +**scripts**🔹 | Map | The scripts or executables that will be installed when installing the package. +**version**🔹 | string | Version of the package. +**authors**?🔹 | Array | The authors of the package.
__*Optional*__ +**classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ +**documentation**?🔹 | string | A URL to the documentation of the project.
__*Optional*__ +**exclude**?🔹 | Array | A list of patterns that will be excluded in the final package.
__*Optional*__ +**homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ +**include**?🔹 | Array | A list of patterns that will be included in the final package.
__*Optional*__ +**keywords**?🔹 | Array | A list of keywords (max: 5) that the package is related to.
__*Optional*__ +**license**?🔹 | string | License of this package as an SPDX identifier.
__*Optional*__ +**maintainers**?🔹 | Array | the maintainers of the package.
__*Optional*__ +**packages**?🔹 | Array | A list of packages and modules to include in the final distribution.
__*Optional*__ +**readme**?🔹 | string | The name of the readme file of the package.
__*Optional*__ +**repository**?🔹 | string | A URL to the repository of the project.
__*Optional*__ + + + ## struct PytestOptions 🔹 @@ -8697,6 +8873,7 @@ Name | Type | Description **outdir**?🔹 | string | The root directory of the project.
__*Default*__: "." **parent**?🔹 | [Project](#projen-project) | The parent project, if this project is part of a bigger project.
__*Optional*__ **pip**?🔹 | boolean | Use pip with a requirements.txt file to track project dependencies.
__*Default*__: true +**poetry**?🔹 | boolean | Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.
__*Default*__: false **projectType**?🔹 | [ProjectType](#projen-projecttype) | Which type of project this is (library/app).
__*Default*__: ProjectType.UNKNOWN **pytest**?🔹 | boolean | Include pytest tests.
__*Default*__: true **pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults @@ -8733,10 +8910,12 @@ Name | Type | Description -----|------|------------- **authorEmail**?🔹 | string | Author's e-mail.
__*Optional*__ **authorName**?🔹 | string | Author's name.
__*Optional*__ +**classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ **description**?🔹 | string | A short project description.
__*Optional*__ **homepage**?🔹 | string | Package's Homepage / Website.
__*Optional*__ **license**?🔹 | string | The project license.
__*Optional*__ **name**?🔹 | string | Name of the package.
__*Optional*__ +**packages**?🔹 | Array | List of submodules to be packaged.
__*Optional*__ **version**?🔹 | string | Manually specify package version.
__*Optional*__ diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 722ae9092b5..aaa7c2bb678 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -8075,6 +8075,18 @@ Array [ "switch": "pip", "type": "boolean", }, + Object { + "default": "false", + "docs": "Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.", + "name": "poetry", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "poetry", + ], + "switch": "poetry", + "type": "boolean", + }, Object { "default": "ProjectType.UNKNOWN", "docs": "Which type of project this is (library/app).", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 283fc0879c5..31b1648d38d 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -840,6 +840,7 @@ const project = new python.PythonProject({ // homepage: undefined, /* The project's homepage / website. */ // license: undefined, /* The project license. */ // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ + // poetry: false, /* Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. */ // pytest: true, /* Include pytest tests. */ // pytestOptions: undefined, /* pytest options. */ // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ diff --git a/src/__tests__/python/poetry.test.ts b/src/__tests__/python/poetry.test.ts new file mode 100644 index 00000000000..d212d393ac8 --- /dev/null +++ b/src/__tests__/python/poetry.test.ts @@ -0,0 +1,30 @@ +import { LogLevel } from '../../logger'; +import { PythonProject, PythonProjectOptions } from '../../python'; +import { mkdtemp, synthSnapshot } from '../util'; + +test('poetry enabled', () => { + const p = new TestPythonProject({ + poetry: true, + authorEmail: 'foo@example.com', + authorName: 'Firstname Lastname', + homepage: 'http://www.example.com', + description: 'a short project description', + license: 'Apache Software License', + }); + + expect(synthSnapshot(p)).toMatchSnapshot(); +}); + +class TestPythonProject extends PythonProject { + constructor(options: Partial = { }) { + super({ + ...options, + clobber: false, + name: 'test-python-project', + pythonPath: '/usr/bin/python', + outdir: mkdtemp(), + logging: { level: LogLevel.OFF }, + jsiiFqn: 'projen.python.PythonProject', + }); + } +} diff --git a/src/python/index.ts b/src/python/index.ts index e3cec9c8209..e68b66bdd30 100644 --- a/src/python/index.ts +++ b/src/python/index.ts @@ -1,4 +1,5 @@ export * from './pip'; +export * from './poetry'; export * from './pytest'; export * from './python-env'; export * from './python-deps'; diff --git a/src/python/poetry.ts b/src/python/poetry.ts new file mode 100644 index 00000000000..03a00d0843b --- /dev/null +++ b/src/python/poetry.ts @@ -0,0 +1,227 @@ +import { Component } from '../component'; +import { DependencyType } from '../deps'; +import { Task, TaskCategory } from '../tasks'; +import { TomlFile } from '../toml'; +import { exec, execOrUndefined } from '../util'; +import { IPythonDeps } from './python-deps'; +import { IPythonEnv } from './python-env'; +import { IPythonPackaging } from './python-packaging'; +import { PythonProject } from './python-project'; + +export interface PoetryOptions {} + +/** + * Manage project dependencies, virtual environments, and packaging through the + * poetry CLI tool. + */ +export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPythonPackaging { + private readonly pythonProject: PythonProject; + public readonly installTask: Task; + + constructor(project: PythonProject, _options: PoetryOptions) { + super(project); + + this.pythonProject = project; + + this.installTask = project.addTask('install', { + description: 'Install and upgrade dependencies', + category: TaskCategory.BUILD, + exec: 'poetry install', + }); + } + + /** + * Adds a runtime dependency. + * + * @param spec Format `@` + */ + public addDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.RUNTIME); + } + + /** + * Adds a dev dependency. + * + * @param spec Format `@` + */ + public addDevDependency(spec: string) { + this.project.deps.addDependency(spec, DependencyType.DEVENV); + } + + /** + * Initializes the virtual environment if it doesn't exist (called during post-synthesis). + */ + public setupEnvironment() { + const result = execOrUndefined('which poetry', { cwd: this.project.outdir }); + if (!result) { + this.project.logger.info('Unable to setup an environment since poetry is not installed. Please install poetry (https://python-poetry.org/docs/) or use a different component for managing environments such as \'venv\'.'); + } + + let envPath = execOrUndefined('poetry env info -p', { cwd: this.project.outdir }); + if (!envPath) { + this.project.logger.info(`Setting up a virtual environment using the python installation that was found: ${this.pythonProject.pythonPath}.`); + exec(`poetry env use ${this.pythonProject.pythonPath}`, { cwd: this.project.outdir }); + envPath = execOrUndefined('poetry env info -p', { cwd: this.project.outdir }); + this.project.logger.info(`Environment successfully created (located in ${envPath}}).`); + } + } + + /** + * Installs dependencies (called during post-synthesis). + */ + public installDependencies() { + this.project.logger.info('Installing dependencies...'); + exec(this.installTask.toShellCommand(), { cwd: this.project.outdir }); + } +} + + +export interface PoetryPyprojectOptions { + /** + * Name of the package. + */ + readonly name: string; + + /** + * Version of the package. + */ + readonly version: string; + + /** + * A short description of the package. + */ + readonly description: string; + + /** + * License of this package as an SPDX identifier. + * + * If the project is proprietary and does not use a specific license, you + * can set this value as "Proprietary". + */ + readonly license?: string; + + /** + * The authors of the package. Must be in the form "name " + */ + readonly authors?: string[]; + + /** + * the maintainers of the package. Must be in the form "name " + */ + readonly maintainers?: string[]; + + /** + * The name of the readme file of the package. + */ + readonly readme?: string; + + /** + * A URL to the website of the project. + */ + readonly homepage?: string; + + /** + * A URL to the repository of the project. + */ + readonly repository?: string; + + /** + * A URL to the documentation of the project. + */ + readonly documentation?: string; + + /** + * A list of keywords (max: 5) that the package is related to. + */ + readonly keywords?: string[]; + + /** + * A list of PyPI trove classifiers that describe the project. + * + * @see https://pypi.org/classifiers/ + */ + readonly classifiers?: string[]; + + /** + * A list of packages and modules to include in the final distribution. + */ + readonly packages?: string[]; + + /** + * A list of patterns that will be included in the final package. + */ + readonly include?: string[]; + + /** + * A list of patterns that will be excluded in the final package. + * + * If a VCS is being used for a package, the exclude field will be seeded with + * the VCS’ ignore settings (.gitignore for git for example). + */ + readonly exclude?: string[]; + + /** + * A list of dependencies for the project. + * + * The python version for which your package is compatible is also required. + * + * @example { requests: "^2.13.0" } + */ + readonly dependencies: { [key: string]: string }; + + /** + * A list of development dependencies for the project. + * + * @example { requests: "^2.13.0" } + */ + readonly devDependencies: { [key: string]: string }; + + /** + * The scripts or executables that will be installed when installing the package. + */ + readonly scripts: { [key: string]: string }; +} + +/** + * Represents configuration of a pyproject.toml file for a Poetry project. + * + * @see https://python-poetry.org/docs/pyproject/ + */ +export class PoetryPyproject extends Component { + public readonly file: TomlFile; + + constructor(project: PythonProject, options: PoetryPyprojectOptions) { + super(project); + + this.file = new TomlFile(project, 'pyproject.toml', { + marker: true, + omitEmpty: true, + obj: { + 'build-system': { + 'requires': ['poetry_core>=1.0.0'], + 'build-backend': 'poetry.core.masonry.api', + }, + 'tool.poetry': { + name: options.name, + version: options.version, + description: options.description, + license: options.license, + authors: options.authors, + maintainers: options.maintainers, + readme: options.readme, + homepage: options.homepage, + repository: options.repository, + documentation: options.documentation, + keywords: options.keywords, + classifiers: options.classifiers, + packages: options.packages, + include: options.include, + exclude: options.exclude, + }, + 'tool.poetry.dependencies': options.dependencies, + 'tool.poetry.dev-dependencies': options.devDependencies, + 'tool.poetry.scripts': options.scripts, + }, + }); + } +} \ No newline at end of file diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 1771e7c3ade..c0878619e13 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -1,5 +1,6 @@ import { Project, ProjectOptions, ProjectType } from '../project'; import { Pip } from './pip'; +import { Poetry } from './poetry'; import { Pytest, PytestOptions } from './pytest'; import { IPythonDeps } from './python-deps'; import { IPythonEnv } from './python-env'; @@ -115,6 +116,14 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly setuptools?: boolean; + /** + * Use poetry to manage your project dependencies, virtual environment, and + * (optional) packaging. + * + * @default false + */ + readonly poetry?: boolean; + /** * Setuptools options * @default - defaults @@ -222,11 +231,12 @@ export class PythonProject extends Project { // this.envManager = this.depsManager; // } - // if (options.poetry ?? false) { - // this.depsManager = new Poetry(this, options); - // this.envManager = this.depsManager; - // this.packagingManager = this.packagingManager; - // } + if (options.poetry ?? false) { + const poetry = new Poetry(this, options); + this.depsManager = poetry; + this.envManager = poetry; + this.packagingManager = poetry; + } if (!this.depsManager) { throw new Error('At least one tool must be chosen for managing dependencies (pip, conda, pipenv, or poetry).'); diff --git a/src/python/setuppy.ts b/src/python/setuppy.ts index 2a1a4247625..f40aefe07ac 100644 --- a/src/python/setuppy.ts +++ b/src/python/setuppy.ts @@ -48,9 +48,9 @@ export interface SetupPyConfigOptions { readonly homepage?: string; /** - * Tags that can be associated with a package. + * A list of PyPI trove classifiers that describe the project. * - * The full list is available at https://pypi.python.org/pypi?%3Aaction=list_classifiers + * @see https://pypi.org/classifiers/ */ readonly classifiers?: string[]; diff --git a/src/python/venv.ts b/src/python/venv.ts index 14aade838bc..7e67806ffd1 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -41,7 +41,7 @@ export class Venv extends Component implements IPythonEnv { if (!fs.pathExistsSync(absoluteEnvdir)) { this.project.logger.info(`Setting up a virtual environment using the python installation that was found: ${this.pythonProject.pythonPath}.`); exec(`${this.pythonProject.pythonPath} -m venv ${this.envdir}`, { cwd: this.project.outdir }); - this.project.logger.info(`Environment successfully created (located in /${this.envdir}).`); + this.project.logger.info(`Environment successfully created (located in ./${this.envdir}).`); } } } From 13f658c300c35cdb077e9b164ead98324830ad87 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 31 Jan 2021 19:45:01 -0500 Subject: [PATCH 15/29] poetry env and deps basics working --- API.md | 65 +++--- .../__snapshots__/inventory.test.ts.snap | 53 ++++- src/__tests__/__snapshots__/new.test.ts.snap | 10 +- .../python/__snapshots__/poetry.test.ts.snap | 185 ++++++++++++++++++ src/__tests__/python/poetry.test.ts | 8 +- src/__tests__/python/python-project.test.ts | 3 + src/__tests__/python/setuptools.test.ts | 19 +- src/python/pip.ts | 2 +- src/python/poetry.ts | 104 +++++++--- src/python/python-project.ts | 96 +++++++-- src/python/venv.ts | 2 +- 11 files changed, 453 insertions(+), 94 deletions(-) create mode 100644 src/__tests__/python/__snapshots__/poetry.test.ts.snap diff --git a/API.md b/API.md index fd4b0347713..f9e753226d6 100644 --- a/API.md +++ b/API.md @@ -55,7 +55,7 @@ Name|Description [java.Pom](#projen-java-pom)|A Project Object Model or POM is the fundamental unit of work in Maven. [java.Projenrc](#projen-java-projenrc)|Allows writing projenrc files in java. [python.Pip](#projen-python-pip)|*No description* -[python.Poetry](#projen-python-poetry)|*No description* +[python.Poetry](#projen-python-poetry)|Manage project dependencies, virtual environments, and packaging through the poetry CLI tool. [python.PoetryPyproject](#projen-python-poetrypyproject)|Represents configuration of a pyproject.toml file for a Poetry project. [python.Pytest](#projen-python-pytest)|*No description* [python.PythonProject](#projen-python-pythonproject)|Python project. @@ -4716,7 +4716,7 @@ __Extends__: [Component](#projen-component) ```ts -new python.Pip(project: PythonProject, _options: PipOptions) +new python.Pip(project: PythonProject, _options?: PipOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* @@ -4776,7 +4776,7 @@ installDependencies(): void ## class Poetry 🔹 - +Manage project dependencies, virtual environments, and packaging through the poetry CLI tool. __Implements__: [python.IPythonDeps](#projen-python-ipythondeps), [python.IPythonEnv](#projen-python-ipythonenv), [python.IPythonPackaging](#projen-python-ipythonpackaging) __Submodule__: python @@ -4789,11 +4789,12 @@ __Extends__: [Component](#projen-component) ```ts -new python.Poetry(project: PythonProject, _options: PoetryOptions) +new python.Poetry(project: PythonProject, options: PoetryOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* -* **_options** ([python.PoetryOptions](#projen-python-poetryoptions)) *No description* +* **options** ([python.PoetryOptions](#projen-python-poetryoptions)) *No description* + * **pyprojectConfig** ([python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)) Configure pyproject.toml. __*Optional*__ @@ -4878,14 +4879,11 @@ new python.PoetryPyproject(project: PythonProject, options: PoetryPyprojectOptio * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **options** ([python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)) *No description* - * **dependencies** (Map) A list of dependencies for the project. - * **description** (string) A short description of the package. - * **devDependencies** (Map) A list of development dependencies for the project. - * **name** (string) Name of the package. - * **scripts** (Map) The scripts or executables that will be installed when installing the package. - * **version** (string) Version of the package. * **authors** (Array) The authors of the package. __*Optional*__ * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **dependencies** (Map) A list of dependencies for the project. __*Optional*__ + * **description** (string) A short description of the package (required). __*Optional*__ + * **devDependencies** (Map) A list of development dependencies for the project. __*Optional*__ * **documentation** (string) A URL to the documentation of the project. __*Optional*__ * **exclude** (Array) A list of patterns that will be excluded in the final package. __*Optional*__ * **homepage** (string) A URL to the website of the project. __*Optional*__ @@ -4893,9 +4891,12 @@ new python.PoetryPyproject(project: PythonProject, options: PoetryPyprojectOptio * **keywords** (Array) A list of keywords (max: 5) that the package is related to. __*Optional*__ * **license** (string) License of this package as an SPDX identifier. __*Optional*__ * **maintainers** (Array) the maintainers of the package. __*Optional*__ + * **name** (string) Name of the package (required). __*Optional*__ * **packages** (Array) A list of packages and modules to include in the final distribution. __*Optional*__ * **readme** (string) The name of the readme file of the package. __*Optional*__ * **repository** (string) A URL to the repository of the project. __*Optional*__ + * **scripts** (Map) The scripts or executables that will be installed when installing the package. __*Optional*__ + * **version** (string) Version of the package (required). __*Optional*__ @@ -4969,16 +4970,20 @@ new python.PythonProject(options: PythonProjectOptions) * **parent** ([Project](#projen-project)) The parent project, if this project is part of a bigger project. __*Optional*__ * **projectType** ([ProjectType](#projen-projecttype)) Which type of project this is (library/app). __*Default*__: ProjectType.UNKNOWN * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } + * **authorEmail** (string) Author's e-mail. + * **authorName** (string) Author's name. * **pythonPath** (string) Absolute path to the user's python installation. - * **authorEmail** (string) Author's e-mail. __*Optional*__ - * **authorName** (string) Author's name. __*Optional*__ + * **version** (string) Manually specify package version. + * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] * **description** (string) A short project description. __*Optional*__ * **devDeps** (Array) List of dev dependencies for this project. __*Default*__: [] * **homepage** (string) The project's homepage / website. __*Optional*__ * **license** (string) The project license. __*Optional*__ * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true + * **pipOptions** ([python.PipOptions](#projen-python-pipoptions)) Pip options. __*Default*__: defaults * **poetry** (boolean) Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. __*Default*__: false + * **poetryOptions** ([python.PoetryOptions](#projen-python-poetryoptions)) Poetry options. __*Default*__: defaults * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Optional*__ @@ -4986,7 +4991,7 @@ new python.PythonProject(options: PythonProjectOptions) * **setuptoolsOptions** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) Setuptools options. __*Default*__: defaults * **testDeps** (Array) List of test dependencies for this project. __*Default*__: [] * **venv** (boolean) Use venv to manage a virtual environment for installing dependencies inside. __*Default*__: true - * **version** (string) Manually specify package version. __*Optional*__ + * **venvOptions** ([python.VenvOptions](#projen-python-venvoptions)) Venv options. __*Default*__: defaults @@ -5000,6 +5005,7 @@ Name | Type | Description **moduleName**🔹 | string | Python module name (the project name, with any hyphens or periods replaced with underscores). **packagingManager**🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library. **pythonPath**🔹 | string | Absolute path to the user's python installation. +**version**🔹 | string | Version of the package for distribution (should follow semver). **pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ ### Methods @@ -5213,7 +5219,7 @@ __Extends__: [Component](#projen-component) ```ts -new python.Venv(project: PythonProject, options: VenvOptions) +new python.Venv(project: PythonProject, options?: VenvOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* @@ -8803,6 +8809,13 @@ __Implemented by__: [python.Poetry](#projen-python-poetry) + +Name | Type | Description +-----|------|------------- +**pyprojectConfig**?🔹 | [python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions) | Configure pyproject.toml.
__*Optional*__ + + + ## struct PoetryPyprojectOptions 🔹 @@ -8812,14 +8825,11 @@ __Implemented by__: [python.Poetry](#projen-python-poetry) Name | Type | Description -----|------|------------- -**dependencies**🔹 | Map | A list of dependencies for the project. -**description**🔹 | string | A short description of the package. -**devDependencies**🔹 | Map | A list of development dependencies for the project. -**name**🔹 | string | Name of the package. -**scripts**🔹 | Map | The scripts or executables that will be installed when installing the package. -**version**🔹 | string | Version of the package. **authors**?🔹 | Array | The authors of the package.
__*Optional*__ **classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ +**dependencies**?🔹 | Map | A list of dependencies for the project.
__*Optional*__ +**description**?🔹 | string | A short description of the package (required).
__*Optional*__ +**devDependencies**?🔹 | Map | A list of development dependencies for the project.
__*Optional*__ **documentation**?🔹 | string | A URL to the documentation of the project.
__*Optional*__ **exclude**?🔹 | Array | A list of patterns that will be excluded in the final package.
__*Optional*__ **homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ @@ -8827,9 +8837,12 @@ Name | Type | Description **keywords**?🔹 | Array | A list of keywords (max: 5) that the package is related to.
__*Optional*__ **license**?🔹 | string | License of this package as an SPDX identifier.
__*Optional*__ **maintainers**?🔹 | Array | the maintainers of the package.
__*Optional*__ +**name**?🔹 | string | Name of the package (required).
__*Optional*__ **packages**?🔹 | Array | A list of packages and modules to include in the final distribution.
__*Optional*__ **readme**?🔹 | string | The name of the readme file of the package.
__*Optional*__ **repository**?🔹 | string | A URL to the repository of the project.
__*Optional*__ +**scripts**?🔹 | Map | The scripts or executables that will be installed when installing the package.
__*Optional*__ +**version**?🔹 | string | Version of the package (required).
__*Optional*__ @@ -8856,10 +8869,12 @@ Options for `PythonProject`. Name | Type | Description -----|------|------------- +**authorEmail**🔹 | string | Author's e-mail. +**authorName**🔹 | string | Author's name. **name**🔹 | string | This is the name of your project. **pythonPath**🔹 | string | Absolute path to the user's python installation. -**authorEmail**?🔹 | string | Author's e-mail.
__*Optional*__ -**authorName**?🔹 | string | Author's name.
__*Optional*__ +**version**🔹 | string | Manually specify package version. +**classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ **clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true **deps**?🔹 | Array | List of runtime dependencies for this project.
__*Default*__: [] **description**?🔹 | string | A short project description.
__*Optional*__ @@ -8873,7 +8888,9 @@ Name | Type | Description **outdir**?🔹 | string | The root directory of the project.
__*Default*__: "." **parent**?🔹 | [Project](#projen-project) | The parent project, if this project is part of a bigger project.
__*Optional*__ **pip**?🔹 | boolean | Use pip with a requirements.txt file to track project dependencies.
__*Default*__: true +**pipOptions**?🔹 | [python.PipOptions](#projen-python-pipoptions) | Pip options.
__*Default*__: defaults **poetry**?🔹 | boolean | Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.
__*Default*__: false +**poetryOptions**?🔹 | [python.PoetryOptions](#projen-python-poetryoptions) | Poetry options.
__*Default*__: defaults **projectType**?🔹 | [ProjectType](#projen-projecttype) | Which type of project this is (library/app).
__*Default*__: ProjectType.UNKNOWN **pytest**?🔹 | boolean | Include pytest tests.
__*Default*__: true **pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults @@ -8883,7 +8900,7 @@ Name | Type | Description **setuptoolsOptions**?🔹 | [python.SetuptoolsOptions](#projen-python-setuptoolsoptions) | Setuptools options.
__*Default*__: defaults **testDeps**?🔹 | Array | List of test dependencies for this project.
__*Default*__: [] **venv**?🔹 | boolean | Use venv to manage a virtual environment for installing dependencies inside.
__*Default*__: true -**version**?🔹 | string | Manually specify package version.
__*Optional*__ +**venvOptions**?🔹 | [python.VenvOptions](#projen-python-venvoptions) | Venv options.
__*Default*__: defaults diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index aaa7c2bb678..001f7b6a977 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -7892,9 +7892,9 @@ Array [ "moduleName": "projen", "options": Array [ Object { + "default": "$GIT_USER_EMAIL", "docs": "Author's e-mail.", "name": "authorEmail", - "optional": true, "parent": "PythonProjectOptions", "path": Array [ "authorEmail", @@ -7903,9 +7903,9 @@ Array [ "type": "string", }, Object { + "default": "$GIT_USER_NAME", "docs": "Author's name.", "name": "authorName", - "optional": true, "parent": "PythonProjectOptions", "path": Array [ "authorName", @@ -7913,6 +7913,17 @@ Array [ "switch": "author-name", "type": "string", }, + Object { + "docs": "A list of PyPI trove classifiers that describe the project.", + "name": "classifiers", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "classifiers", + ], + "switch": "classifiers", + "type": "unknown", + }, Object { "default": "true", "docs": "Add a \`clobber\` task which resets the repo to origin.", @@ -8075,6 +8086,18 @@ Array [ "switch": "pip", "type": "boolean", }, + Object { + "default": "- defaults", + "docs": "Pip options.", + "name": "pipOptions", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "pipOptions", + ], + "switch": "pip-options", + "type": "PipOptions", + }, Object { "default": "false", "docs": "Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.", @@ -8087,6 +8110,18 @@ Array [ "switch": "poetry", "type": "boolean", }, + Object { + "default": "- defaults", + "docs": "Poetry options.", + "name": "poetryOptions", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "poetryOptions", + ], + "switch": "poetry-options", + "type": "PoetryOptions", + }, Object { "default": "ProjectType.UNKNOWN", "docs": "Which type of project this is (library/app).", @@ -8206,9 +8241,21 @@ Array [ "type": "boolean", }, Object { + "default": "- defaults", + "docs": "Venv options.", + "name": "venvOptions", + "optional": true, + "parent": "PythonProjectOptions", + "path": Array [ + "venvOptions", + ], + "switch": "venv-options", + "type": "VenvOptions", + }, + Object { + "default": "\\"0.1.0\\"", "docs": "Manually specify package version.", "name": "version", - "optional": true, "parent": "PythonProjectOptions", "path": Array [ "version", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 31b1648d38d..51a957a5044 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -817,9 +817,12 @@ exports[`projen new python 1`] = ` "const { python } = require('projen'); const project = new python.PythonProject({ + authorEmail: 'my@user.email.com', + authorName: 'My User Name', jsiiFqn: \\"projen.python.PythonProject\\", name: 'my-project', pythonPath: '/usr/bin/python', + version: '0.1.0', /* ProjectOptions */ // clobber: true, /* Add a \`clobber\` task which resets the repo to origin. */ @@ -832,15 +835,16 @@ const project = new python.PythonProject({ // readme: undefined, /* The README setup. */ /* PythonProjectOptions */ - // authorEmail: undefined, /* Author's e-mail. */ - // authorName: undefined, /* Author's name. */ + // classifiers: undefined, /* A list of PyPI trove classifiers that describe the project. */ // deps: [], /* List of runtime dependencies for this project. */ // description: undefined, /* A short project description. */ // devDeps: [], /* List of dev dependencies for this project. */ // homepage: undefined, /* The project's homepage / website. */ // license: undefined, /* The project license. */ // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ + // pipOptions: undefined, /* Pip options. */ // poetry: false, /* Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. */ + // poetryOptions: undefined, /* Poetry options. */ // pytest: true, /* Include pytest tests. */ // pytestOptions: undefined, /* pytest options. */ // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ @@ -848,7 +852,7 @@ const project = new python.PythonProject({ // setuptoolsOptions: undefined, /* Setuptools options. */ // testDeps: [], /* List of test dependencies for this project. */ // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ - // version: undefined, /* Manually specify package version. */ + // venvOptions: undefined, /* Venv options. */ }); project.synth(); diff --git a/src/__tests__/python/__snapshots__/poetry.test.ts.snap b/src/__tests__/python/__snapshots__/poetry.test.ts.snap new file mode 100644 index 00000000000..062d1bb855f --- /dev/null +++ b/src/__tests__/python/__snapshots__/poetry.test.ts.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`poetry enabled 1`] = ` +Object { + ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +*$py.class +*.cover +*.egg +*.egg-info/ +*.log +*.manifest +*.mo +*.pot +*.py,cover +*.py[cod] +*.sage.py +*.so +*.spec +.Python +.cache +.coverage +.coverage.* +.dmypy.json +.eggs/ +.env +.hypothesis/ +.installed.cfg +.ipynb_checkpoints +.mypy_cache/ +.nox/ +.pybuilder/ +.pyre/ +.pytest_cache/ +.pytype/ +.ropeproject +.scrapy +.spyderproject +.spyproject +.tox/ +.venv +.webassets-cache +/site +ENV/ +MANIFEST +__pycache__/ +__pypackages__/ +build/ +celerybeat-schedule +celerybeat.pid +cover/ +coverage.xml +cython_debug/ +db.sqlite3 +db.sqlite3-journal +develop-eggs/ +dist/ +dmypy.json +docs/_build/ +downloads/ +eggs/ +env.bak/ +env/ +htmlcov/ +instance/ +ipython_config.py +lib/ +lib64/ +local_settings.py +node_modules/ +nosetests.xml +parts/ +pip-delete-this-directory.txt +pip-log.txt +profile_default/ +sdist/ +share/python-wheels/ +target/ +var/ +venv.bak/ +venv/ +wheels/ +!/.projen/deps.json +!/.projen/tasks.json +!/pyproject.toml +", + ".projen/deps.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "dependencies": Array [ + Object { + "name": "pytest", + "type": "devenv", + "version": "6.2.1", + }, + Object { + "name": "python", + "type": "runtime", + "version": "^3.6", + }, + ], + }, + ".projen/tasks.json": Object { + "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "tasks": Object { + "install": Object { + "category": "00.build", + "description": "Install and upgrade dependencies", + "name": "install", + "steps": Array [ + Object { + "exec": "poetry update", + }, + ], + }, + "test": Object { + "category": "10.test", + "description": "Runs tests", + "name": "test", + "steps": Array [ + Object { + "exec": "pytest", + }, + ], + }, + }, + }, + "README.md": "# replace this", + "pyproject.toml": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". + +[build-system] +requires = [ \\"poetry_core>=1.0.0\\" ] +build-backend = \\"poetry.core.masonry.api\\" + +[tool.poetry] +name = \\"test-python-project\\" +version = \\"0.1.0\\" +description = \\"a short project description\\" +license = \\"Apache Software License\\" +authors = [ \\"First Last \\" ] +readme = \\"README.md\\" +homepage = \\"http://www.example.com\\" +scripts = { } + + [tool.poetry.dependencies] + python = \\"^3.6\\" + + [tool.poetry.dev-dependencies] + pytest = \\"6.2.1\\" +", + "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" +", + "test_python-project/__main__.py": "from .example import hello + +if __name__ == \\"__main__\\": + name = input(\\"What is your name? \\") + print(hello(name)) +", + "test_python-project/example.py": "def hello(name: str) -> str: + \\"\\"\\"A simple greeting. + Args: + name (str): Name to greet. + Returns: + str: greeting message + \\"\\"\\" + return f\\"Hello {name}!\\" +", + "tests/__init__.py": "", + "tests/test_example.py": "import pytest + +from test_python-project.example import hello + +@pytest.mark.parametrize( + (\\"name\\", \\"expected\\"), + [ + (\\"A. Musing\\", \\"Hello A. Musing!\\"), + (\\"traveler\\", \\"Hello traveler!\\"), + (\\"projen developer\\", \\"Hello projen developer!\\"), + ], +) +def test_hello(name, expected): + \\"\\"\\"Example test with parametrization.\\"\\"\\" + assert hello(name) == expected +", +} +`; diff --git a/src/__tests__/python/poetry.test.ts b/src/__tests__/python/poetry.test.ts index d212d393ac8..32c9bcc7f2a 100644 --- a/src/__tests__/python/poetry.test.ts +++ b/src/__tests__/python/poetry.test.ts @@ -4,9 +4,10 @@ import { mkdtemp, synthSnapshot } from '../util'; test('poetry enabled', () => { const p = new TestPythonProject({ + venv: false, + pip: false, + setuptools: false, poetry: true, - authorEmail: 'foo@example.com', - authorName: 'Firstname Lastname', homepage: 'http://www.example.com', description: 'a short project description', license: 'Apache Software License', @@ -22,6 +23,9 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', pythonPath: '/usr/bin/python', + authorName: 'First Last', + authorEmail: 'email@example.com', + version: '0.1.0', outdir: mkdtemp(), logging: { level: LogLevel.OFF }, jsiiFqn: 'projen.python.PythonProject', diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index 7b268ff5808..1c9ff31d535 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -43,6 +43,9 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', pythonPath: '/usr/bin/python', + authorName: 'First Last', + authorEmail: 'email@example.com', + version: '0.1.0', outdir: mkdtemp(), logging: { level: LogLevel.OFF }, jsiiFqn: 'projen.python.PythonProject', diff --git a/src/__tests__/python/setuptools.test.ts b/src/__tests__/python/setuptools.test.ts index 56373ed227e..4c9b2575766 100644 --- a/src/__tests__/python/setuptools.test.ts +++ b/src/__tests__/python/setuptools.test.ts @@ -5,23 +5,17 @@ import { mkdtemp, synthSnapshot } from '../util'; test('setuptools enabled', () => { const p = new TestPythonProject({ setuptools: true, - authorEmail: 'foo@example.com', - authorName: 'Firstname Lastname', homepage: 'http://www.example.com', description: 'a short project description', license: 'Apache Software License', - setuptoolsOptions: { - setupConfig: { - classifiers: [ - 'Development Status :: 4 - Beta', - ], - }, - }, + classifiers: [ + 'Development Status :: 4 - Beta', + ], }); const snapshot = synthSnapshot(p); - expect(snapshot['setup.py']).toContain('foo@example.com'); - expect(snapshot['setup.py']).toContain('Firstname Lastname'); + expect(snapshot['setup.py']).toContain('First Last'); + expect(snapshot['setup.py']).toContain('email@example.com'); expect(snapshot['setup.py']).toContain('http://www.example.com'); expect(snapshot['setup.py']).toContain('a short project description'); expect(snapshot['setup.py']).toContain('Apache Software License'); @@ -35,6 +29,9 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', pythonPath: '/usr/bin/python', + authorName: 'First Last', + authorEmail: 'email@example.com', + version: '0.1.0', outdir: mkdtemp(), logging: { level: LogLevel.OFF }, jsiiFqn: 'projen.python.PythonProject', diff --git a/src/python/pip.ts b/src/python/pip.ts index ad7d50b4f2f..53849720b5b 100644 --- a/src/python/pip.ts +++ b/src/python/pip.ts @@ -11,7 +11,7 @@ export interface PipOptions {} export class Pip extends Component implements IPythonDeps { public readonly installTask: Task; - constructor(project: PythonProject, _options: PipOptions) { + constructor(project: PythonProject, _options: PipOptions = {}) { super(project); new RequirementsFile(project, 'requirements.txt', { _lazyPackages: () => this.synthDependencies() }); diff --git a/src/python/poetry.ts b/src/python/poetry.ts index 03a00d0843b..96db79f6dc7 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -8,7 +8,12 @@ import { IPythonEnv } from './python-env'; import { IPythonPackaging } from './python-packaging'; import { PythonProject } from './python-project'; -export interface PoetryOptions {} +export interface PoetryOptions { + /** + * Configure pyproject.toml + */ + readonly pyprojectConfig?: PoetryPyprojectOptions; +} /** * Manage project dependencies, virtual environments, and packaging through the @@ -18,7 +23,7 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho private readonly pythonProject: PythonProject; public readonly installTask: Task; - constructor(project: PythonProject, _options: PoetryOptions) { + constructor(project: PythonProject, options: PoetryOptions) { super(project); this.pythonProject = project; @@ -26,8 +31,41 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho this.installTask = project.addTask('install', { description: 'Install and upgrade dependencies', category: TaskCategory.BUILD, - exec: 'poetry install', + exec: 'poetry update', }); + + // declare the python versions for which the package is compatible + this.addDependency('python@^3.6'); + + new PoetryPyproject(project, { + name: project.name, + // version: options.pyprojectConfig.version, + // description: options.pyprojectConfig.description, + dependencies: () => this.synthDependencies(), + devDependencies: () => this.synthDevDependencies(), + scripts: {}, + ...options.pyprojectConfig, + }); + } + + private synthDependencies() { + const dependencies: { [key: string]: any } = {}; + for (const pkg of this.project.deps.all) { + if (pkg.type === DependencyType.RUNTIME) { + dependencies[pkg.name] = pkg.version; + } + } + return dependencies; + } + + private synthDevDependencies() { + const dependencies: { [key: string]: any } = {}; + for (const pkg of this.project.deps.all) { + if ([DependencyType.DEVENV].includes(pkg.type)) { + dependencies[pkg.name] = pkg.version; + } + } + return dependencies; } /** @@ -78,19 +116,19 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho export interface PoetryPyprojectOptions { /** - * Name of the package. + * Name of the package (required). */ - readonly name: string; + readonly name?: string; /** - * Version of the package. + * Version of the package (required). */ - readonly version: string; + readonly version?: string; /** - * A short description of the package. + * A short description of the package (required). */ - readonly description: string; + readonly description?: string; /** * License of this package as an SPDX identifier. @@ -167,19 +205,19 @@ export interface PoetryPyprojectOptions { * * @example { requests: "^2.13.0" } */ - readonly dependencies: { [key: string]: string }; + readonly dependencies?: { [key: string]: any }; /** * A list of development dependencies for the project. * * @example { requests: "^2.13.0" } */ - readonly devDependencies: { [key: string]: string }; + readonly devDependencies?: { [key: string]: any }; /** * The scripts or executables that will be installed when installing the package. */ - readonly scripts: { [key: string]: string }; + readonly scripts?: { [key: string]: any }; } /** @@ -195,32 +233,34 @@ export class PoetryPyproject extends Component { this.file = new TomlFile(project, 'pyproject.toml', { marker: true, - omitEmpty: true, + omitEmpty: false, obj: { 'build-system': { 'requires': ['poetry_core>=1.0.0'], 'build-backend': 'poetry.core.masonry.api', }, - 'tool.poetry': { - name: options.name, - version: options.version, - description: options.description, - license: options.license, - authors: options.authors, - maintainers: options.maintainers, - readme: options.readme, - homepage: options.homepage, - repository: options.repository, - documentation: options.documentation, - keywords: options.keywords, - classifiers: options.classifiers, - packages: options.packages, - include: options.include, - exclude: options.exclude, + 'tool': { + poetry: { + 'name': options.name, + 'version': options.version, + 'description': options.description, + 'license': options.license, + 'authors': options.authors, + 'maintainers': options.maintainers, + 'readme': options.readme, + 'homepage': options.homepage, + 'repository': options.repository, + 'documentation': options.documentation, + 'keywords': options.keywords, + 'classifiers': options.classifiers, + 'packages': options.packages, + 'include': options.include, + 'exclude': options.exclude, + 'dependencies': options.dependencies, + 'dev-dependencies': options.devDependencies, + 'scripts': options.scripts, + }, }, - 'tool.poetry.dependencies': options.dependencies, - 'tool.poetry.dev-dependencies': options.devDependencies, - 'tool.poetry.scripts': options.scripts, }, }); } diff --git a/src/python/python-project.ts b/src/python/python-project.ts index c0878619e13..a0dd53cd6fc 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -1,13 +1,13 @@ import { Project, ProjectOptions, ProjectType } from '../project'; -import { Pip } from './pip'; -import { Poetry } from './poetry'; +import { Pip, PipOptions } from './pip'; +import { Poetry, PoetryOptions } from './poetry'; import { Pytest, PytestOptions } from './pytest'; import { IPythonDeps } from './python-deps'; import { IPythonEnv } from './python-env'; import { IPythonPackaging } from './python-packaging'; import { PythonSample } from './python-sample'; import { Setuptools, SetuptoolsOptions } from './setuptools'; -import { Venv } from './venv'; +import { Venv, VenvOptions } from './venv'; /** Allowed characters in python project names */ @@ -30,18 +30,23 @@ export interface PythonProjectOptions extends ProjectOptions { /** * Author's name + * + * @default $GIT_USER_NAME */ - readonly authorName?: string; + readonly authorName: string; /** * Author's e-mail + * + * @default $GIT_USER_EMAIL */ - readonly authorEmail?: string; + readonly authorEmail: string; /** * Manually specify package version + * @default "0.1.0" */ - readonly version?: string; + readonly version: string; /** * A short project description @@ -58,6 +63,13 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly homepage?: string; + /** + * A list of PyPI trove classifiers that describe the project. + * + * @see https://pypi.org/classifiers/ + */ + readonly classifiers?: string[]; + // -- dependencies -- /** @@ -102,6 +114,12 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly pip?: boolean; + /** + * Pip options + * @default - defaults + */ + readonly pipOptions?: PipOptions; + /** * Use venv to manage a virtual environment for installing dependencies inside. * @@ -109,6 +127,12 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly venv?: boolean; + /** + * Venv options + * @default - defaults + */ + readonly venvOptions?: VenvOptions; + /** * Use setuptools with a setup.py script for packaging and distribution. * @@ -116,6 +140,12 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly setuptools?: boolean; + /** + * Setuptools options + * @default - defaults + */ + readonly setuptoolsOptions?: SetuptoolsOptions; + /** * Use poetry to manage your project dependencies, virtual environment, and * (optional) packaging. @@ -125,10 +155,10 @@ export interface PythonProjectOptions extends ProjectOptions { readonly poetry?: boolean; /** - * Setuptools options + * Poetry options * @default - defaults */ - readonly setuptoolsOptions?: SetuptoolsOptions; + readonly poetryOptions?: PoetryOptions; // -- optional components -- @@ -160,13 +190,18 @@ export class PythonProject extends Project { * Absolute path to the user's python installation. This will be used for * setting up the virtual environment. */ - readonly pythonPath: string; + public readonly pythonPath: string; /** * Python module name (the project name, with any hyphens or periods replaced * with underscores). */ - readonly moduleName: string; + public readonly moduleName: string; + + /** + * Version of the package for distribution (should follow semver). + */ + public readonly version: string; /** * API for managing dependencies. @@ -197,13 +232,14 @@ export class PythonProject extends Project { this.moduleName = this.safeName(options.name); this.pythonPath = options.pythonPath; + this.version = options.version; if (options.venv ?? true) { - this.envManager = new Venv(this, {}); + this.envManager = new Venv(this, options.venvOptions); } if (options.pip ?? true) { - this.depsManager = new Pip(this, {}); + this.depsManager = new Pip(this, options.pipOptions); } if (options.setuptools ?? (this.projectType === ProjectType.LIB)) { @@ -216,6 +252,7 @@ export class PythonProject extends Project { description: options.description, license: options.license, homepage: options.homepage, + classifiers: options.classifiers, ...options.setuptoolsOptions?.setupConfig, }, }); @@ -232,20 +269,33 @@ export class PythonProject extends Project { // } if (options.poetry ?? false) { - const poetry = new Poetry(this, options); + const poetry = new Poetry(this, { + ...options.poetryOptions, + pyprojectConfig: { + name: options.name, + version: this.version, + description: options.description ?? '', + license: options.license, + authors: [`${options.authorName} <${options.authorEmail}>`], + readme: options.readme?.filename ?? 'README.md', + homepage: options.homepage, + classifiers: options.classifiers, + ...options.poetryOptions?.pyprojectConfig, + }, + }); this.depsManager = poetry; this.envManager = poetry; this.packagingManager = poetry; } - if (!this.depsManager) { - throw new Error('At least one tool must be chosen for managing dependencies (pip, conda, pipenv, or poetry).'); - } - if (!this.envManager) { throw new Error('At least one tool must be chosen for managing the environment (venv, conda, pipenv, or poetry).'); } + if (!this.depsManager) { + throw new Error('At least one tool must be chosen for managing dependencies (pip, conda, pipenv, or poetry).'); + } + if (!this.packagingManager) { if (this.projectType === ProjectType.LIB) { throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); @@ -254,6 +304,18 @@ export class PythonProject extends Project { } } + if (Number(options.venv ?? true) + Number(options.poetry ?? false) > 1) { + throw new Error('More than one component has been chosen for managing the environment (venv, conda, pipenv, or poetry)'); + } + + if (Number(options.pip ?? true) + Number(options.poetry ?? false) > 1) { + throw new Error('More than one component has been chosen for managing dependencies (pip, conda, pipenv, or poetry)'); + } + + if (Number(options.setuptools ?? true) + Number(options.poetry ?? false) > 1) { + throw new Error('More than one component has been chosen for managing packaging (setuptools or poetry)'); + } + if (options.pytest ?? true) { this.pytest = new Pytest(this, {}); } diff --git a/src/python/venv.ts b/src/python/venv.ts index 7e67806ffd1..36694e072dc 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -21,7 +21,7 @@ export class Venv extends Component implements IPythonEnv { private readonly envdir: string; private readonly pythonProject: PythonProject; - constructor(project: PythonProject, options: VenvOptions) { + constructor(project: PythonProject, options: VenvOptions = {}) { super(project); this.envdir = options.envdir ?? '.env'; From ddd87172efd02b00af186ccdf56e9129c3c2c449 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Sun, 31 Jan 2021 20:12:11 -0500 Subject: [PATCH 16/29] fix test task not working --- src/python/poetry.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/python/poetry.ts b/src/python/poetry.ts index 96db79f6dc7..a030eeaf2c1 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -34,6 +34,9 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho exec: 'poetry update', }); + this.project.tasks.addEnvironment('VIRTUAL_ENV', '$(poetry env info -p)'); + this.project.tasks.addEnvironment('PATH', '$(echo $(poetry env info -p)/bin:$PATH)'); + // declare the python versions for which the package is compatible this.addDependency('python@^3.6'); From e7771cd86570aeb96d99124f544e7c63ecaee3f2 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 1 Feb 2021 15:19:36 -0500 Subject: [PATCH 17/29] commit suggestions to docs/python.md --- docs/python.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/python.md b/docs/python.md index 75efe65591e..40df9623726 100644 --- a/docs/python.md +++ b/docs/python.md @@ -8,7 +8,7 @@ you want to use, you can specify the right path when setting up the project. To create a new Python project, use `projen new python`: ```shell -$ projen new python --name=my-project --python-path=/usr/bin/python +$ projen new python --name=my-project [--python-path=/usr/bin/python] ``` This will synthesize a standard project directory structure with some sample @@ -56,7 +56,7 @@ The following sections describe the various features of Python projects. Every Python project must have a component for managing/installing dependencies, a component for managing the Python virtual environment, and if it is a library, -a component for managing packaging the library. Some components satisfy multiple +a component for packaging the library. Some components satisfy multiple requirements. See the list below: - pip: dependency manager @@ -93,7 +93,7 @@ You can define dependencies when defining the project itself: const project = new python.PythonProject({ deps: [ 'Django@3.1.5', - 'aws-cdk.core@*', + 'aws-cdk.core', // implies "@*" ], testDeps: [ 'hypothesis@^6.0.3', @@ -115,8 +115,9 @@ Notice the syntax for dependencies: Where `module` is the module name and `version` is the [semantic version requirement](https://semver.org) for the dependency. The semver syntax will be -converted to the appropriate syntax in synthesized files. For example, -`lib^3.1.0` will be converted to `lib>=3.1.0, <4.0.0` in `requirements.txt`. +converted to the appropriate dependency manager syntax in synthesized files. For +example, `lib^3.1.0` will be converted to `lib>=3.1.0, <4.0.0` in +`requirements.txt`. ## Unit Testing with Pytest From 335b1a34c885d3e694c405d4f3865664cd825c0c Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 1 Feb 2021 19:24:16 -0500 Subject: [PATCH 18/29] add docs and incorporate other feedback --- API.md | 67 ++++++++++++++----- .../python/__snapshots__/poetry.test.ts.snap | 4 ++ src/python/pip.ts | 48 ++++++------- src/python/poetry.ts | 3 + src/python/python-deps.ts | 15 +++++ src/python/python-project.ts | 4 +- src/python/requirements-file.ts | 28 +++++--- src/python/setuppy.ts | 5 +- src/python/setuptools.ts | 6 ++ src/python/venv.ts | 6 ++ 10 files changed, 131 insertions(+), 55 deletions(-) diff --git a/API.md b/API.md index f9e753226d6..9b41eed2acd 100644 --- a/API.md +++ b/API.md @@ -54,16 +54,16 @@ Name|Description [java.MavenSample](#projen-java-mavensample)|Java code sample. [java.Pom](#projen-java-pom)|A Project Object Model or POM is the fundamental unit of work in Maven. [java.Projenrc](#projen-java-projenrc)|Allows writing projenrc files in java. -[python.Pip](#projen-python-pip)|*No description* +[python.Pip](#projen-python-pip)|Manages dependencies using a requirements.txt file and the pip CLI tool. [python.Poetry](#projen-python-poetry)|Manage project dependencies, virtual environments, and packaging through the poetry CLI tool. [python.PoetryPyproject](#projen-python-poetrypyproject)|Represents configuration of a pyproject.toml file for a Poetry project. [python.Pytest](#projen-python-pytest)|*No description* [python.PythonProject](#projen-python-pythonproject)|Python project. [python.PythonSample](#projen-python-pythonsample)|Python code sample. [python.RequirementsFile](#projen-python-requirementsfile)|Specifies a list of packages to be installed using pip. -[python.SetupPy](#projen-python-setuppy)|*No description* -[python.Setuptools](#projen-python-setuptools)|*No description* -[python.Venv](#projen-python-venv)|*No description* +[python.SetupPy](#projen-python-setuppy)|Python packaging script where package metadata can be placed. +[python.Setuptools](#projen-python-setuptools)|Manages packaging through setuptools with a setup.py script. +[python.Venv](#projen-python-venv)|Manages a virtual environment through the Python venv module. [tasks.Task](#projen-tasks-task)|A task that can be performed on the project. [tasks.TaskRuntime](#projen-tasks-taskruntime)|The runtime component of the tasks engine. [tasks.Tasks](#projen-tasks-tasks)|Defines project tasks. @@ -160,8 +160,8 @@ Name|Description [java.PomOptions](#projen-java-pomoptions)|Options for `Pom`. [java.ProjenrcCommonOptions](#projen-java-projenrccommonoptions)|Options for `Projenrc`. [java.ProjenrcOptions](#projen-java-projenrcoptions)|*No description* -[python.PipOptions](#projen-python-pipoptions)|*No description* -[python.PoetryOptions](#projen-python-poetryoptions)|*No description* +[python.PipOptions](#projen-python-pipoptions)|Options for pip. +[python.PoetryOptions](#projen-python-poetryoptions)|Options for poetry. [python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)|*No description* [python.PytestOptions](#projen-python-pytestoptions)|*No description* [python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. @@ -169,8 +169,8 @@ Name|Description [python.RequirementsFileOptions](#projen-python-requirementsfileoptions)|*No description* [python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)|Fields to pass in the setup() function of setup.py. [python.SetupPyOptions](#projen-python-setuppyoptions)|*No description* -[python.SetuptoolsOptions](#projen-python-setuptoolsoptions)|*No description* -[python.VenvOptions](#projen-python-venvoptions)|*No description* +[python.SetuptoolsOptions](#projen-python-setuptoolsoptions)|Options for setuptools. +[python.VenvOptions](#projen-python-venvoptions)|Options for venv. [tasks.TaskCommonOptions](#projen-tasks-taskcommonoptions)|*No description* [tasks.TaskOptions](#projen-tasks-taskoptions)|*No description* [tasks.TaskSpec](#projen-tasks-taskspec)|Specification of a single task. @@ -204,6 +204,7 @@ Name|Description [IDockerComposeVolumeConfig](#projen-idockercomposevolumeconfig)|Storage for volume configuration. [IMarkableFile](#projen-imarkablefile)|Files that may include the Projen marker. [IResolver](#projen-iresolver)|API for resolving tokens when synthesizing file content. +[python.IPackageProvider](#projen-python-ipackageprovider)|*No description* [python.IPythonDeps](#projen-python-ipythondeps)|*No description* [python.IPythonEnv](#projen-python-ipythonenv)|*No description* [python.IPythonPackaging](#projen-python-ipythonpackaging)|*No description* @@ -4703,7 +4704,7 @@ Name | Type | Description ## class Pip 🔹 - +Manages dependencies using a requirements.txt file and the pip CLI tool. __Implements__: [python.IPythonDeps](#projen-python-ipythondeps) __Submodule__: python @@ -4729,7 +4730,7 @@ new python.Pip(project: PythonProject, _options?: PipOptions) Name | Type | Description -----|------|------------- -**installTask**🔹 | [tasks.Task](#projen-tasks-task) | +**installTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that installs and updates dependencies. ### Methods @@ -4803,7 +4804,7 @@ new python.Poetry(project: PythonProject, options: PoetryOptions) Name | Type | Description -----|------|------------- -**installTask**🔹 | [tasks.Task](#projen-tasks-task) | +**installTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that installs and updates dependencies. ### Methods @@ -5096,6 +5097,7 @@ new python.RequirementsFile(project: Project, filePath: string, options: Require * **project** ([Project](#projen-project)) *No description* * **filePath** (string) *No description* * **options** ([python.RequirementsFileOptions](#projen-python-requirementsfileoptions)) *No description* + * **packageProvider** ([python.IPackageProvider](#projen-python-ipackageprovider)) Provide a list of packages that can be dynamically updated. __*Optional*__ ### Methods @@ -5133,7 +5135,7 @@ __Returns__: ## class SetupPy 🔹 - +Python packaging script where package metadata can be placed. __Submodule__: python @@ -5173,7 +5175,7 @@ __Returns__: ## class Setuptools 🔹 - +Manages packaging through setuptools with a setup.py script. __Submodule__: python @@ -5206,7 +5208,7 @@ Name | Type | Description ## class Venv 🔹 - +Manages a virtual environment through the Python venv module. __Implements__: [python.IPythonEnv](#projen-python-ipythonenv) __Submodule__: python @@ -8720,11 +8722,33 @@ Name | Type | Description +## interface IPackageProvider 🔹 + + + + +### Properties + + +Name | Type | Description +-----|------|------------- +**packages**🔹 | Array<[deps.Dependency](#projen-deps-dependency)> | An array of packages (may be dynamically generated). + + + ## interface IPythonDeps 🔹 __Implemented by__: [python.Pip](#projen-python-pip), [python.Poetry](#projen-python-poetry) + +### Properties + + +Name | Type | Description +-----|------|------------- +**installTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that installs and updates dependencies. + ### Methods @@ -8800,13 +8824,13 @@ __Implemented by__: [python.Poetry](#projen-python-poetry) ## struct PipOptions 🔹 - +Options for pip. ## struct PoetryOptions 🔹 - +Options for poetry. @@ -8916,6 +8940,13 @@ Options for python sample code. + +Name | Type | Description +-----|------|------------- +**packageProvider**?🔹 | [python.IPackageProvider](#projen-python-ipackageprovider) | Provide a list of packages that can be dynamically updated.
__*Optional*__ + + + ## struct SetupPyConfigOptions 🔹 @@ -8953,7 +8984,7 @@ Name | Type | Description ## struct SetuptoolsOptions 🔹 - +Options for setuptools. @@ -8966,7 +8997,7 @@ Name | Type | Description ## struct VenvOptions 🔹 - +Options for venv. diff --git a/src/__tests__/python/__snapshots__/poetry.test.ts.snap b/src/__tests__/python/__snapshots__/poetry.test.ts.snap index 062d1bb855f..2a6c15daa1c 100644 --- a/src/__tests__/python/__snapshots__/poetry.test.ts.snap +++ b/src/__tests__/python/__snapshots__/poetry.test.ts.snap @@ -101,6 +101,10 @@ wheels/ }, ".projen/tasks.json": Object { "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", + "env": Object { + "PATH": "$(echo $(poetry env info -p)/bin:$PATH)", + "VIRTUAL_ENV": "$(poetry env info -p)", + }, "tasks": Object { "install": Object { "category": "00.build", diff --git a/src/python/pip.ts b/src/python/pip.ts index 53849720b5b..587f208b07f 100644 --- a/src/python/pip.ts +++ b/src/python/pip.ts @@ -1,21 +1,27 @@ import { Component } from '../component'; -import { DependencyType } from '../deps'; +import { Dependency, DependencyType } from '../deps'; import { Task, TaskCategory } from '../tasks'; import { exec } from '../util'; -import { IPythonDeps } from './python-deps'; +import { IPackageProvider, IPythonDeps } from './python-deps'; import { PythonProject } from './python-project'; import { RequirementsFile } from './requirements-file'; +/** + * Options for pip + */ export interface PipOptions {} +/** + * Manages dependencies using a requirements.txt file and the pip CLI tool. + */ export class Pip extends Component implements IPythonDeps { public readonly installTask: Task; constructor(project: PythonProject, _options: PipOptions = {}) { super(project); - new RequirementsFile(project, 'requirements.txt', { _lazyPackages: () => this.synthDependencies() }); - new RequirementsFile(project, 'requirements-dev.txt', { _lazyPackages: () => this.synthDevDependencies() }); + new RequirementsFile(project, 'requirements.txt', { packageProvider: new RuntimeDependencyProvider(project) }); + new RequirementsFile(project, 'requirements-dev.txt', { packageProvider: new DevDependencyProvider(project) }); this.installTask = project.addTask('install', { description: 'Install and upgrade dependencies', @@ -26,26 +32,6 @@ export class Pip extends Component implements IPythonDeps { this.installTask.exec('pip install -r requirements-dev.txt'); } - private synthDependencies() { - const dependencies: string[] = []; - for (const pkg of this.project.deps.all) { - if (pkg.type === DependencyType.RUNTIME) { - dependencies.push( `${pkg.name}@${pkg.version}`); - } - } - return dependencies; - } - - private synthDevDependencies() { - const dependencies: string[] = []; - for (const pkg of this.project.deps.all) { - if ([DependencyType.DEVENV].includes(pkg.type)) { - dependencies.push( `${pkg.name}@${pkg.version}`); - } - } - return dependencies; - } - /** * Adds a runtime dependency. * @@ -71,4 +57,18 @@ export class Pip extends Component implements IPythonDeps { this.project.logger.info('Installing dependencies...'); exec(this.installTask.toShellCommand(), { cwd: this.project.outdir }); } +} + +class RuntimeDependencyProvider implements IPackageProvider { + constructor(private readonly pythonProject: PythonProject) {} + public get packages(): Dependency[] { + return this.pythonProject.deps.all.filter(dep => dep.type === DependencyType.RUNTIME); + } +} + +class DevDependencyProvider implements IPackageProvider { + constructor(private readonly pythonProject: PythonProject) {} + public get packages(): Dependency[] { + return this.pythonProject.deps.all.filter(dep => dep.type === DependencyType.DEVENV); + } } \ No newline at end of file diff --git a/src/python/poetry.ts b/src/python/poetry.ts index a030eeaf2c1..b7e7fdb06b9 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -8,6 +8,9 @@ import { IPythonEnv } from './python-env'; import { IPythonPackaging } from './python-packaging'; import { PythonProject } from './python-project'; +/** + * Options for poetry. + */ export interface PoetryOptions { /** * Configure pyproject.toml diff --git a/src/python/python-deps.ts b/src/python/python-deps.ts index c70ba5b05e5..7fa217d930e 100644 --- a/src/python/python-deps.ts +++ b/src/python/python-deps.ts @@ -1,4 +1,12 @@ +import { Dependency } from '../deps'; +import { Task } from '../tasks'; + export interface IPythonDeps { + /** + * A task that installs and updates dependencies. + */ + readonly installTask: Task; + /** * Adds a runtime dependency. * @@ -18,3 +26,10 @@ export interface IPythonDeps { */ installDependencies(): void; } + +export interface IPackageProvider { + /** + * An array of packages (may be dynamically generated). + */ + readonly packages: Dependency[]; +} diff --git a/src/python/python-project.ts b/src/python/python-project.ts index a0dd53cd6fc..1cbf3668472 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -26,8 +26,6 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly pythonPath: string; - // -- general information -- - /** * Author's name * @@ -48,6 +46,8 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly version: string; + // -- general information -- + /** * A short project description */ diff --git a/src/python/requirements-file.ts b/src/python/requirements-file.ts index e3428be5220..d5da7f24bef 100644 --- a/src/python/requirements-file.ts +++ b/src/python/requirements-file.ts @@ -1,14 +1,14 @@ -import { Dependencies } from '../deps'; +import { Dependencies, DependencyCoordinates } from '../deps'; import { FileBase, IResolver } from '../file'; import { Project } from '../project'; import { toPythonVersionRange } from '../util/semver'; +import { IPackageProvider } from './python-deps'; export interface RequirementsFileOptions { /** - * Accepts a function that resolves to an list of packages that should get included. - * @internal + * Provide a list of packages that can be dynamically updated. */ - readonly _lazyPackages?: any; + readonly packageProvider?: IPackageProvider; } /** @@ -18,12 +18,12 @@ export interface RequirementsFileOptions { */ export class RequirementsFile extends FileBase { private readonly packages = new Array(); - private readonly lazyPackages: any; + private readonly packageProvider?: IPackageProvider; constructor(project: Project, filePath: string, options: RequirementsFileOptions) { super(project, filePath); - this.lazyPackages = options._lazyPackages; + this.packageProvider = options.packageProvider; } /** @@ -48,15 +48,23 @@ export class RequirementsFile extends FileBase { } } + private formatDependency(dep: DependencyCoordinates) { + if (dep.version) { + return `${dep.name}${toPythonVersionRange(dep.version)}`; + } else { + return dep.name; + } + } + protected synthesizeContent(resolver: IResolver): string | undefined { - const additionalPackages = resolver.resolve(this.lazyPackages); - if (additionalPackages) { - this.addPackages(...additionalPackages); + const allPackages = [...this.packages]; + if (this.packageProvider) { + allPackages.push(...this.packageProvider.packages.map(dep => this.formatDependency(dep))); } return `${resolver.resolve([ `# ${FileBase.PROJEN_MARKER}`, - ...this.packages, + ...allPackages, ]).join('\n')}\n`; } } diff --git a/src/python/setuppy.ts b/src/python/setuppy.ts index f40aefe07ac..47addfbb2ae 100644 --- a/src/python/setuppy.ts +++ b/src/python/setuppy.ts @@ -67,6 +67,9 @@ export interface SetupPyOptions { readonly setupConfig?: SetupPyConfigOptions; } +/** + * Python packaging script where package metadata can be placed. + */ export class SetupPy extends FileBase { private readonly setupConfig: any; @@ -108,7 +111,7 @@ export class SetupPy extends FileBase { return `${resolver.resolve(lines).join('\n')}\n`; } - // modify some key names since JSII interfaces must have camelCase fields + // modify some key names since JSII interfaces require fields to be camelCase private renameFields(options: SetupPyConfigOptions): any { const obj: { [key: string]: any } = {}; for (const [key, value] of Object.entries(options)) { diff --git a/src/python/setuptools.ts b/src/python/setuptools.ts index 0080a29e8c4..472c7ea63c8 100644 --- a/src/python/setuptools.ts +++ b/src/python/setuptools.ts @@ -3,8 +3,14 @@ import { Task, TaskCategory } from '../tasks'; import { PythonProject } from './python-project'; import { SetupPy, SetupPyOptions } from './setuppy'; +/** + * Options for setuptools + */ export interface SetuptoolsOptions extends SetupPyOptions {} +/** + * Manages packaging through setuptools with a setup.py script. + */ export class Setuptools extends Component { public readonly packageTask: Task; public readonly uploadTask: Task; diff --git a/src/python/venv.ts b/src/python/venv.ts index 36694e072dc..2998fecf3cb 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -5,6 +5,9 @@ import { exec } from '../util'; import { IPythonEnv } from './python-env'; import { PythonProject } from './python-project'; +/** + * Options for venv. + */ export interface VenvOptions { /** * Name of directory to store the environment in @@ -14,6 +17,9 @@ export interface VenvOptions { readonly envdir?: string; } +/** + * Manages a virtual environment through the Python venv module. + */ export class Venv extends Component implements IPythonEnv { /** * Name of directory to store the environment in From 3bf98b1a6fda71a2ada17d899c3b49e0b57ba799 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 1 Feb 2021 20:52:44 -0500 Subject: [PATCH 19/29] add proper packaging and uploading support for poetry --- API.md | 23 +++++++--- .../__snapshots__/inventory.test.ts.snap | 24 +++++----- src/__tests__/__snapshots__/new.test.ts.snap | 4 +- src/__tests__/new.test.ts | 6 ++- .../python/__snapshots__/poetry.test.ts.snap | 43 ++++++++++++++++-- .../__snapshots__/python-project.test.ts.snap | 30 ++++++------- src/__tests__/python/poetry.test.ts | 1 + src/__tests__/python/python-project.test.ts | 1 + src/__tests__/python/setuptools.test.ts | 1 + src/__tests__/util.test.ts | 7 ++- src/cli/macros.ts | 3 +- src/python/poetry.ts | 41 +++++++++++++++-- src/python/python-packaging.ts | 14 +++++- src/python/python-project.ts | 44 ++++++------------- src/python/setuptools.ts | 14 +++++- src/util.ts | 4 ++ 16 files changed, 184 insertions(+), 76 deletions(-) diff --git a/API.md b/API.md index 9b41eed2acd..1dd1ef627d5 100644 --- a/API.md +++ b/API.md @@ -4805,6 +4805,9 @@ new python.Poetry(project: PythonProject, options: PoetryOptions) Name | Type | Description -----|------|------------- **installTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that installs and updates dependencies. +**packageTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that packages the project for distribution. +**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package package repository. +**uploadTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. ### Methods @@ -4973,6 +4976,7 @@ new python.PythonProject(options: PythonProjectOptions) * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } * **authorEmail** (string) Author's e-mail. * **authorName** (string) Author's name. + * **moduleName** (string) Name of the python package as used in imports and filenames. * **pythonPath** (string) Absolute path to the user's python installation. * **version** (string) Manually specify package version. * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ @@ -4987,10 +4991,9 @@ new python.PythonProject(options: PythonProjectOptions) * **poetryOptions** ([python.PoetryOptions](#projen-python-poetryoptions)) Poetry options. __*Default*__: defaults * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults - * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Optional*__ + * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Default*__: true * **setuptools** (boolean) Use setuptools with a setup.py script for packaging and distribution. __*Default*__: true if the project type is library * **setuptoolsOptions** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) Setuptools options. __*Default*__: defaults - * **testDeps** (Array) List of test dependencies for this project. __*Default*__: [] * **venv** (boolean) Use venv to manage a virtual environment for installing dependencies inside. __*Default*__: true * **venvOptions** ([python.VenvOptions](#projen-python-venvoptions)) Venv options. __*Default*__: defaults @@ -5004,9 +5007,9 @@ Name | Type | Description **depsManager**🔹 | [python.IPythonDeps](#projen-python-ipythondeps) | API for managing dependencies. **envManager**🔹 | [python.IPythonEnv](#projen-python-ipythonenv) | API for mangaging the Python runtime environment. **moduleName**🔹 | string | Python module name (the project name, with any hyphens or periods replaced with underscores). -**packagingManager**🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library. **pythonPath**🔹 | string | Absolute path to the user's python installation. **version**🔹 | string | Version of the package for distribution (should follow semver). +**packagingManager**?🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library.
__*Optional*__ **pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ ### Methods @@ -5203,6 +5206,7 @@ Name | Type | Description -----|------|------------- **packageTask**🔹 | [tasks.Task](#projen-tasks-task) | **uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | +**uploadTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. @@ -8820,6 +8824,15 @@ __Implemented by__: [python.Poetry](#projen-python-poetry) +### Properties + + +Name | Type | Description +-----|------|------------- +**packageTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that packages the project for distribution. +**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package package repository. + + ## struct PipOptions 🔹 @@ -8895,6 +8908,7 @@ Name | Type | Description -----|------|------------- **authorEmail**🔹 | string | Author's e-mail. **authorName**🔹 | string | Author's name. +**moduleName**🔹 | string | Name of the python package as used in imports and filenames. **name**🔹 | string | This is the name of your project. **pythonPath**🔹 | string | Absolute path to the user's python installation. **version**🔹 | string | Manually specify package version. @@ -8919,10 +8933,9 @@ Name | Type | Description **pytest**?🔹 | boolean | Include pytest tests.
__*Default*__: true **pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults **readme**?🔹 | [SampleReadmeProps](#projen-samplereadmeprops) | The README setup.
__*Default*__: { filename: 'README.md', contents: '# replace this' } -**sample**?🔹 | boolean | Include sample code and test if the relevant directories don't exist.
__*Optional*__ +**sample**?🔹 | boolean | Include sample code and test if the relevant directories don't exist.
__*Default*__: true **setuptools**?🔹 | boolean | Use setuptools with a setup.py script for packaging and distribution.
__*Default*__: true if the project type is library **setuptoolsOptions**?🔹 | [python.SetuptoolsOptions](#projen-python-setuptoolsoptions) | Setuptools options.
__*Default*__: defaults -**testDeps**?🔹 | Array | List of test dependencies for this project.
__*Default*__: [] **venv**?🔹 | boolean | Use venv to manage a virtual environment for installing dependencies inside.
__*Default*__: true **venvOptions**?🔹 | [python.VenvOptions](#projen-python-venvoptions) | Venv options.
__*Default*__: defaults diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 001f7b6a977..a3d17c891c6 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -8040,6 +8040,17 @@ Array [ "switch": "logging", "type": "LoggerOptions", }, + Object { + "default": "$PYTHON_MODULE_NAME", + "docs": "Name of the python package as used in imports and filenames.", + "name": "moduleName", + "parent": "PythonProjectOptions", + "path": Array [ + "moduleName", + ], + "switch": "module-name", + "type": "string", + }, Object { "default": "$BASEDIR", "docs": "This is the name of your project.", @@ -8182,6 +8193,7 @@ Array [ "type": "SampleReadmeProps", }, Object { + "default": "true", "docs": "Include sample code and test if the relevant directories don't exist.", "name": "sample", "optional": true, @@ -8216,18 +8228,6 @@ Array [ "switch": "setuptools-options", "type": "SetuptoolsOptions", }, - Object { - "default": "[]", - "docs": "List of test dependencies for this project.", - "name": "testDeps", - "optional": true, - "parent": "PythonProjectOptions", - "path": Array [ - "testDeps", - ], - "switch": "test-deps", - "type": "unknown", - }, Object { "default": "true", "docs": "Use venv to manage a virtual environment for installing dependencies inside.", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 51a957a5044..679421b6a0f 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -820,6 +820,7 @@ const project = new python.PythonProject({ authorEmail: 'my@user.email.com', authorName: 'My User Name', jsiiFqn: \\"projen.python.PythonProject\\", + moduleName: 'my_project', name: 'my-project', pythonPath: '/usr/bin/python', version: '0.1.0', @@ -847,10 +848,9 @@ const project = new python.PythonProject({ // poetryOptions: undefined, /* Poetry options. */ // pytest: true, /* Include pytest tests. */ // pytestOptions: undefined, /* pytest options. */ - // sample: undefined, /* Include sample code and test if the relevant directories don't exist. */ + // sample: true, /* Include sample code and test if the relevant directories don't exist. */ // setuptools: undefined, /* Use setuptools with a setup.py script for packaging and distribution. */ // setuptoolsOptions: undefined, /* Setuptools options. */ - // testDeps: [], /* List of test dependencies for this project. */ // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ // venvOptions: undefined, /* Venv options. */ }); diff --git a/src/__tests__/new.test.ts b/src/__tests__/new.test.ts index fac117fb460..5429fec0a1e 100644 --- a/src/__tests__/new.test.ts +++ b/src/__tests__/new.test.ts @@ -14,7 +14,11 @@ for (const type of inventory.discover()) { const projectdir = createProjectDir(outdir); // execute `projen new PJID --no-synth` in the project directory - execProjenCLI(projectdir, ['new', '--no-synth', '--python-path=/usr/bin/python', type.pjid]); + if (type.pjid.includes('python')) { + execProjenCLI(projectdir, ['new', '--no-synth', '--python-path=/usr/bin/python', type.pjid]); + } else { + execProjenCLI(projectdir, ['new', '--no-synth', type.pjid]); + } // compare generated .projenrc.js to the snapshot const projenrc = readFileSync(join(projectdir, PROJEN_RC), 'utf-8'); diff --git a/src/__tests__/python/__snapshots__/poetry.test.ts.snap b/src/__tests__/python/__snapshots__/poetry.test.ts.snap index 2a6c15daa1c..22e0010e417 100644 --- a/src/__tests__/python/__snapshots__/poetry.test.ts.snap +++ b/src/__tests__/python/__snapshots__/poetry.test.ts.snap @@ -40,6 +40,7 @@ Object { .tox/ .venv .webassets-cache +/poetry.toml /site ENV/ MANIFEST @@ -116,6 +117,16 @@ wheels/ }, ], }, + "package": Object { + "category": "20.release", + "description": "Creates source archive and wheel for distribution.", + "name": "package", + "steps": Array [ + Object { + "exec": "poetry build", + }, + ], + }, "test": Object { "category": "10.test", "description": "Runs tests", @@ -126,9 +137,33 @@ wheels/ }, ], }, + "upload": Object { + "category": "20.release", + "description": "Uploads the package to PyPI.", + "name": "upload", + "steps": Array [ + Object { + "exec": "poetry publish", + }, + ], + }, + "upload:test": Object { + "category": "20.release", + "description": "Uploads the package against a test PyPI endpoint.", + "name": "upload:test", + "steps": Array [ + Object { + "exec": "poetry publish -r testpypi", + }, + ], + }, }, }, "README.md": "# replace this", + "poetry.toml": " +[repositories.testpypi] +url = \\"https://test.pypi.org/legacy/\\" +", "pyproject.toml": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". [build-system] @@ -151,15 +186,15 @@ scripts = { } [tool.poetry.dev-dependencies] pytest = \\"6.2.1\\" ", - "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test_python-project/__main__.py": "from .example import hello + "test_python_project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test_python-project/example.py": "def hello(name: str) -> str: + "test_python_project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -171,7 +206,7 @@ if __name__ == \\"__main__\\": "tests/__init__.py": "", "tests/test_example.py": "import pytest -from test_python-project.example import hello +from test_python_project.example import hello @pytest.mark.parametrize( (\\"name\\", \\"expected\\"), diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index 4164442a094..37262b97363 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -137,15 +137,15 @@ pytest==6.2.1 ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", - "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test_python-project/__main__.py": "from .example import hello + "test_python_project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test_python-project/example.py": "def hello(name: str) -> str: + "test_python_project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -157,7 +157,7 @@ if __name__ == \\"__main__\\": "tests/__init__.py": "", "tests/test_example.py": "import pytest -from test_python-project.example import hello +from test_python_project.example import hello @pytest.mark.parametrize( (\\"name\\", \\"expected\\"), @@ -329,15 +329,15 @@ pytest==6.2.1 aws-cdk.core>=0.0.0 Django==3.1.5 ", - "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test_python-project/__main__.py": "from .example import hello + "test_python_project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test_python-project/example.py": "def hello(name: str) -> str: + "test_python_project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -349,7 +349,7 @@ if __name__ == \\"__main__\\": "tests/__init__.py": "", "tests/test_example.py": "import pytest -from test_python-project.example import hello +from test_python_project.example import hello @pytest.mark.parametrize( (\\"name\\", \\"expected\\"), @@ -521,15 +521,15 @@ pytest==6.2.1 aws-cdk.core>=0.0.0 Django==3.1.5 ", - "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test_python-project/__main__.py": "from .example import hello + "test_python_project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test_python-project/example.py": "def hello(name: str) -> str: + "test_python_project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. @@ -541,7 +541,7 @@ if __name__ == \\"__main__\\": "tests/__init__.py": "", "tests/test_example.py": "import pytest -from test_python-project.example import hello +from test_python_project.example import hello @pytest.mark.parametrize( (\\"name\\", \\"expected\\"), @@ -674,15 +674,15 @@ wheels/ ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". ", - "test_python-project/__init__.py": "__version__ = \\"0.1.0\\" + "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" ", - "test_python-project/__main__.py": "from .example import hello + "test_python_project/__main__.py": "from .example import hello if __name__ == \\"__main__\\": name = input(\\"What is your name? \\") print(hello(name)) ", - "test_python-project/example.py": "def hello(name: str) -> str: + "test_python_project/example.py": "def hello(name: str) -> str: \\"\\"\\"A simple greeting. Args: name (str): Name to greet. diff --git a/src/__tests__/python/poetry.test.ts b/src/__tests__/python/poetry.test.ts index 32c9bcc7f2a..0ccaed98571 100644 --- a/src/__tests__/python/poetry.test.ts +++ b/src/__tests__/python/poetry.test.ts @@ -22,6 +22,7 @@ class TestPythonProject extends PythonProject { ...options, clobber: false, name: 'test-python-project', + moduleName: 'test_python_project', pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index 1c9ff31d535..f66df68ee8b 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -42,6 +42,7 @@ class TestPythonProject extends PythonProject { ...options, clobber: false, name: 'test-python-project', + moduleName: 'test_python_project', pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', diff --git a/src/__tests__/python/setuptools.test.ts b/src/__tests__/python/setuptools.test.ts index 4c9b2575766..fab09da5d24 100644 --- a/src/__tests__/python/setuptools.test.ts +++ b/src/__tests__/python/setuptools.test.ts @@ -28,6 +28,7 @@ class TestPythonProject extends PythonProject { ...options, clobber: false, name: 'test-python-project', + moduleName: 'test_python_project', pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', diff --git a/src/__tests__/util.test.ts b/src/__tests__/util.test.ts index 178eee555e3..fe3ac5d4caf 100644 --- a/src/__tests__/util.test.ts +++ b/src/__tests__/util.test.ts @@ -1,5 +1,5 @@ import { JsonFile } from '../json'; -import { decamelizeKeysRecursively, dedupArray, deepMerge, isTruthy, getFilePermissions } from '../util'; +import { decamelizeKeysRecursively, dedupArray, deepMerge, isTruthy, getFilePermissions, formatAsPythonModule } from '../util'; import { TestProject } from './util'; describe('decamelizeRecursively', () => { @@ -228,3 +228,8 @@ test('getFilePermissions', () => { expect(getFilePermissions({ readonly: false })).toEqual('644'); expect(getFilePermissions({ executable: true })).toEqual('755'); }); + +test('formatAsPythonModule', () => { + expect(formatAsPythonModule('foo-bar-baz')).toEqual('foo_bar_baz'); + expect(formatAsPythonModule('foo.bar.baz')).toEqual('foo_bar_baz'); +}); diff --git a/src/cli/macros.ts b/src/cli/macros.ts index a92bbec691c..ee056c0235a 100644 --- a/src/cli/macros.ts +++ b/src/cli/macros.ts @@ -1,6 +1,6 @@ import * as os from 'os'; import * as path from 'path'; -import { execOrUndefined } from '../util'; +import { execOrUndefined, formatAsPythonModule } from '../util'; export function tryProcessMacro(macro: string) { if (!macro.startsWith('$')) { return undefined; } @@ -20,6 +20,7 @@ export function tryProcessMacro(macro: string) { case '$GIT_USER_NAME': return getFromGitConfig('user.name') ?? 'user'; case '$GIT_USER_EMAIL': return resolveEmail(); case '$PYTHON_PATH': return resolvePython(); + case '$PYTHON_MODULE_NAME': return formatAsPythonModule(basedir); } return undefined; diff --git a/src/python/poetry.ts b/src/python/poetry.ts index b7e7fdb06b9..706c602f4f4 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -25,6 +25,13 @@ export interface PoetryOptions { export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPythonPackaging { private readonly pythonProject: PythonProject; public readonly installTask: Task; + public readonly packageTask: Task; + public readonly uploadTask: Task; + + /** + * A task that uploads the package to the Test PyPI repository. + */ + public readonly uploadTestTask: Task; constructor(project: PythonProject, options: PoetryOptions) { super(project); @@ -43,15 +50,43 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho // declare the python versions for which the package is compatible this.addDependency('python@^3.6'); + this.packageTask = project.addTask('package', { + description: 'Creates source archive and wheel for distribution.', + category: TaskCategory.RELEASE, + exec: 'poetry build', + }); + + this.uploadTestTask = project.addTask('upload:test', { + description: 'Uploads the package against a test PyPI endpoint.', + category: TaskCategory.RELEASE, + exec: 'poetry publish -r testpypi', + }); + + this.uploadTask = project.addTask('upload', { + description: 'Uploads the package to PyPI.', + category: TaskCategory.RELEASE, + exec: 'poetry publish', + }); + new PoetryPyproject(project, { name: project.name, - // version: options.pyprojectConfig.version, - // description: options.pyprojectConfig.description, dependencies: () => this.synthDependencies(), devDependencies: () => this.synthDevDependencies(), scripts: {}, ...options.pyprojectConfig, }); + + new TomlFile(project, 'poetry.toml', { + committed: false, + readonly: false, // let user manage set local options through `poetry config` + obj: { + repositories: { + testpypi: { + url: 'https://test.pypi.org/legacy/', + }, + }, + }, + }); } private synthDependencies() { @@ -270,4 +305,4 @@ export class PoetryPyproject extends Component { }, }); } -} \ No newline at end of file +} diff --git a/src/python/python-packaging.ts b/src/python/python-packaging.ts index a60ab930862..dc493c9ba10 100644 --- a/src/python/python-packaging.ts +++ b/src/python/python-packaging.ts @@ -1 +1,13 @@ -export interface IPythonPackaging {} +import { Task } from '../tasks'; + +export interface IPythonPackaging { + /** + * A task that packages the project for distribution. + */ + readonly packageTask: Task; + + /** + * A task that uploads the package to a package package repository. + */ + readonly uploadTask: Task; +} diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 1cbf3668472..9e39ca7a637 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -26,6 +26,15 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly pythonPath: string; + /** + * Name of the python package as used in imports and filenames. + * + * Must only consist of alphanumeric characters and underscores. + * + * @default $PYTHON_MODULE_NAME + */ + readonly moduleName: string; + /** * Author's name * @@ -83,17 +92,6 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly deps?: string[]; - /** - * List of test dependencies for this project. - * - * Dependencies use the format: `@` - * - * Additional dependencies can be added via `project.addTestDependency()`. - * - * @default [] - */ - readonly testDeps?: string[]; - /** * List of dev dependencies for this project. * @@ -176,6 +174,7 @@ export interface PythonProjectOptions extends ProjectOptions { /** * Include sample code and test if the relevant directories don't exist. + * @default true */ readonly sample?: boolean; } @@ -216,7 +215,7 @@ export class PythonProject extends Project { /** * API for managing packaging the project as a library. Only applies when the `projectType` is LIB. */ - public readonly packagingManager!: IPythonPackaging; + public readonly packagingManager?: IPythonPackaging; /** * Pytest component. @@ -230,7 +229,7 @@ export class PythonProject extends Project { throw new Error('Python projects must only consist of alphanumeric characters, hyphens, and underscores.'); } - this.moduleName = this.safeName(options.name); + this.moduleName = options.moduleName; this.pythonPath = options.pythonPath; this.version = options.version; @@ -296,12 +295,8 @@ export class PythonProject extends Project { throw new Error('At least one tool must be chosen for managing dependencies (pip, conda, pipenv, or poetry).'); } - if (!this.packagingManager) { - if (this.projectType === ProjectType.LIB) { - throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); - } else { - this.packagingManager = {}; // no-op packaging manager - } + if (!this.packagingManager && this.projectType === ProjectType.LIB) { + throw new Error('At least one tool must be chosen for managing packaging (setuptools or poetry).'); } if (Number(options.venv ?? true) + Number(options.poetry ?? false) > 1) { @@ -335,17 +330,6 @@ export class PythonProject extends Project { this.addDefaultGitIgnore(); } - /** - * Convert an arbitrary string to a valid module filename. - * - * Replaces hyphens with underscores. - * - * @param name project name - */ - private safeName(name: string) { - return name.replace('-', '_').replace('.', '_'); - } - /** * Adds default gitignore options for a Python project based on * https://github.com/github/gitignore/blob/master/Python.gitignore diff --git a/src/python/setuptools.ts b/src/python/setuptools.ts index 472c7ea63c8..d6d8e2e1aff 100644 --- a/src/python/setuptools.ts +++ b/src/python/setuptools.ts @@ -14,6 +14,12 @@ export interface SetuptoolsOptions extends SetupPyOptions {} export class Setuptools extends Component { public readonly packageTask: Task; public readonly uploadTask: Task; + + /** + * A task that uploads the package to the Test PyPI repository. + */ + public readonly uploadTestTask: Task; + constructor(project: PythonProject, options: SetuptoolsOptions) { super(project); @@ -26,12 +32,18 @@ export class Setuptools extends Component { exec: 'rm -fr dist/* && python setup.py sdist bdist_wheel', }); - this.uploadTask = project.addTask('upload:test', { + this.uploadTestTask = project.addTask('upload:test', { description: 'Uploads the package against a test PyPI endpoint.', category: TaskCategory.RELEASE, exec: 'twine upload --repository-url https://test.pypi.org/legacy/ dist/*', }); + this.uploadTask = project.addTask('upload', { + description: 'Uploads the package against a test PyPI endpoint.', + category: TaskCategory.RELEASE, + exec: 'twine upload dist/*', + }); + new SetupPy(project, options); } } \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index f00f94c7a1c..f128bcf0f7c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -232,3 +232,7 @@ export function sorted(x: T) { return x; } } + +export function formatAsPythonModule(name: string) { + return name.replace(/-/g, '_').replace(/\./g, '_'); +} From 02156eb5c40443a54a1fad86a4a367274b7e3085 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Mon, 1 Feb 2021 21:48:52 -0500 Subject: [PATCH 20/29] update docs and some unit tests --- docs/python.md | 14 +- .../python/__snapshots__/poetry.test.ts.snap | 224 ------------------ .../__snapshots__/python-project.test.ts.snap | 3 +- src/__tests__/python/poetry.test.ts | 13 +- src/__tests__/python/python-project.test.ts | 2 +- 5 files changed, 21 insertions(+), 235 deletions(-) delete mode 100644 src/__tests__/python/__snapshots__/poetry.test.ts.snap diff --git a/docs/python.md b/docs/python.md index 40df9623726..e32ffc98eb8 100644 --- a/docs/python.md +++ b/docs/python.md @@ -61,9 +61,9 @@ requirements. See the list below: - pip: dependency manager - venv: environment manager +- poetry: dependency, environment, and packaging manager - pipenv (TBD): dependency and environment manager - setuptools (TBD): packaging manager -- poetry (TBD): dependency, environment, and packaging manager By default, pip, venv, and setuptools will be used. But these can be swapped out as needed by using the provided flags, for example: @@ -136,10 +136,12 @@ TBD > In the future, it will be possible to write your projenrc file in Python. -## Packaging +## Packaging and Publishing -TBD - -## Publishing +Python projects can be packaged by using either the `Setuptools` or `Poetry` +component. `Setuptools` records package metadata within a traditional `setup.py` +script, while `Poetry` stores metadata in the more-recent `pyproject.toml` file +format as introduced in PEP 518. -TBD. +Run `projen package` to generate a source distribution and wheel, and run +`projen upload` to upload the package to PyPI. diff --git a/src/__tests__/python/__snapshots__/poetry.test.ts.snap b/src/__tests__/python/__snapshots__/poetry.test.ts.snap deleted file mode 100644 index 22e0010e417..00000000000 --- a/src/__tests__/python/__snapshots__/poetry.test.ts.snap +++ /dev/null @@ -1,224 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`poetry enabled 1`] = ` -Object { - ".gitignore": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". - -*$py.class -*.cover -*.egg -*.egg-info/ -*.log -*.manifest -*.mo -*.pot -*.py,cover -*.py[cod] -*.sage.py -*.so -*.spec -.Python -.cache -.coverage -.coverage.* -.dmypy.json -.eggs/ -.env -.hypothesis/ -.installed.cfg -.ipynb_checkpoints -.mypy_cache/ -.nox/ -.pybuilder/ -.pyre/ -.pytest_cache/ -.pytype/ -.ropeproject -.scrapy -.spyderproject -.spyproject -.tox/ -.venv -.webassets-cache -/poetry.toml -/site -ENV/ -MANIFEST -__pycache__/ -__pypackages__/ -build/ -celerybeat-schedule -celerybeat.pid -cover/ -coverage.xml -cython_debug/ -db.sqlite3 -db.sqlite3-journal -develop-eggs/ -dist/ -dmypy.json -docs/_build/ -downloads/ -eggs/ -env.bak/ -env/ -htmlcov/ -instance/ -ipython_config.py -lib/ -lib64/ -local_settings.py -node_modules/ -nosetests.xml -parts/ -pip-delete-this-directory.txt -pip-log.txt -profile_default/ -sdist/ -share/python-wheels/ -target/ -var/ -venv.bak/ -venv/ -wheels/ -!/.projen/deps.json -!/.projen/tasks.json -!/pyproject.toml -", - ".projen/deps.json": Object { - "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", - "dependencies": Array [ - Object { - "name": "pytest", - "type": "devenv", - "version": "6.2.1", - }, - Object { - "name": "python", - "type": "runtime", - "version": "^3.6", - }, - ], - }, - ".projen/tasks.json": Object { - "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\".", - "env": Object { - "PATH": "$(echo $(poetry env info -p)/bin:$PATH)", - "VIRTUAL_ENV": "$(poetry env info -p)", - }, - "tasks": Object { - "install": Object { - "category": "00.build", - "description": "Install and upgrade dependencies", - "name": "install", - "steps": Array [ - Object { - "exec": "poetry update", - }, - ], - }, - "package": Object { - "category": "20.release", - "description": "Creates source archive and wheel for distribution.", - "name": "package", - "steps": Array [ - Object { - "exec": "poetry build", - }, - ], - }, - "test": Object { - "category": "10.test", - "description": "Runs tests", - "name": "test", - "steps": Array [ - Object { - "exec": "pytest", - }, - ], - }, - "upload": Object { - "category": "20.release", - "description": "Uploads the package to PyPI.", - "name": "upload", - "steps": Array [ - Object { - "exec": "poetry publish", - }, - ], - }, - "upload:test": Object { - "category": "20.release", - "description": "Uploads the package against a test PyPI endpoint.", - "name": "upload:test", - "steps": Array [ - Object { - "exec": "poetry publish -r testpypi", - }, - ], - }, - }, - }, - "README.md": "# replace this", - "poetry.toml": " -[repositories.testpypi] -url = \\"https://test.pypi.org/legacy/\\" -", - "pyproject.toml": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". - -[build-system] -requires = [ \\"poetry_core>=1.0.0\\" ] -build-backend = \\"poetry.core.masonry.api\\" - -[tool.poetry] -name = \\"test-python-project\\" -version = \\"0.1.0\\" -description = \\"a short project description\\" -license = \\"Apache Software License\\" -authors = [ \\"First Last \\" ] -readme = \\"README.md\\" -homepage = \\"http://www.example.com\\" -scripts = { } - - [tool.poetry.dependencies] - python = \\"^3.6\\" - - [tool.poetry.dev-dependencies] - pytest = \\"6.2.1\\" -", - "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" -", - "test_python_project/__main__.py": "from .example import hello - -if __name__ == \\"__main__\\": - name = input(\\"What is your name? \\") - print(hello(name)) -", - "test_python_project/example.py": "def hello(name: str) -> str: - \\"\\"\\"A simple greeting. - Args: - name (str): Name to greet. - Returns: - str: greeting message - \\"\\"\\" - return f\\"Hello {name}!\\" -", - "tests/__init__.py": "", - "tests/test_example.py": "import pytest - -from test_python_project.example import hello - -@pytest.mark.parametrize( - (\\"name\\", \\"expected\\"), - [ - (\\"A. Musing\\", \\"Hello A. Musing!\\"), - (\\"traveler\\", \\"Hello traveler!\\"), - (\\"projen developer\\", \\"Hello projen developer!\\"), - ], -) -def test_hello(name, expected): - \\"\\"\\"Example test with parametrization.\\"\\"\\" - assert hello(name) == expected -", -} -`; diff --git a/src/__tests__/python/__snapshots__/python-project.test.ts.snap b/src/__tests__/python/__snapshots__/python-project.test.ts.snap index 37262b97363..f3bd091f4f1 100644 --- a/src/__tests__/python/__snapshots__/python-project.test.ts.snap +++ b/src/__tests__/python/__snapshots__/python-project.test.ts.snap @@ -468,7 +468,6 @@ wheels/ Object { "name": "aws-cdk.core", "type": "runtime", - "version": "*", }, Object { "name": "Django", @@ -518,7 +517,7 @@ hypothesis>=6.0.3, <7.0.0 pytest==6.2.1 ", "requirements.txt": "# ~~ Generated by projen. To modify, edit .projenrc.js and run \\"npx projen\\". -aws-cdk.core>=0.0.0 +aws-cdk.core Django==3.1.5 ", "test_python_project/__init__.py": "__version__ = \\"0.1.0\\" diff --git a/src/__tests__/python/poetry.test.ts b/src/__tests__/python/poetry.test.ts index 0ccaed98571..beab2153da1 100644 --- a/src/__tests__/python/poetry.test.ts +++ b/src/__tests__/python/poetry.test.ts @@ -10,10 +10,19 @@ test('poetry enabled', () => { poetry: true, homepage: 'http://www.example.com', description: 'a short project description', - license: 'Apache Software License', + license: 'Apache-2.0', + classifiers: [ + 'Development Status :: 4 - Beta', + ], }); - expect(synthSnapshot(p)).toMatchSnapshot(); + const snapshot = synthSnapshot(p); + expect(snapshot['pyproject.toml']).toContain('First Last'); + expect(snapshot['pyproject.toml']).toContain('email@example.com'); + expect(snapshot['pyproject.toml']).toContain('http://www.example.com'); + expect(snapshot['pyproject.toml']).toContain('a short project description'); + expect(snapshot['pyproject.toml']).toContain('Apache-2.0'); + expect(snapshot['pyproject.toml']).toContain('Development Status :: 4 - Beta'); }); class TestPythonProject extends PythonProject { diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index f66df68ee8b..f9f543f82fe 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -19,7 +19,7 @@ test('dependencies via ctor', () => { const p = new TestPythonProject({ deps: [ 'Django@3.1.5', - 'aws-cdk.core@*', + 'aws-cdk.core', ], devDeps: [ 'hypothesis@^6.0.3', From 0a6ee721c3e2d2f13853b70ece8cd29b5d28f6f5 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Wed, 3 Feb 2021 01:55:59 -0500 Subject: [PATCH 21/29] remove pythonPath option --- API.md | 3 --- src/__tests__/__snapshots__/inventory.test.ts.snap | 11 ----------- src/__tests__/__snapshots__/new.test.ts.snap | 1 - src/__tests__/python/poetry.test.ts | 1 - src/__tests__/python/python-project.test.ts | 1 - src/__tests__/python/setuptools.test.ts | 1 - src/python/poetry.ts | 7 ++----- src/python/python-project.ts | 14 -------------- src/python/venv.ts | 6 ++---- 9 files changed, 4 insertions(+), 41 deletions(-) diff --git a/API.md b/API.md index 3c547da22d2..e11bde2355e 100644 --- a/API.md +++ b/API.md @@ -5130,7 +5130,6 @@ new python.PythonProject(options: PythonProjectOptions) * **authorEmail** (string) Author's e-mail. * **authorName** (string) Author's name. * **moduleName** (string) Name of the python package as used in imports and filenames. - * **pythonPath** (string) Absolute path to the user's python installation. * **version** (string) Manually specify package version. * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] @@ -5160,7 +5159,6 @@ Name | Type | Description **depsManager**🔹 | [python.IPythonDeps](#projen-python-ipythondeps) | API for managing dependencies. **envManager**🔹 | [python.IPythonEnv](#projen-python-ipythonenv) | API for mangaging the Python runtime environment. **moduleName**🔹 | string | Python module name (the project name, with any hyphens or periods replaced with underscores). -**pythonPath**🔹 | string | Absolute path to the user's python installation. **version**🔹 | string | Version of the package for distribution (should follow semver). **packagingManager**?🔹 | [python.IPythonPackaging](#projen-python-ipythonpackaging) | API for managing packaging the project as a library.
__*Optional*__ **pytest**?🔹 | [python.Pytest](#projen-python-pytest) | Pytest component.
__*Optional*__ @@ -9193,7 +9191,6 @@ Name | Type | Description **authorName**🔹 | string | Author's name. **moduleName**🔹 | string | Name of the python package as used in imports and filenames. **name**🔹 | string | This is the name of your project. -**pythonPath**🔹 | string | Absolute path to the user's python installation. **version**🔹 | string | Manually specify package version. **classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ **clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index d9efd99190d..06c5ce8e43f 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -8337,17 +8337,6 @@ Array [ "switch": "pytest-options", "type": "PytestOptions", }, - Object { - "default": "$PYTHON_PATH", - "docs": "Absolute path to the user's python installation.", - "name": "pythonPath", - "parent": "PythonProjectOptions", - "path": Array [ - "pythonPath", - ], - "switch": "python-path", - "type": "string", - }, Object { "default": "- { filename: 'README.md', contents: '# replace this' }", "docs": "The README setup.", diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index ddefd8849d3..7dedc991f87 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -836,7 +836,6 @@ const project = new python.PythonProject({ jsiiFqn: \\"projen.python.PythonProject\\", moduleName: 'my_project', name: 'my-project', - pythonPath: '/usr/bin/python', version: '0.1.0', /* ProjectOptions */ diff --git a/src/__tests__/python/poetry.test.ts b/src/__tests__/python/poetry.test.ts index beab2153da1..228b49dc3f0 100644 --- a/src/__tests__/python/poetry.test.ts +++ b/src/__tests__/python/poetry.test.ts @@ -32,7 +32,6 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', moduleName: 'test_python_project', - pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', version: '0.1.0', diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index f9f543f82fe..64997fc8a2c 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -43,7 +43,6 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', moduleName: 'test_python_project', - pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', version: '0.1.0', diff --git a/src/__tests__/python/setuptools.test.ts b/src/__tests__/python/setuptools.test.ts index fab09da5d24..6ff4e24dff4 100644 --- a/src/__tests__/python/setuptools.test.ts +++ b/src/__tests__/python/setuptools.test.ts @@ -29,7 +29,6 @@ class TestPythonProject extends PythonProject { clobber: false, name: 'test-python-project', moduleName: 'test_python_project', - pythonPath: '/usr/bin/python', authorName: 'First Last', authorEmail: 'email@example.com', version: '0.1.0', diff --git a/src/python/poetry.ts b/src/python/poetry.ts index 706c602f4f4..2d239174e19 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -23,7 +23,6 @@ export interface PoetryOptions { * poetry CLI tool. */ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPythonPackaging { - private readonly pythonProject: PythonProject; public readonly installTask: Task; public readonly packageTask: Task; public readonly uploadTask: Task; @@ -36,8 +35,6 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho constructor(project: PythonProject, options: PoetryOptions) { super(project); - this.pythonProject = project; - this.installTask = project.addTask('install', { description: 'Install and upgrade dependencies', category: TaskCategory.BUILD, @@ -138,8 +135,8 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho let envPath = execOrUndefined('poetry env info -p', { cwd: this.project.outdir }); if (!envPath) { - this.project.logger.info(`Setting up a virtual environment using the python installation that was found: ${this.pythonProject.pythonPath}.`); - exec(`poetry env use ${this.pythonProject.pythonPath}`, { cwd: this.project.outdir }); + this.project.logger.info('Setting up a virtual environment...'); + exec('poetry env use python', { cwd: this.project.outdir }); envPath = execOrUndefined('poetry env info -p', { cwd: this.project.outdir }); this.project.logger.info(`Environment successfully created (located in ${envPath}}).`); } diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 9e39ca7a637..16bce4ca809 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -19,13 +19,6 @@ const PYTHON_PROJECT_NAME_REGEX = /^[A-Za-z0-9-_\.]+$/; export interface PythonProjectOptions extends ProjectOptions { // -- required options -- - /** - * Absolute path to the user's python installation. - * - * @default $PYTHON_PATH - */ - readonly pythonPath: string; - /** * Name of the python package as used in imports and filenames. * @@ -185,12 +178,6 @@ export interface PythonProjectOptions extends ProjectOptions { * @pjid python */ export class PythonProject extends Project { - /** - * Absolute path to the user's python installation. This will be used for - * setting up the virtual environment. - */ - public readonly pythonPath: string; - /** * Python module name (the project name, with any hyphens or periods replaced * with underscores). @@ -230,7 +217,6 @@ export class PythonProject extends Project { } this.moduleName = options.moduleName; - this.pythonPath = options.pythonPath; this.version = options.version; if (options.venv ?? true) { diff --git a/src/python/venv.ts b/src/python/venv.ts index 2998fecf3cb..ac597c414f2 100644 --- a/src/python/venv.ts +++ b/src/python/venv.ts @@ -25,13 +25,11 @@ export class Venv extends Component implements IPythonEnv { * Name of directory to store the environment in */ private readonly envdir: string; - private readonly pythonProject: PythonProject; constructor(project: PythonProject, options: VenvOptions = {}) { super(project); this.envdir = options.envdir ?? '.env'; - this.pythonProject = project; this.project.gitignore.exclude(`/${this.envdir}`); @@ -45,8 +43,8 @@ export class Venv extends Component implements IPythonEnv { public setupEnvironment() { const absoluteEnvdir = path.join(this.project.outdir, this.envdir); if (!fs.pathExistsSync(absoluteEnvdir)) { - this.project.logger.info(`Setting up a virtual environment using the python installation that was found: ${this.pythonProject.pythonPath}.`); - exec(`${this.pythonProject.pythonPath} -m venv ${this.envdir}`, { cwd: this.project.outdir }); + this.project.logger.info('Setting up a virtual environment...'); + exec(`python -m venv ${this.envdir}`, { cwd: this.project.outdir }); this.project.logger.info(`Environment successfully created (located in ./${this.envdir}).`); } } From 729a9677f826e8764552ac549293cde479766d3f Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Wed, 3 Feb 2021 03:39:34 -0500 Subject: [PATCH 22/29] combine packaging options into a single interface --- API.md | 161 +++++++++++------- .../__snapshots__/inventory.test.ts.snap | 68 +++----- src/__tests__/__snapshots__/new.test.ts.snap | 19 ++- src/python/poetry.ts | 38 ++--- src/python/python-packaging.ts | 56 ++++++ src/python/python-project.ts | 118 +++---------- src/python/setuppy.ts | 13 +- src/python/setuptools.ts | 25 ++- 8 files changed, 251 insertions(+), 247 deletions(-) diff --git a/API.md b/API.md index e11bde2355e..fda9ddf308f 100644 --- a/API.md +++ b/API.md @@ -168,15 +168,14 @@ Name|Description [java.ProjenrcCommonOptions](#projen-java-projenrccommonoptions)|Options for `Projenrc`. [java.ProjenrcOptions](#projen-java-projenrcoptions)|*No description* [python.PipOptions](#projen-python-pipoptions)|Options for pip. -[python.PoetryOptions](#projen-python-poetryoptions)|Options for poetry. [python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)|*No description* +[python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps)|*No description* [python.PytestOptions](#projen-python-pytestoptions)|*No description* +[python.PythonPackagingOptions](#projen-python-pythonpackagingoptions)|*No description* [python.PythonProjectOptions](#projen-python-pythonprojectoptions)|Options for `PythonProject`. [python.PythonSampleOptions](#projen-python-pythonsampleoptions)|Options for python sample code. [python.RequirementsFileOptions](#projen-python-requirementsfileoptions)|*No description* -[python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)|Fields to pass in the setup() function of setup.py. -[python.SetupPyOptions](#projen-python-setuppyoptions)|*No description* -[python.SetuptoolsOptions](#projen-python-setuptoolsoptions)|Options for setuptools. +[python.SetupPyOptions](#projen-python-setuppyoptions)|Fields to pass in the setup() function of setup.py. [python.VenvOptions](#projen-python-venvoptions)|Options for venv. [tasks.TaskCommonOptions](#projen-tasks-taskcommonoptions)|*No description* [tasks.TaskOptions](#projen-tasks-taskoptions)|*No description* @@ -4943,12 +4942,20 @@ __Extends__: [Component](#projen-component) ```ts -new python.Poetry(project: PythonProject, options: PoetryOptions) +new python.Poetry(project: PythonProject, options: PythonPackagingOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* -* **options** ([python.PoetryOptions](#projen-python-poetryoptions)) *No description* - * **pyprojectConfig** ([python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)) Configure pyproject.toml. __*Optional*__ +* **options** ([python.PythonPackagingOptions](#projen-python-pythonpackagingoptions)) *No description* + * **authorEmail** (string) Author's e-mail. + * **authorName** (string) Author's name. + * **version** (string) Version of the package. + * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **description** (string) A short description of the package. __*Optional*__ + * **homepage** (string) A URL to the website of the project. __*Optional*__ + * **license** (string) License of this package as an SPDX identifier. __*Optional*__ + * **poetryOptions** ([python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps)) Additional options to set for poetry if using poetry. __*Optional*__ + * **setupConfig** (Map) Additional fields to pass in the setup() function if using setuptools. __*Optional*__ @@ -5038,9 +5045,7 @@ new python.PoetryPyproject(project: PythonProject, options: PoetryPyprojectOptio * **options** ([python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions)) *No description* * **authors** (Array) The authors of the package. __*Optional*__ * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ - * **dependencies** (Map) A list of dependencies for the project. __*Optional*__ * **description** (string) A short description of the package (required). __*Optional*__ - * **devDependencies** (Map) A list of development dependencies for the project. __*Optional*__ * **documentation** (string) A URL to the documentation of the project. __*Optional*__ * **exclude** (Array) A list of patterns that will be excluded in the final package. __*Optional*__ * **homepage** (string) A URL to the website of the project. __*Optional*__ @@ -5054,6 +5059,8 @@ new python.PoetryPyproject(project: PythonProject, options: PoetryPyprojectOptio * **repository** (string) A URL to the repository of the project. __*Optional*__ * **scripts** (Map) The scripts or executables that will be installed when installing the package. __*Optional*__ * **version** (string) Version of the package (required). __*Optional*__ + * **dependencies** (Map) A list of dependencies for the project. __*Optional*__ + * **devDependencies** (Map) A list of development dependencies for the project. __*Optional*__ @@ -5129,23 +5136,22 @@ new python.PythonProject(options: PythonProjectOptions) * **readme** ([SampleReadmeProps](#projen-samplereadmeprops)) The README setup. __*Default*__: { filename: 'README.md', contents: '# replace this' } * **authorEmail** (string) Author's e-mail. * **authorName** (string) Author's name. - * **moduleName** (string) Name of the python package as used in imports and filenames. - * **version** (string) Manually specify package version. + * **version** (string) Version of the package. * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **description** (string) A short description of the package. __*Optional*__ + * **homepage** (string) A URL to the website of the project. __*Optional*__ + * **license** (string) License of this package as an SPDX identifier. __*Optional*__ + * **poetryOptions** ([python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps)) Additional options to set for poetry if using poetry. __*Optional*__ + * **setupConfig** (Map) Additional fields to pass in the setup() function if using setuptools. __*Optional*__ + * **moduleName** (string) Name of the python package as used in imports and filenames. * **deps** (Array) List of runtime dependencies for this project. __*Default*__: [] - * **description** (string) A short project description. __*Optional*__ * **devDeps** (Array) List of dev dependencies for this project. __*Default*__: [] - * **homepage** (string) The project's homepage / website. __*Optional*__ - * **license** (string) The project license. __*Optional*__ * **pip** (boolean) Use pip with a requirements.txt file to track project dependencies. __*Default*__: true - * **pipOptions** ([python.PipOptions](#projen-python-pipoptions)) Pip options. __*Default*__: defaults - * **poetry** (boolean) Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. __*Default*__: false - * **poetryOptions** ([python.PoetryOptions](#projen-python-poetryoptions)) Poetry options. __*Default*__: defaults + * **poetry** (boolean) Use poetry to manage your project dependencies, virtual environment, and (optional) packaging/publishing. __*Default*__: false * **pytest** (boolean) Include pytest tests. __*Default*__: true * **pytestOptions** ([python.PytestOptions](#projen-python-pytestoptions)) pytest options. __*Default*__: defaults * **sample** (boolean) Include sample code and test if the relevant directories don't exist. __*Default*__: true - * **setuptools** (boolean) Use setuptools with a setup.py script for packaging and distribution. __*Default*__: true if the project type is library - * **setuptoolsOptions** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) Setuptools options. __*Default*__: defaults + * **setuptools** (boolean) Use setuptools with a setup.py script for packaging and publishing. __*Default*__: true if the project type is library * **venv** (boolean) Use venv to manage a virtual environment for installing dependencies inside. __*Default*__: true * **venvOptions** ([python.VenvOptions](#projen-python-venvoptions)) Venv options. __*Default*__: defaults @@ -5306,7 +5312,15 @@ new python.SetupPy(project: PythonProject, options: SetupPyOptions) * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **options** ([python.SetupPyOptions](#projen-python-setuppyoptions)) *No description* - * **setupConfig** ([python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)) Fields to pass in the setup() function. __*Optional*__ + * **authorEmail** (string) Author's e-mail. __*Optional*__ + * **authorName** (string) Author's name. __*Optional*__ + * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **description** (string) A short project description. __*Optional*__ + * **homepage** (string) Package's Homepage / Website. __*Optional*__ + * **license** (string) The project license. __*Optional*__ + * **name** (string) Name of the package. __*Optional*__ + * **packages** (Array) List of submodules to be packaged. __*Optional*__ + * **version** (string) Manually specify package version. __*Optional*__ ### Methods @@ -5341,12 +5355,20 @@ __Extends__: [Component](#projen-component) ```ts -new python.Setuptools(project: PythonProject, options: SetuptoolsOptions) +new python.Setuptools(project: PythonProject, options: PythonPackagingOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* -* **options** ([python.SetuptoolsOptions](#projen-python-setuptoolsoptions)) *No description* - * **setupConfig** ([python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions)) Fields to pass in the setup() function. __*Optional*__ +* **options** ([python.PythonPackagingOptions](#projen-python-pythonpackagingoptions)) *No description* + * **authorEmail** (string) Author's e-mail. + * **authorName** (string) Author's name. + * **version** (string) Version of the package. + * **classifiers** (Array) A list of PyPI trove classifiers that describe the project. __*Optional*__ + * **description** (string) A short description of the package. __*Optional*__ + * **homepage** (string) A URL to the website of the project. __*Optional*__ + * **license** (string) License of this package as an SPDX identifier. __*Optional*__ + * **poetryOptions** ([python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps)) Additional options to set for poetry if using poetry. __*Optional*__ + * **setupConfig** (Map) Additional fields to pass in the setup() function if using setuptools. __*Optional*__ @@ -9121,20 +9143,37 @@ Name | Type | Description Options for pip. -## struct PoetryOptions 🔹 +## struct PoetryPyprojectOptions 🔹 + -Options for poetry. Name | Type | Description -----|------|------------- -**pyprojectConfig**?🔹 | [python.PoetryPyprojectOptions](#projen-python-poetrypyprojectoptions) | Configure pyproject.toml.
__*Optional*__ +**authors**?🔹 | Array | The authors of the package.
__*Optional*__ +**classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ +**dependencies**?🔹 | Map | A list of dependencies for the project.
__*Optional*__ +**description**?🔹 | string | A short description of the package (required).
__*Optional*__ +**devDependencies**?🔹 | Map | A list of development dependencies for the project.
__*Optional*__ +**documentation**?🔹 | string | A URL to the documentation of the project.
__*Optional*__ +**exclude**?🔹 | Array | A list of patterns that will be excluded in the final package.
__*Optional*__ +**homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ +**include**?🔹 | Array | A list of patterns that will be included in the final package.
__*Optional*__ +**keywords**?🔹 | Array | A list of keywords (max: 5) that the package is related to.
__*Optional*__ +**license**?🔹 | string | License of this package as an SPDX identifier.
__*Optional*__ +**maintainers**?🔹 | Array | the maintainers of the package.
__*Optional*__ +**name**?🔹 | string | Name of the package (required).
__*Optional*__ +**packages**?🔹 | Array | A list of packages and modules to include in the final distribution.
__*Optional*__ +**readme**?🔹 | string | The name of the readme file of the package.
__*Optional*__ +**repository**?🔹 | string | A URL to the repository of the project.
__*Optional*__ +**scripts**?🔹 | Map | The scripts or executables that will be installed when installing the package.
__*Optional*__ +**version**?🔹 | string | Version of the package (required).
__*Optional*__ -## struct PoetryPyprojectOptions 🔹 +## struct PoetryPyprojectOptionsWithoutDeps 🔹 @@ -9145,9 +9184,7 @@ Name | Type | Description -----|------|------------- **authors**?🔹 | Array | The authors of the package.
__*Optional*__ **classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ -**dependencies**?🔹 | Map | A list of dependencies for the project.
__*Optional*__ **description**?🔹 | string | A short description of the package (required).
__*Optional*__ -**devDependencies**?🔹 | Map | A list of development dependencies for the project.
__*Optional*__ **documentation**?🔹 | string | A URL to the documentation of the project.
__*Optional*__ **exclude**?🔹 | Array | A list of patterns that will be excluded in the final package.
__*Optional*__ **homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ @@ -9178,6 +9215,27 @@ Name | Type | Description +## struct PythonPackagingOptions 🔹 + + + + + + +Name | Type | Description +-----|------|------------- +**authorEmail**🔹 | string | Author's e-mail. +**authorName**🔹 | string | Author's name. +**version**🔹 | string | Version of the package. +**classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ +**description**?🔹 | string | A short description of the package.
__*Optional*__ +**homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ +**license**?🔹 | string | License of this package as an SPDX identifier.
__*Optional*__ +**poetryOptions**?🔹 | [python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps) | Additional options to set for poetry if using poetry.
__*Optional*__ +**setupConfig**?🔹 | Map | Additional fields to pass in the setup() function if using setuptools.
__*Optional*__ + + + ## struct PythonProjectOptions 🔹 @@ -9191,31 +9249,30 @@ Name | Type | Description **authorName**🔹 | string | Author's name. **moduleName**🔹 | string | Name of the python package as used in imports and filenames. **name**🔹 | string | This is the name of your project. -**version**🔹 | string | Manually specify package version. +**version**🔹 | string | Version of the package. **classifiers**?🔹 | Array | A list of PyPI trove classifiers that describe the project.
__*Optional*__ **clobber**?🔹 | boolean | Add a `clobber` task which resets the repo to origin.
__*Default*__: true **deps**?🔹 | Array | List of runtime dependencies for this project.
__*Default*__: [] -**description**?🔹 | string | A short project description.
__*Optional*__ +**description**?🔹 | string | A short description of the package.
__*Optional*__ **devContainer**?🔹 | boolean | Add a VSCode development environment (used for GitHub Codespaces).
__*Default*__: false **devDeps**?🔹 | Array | List of dev dependencies for this project.
__*Default*__: [] **gitpod**?🔹 | boolean | Add a Gitpod development environment.
__*Default*__: false -**homepage**?🔹 | string | The project's homepage / website.
__*Optional*__ +**homepage**?🔹 | string | A URL to the website of the project.
__*Optional*__ **jsiiFqn**?🔹 | string | The JSII FQN (fully qualified name) of the project class.
__*Default*__: undefined -**license**?🔹 | string | The project license.
__*Optional*__ +**license**?🔹 | string | License of this package as an SPDX identifier.
__*Optional*__ **logging**?🔹 | [LoggerOptions](#projen-loggeroptions) | Configure logging options such as verbosity.
__*Default*__: {} **outdir**?🔹 | string | The root directory of the project.
__*Default*__: "." **parent**?🔹 | [Project](#projen-project) | The parent project, if this project is part of a bigger project.
__*Optional*__ **pip**?🔹 | boolean | Use pip with a requirements.txt file to track project dependencies.
__*Default*__: true -**pipOptions**?🔹 | [python.PipOptions](#projen-python-pipoptions) | Pip options.
__*Default*__: defaults -**poetry**?🔹 | boolean | Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.
__*Default*__: false -**poetryOptions**?🔹 | [python.PoetryOptions](#projen-python-poetryoptions) | Poetry options.
__*Default*__: defaults +**poetry**?🔹 | boolean | Use poetry to manage your project dependencies, virtual environment, and (optional) packaging/publishing.
__*Default*__: false +**poetryOptions**?🔹 | [python.PoetryPyprojectOptionsWithoutDeps](#projen-python-poetrypyprojectoptionswithoutdeps) | Additional options to set for poetry if using poetry.
__*Optional*__ **projectType**?🔹 | [ProjectType](#projen-projecttype) | Which type of project this is (library/app).
__*Default*__: ProjectType.UNKNOWN **pytest**?🔹 | boolean | Include pytest tests.
__*Default*__: true **pytestOptions**?🔹 | [python.PytestOptions](#projen-python-pytestoptions) | pytest options.
__*Default*__: defaults **readme**?🔹 | [SampleReadmeProps](#projen-samplereadmeprops) | The README setup.
__*Default*__: { filename: 'README.md', contents: '# replace this' } **sample**?🔹 | boolean | Include sample code and test if the relevant directories don't exist.
__*Default*__: true -**setuptools**?🔹 | boolean | Use setuptools with a setup.py script for packaging and distribution.
__*Default*__: true if the project type is library -**setuptoolsOptions**?🔹 | [python.SetuptoolsOptions](#projen-python-setuptoolsoptions) | Setuptools options.
__*Default*__: defaults +**setupConfig**?🔹 | Map | Additional fields to pass in the setup() function if using setuptools.
__*Optional*__ +**setuptools**?🔹 | boolean | Use setuptools with a setup.py script for packaging and publishing.
__*Default*__: true if the project type is library **venv**?🔹 | boolean | Use venv to manage a virtual environment for installing dependencies inside.
__*Default*__: true **venvOptions**?🔹 | [python.VenvOptions](#projen-python-venvoptions) | Venv options.
__*Default*__: defaults @@ -9240,7 +9297,7 @@ Name | Type | Description -## struct SetupPyConfigOptions 🔹 +## struct SetupPyOptions 🔹 Fields to pass in the setup() function of setup.py. @@ -9261,32 +9318,6 @@ Name | Type | Description -## struct SetupPyOptions 🔹 - - - - - - -Name | Type | Description ------|------|------------- -**setupConfig**?🔹 | [python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions) | Fields to pass in the setup() function.
__*Optional*__ - - - -## struct SetuptoolsOptions 🔹 - - -Options for setuptools. - - - -Name | Type | Description ------|------|------------- -**setupConfig**?🔹 | [python.SetupPyConfigOptions](#projen-python-setuppyconfigoptions) | Fields to pass in the setup() function.
__*Optional*__ - - - ## struct VenvOptions 🔹 diff --git a/src/__tests__/__snapshots__/inventory.test.ts.snap b/src/__tests__/__snapshots__/inventory.test.ts.snap index 06c5ce8e43f..cee0a20abab 100644 --- a/src/__tests__/__snapshots__/inventory.test.ts.snap +++ b/src/__tests__/__snapshots__/inventory.test.ts.snap @@ -8063,7 +8063,7 @@ Array [ "default": "$GIT_USER_EMAIL", "docs": "Author's e-mail.", "name": "authorEmail", - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "authorEmail", ], @@ -8074,7 +8074,7 @@ Array [ "default": "$GIT_USER_NAME", "docs": "Author's name.", "name": "authorName", - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "authorName", ], @@ -8085,7 +8085,7 @@ Array [ "docs": "A list of PyPI trove classifiers that describe the project.", "name": "classifiers", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "classifiers", ], @@ -8117,10 +8117,10 @@ Array [ "type": "unknown", }, Object { - "docs": "A short project description.", + "docs": "A short description of the package.", "name": "description", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "description", ], @@ -8164,10 +8164,10 @@ Array [ "type": "boolean", }, Object { - "docs": "The project's homepage / website.", + "docs": "A URL to the website of the project.", "name": "homepage", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "homepage", ], @@ -8186,10 +8186,10 @@ Array [ "type": "string", }, Object { - "docs": "The project license.", + "docs": "License of this package as an SPDX identifier.", "name": "license", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "license", ], @@ -8265,21 +8265,9 @@ Array [ "switch": "pip", "type": "boolean", }, - Object { - "default": "- defaults", - "docs": "Pip options.", - "name": "pipOptions", - "optional": true, - "parent": "PythonProjectOptions", - "path": Array [ - "pipOptions", - ], - "switch": "pip-options", - "type": "PipOptions", - }, Object { "default": "false", - "docs": "Use poetry to manage your project dependencies, virtual environment, and (optional) packaging.", + "docs": "Use poetry to manage your project dependencies, virtual environment, and (optional) packaging/publishing.", "name": "poetry", "optional": true, "parent": "PythonProjectOptions", @@ -8290,16 +8278,15 @@ Array [ "type": "boolean", }, Object { - "default": "- defaults", - "docs": "Poetry options.", + "docs": "Additional options to set for poetry if using poetry.", "name": "poetryOptions", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "poetryOptions", ], "switch": "poetry-options", - "type": "PoetryOptions", + "type": "PoetryPyprojectOptionsWithoutDeps", }, Object { "default": "ProjectType.UNKNOWN", @@ -8362,28 +8349,27 @@ Array [ "type": "boolean", }, Object { - "default": "- true if the project type is library", - "docs": "Use setuptools with a setup.py script for packaging and distribution.", - "name": "setuptools", + "docs": "Additional fields to pass in the setup() function if using setuptools.", + "name": "setupConfig", "optional": true, - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ - "setuptools", + "setupConfig", ], - "switch": "setuptools", - "type": "boolean", + "switch": "setup-config", + "type": "unknown", }, Object { - "default": "- defaults", - "docs": "Setuptools options.", - "name": "setuptoolsOptions", + "default": "- true if the project type is library", + "docs": "Use setuptools with a setup.py script for packaging and publishing.", + "name": "setuptools", "optional": true, "parent": "PythonProjectOptions", "path": Array [ - "setuptoolsOptions", + "setuptools", ], - "switch": "setuptools-options", - "type": "SetuptoolsOptions", + "switch": "setuptools", + "type": "boolean", }, Object { "default": "true", @@ -8411,9 +8397,9 @@ Array [ }, Object { "default": "\\"0.1.0\\"", - "docs": "Manually specify package version.", + "docs": "Version of the package.", "name": "version", - "parent": "PythonProjectOptions", + "parent": "PythonPackagingOptions", "path": Array [ "version", ], diff --git a/src/__tests__/__snapshots__/new.test.ts.snap b/src/__tests__/__snapshots__/new.test.ts.snap index 7dedc991f87..5bb7b6c8023 100644 --- a/src/__tests__/__snapshots__/new.test.ts.snap +++ b/src/__tests__/__snapshots__/new.test.ts.snap @@ -848,22 +848,23 @@ const project = new python.PythonProject({ // projectType: ProjectType.UNKNOWN, /* Which type of project this is (library/app). */ // readme: undefined, /* The README setup. */ - /* PythonProjectOptions */ + /* PythonPackagingOptions */ // classifiers: undefined, /* A list of PyPI trove classifiers that describe the project. */ + // description: undefined, /* A short description of the package. */ + // homepage: undefined, /* A URL to the website of the project. */ + // license: undefined, /* License of this package as an SPDX identifier. */ + // poetryOptions: undefined, /* Additional options to set for poetry if using poetry. */ + // setupConfig: undefined, /* Additional fields to pass in the setup() function if using setuptools. */ + + /* PythonProjectOptions */ // deps: [], /* List of runtime dependencies for this project. */ - // description: undefined, /* A short project description. */ // devDeps: [], /* List of dev dependencies for this project. */ - // homepage: undefined, /* The project's homepage / website. */ - // license: undefined, /* The project license. */ // pip: true, /* Use pip with a requirements.txt file to track project dependencies. */ - // pipOptions: undefined, /* Pip options. */ - // poetry: false, /* Use poetry to manage your project dependencies, virtual environment, and (optional) packaging. */ - // poetryOptions: undefined, /* Poetry options. */ + // poetry: false, /* Use poetry to manage your project dependencies, virtual environment, and (optional) packaging/publishing. */ // pytest: true, /* Include pytest tests. */ // pytestOptions: undefined, /* pytest options. */ // sample: true, /* Include sample code and test if the relevant directories don't exist. */ - // setuptools: undefined, /* Use setuptools with a setup.py script for packaging and distribution. */ - // setuptoolsOptions: undefined, /* Setuptools options. */ + // setuptools: undefined, /* Use setuptools with a setup.py script for packaging and publishing. */ // venv: true, /* Use venv to manage a virtual environment for installing dependencies inside. */ // venvOptions: undefined, /* Venv options. */ }); diff --git a/src/python/poetry.ts b/src/python/poetry.ts index 2d239174e19..aeeba232595 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -5,19 +5,9 @@ import { TomlFile } from '../toml'; import { exec, execOrUndefined } from '../util'; import { IPythonDeps } from './python-deps'; import { IPythonEnv } from './python-env'; -import { IPythonPackaging } from './python-packaging'; +import { IPythonPackaging, PythonPackagingOptions } from './python-packaging'; import { PythonProject } from './python-project'; -/** - * Options for poetry. - */ -export interface PoetryOptions { - /** - * Configure pyproject.toml - */ - readonly pyprojectConfig?: PoetryPyprojectOptions; -} - /** * Manage project dependencies, virtual environments, and packaging through the * poetry CLI tool. @@ -32,7 +22,7 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho */ public readonly uploadTestTask: Task; - constructor(project: PythonProject, options: PoetryOptions) { + constructor(project: PythonProject, options: PythonPackagingOptions) { super(project); this.installTask = project.addTask('install', { @@ -67,15 +57,19 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho new PoetryPyproject(project, { name: project.name, + version: options.version, + description: options.description, + license: options.license, + authors: [`${options.authorName} <${options.authorEmail}>`], + homepage: options.homepage, + classifiers: options.classifiers, + ...options.poetryOptions, dependencies: () => this.synthDependencies(), devDependencies: () => this.synthDevDependencies(), - scripts: {}, - ...options.pyprojectConfig, }); new TomlFile(project, 'poetry.toml', { committed: false, - readonly: false, // let user manage set local options through `poetry config` obj: { repositories: { testpypi: { @@ -152,7 +146,7 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho } -export interface PoetryPyprojectOptions { +export interface PoetryPyprojectOptionsWithoutDeps { /** * Name of the package (required). */ @@ -236,6 +230,13 @@ export interface PoetryPyprojectOptions { */ readonly exclude?: string[]; + /** + * The scripts or executables that will be installed when installing the package. + */ + readonly scripts?: { [key: string]: any }; +} + +export interface PoetryPyprojectOptions extends PoetryPyprojectOptionsWithoutDeps { /** * A list of dependencies for the project. * @@ -251,11 +252,6 @@ export interface PoetryPyprojectOptions { * @example { requests: "^2.13.0" } */ readonly devDependencies?: { [key: string]: any }; - - /** - * The scripts or executables that will be installed when installing the package. - */ - readonly scripts?: { [key: string]: any }; } /** diff --git a/src/python/python-packaging.ts b/src/python/python-packaging.ts index dc493c9ba10..4566c614e6a 100644 --- a/src/python/python-packaging.ts +++ b/src/python/python-packaging.ts @@ -1,4 +1,5 @@ import { Task } from '../tasks'; +import { PoetryPyprojectOptionsWithoutDeps } from './poetry'; export interface IPythonPackaging { /** @@ -11,3 +12,58 @@ export interface IPythonPackaging { */ readonly uploadTask: Task; } + +export interface PythonPackagingOptions { + /** + * Author's name + * + * @default $GIT_USER_NAME + */ + readonly authorName: string; + + /** + * Author's e-mail + * + * @default $GIT_USER_EMAIL + */ + readonly authorEmail: string; + + /** + * Version of the package. + * + * @default "0.1.0" + */ + readonly version: string; + + /** + * A short description of the package. + */ + readonly description?: string; + + /** + * License of this package as an SPDX identifier. + */ + readonly license?: string; + + /** + * A URL to the website of the project. + */ + readonly homepage?: string; + + /** + * A list of PyPI trove classifiers that describe the project. + * + * @see https://pypi.org/classifiers/ + */ + readonly classifiers?: string[]; + + /** + * Additional fields to pass in the setup() function if using setuptools + */ + readonly setupConfig?: { [key: string]: any }; + + /** + * Additional options to set for poetry if using poetry + */ + readonly poetryOptions?: PoetryPyprojectOptionsWithoutDeps; +} diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 16bce4ca809..7b5a15fed6f 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -1,12 +1,12 @@ import { Project, ProjectOptions, ProjectType } from '../project'; -import { Pip, PipOptions } from './pip'; -import { Poetry, PoetryOptions } from './poetry'; +import { Pip } from './pip'; +import { Poetry } from './poetry'; import { Pytest, PytestOptions } from './pytest'; import { IPythonDeps } from './python-deps'; import { IPythonEnv } from './python-env'; -import { IPythonPackaging } from './python-packaging'; +import { IPythonPackaging, PythonPackagingOptions } from './python-packaging'; import { PythonSample } from './python-sample'; -import { Setuptools, SetuptoolsOptions } from './setuptools'; +import { Setuptools } from './setuptools'; import { Venv, VenvOptions } from './venv'; @@ -16,7 +16,7 @@ const PYTHON_PROJECT_NAME_REGEX = /^[A-Za-z0-9-_\.]+$/; /** * Options for `PythonProject`. */ -export interface PythonProjectOptions extends ProjectOptions { +export interface PythonProjectOptions extends ProjectOptions, PythonPackagingOptions { // -- required options -- /** @@ -28,50 +28,6 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly moduleName: string; - /** - * Author's name - * - * @default $GIT_USER_NAME - */ - readonly authorName: string; - - /** - * Author's e-mail - * - * @default $GIT_USER_EMAIL - */ - readonly authorEmail: string; - - /** - * Manually specify package version - * @default "0.1.0" - */ - readonly version: string; - - // -- general information -- - - /** - * A short project description - */ - readonly description?: string; - - /** - * The project license - */ - readonly license?: string; - - /** - * The project's homepage / website - */ - readonly homepage?: string; - - /** - * A list of PyPI trove classifiers that describe the project. - * - * @see https://pypi.org/classifiers/ - */ - readonly classifiers?: string[]; - // -- dependencies -- /** @@ -105,12 +61,6 @@ export interface PythonProjectOptions extends ProjectOptions { */ readonly pip?: boolean; - /** - * Pip options - * @default - defaults - */ - readonly pipOptions?: PipOptions; - /** * Use venv to manage a virtual environment for installing dependencies inside. * @@ -125,32 +75,20 @@ export interface PythonProjectOptions extends ProjectOptions { readonly venvOptions?: VenvOptions; /** - * Use setuptools with a setup.py script for packaging and distribution. + * Use setuptools with a setup.py script for packaging and publishing. * * @default - true if the project type is library */ readonly setuptools?: boolean; - /** - * Setuptools options - * @default - defaults - */ - readonly setuptoolsOptions?: SetuptoolsOptions; - /** * Use poetry to manage your project dependencies, virtual environment, and - * (optional) packaging. + * (optional) packaging/publishing. * * @default false */ readonly poetry?: boolean; - /** - * Poetry options - * @default - defaults - */ - readonly poetryOptions?: PoetryOptions; - // -- optional components -- /** @@ -220,26 +158,23 @@ export class PythonProject extends Project { this.version = options.version; if (options.venv ?? true) { - this.envManager = new Venv(this, options.venvOptions); + this.envManager = new Venv(this); } if (options.pip ?? true) { - this.depsManager = new Pip(this, options.pipOptions); + this.depsManager = new Pip(this); } if (options.setuptools ?? (this.projectType === ProjectType.LIB)) { this.packagingManager = new Setuptools(this, { - ...options.setuptoolsOptions, - setupConfig: { - authorName: options.authorName, - authorEmail: options.authorEmail, - version: options.version, - description: options.description, - license: options.license, - homepage: options.homepage, - classifiers: options.classifiers, - ...options.setuptoolsOptions?.setupConfig, - }, + version: options.version, + description: options.description, + authorName: options.authorName, + authorEmail: options.authorEmail, + license: options.license, + homepage: options.homepage, + classifiers: options.classifiers, + setupConfig: options.setupConfig, }); } @@ -255,17 +190,16 @@ export class PythonProject extends Project { if (options.poetry ?? false) { const poetry = new Poetry(this, { - ...options.poetryOptions, - pyprojectConfig: { - name: options.name, - version: this.version, - description: options.description ?? '', - license: options.license, - authors: [`${options.authorName} <${options.authorEmail}>`], + version: options.version, + description: options.description, + authorName: options.authorName, + authorEmail: options.authorEmail, + license: options.license, + homepage: options.homepage, + classifiers: options.classifiers, + poetryOptions: { readme: options.readme?.filename ?? 'README.md', - homepage: options.homepage, - classifiers: options.classifiers, - ...options.poetryOptions?.pyprojectConfig, + ...options.poetryOptions, }, }); this.depsManager = poetry; diff --git a/src/python/setuppy.ts b/src/python/setuppy.ts index 47addfbb2ae..8eb10e7cd3a 100644 --- a/src/python/setuppy.ts +++ b/src/python/setuppy.ts @@ -6,7 +6,7 @@ import { PythonProject } from './python-project'; * * @see https://docs.python.org/3/distutils/setupscript.html */ -export interface SetupPyConfigOptions { +export interface SetupPyOptions { /** * Name of the package */ @@ -60,13 +60,6 @@ export interface SetupPyConfigOptions { readonly [name: string]: any; } -export interface SetupPyOptions { - /** - * Fields to pass in the setup() function - */ - readonly setupConfig?: SetupPyConfigOptions; -} - /** * Python packaging script where package metadata can be placed. */ @@ -88,7 +81,7 @@ export class SetupPy extends FileBase { 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ], - ...options.setupConfig ? this.renameFields(options.setupConfig) : [], + ...options ? this.renameFields(options) : [], }; } @@ -112,7 +105,7 @@ export class SetupPy extends FileBase { } // modify some key names since JSII interfaces require fields to be camelCase - private renameFields(options: SetupPyConfigOptions): any { + private renameFields(options: SetupPyOptions): any { const obj: { [key: string]: any } = {}; for (const [key, value] of Object.entries(options)) { if (key === 'authorName') { diff --git a/src/python/setuptools.ts b/src/python/setuptools.ts index d6d8e2e1aff..e58c78620dd 100644 --- a/src/python/setuptools.ts +++ b/src/python/setuptools.ts @@ -1,12 +1,8 @@ import { Component } from '../component'; import { Task, TaskCategory } from '../tasks'; +import { PythonPackagingOptions } from './python-packaging'; import { PythonProject } from './python-project'; -import { SetupPy, SetupPyOptions } from './setuppy'; - -/** - * Options for setuptools - */ -export interface SetuptoolsOptions extends SetupPyOptions {} +import { SetupPy } from './setuppy'; /** * Manages packaging through setuptools with a setup.py script. @@ -20,7 +16,7 @@ export class Setuptools extends Component { */ public readonly uploadTestTask: Task; - constructor(project: PythonProject, options: SetuptoolsOptions) { + constructor(project: PythonProject, options: PythonPackagingOptions) { super(project); project.addDevDependency('wheel@0.36.2'); @@ -29,7 +25,7 @@ export class Setuptools extends Component { this.packageTask = project.addTask('package', { description: 'Creates source archive and wheel for distribution.', category: TaskCategory.RELEASE, - exec: 'rm -fr dist/* && python setup.py sdist bdist_wheel', + exec: 'python setup.py sdist bdist_wheel', }); this.uploadTestTask = project.addTask('upload:test', { @@ -44,6 +40,17 @@ export class Setuptools extends Component { exec: 'twine upload dist/*', }); - new SetupPy(project, options); + new SetupPy(project, { + name: project.name, + packages: [project.moduleName], + authorName: options.authorName, + authorEmail: options.authorEmail, + version: options.version, + description: options.description, + license: options.license, + homepage: options.homepage, + classifiers: options.classifiers, + ...options.setupConfig, + }); } } \ No newline at end of file From d33ae44af363ef1fe97bcdc9d26ad4096f0eed11 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Wed, 3 Feb 2021 03:41:56 -0500 Subject: [PATCH 23/29] fix docs --- docs/python.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/python.md b/docs/python.md index e32ffc98eb8..a9bb5223377 100644 --- a/docs/python.md +++ b/docs/python.md @@ -74,9 +74,6 @@ const project = new python.PythonProject({ venv: false, setuptools: false, poetry: true, - poetryOptions: { - ... - }, }); ``` From 39cbbcc69b015926e3dee20f568fb5d2cc00bf9d Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Wed, 3 Feb 2021 03:43:40 -0500 Subject: [PATCH 24/29] revert change to new.test.ts --- src/__tests__/new.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/__tests__/new.test.ts b/src/__tests__/new.test.ts index 5429fec0a1e..5abd01fc3ea 100644 --- a/src/__tests__/new.test.ts +++ b/src/__tests__/new.test.ts @@ -14,11 +14,7 @@ for (const type of inventory.discover()) { const projectdir = createProjectDir(outdir); // execute `projen new PJID --no-synth` in the project directory - if (type.pjid.includes('python')) { - execProjenCLI(projectdir, ['new', '--no-synth', '--python-path=/usr/bin/python', type.pjid]); - } else { - execProjenCLI(projectdir, ['new', '--no-synth', type.pjid]); - } + execProjenCLI(projectdir, ['new', '--no-synth', type.pjid]); // compare generated .projenrc.js to the snapshot const projenrc = readFileSync(join(projectdir, PROJEN_RC), 'utf-8'); From 16ace94f660d147ea879e6a0d9e458e6cb7345ae Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Thu, 4 Feb 2021 01:23:32 -0500 Subject: [PATCH 25/29] resolve comments --- API.md | 15 ++++++++------- src/cli/macros.ts | 8 -------- src/python/poetry.ts | 8 ++++---- src/python/python-packaging.ts | 4 ++-- src/python/setuptools.ts | 12 ++++++------ 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/API.md b/API.md index fda9ddf308f..71c0602d2b9 100644 --- a/API.md +++ b/API.md @@ -4966,8 +4966,8 @@ Name | Type | Description -----|------|------------- **installTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that installs and updates dependencies. **packageTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that packages the project for distribution. -**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package package repository. -**uploadTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. +**publishTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package repository. +**publishTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. ### Methods @@ -5345,6 +5345,7 @@ __Returns__: Manages packaging through setuptools with a setup.py script. +__Implements__: [python.IPythonPackaging](#projen-python-ipythonpackaging) __Submodule__: python __Extends__: [Component](#projen-component) @@ -5377,9 +5378,9 @@ new python.Setuptools(project: PythonProject, options: PythonPackagingOptions) Name | Type | Description -----|------|------------- -**packageTask**🔹 | [tasks.Task](#projen-tasks-task) | -**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | -**uploadTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. +**packageTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that packages the project for distribution. +**publishTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package repository. +**publishTestTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to the Test PyPI repository. @@ -9123,7 +9124,7 @@ setupEnvironment(): void ## interface IPythonPackaging 🔹 -__Implemented by__: [python.Poetry](#projen-python-poetry) +__Implemented by__: [python.Poetry](#projen-python-poetry), [python.Setuptools](#projen-python-setuptools) @@ -9133,7 +9134,7 @@ __Implemented by__: [python.Poetry](#projen-python-poetry) Name | Type | Description -----|------|------------- **packageTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that packages the project for distribution. -**uploadTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package package repository. +**publishTask**🔹 | [tasks.Task](#projen-tasks-task) | A task that uploads the package to a package repository. diff --git a/src/cli/macros.ts b/src/cli/macros.ts index ee056c0235a..c0e344f82a8 100644 --- a/src/cli/macros.ts +++ b/src/cli/macros.ts @@ -1,4 +1,3 @@ -import * as os from 'os'; import * as path from 'path'; import { execOrUndefined, formatAsPythonModule } from '../util'; @@ -19,7 +18,6 @@ export function tryProcessMacro(macro: string) { case '$GIT_USER_NAME': return getFromGitConfig('user.name') ?? 'user'; case '$GIT_USER_EMAIL': return resolveEmail(); - case '$PYTHON_PATH': return resolvePython(); case '$PYTHON_MODULE_NAME': return formatAsPythonModule(basedir); } @@ -38,9 +36,3 @@ function getFromGitConfig(key: string): string | undefined { function resolveEmail(): string { return getFromGitConfig('user.email') ?? 'user@domain.com'; } - -function resolvePython(): string { - const command = os.platform() === 'win32' ? '(Get-Command python).Path' : 'which python'; - const defaultValue = os.platform() === 'win32' ? 'C:\\Python36\\python.exe' : '/usr/bin/python'; - return execOrUndefined(command) ?? defaultValue; -} diff --git a/src/python/poetry.ts b/src/python/poetry.ts index aeeba232595..d3ec3edea5d 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -15,12 +15,12 @@ import { PythonProject } from './python-project'; export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPythonPackaging { public readonly installTask: Task; public readonly packageTask: Task; - public readonly uploadTask: Task; + public readonly publishTask: Task; /** * A task that uploads the package to the Test PyPI repository. */ - public readonly uploadTestTask: Task; + public readonly publishTestTask: Task; constructor(project: PythonProject, options: PythonPackagingOptions) { super(project); @@ -43,13 +43,13 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho exec: 'poetry build', }); - this.uploadTestTask = project.addTask('upload:test', { + this.publishTestTask = project.addTask('publish:test', { description: 'Uploads the package against a test PyPI endpoint.', category: TaskCategory.RELEASE, exec: 'poetry publish -r testpypi', }); - this.uploadTask = project.addTask('upload', { + this.publishTask = project.addTask('publish', { description: 'Uploads the package to PyPI.', category: TaskCategory.RELEASE, exec: 'poetry publish', diff --git a/src/python/python-packaging.ts b/src/python/python-packaging.ts index 4566c614e6a..014ca86afe5 100644 --- a/src/python/python-packaging.ts +++ b/src/python/python-packaging.ts @@ -8,9 +8,9 @@ export interface IPythonPackaging { readonly packageTask: Task; /** - * A task that uploads the package to a package package repository. + * A task that uploads the package to a package repository. */ - readonly uploadTask: Task; + readonly publishTask: Task; } export interface PythonPackagingOptions { diff --git a/src/python/setuptools.ts b/src/python/setuptools.ts index e58c78620dd..1b3b75716f9 100644 --- a/src/python/setuptools.ts +++ b/src/python/setuptools.ts @@ -1,20 +1,20 @@ import { Component } from '../component'; import { Task, TaskCategory } from '../tasks'; -import { PythonPackagingOptions } from './python-packaging'; +import { IPythonPackaging, PythonPackagingOptions } from './python-packaging'; import { PythonProject } from './python-project'; import { SetupPy } from './setuppy'; /** * Manages packaging through setuptools with a setup.py script. */ -export class Setuptools extends Component { +export class Setuptools extends Component implements IPythonPackaging { public readonly packageTask: Task; - public readonly uploadTask: Task; + public readonly publishTask: Task; /** * A task that uploads the package to the Test PyPI repository. */ - public readonly uploadTestTask: Task; + public readonly publishTestTask: Task; constructor(project: PythonProject, options: PythonPackagingOptions) { super(project); @@ -28,13 +28,13 @@ export class Setuptools extends Component { exec: 'python setup.py sdist bdist_wheel', }); - this.uploadTestTask = project.addTask('upload:test', { + this.publishTestTask = project.addTask('publish:test', { description: 'Uploads the package against a test PyPI endpoint.', category: TaskCategory.RELEASE, exec: 'twine upload --repository-url https://test.pypi.org/legacy/ dist/*', }); - this.uploadTask = project.addTask('upload', { + this.publishTask = project.addTask('publish', { description: 'Uploads the package against a test PyPI endpoint.', category: TaskCategory.RELEASE, exec: 'twine upload dist/*', From 6fd989921d25edc66b8644fc7699df41ee606de5 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Thu, 4 Feb 2021 01:33:11 -0500 Subject: [PATCH 26/29] add a pytest option as an example --- API.md | 4 +++- src/__tests__/python/python-project.test.ts | 10 ++++++++++ src/python/pytest.ts | 12 ++++++++++-- src/python/python-project.ts | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/API.md b/API.md index 71c0602d2b9..e2fe925ae98 100644 --- a/API.md +++ b/API.md @@ -5087,11 +5087,12 @@ __Extends__: [Component](#projen-component) ```ts -new python.Pytest(project: PythonProject, options: PytestOptions) +new python.Pytest(project: PythonProject, options?: PytestOptions) ``` * **project** ([python.PythonProject](#projen-python-pythonproject)) *No description* * **options** ([python.PytestOptions](#projen-python-pytestoptions)) *No description* + * **maxFailures** (number) Stop the testing process after the first N failures. __*Optional*__ * **testdir** (string) Directory with tests. __*Default*__: 'tests' * **version** (string) Pytest version. __*Default*__: "6.2.1" @@ -9211,6 +9212,7 @@ Name | Type | Description Name | Type | Description -----|------|------------- +**maxFailures**?🔹 | number | Stop the testing process after the first N failures.
__*Optional*__ **testdir**?🔹 | string | Directory with tests.
__*Default*__: 'tests' **version**?🔹 | string | Pytest version.
__*Default*__: "6.2.1" diff --git a/src/__tests__/python/python-project.test.ts b/src/__tests__/python/python-project.test.ts index 64997fc8a2c..42781849566 100644 --- a/src/__tests__/python/python-project.test.ts +++ b/src/__tests__/python/python-project.test.ts @@ -36,6 +36,16 @@ test('no pytest', () => { expect(synthSnapshot(p)).toMatchSnapshot(); }); +test('pytest maxfailures', () => { + const p = new TestPythonProject({ + pytestOptions: { + maxFailures: 3, + }, + }); + + expect(synthSnapshot(p)['.projen/tasks.json'].tasks.test.steps[0].exec).toContain('--maxfail=3'); +}); + class TestPythonProject extends PythonProject { constructor(options: Partial = { }) { super({ diff --git a/src/python/pytest.ts b/src/python/pytest.ts index 34adc1f5f44..2990a5a4062 100644 --- a/src/python/pytest.ts +++ b/src/python/pytest.ts @@ -17,12 +17,17 @@ export interface PytestOptions { * @default 'tests' */ readonly testdir?: string; + + /** + * Stop the testing process after the first N failures + */ + readonly maxFailures?: number; } export class Pytest extends Component { public readonly testTask: Task; - constructor(project: PythonProject, options: PytestOptions) { + constructor(project: PythonProject, options: PytestOptions = {}) { super(project); const version = options.version ?? '6.2.1'; @@ -32,7 +37,10 @@ export class Pytest extends Component { this.testTask = project.addTask('test', { description: 'Runs tests', category: TaskCategory.TEST, - exec: 'pytest', + exec: [ + 'pytest', + ...(options.maxFailures ? [`--maxfail=${options.maxFailures}`] : []), + ].join(' '), }); new SampleDir(project, 'tests', { diff --git a/src/python/python-project.ts b/src/python/python-project.ts index 7b5a15fed6f..db6839a18db 100644 --- a/src/python/python-project.ts +++ b/src/python/python-project.ts @@ -232,7 +232,7 @@ export class PythonProject extends Project { } if (options.pytest ?? true) { - this.pytest = new Pytest(this, {}); + this.pytest = new Pytest(this, options.pytestOptions); } if (options.sample ?? true) { From f327f52aa4d61ba15a270d06f3ceb1619a65d880 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Thu, 4 Feb 2021 01:52:04 -0500 Subject: [PATCH 27/29] bug fix so users don't have to specify description --- src/python/poetry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python/poetry.ts b/src/python/poetry.ts index d3ec3edea5d..f9def2571a6 100644 --- a/src/python/poetry.ts +++ b/src/python/poetry.ts @@ -58,7 +58,7 @@ export class Poetry extends Component implements IPythonDeps, IPythonEnv, IPytho new PoetryPyproject(project, { name: project.name, version: options.version, - description: options.description, + description: options.description ?? '', license: options.license, authors: [`${options.authorName} <${options.authorEmail}>`], homepage: options.homepage, From 2a0bd5b5950591c92d8411089df07c80109d30b2 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Thu, 4 Feb 2021 01:56:59 -0500 Subject: [PATCH 28/29] remove references to pythonpath --- docs/python.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/python.md b/docs/python.md index a9bb5223377..9a4be1dea68 100644 --- a/docs/python.md +++ b/docs/python.md @@ -1,14 +1,14 @@ # Python Projects Before creating a new project, make sure you have the version of Python you want -to use set up in your terminal. If running `which python` on UNIX/macOS or -`Get-Command python` on Windows does not print the path to the python version -you want to use, you can specify the right path when setting up the project. +to use set up in your terminal. Running `which python` on UNIX/macOS or +`Get-Command python` on Windows should print the path to the python version you +want to use. To create a new Python project, use `projen new python`: ```shell -$ projen new python --name=my-project [--python-path=/usr/bin/python] +$ projen new python --name=my-project ``` This will synthesize a standard project directory structure with some sample From c8fbe2702d48f8c447f3b5830ced41365ed9e246 Mon Sep 17 00:00:00 2001 From: Chriscbr Date: Thu, 4 Feb 2021 02:02:39 -0500 Subject: [PATCH 29/29] more docs edits --- docs/python.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/python.md b/docs/python.md index 9a4be1dea68..f8543d084fb 100644 --- a/docs/python.md +++ b/docs/python.md @@ -25,7 +25,7 @@ code. ``` The default options will setup a Python environment using `venv`, and will -create a `requirements.txt` file for installing dependencies via pip. +create a `requirements.txt` file for installing dependencies via `pip`. The `projen new` command will also generate a `.projenrc.js` file which includes the definition of your project with any options you specified in the command @@ -37,7 +37,7 @@ const { python } = require('projen'); const project = new python.PythonProject({ jsiiFqn: "projen.python.PythonProject", name: 'my-project', - pythonPath: '/usr/bin/python', + moduleName: 'my_project', }); project.synth(); @@ -61,12 +61,21 @@ requirements. See the list below: - pip: dependency manager - venv: environment manager +- setuptools: packaging manager - poetry: dependency, environment, and packaging manager - pipenv (TBD): dependency and environment manager -- setuptools (TBD): packaging manager +- conda (TBD): dependency and environment manager -By default, pip, venv, and setuptools will be used. But these can be swapped out -as needed by using the provided flags, for example: +By default, pip, and venv will be used, along with setuptools if the project is a library: + +```js +const project = new python.PythonProject({ + ... + projectType: ProjectType.LIB +}); +``` + +But these can be swapped out as needed by using the provided flags, for example: ```js const project = new python.PythonProject({