Skip to content

Commit

Permalink
feat(nx-python): add support for [email protected]
Browse files Browse the repository at this point in the history
In the [email protected] the requirements.txt format has changed for local dependencies
from `<packageName> @ file://<location>` to `-e file:///`

re #206
  • Loading branch information
lucasvieirasilva committed May 14, 2024
1 parent cc94090 commit 038b936
Show file tree
Hide file tree
Showing 6 changed files with 416 additions and 32 deletions.
2 changes: 1 addition & 1 deletion packages/nx-python/src/dependency/update-dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function updateDependencyTree(context: ExecutorContext) {
);

runPoetry(['lock', '--no-update']);
runPoetry(['install']);
runPoetry(['install', '--no-root']);
}
}
}
Expand Down
26 changes: 18 additions & 8 deletions packages/nx-python/src/executors/add/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1060,10 +1060,15 @@ version = "1.0.0"
stdio: 'inherit',
},
);
expect(spawn.sync).toHaveBeenNthCalledWith(3, 'poetry', ['install'], {
shell: false,
stdio: 'inherit',
});
expect(spawn.sync).toHaveBeenNthCalledWith(
3,
'poetry',
['install', '--no-root'],
{
shell: false,
stdio: 'inherit',
},
);
expect(output.success).toBe(true);
});

Expand Down Expand Up @@ -1138,10 +1143,15 @@ version = "1.0.0"
stdio: 'inherit',
},
);
expect(spawn.sync).toHaveBeenNthCalledWith(3, 'poetry', ['install'], {
shell: false,
stdio: 'inherit',
});
expect(spawn.sync).toHaveBeenNthCalledWith(
3,
'poetry',
['install', '--no-root'],
{
shell: false,
stdio: 'inherit',
},
);
expect(output.success).toBe(true);
});
});
332 changes: 332 additions & 0 deletions packages/nx-python/src/executors/build/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,338 @@ describe('Build Executor', () => {
expect(output.success).toBe(true);
});

it('should build python project with local dependencies [email protected]', async () => {
fsMock({
'apps/app/.venv/pyvenv.cfg': 'fake',
'apps/app/app/index.py': 'print("Hello from app")',
'apps/app/poetry.lock': dedent`
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "dep1"
version = "1.0.0"
description = "Dep1"
category = "main"
optional = false
python-versions = "^3.8"
develop = false
[package.dependencies]
numpy = "1.21.0"
[package.source]
type = "directory"
url = "../../libs/dep1"
[[package]]
name = "numpy"
version = "1.21.0"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.7"
`,

'apps/app/pyproject.toml': dedent`
[tool.poetry]
name = "app"
version = "1.0.0"
[[tool.poetry.packages]]
include = "app"
[tool.poetry.dependencies]
python = "^3.8"
click = "7.1.2"
dep1 = { path = "../../libs/dep1" }
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,

'libs/dep1/dep1/index.py': 'print("Hello from dep1")',
'libs/dep1/pyproject.toml': dedent`
[tool.poetry]
name = "dep1"
version = "1.0.0"
[[tool.poetry.packages]]
include = "dep1"
[tool.poetry.dependencies]
python = "^3.8"
numpy = "1.21.0"
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,

'libs/dep2/dep2/index.py': 'print("Hello from dep2")',
'libs/dep2/pyproject.toml': dedent`
[tool.poetry]
name = "dep2"
version = "1.0.0"
[[tool.poetry.packages]]
include = "dep2"
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,
});

vi.mocked(spawn.sync).mockImplementation((_, args, opts) => {
if (args[0] == 'build') {
spawnBuildMockImpl(opts);
} else if (args[0] == 'export' && opts.cwd === 'apps/app') {
writeFileSync(
join(buildPath, 'requirements.txt'),
dedent`
click==7.1.2
-e file://${process.cwd()}/libs/dep1
numpy==1.21.0; python_version >= "3.8" and python_version < "4.0"
`,
);
}
return {
status: 0,
output: [''],
pid: 0,
signal: null,
stderr: null,
stdout: null,
};
});

const options: BuildExecutorSchema = {
ignorePaths: ['.venv', '.tox', 'tests/'],
silent: false,
outputPath: 'dist/apps/app',
keepBuildFolder: true,
devDependencies: false,
lockedVersions: true,
bundleLocalDependencies: true,
};

const output = await executor(options, {
cwd: '',
root: '.',
isVerbose: false,
projectName: 'app',
workspace: {
version: 2,
projects: {
app: {
root: 'apps/app',
targets: {},
},
dep1: {
root: 'libs/dep1',
targets: {},
},
dep2: {
root: 'libs/dep2',
targets: {},
},
},
},
});

expect(checkPoetryExecutableMock).toHaveBeenCalled();
expect(activateVenvMock).toHaveBeenCalledWith('.');
expect(existsSync(buildPath)).toBeTruthy();
expect(existsSync(`${buildPath}/app`)).toBeTruthy();
expect(existsSync(`${buildPath}/dep1`)).toBeTruthy();
expect(existsSync(`${buildPath}/dist/app.fake`)).toBeTruthy();
expect(spawn.sync).toHaveBeenCalledWith('poetry', ['build'], {
cwd: buildPath,
shell: false,
stdio: 'inherit',
});

const projectTomlData = parse(
readFileSync(`${buildPath}/pyproject.toml`).toString('utf-8'),
) as PyprojectToml;

expect(projectTomlData.tool.poetry.packages).toStrictEqual([
{
include: 'app',
},
{
include: 'dep1',
},
]);

expect(projectTomlData.tool.poetry.dependencies).toStrictEqual({
python: '^3.8',
click: '7.1.2',
numpy: {
version: '1.21.0',
optional: false,
markers: 'python_version >= "3.8" and python_version < "4.0"',
},
});
expect(projectTomlData.tool.poetry.group.dev.dependencies).toStrictEqual(
{},
);

expect(output.success).toBe(true);
});

it('should throw an exception when [email protected] local project is not a valid poetry project', async () => {
fsMock({
'apps/app/.venv/pyvenv.cfg': 'fake',
'apps/app/app/index.py': 'print("Hello from app")',
'apps/app/poetry.lock': dedent`
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "dep1"
version = "1.0.0"
description = "Dep1"
category = "main"
optional = false
python-versions = "^3.8"
develop = false
[package.dependencies]
numpy = "1.21.0"
[package.source]
type = "directory"
url = "../../libs/dep1"
[[package]]
name = "numpy"
version = "1.21.0"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
python-versions = ">=3.7"
`,

'apps/app/pyproject.toml': dedent`
[tool.poetry]
name = "app"
version = "1.0.0"
[[tool.poetry.packages]]
include = "app"
[tool.poetry.dependencies]
python = "^3.8"
click = "7.1.2"
dep1 = { path = "../../libs/dep1" }
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,

'libs/dep1/dep1/index.py': 'print("Hello from dep1")',
'libs/dep1/pyproject.toml': dedent`
[tool.poetry]
name = "dep1"
version = "1.0.0"
[[tool.poetry.packages]]
include = "dep1"
[tool.poetry.dependencies]
python = "^3.8"
numpy = "1.21.0"
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,

'libs/dep2/dep2/index.py': 'print("Hello from dep2")',
'libs/dep2/pyproject.toml': dedent`
[tool.poetry]
name = "dep2"
version = "1.0.0"
[[tool.poetry.packages]]
include = "dep2"
[tool.poetry.dependencies]
python = "^3.8"
[tool.poetry.group.dev.dependencies]
pytest = "6.2.4"
`,
});

vi.mocked(spawn.sync).mockImplementation((_, args, opts) => {
if (args[0] == 'build') {
spawnBuildMockImpl(opts);
} else if (args[0] == 'export' && opts.cwd === 'apps/app') {
writeFileSync(
join(buildPath, 'requirements.txt'),
dedent`
click==7.1.2
-e file://${process.cwd()}/libs/dep10
numpy==1.21.0; python_version >= "3.8" and python_version < "4.0"
`,
);
}
return {
status: 0,
output: [''],
pid: 0,
signal: null,
stderr: null,
stdout: null,
};
});

const options: BuildExecutorSchema = {
ignorePaths: ['.venv', '.tox', 'tests/'],
silent: false,
outputPath: 'dist/apps/app',
keepBuildFolder: true,
devDependencies: false,
lockedVersions: true,
bundleLocalDependencies: true,
};

const output = await executor(options, {
cwd: '',
root: '.',
isVerbose: false,
projectName: 'app',
workspace: {
version: 2,
projects: {
app: {
root: 'apps/app',
targets: {},
},
dep1: {
root: 'libs/dep1',
targets: {},
},
dep2: {
root: 'libs/dep2',
targets: {},
},
},
},
});

expect(output.success).toBe(false);
});

it('should build python project with git dependency with revision and markers', async () => {
fsMock({
'apps/app/.venv/pyvenv.cfg': 'fake',
Expand Down
Loading

0 comments on commit 038b936

Please sign in to comment.