Skip to content

Commit 5b087e7

Browse files
committed
fix: poetry execution bug fixes
1 parent 343160a commit 5b087e7

File tree

5 files changed

+254
-92
lines changed

5 files changed

+254
-92
lines changed

src/managers/common/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,28 @@ export function mergePackages(common: Installable[], installed: string[]): Insta
8787
export function pathForGitBash(binPath: string): string {
8888
return isWindows() ? binPath.replace(/\\/g, '/').replace(/^([a-zA-Z]):/, '/$1') : binPath;
8989
}
90+
91+
/**
92+
* Compares two semantic version strings. Support sonly simple 1.1.1 style versions.
93+
* @param version1 First version
94+
* @param version2 Second version
95+
* @returns -1 if version1 < version2, 0 if equal, 1 if version1 > version2
96+
*/
97+
export function compareVersions(version1: string, version2: string): number {
98+
const v1Parts = version1.split('.').map(Number);
99+
const v2Parts = version2.split('.').map(Number);
100+
101+
for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
102+
const v1Part = v1Parts[i] || 0;
103+
const v2Part = v2Parts[i] || 0;
104+
105+
if (v1Part > v2Part) {
106+
return 1;
107+
}
108+
if (v1Part < v2Part) {
109+
return -1;
110+
}
111+
}
112+
113+
return 0;
114+
}

src/managers/poetry/main.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { Disposable, LogOutputChannel } from 'vscode';
1+
import { Disposable, l10n, LogOutputChannel } from 'vscode';
22
import { PythonEnvironmentApi } from '../../api';
33
import { traceInfo } from '../../common/logging';
4+
import { showErrorMessage } from '../../common/window.apis';
45
import { getPythonApi } from '../../features/pythonApi';
56
import { NativePythonFinder } from '../common/nativePythonFinder';
7+
import { compareVersions } from '../common/utils';
68
import { PoetryManager } from './poetryManager';
79
import { PoetryPackageManager } from './poetryPackageManager';
8-
import { getPoetry } from './poetryUtils';
10+
import { getPoetry, getPoetryVersion, isPoetryShellPluginInstalled } from './poetryUtils';
911

1012
export async function registerPoetryFeatures(
1113
nativeFinder: NativePythonFinder,
@@ -15,16 +17,35 @@ export async function registerPoetryFeatures(
1517
const api: PythonEnvironmentApi = await getPythonApi();
1618

1719
try {
18-
await getPoetry(nativeFinder);
20+
const poetryPath = await getPoetry(nativeFinder);
21+
let shellSupported = true;
22+
if (poetryPath) {
23+
const version = await getPoetryVersion(poetryPath);
24+
if (!version) {
25+
showErrorMessage(l10n.t('Poetry version could not be determined.'));
26+
return;
27+
}
28+
if (version && compareVersions(version, '2.0.0') >= 0) {
29+
shellSupported = await isPoetryShellPluginInstalled(poetryPath);
30+
if (!shellSupported) {
31+
showErrorMessage(
32+
l10n.t(
33+
'Poetry 2.0.0+ detected. The `shell` command is not available by default. Please install the shell plugin to enable shell activation. See [here](https://python-poetry.org/docs/managing-environments/#activating-the-environment), shell [plugin](https://github.com/python-poetry/poetry-plugin-shell)',
34+
),
35+
);
36+
return;
37+
}
38+
}
39+
}
1940

2041
const envManager = new PoetryManager(nativeFinder, api);
2142
const pkgManager = new PoetryPackageManager(api, outputChannel, envManager);
22-
43+
2344
disposables.push(
24-
envManager,
45+
envManager,
2546
pkgManager,
2647
api.registerEnvironmentManager(envManager),
27-
api.registerPackageManager(pkgManager)
48+
api.registerPackageManager(pkgManager),
2849
);
2950
} catch (ex) {
3051
traceInfo('Poetry not found, turning off poetry features.', ex);

src/managers/poetry/poetryManager.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
clearPoetryCache,
2626
getPoetryForGlobal,
2727
getPoetryForWorkspace,
28-
POETRY_ENVIRONMENTS,
28+
POETRY_GLOBAL,
2929
refreshPoetry,
3030
resolvePoetryPath,
3131
setPoetryForGlobal,
@@ -98,7 +98,7 @@ export class PoetryManager implements EnvironmentManager, Disposable {
9898

9999
if (scope === 'global') {
100100
return this.collection.filter((env) => {
101-
return env.group === POETRY_ENVIRONMENTS;
101+
return env.group === POETRY_GLOBAL;
102102
});
103103
}
104104

@@ -155,7 +155,7 @@ export class PoetryManager implements EnvironmentManager, Disposable {
155155

156156
return this.globalEnv;
157157
}
158-
158+
159159
async set(scope: SetEnvironmentScope, environment?: PythonEnvironment | undefined): Promise<void> {
160160
if (scope === undefined) {
161161
await setPoetryForGlobal(environment?.environmentPath?.fsPath);
@@ -251,7 +251,7 @@ export class PoetryManager implements EnvironmentManager, Disposable {
251251
}
252252

253253
if (!this.globalEnv) {
254-
this.globalEnv = getLatest(this.collection.filter((e) => e.group === POETRY_ENVIRONMENTS));
254+
this.globalEnv = getLatest(this.collection.filter((e) => e.group === POETRY_GLOBAL));
255255
}
256256

257257
// Find any poetry environments that might be associated with the current projects

src/managers/poetry/poetryPackageManager.ts

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
MarkdownString,
99
ProgressLocation,
1010
ThemeIcon,
11-
window
1211
} from 'vscode';
1312
import { Disposable } from 'vscode-jsonrpc';
1413
import {
@@ -21,6 +20,7 @@ import {
2120
PythonEnvironment,
2221
PythonEnvironmentApi,
2322
} from '../../api';
23+
import { showErrorMessage, showInputBox, withProgress } from '../../common/window.apis';
2424
import { PoetryManager } from './poetryManager';
2525
import { getPoetry } from './poetryUtils';
2626

@@ -64,21 +64,24 @@ export class PoetryPackageManager implements PackageManager, Disposable {
6464

6565
if (toInstall.length === 0 && toUninstall.length === 0) {
6666
// Show package input UI if no packages are specified
67-
const installInput = await window.showInputBox({
67+
const installInput = await showInputBox({
6868
prompt: 'Enter packages to install (comma separated)',
69-
placeHolder: 'e.g., requests, pytest, black'
69+
placeHolder: 'e.g., requests, pytest, black',
7070
});
71-
71+
7272
if (installInput) {
73-
toInstall = installInput.split(',').map(p => p.trim()).filter(p => p.length > 0);
73+
toInstall = installInput
74+
.split(',')
75+
.map((p) => p.trim())
76+
.filter((p) => p.length > 0);
7477
}
75-
78+
7679
if (toInstall.length === 0) {
7780
return;
7881
}
7982
}
8083

81-
await window.withProgress(
84+
await withProgress(
8285
{
8386
location: ProgressLocation.Notification,
8487
title: 'Managing packages with Poetry',
@@ -87,7 +90,11 @@ export class PoetryPackageManager implements PackageManager, Disposable {
8790
async (_progress, token) => {
8891
try {
8992
const before = this.packages.get(environment.envId.id) ?? [];
90-
const after = await this.managePackages(environment, {install: toInstall, uninstall: toUninstall}, token);
93+
const after = await this.managePackages(
94+
environment,
95+
{ install: toInstall, uninstall: toUninstall },
96+
token,
97+
);
9198
const changes = getChanges(before, after);
9299
this.packages.set(environment.envId.id, after);
93100
this._onDidChangePackages.fire({ environment, manager: this, changes });
@@ -97,7 +104,7 @@ export class PoetryPackageManager implements PackageManager, Disposable {
97104
}
98105
this.log.error('Error managing packages with Poetry', e);
99106
setImmediate(async () => {
100-
const result = await window.showErrorMessage('Error managing packages with Poetry', 'View Output');
107+
const result = await showErrorMessage('Error managing packages with Poetry', 'View Output');
101108
if (result === 'View Output') {
102109
this.log.show();
103110
}
@@ -109,23 +116,34 @@ export class PoetryPackageManager implements PackageManager, Disposable {
109116
}
110117

111118
async refresh(environment: PythonEnvironment): Promise<void> {
112-
await window.withProgress(
119+
await withProgress(
113120
{
114121
location: ProgressLocation.Window,
115122
title: 'Refreshing Poetry packages',
116123
},
117124
async () => {
118-
const before = this.packages.get(environment.envId.id) ?? [];
119-
const after = await this.refreshPackages(environment);
120-
const changes = getChanges(before, after);
121-
this.packages.set(environment.envId.id, after);
122-
if (changes.length > 0) {
123-
this._onDidChangePackages.fire({ environment, manager: this, changes });
125+
try {
126+
const before = this.packages.get(environment.envId.id) ?? [];
127+
const after = await this.refreshPackages(environment);
128+
const changes = getChanges(before, after);
129+
this.packages.set(environment.envId.id, after);
130+
if (changes.length > 0) {
131+
this._onDidChangePackages.fire({ environment, manager: this, changes });
132+
}
133+
} catch (error) {
134+
this.log.error(`Failed to refresh packages: ${error}`);
135+
// Show error to user but don't break the UI
136+
setImmediate(async () => {
137+
const result = await showErrorMessage('Error refreshing Poetry packages', 'View Output');
138+
if (result === 'View Output') {
139+
this.log.show();
140+
}
141+
});
124142
}
125143
},
126144
);
127145
}
128-
146+
129147
async getPackages(environment: PythonEnvironment): Promise<Package[] | undefined> {
130148
if (!this.packages.has(environment.envId.id)) {
131149
await this.refresh(environment);
@@ -141,16 +159,14 @@ export class PoetryPackageManager implements PackageManager, Disposable {
141159
private async managePackages(
142160
environment: PythonEnvironment,
143161
options: { install?: string[]; uninstall?: string[] },
144-
token?: CancellationToken
162+
token?: CancellationToken,
145163
): Promise<Package[]> {
146-
const cwd = environment.environmentPath.fsPath;
147-
148164
// Handle uninstalls first
149165
if (options.uninstall && options.uninstall.length > 0) {
150166
try {
151167
const args = ['remove', ...options.uninstall];
152168
this.log.info(`Running: poetry ${args.join(' ')}`);
153-
const result = await runPoetry(args, cwd, this.log, token);
169+
const result = await runPoetry(args, undefined, this.log, token);
154170
this.log.info(result);
155171
} catch (err) {
156172
this.log.error(`Error removing packages with Poetry: ${err}`);
@@ -163,7 +179,7 @@ export class PoetryPackageManager implements PackageManager, Disposable {
163179
try {
164180
const args = ['add', ...options.install];
165181
this.log.info(`Running: poetry ${args.join(' ')}`);
166-
const result = await runPoetry(args, cwd, this.log, token);
182+
const result = await runPoetry(args, undefined, this.log, token);
167183
this.log.info(result);
168184
} catch (err) {
169185
this.log.error(`Error adding packages with Poetry: ${err}`);
@@ -176,12 +192,12 @@ export class PoetryPackageManager implements PackageManager, Disposable {
176192
}
177193

178194
private async refreshPackages(environment: PythonEnvironment): Promise<Package[]> {
179-
const cwd = environment.environmentPath.fsPath;
180195
const poetryPackages: { name: string; version: string; displayName: string; description: string }[] = [];
181196

182197
try {
183-
const result = await runPoetry(['show', '--no-ansi'], cwd, this.log);
184-
198+
this.log.info(`Running: ${await getPoetry()} show --no-ansi`);
199+
const result = await runPoetry(['show', '--no-ansi'], undefined, this.log);
200+
185201
// Parse poetry show output
186202
// Format: name version description
187203
const lines = result.split('\n');
@@ -199,28 +215,30 @@ export class PoetryPackageManager implements PackageManager, Disposable {
199215
}
200216
} catch (err) {
201217
this.log.error(`Error refreshing packages with Poetry: ${err}`);
202-
throw err;
218+
// Return empty array instead of throwing to avoid breaking the UI
219+
return [];
203220
}
204221

205222
// Convert to Package objects using the API
206-
return poetryPackages.map(pkg => this.api.createPackageItem(pkg, environment, this));
223+
return poetryPackages.map((pkg) => this.api.createPackageItem(pkg, environment, this));
207224
}
208225
}
209226

210227
export async function runPoetry(
211228
args: string[],
212229
cwd?: string,
213230
log?: LogOutputChannel,
214-
token?: CancellationToken
231+
token?: CancellationToken,
215232
): Promise<string> {
216233
const poetry = await getPoetry();
217234
if (!poetry) {
218235
throw new Error('Poetry executable not found');
219236
}
220-
237+
221238
log?.info(`Running: ${poetry} ${args.join(' ')}`);
239+
222240
return new Promise<string>((resolve, reject) => {
223-
const proc = ch.spawn(poetry, args, { cwd: cwd });
241+
const proc = ch.spawn(poetry, args, { cwd });
224242
token?.onCancellationRequested(() => {
225243
proc.kill();
226244
reject(new CancellationError());
@@ -239,6 +257,10 @@ export async function runPoetry(
239257
proc.on('close', () => {
240258
resolve(builder);
241259
});
260+
proc.on('error', (error) => {
261+
log?.error(`Error executing poetry command: ${error}`);
262+
reject(error);
263+
});
242264
proc.on('exit', (code) => {
243265
if (code !== 0) {
244266
reject(new Error(`Failed to run poetry ${args.join(' ')}`));

0 commit comments

Comments
 (0)