Skip to content

Commit 80161e2

Browse files
authored
feat: enhance environment creation with detailed result handling (#572)
refactoring how environment creation for venv is handled and returned to enable future improvements with error handling and resolution
1 parent 44a683d commit 80161e2

File tree

2 files changed

+73
-32
lines changed

2 files changed

+73
-32
lines changed

src/managers/builtin/venvManager.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { NativePythonFinder } from '../common/nativePythonFinder';
3838
import { getLatest, shortVersion, sortEnvironments } from '../common/utils';
3939
import {
4040
clearVenvCache,
41+
CreateEnvironmentResult,
4142
createPythonVenv,
4243
findVirtualEnvironments,
4344
getDefaultGlobalVenvLocation,
@@ -140,39 +141,45 @@ export class VenvManager implements EnvironmentManager {
140141
const venvRoot: Uri = Uri.file(await findParentIfFile(uri.fsPath));
141142

142143
const globals = await this.baseManager.getEnvironments('global');
143-
let environment: PythonEnvironment | undefined = undefined;
144+
let result: CreateEnvironmentResult | undefined = undefined;
144145
if (options?.quickCreate) {
145-
if (this.globalEnv && this.globalEnv.version.startsWith('3.')) {
146-
environment = await quickCreateVenv(
147-
this.nativeFinder,
148-
this.api,
149-
this.log,
150-
this,
151-
this.globalEnv,
152-
venvRoot,
153-
options?.additionalPackages,
154-
);
155-
} else if (!this.globalEnv) {
146+
// error on missing information
147+
if (!this.globalEnv) {
156148
this.log.error('No base python found');
157149
showErrorMessage(VenvManagerStrings.venvErrorNoBasePython);
158150
throw new Error('No base python found');
159-
} else if (!this.globalEnv.version.startsWith('3.')) {
151+
}
152+
if (!this.globalEnv.version.startsWith('3.')) {
160153
this.log.error('Did not find any base python 3.*');
161154
globals.forEach((e, i) => {
162155
this.log.error(`${i}: ${e.version} : ${e.environmentPath.fsPath}`);
163156
});
164157
showErrorMessage(VenvManagerStrings.venvErrorNoPython3);
165158
throw new Error('Did not find any base python 3.*');
166159
}
160+
if (this.globalEnv && this.globalEnv.version.startsWith('3.')) {
161+
// quick create given correct information
162+
result = await quickCreateVenv(
163+
this.nativeFinder,
164+
this.api,
165+
this.log,
166+
this,
167+
this.globalEnv,
168+
venvRoot,
169+
options?.additionalPackages,
170+
);
171+
}
167172
} else {
168-
environment = await createPythonVenv(this.nativeFinder, this.api, this.log, this, globals, venvRoot, {
169-
// If quickCreate is not set that means the user triggered this method from
170-
// environment manager View, by selecting the venv manager.
173+
// If quickCreate is not set that means the user triggered this method from
174+
// environment manager View, by selecting the venv manager.
175+
result = await createPythonVenv(this.nativeFinder, this.api, this.log, this, globals, venvRoot, {
171176
showQuickAndCustomOptions: options?.quickCreate === undefined,
172177
});
173178
}
174179

175-
if (environment) {
180+
if (result?.environment) {
181+
const environment = result.environment;
182+
176183
this.addEnvironment(environment, true);
177184

178185
// Add .gitignore to the .venv folder
@@ -201,7 +208,7 @@ export class VenvManager implements EnvironmentManager {
201208
);
202209
}
203210
}
204-
return environment;
211+
return result?.environment ?? undefined;
205212
} finally {
206213
this.skipWatcherRefresh = false;
207214
}

src/managers/builtin/venvUtils.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ import { resolveSystemPythonEnvironmentPath } from './utils';
3333
export const VENV_WORKSPACE_KEY = `${ENVS_EXTENSION_ID}:venv:WORKSPACE_SELECTED`;
3434
export const VENV_GLOBAL_KEY = `${ENVS_EXTENSION_ID}:venv:GLOBAL_SELECTED`;
3535

36+
/**
37+
* Result of environment creation operation.
38+
*/
39+
export interface CreateEnvironmentResult {
40+
/**
41+
* The created environment, if successful.
42+
*/
43+
environment?: PythonEnvironment;
44+
45+
/*
46+
* Exists if error occurred during environment creation and includes error explanation.
47+
*/
48+
envCreationErr?: string;
49+
50+
/*
51+
* Exists if error occurred while installing packages and includes error description.
52+
*/
53+
pkgInstallationErr?: string;
54+
}
55+
3656
export async function clearVenvCache(): Promise<void> {
3757
const keys = [VENV_WORKSPACE_KEY, VENV_GLOBAL_KEY];
3858
const state = await getWorkspacePersistentState();
@@ -281,7 +301,7 @@ async function createWithProgress(
281301
venvRoot: Uri,
282302
envPath: string,
283303
packages?: PipPackages,
284-
) {
304+
): Promise<CreateEnvironmentResult | undefined> {
285305
const pythonPath =
286306
os.platform() === 'win32' ? path.join(envPath, 'Scripts', 'python.exe') : path.join(envPath, 'bin', 'python');
287307

@@ -295,8 +315,10 @@ async function createWithProgress(
295315
),
296316
},
297317
async () => {
318+
const result: CreateEnvironmentResult = {};
298319
try {
299320
const useUv = await isUvInstalled(log);
321+
// env creation
300322
if (basePython.execInfo?.run.executable) {
301323
if (useUv) {
302324
await runUV(
@@ -313,26 +335,33 @@ async function createWithProgress(
313335
);
314336
}
315337
if (!(await fsapi.pathExists(pythonPath))) {
316-
log.error('no python executable found in virtual environment');
317338
throw new Error('no python executable found in virtual environment');
318339
}
319340
}
320341

342+
// handle admin of new env
321343
const resolved = await nativeFinder.resolve(pythonPath);
322344
const env = api.createPythonEnvironmentItem(await getPythonInfo(resolved), manager);
345+
346+
// install packages
323347
if (packages && (packages.install.length > 0 || packages.uninstall.length > 0)) {
324-
await api.managePackages(env, {
325-
upgrade: false,
326-
install: packages?.install,
327-
uninstall: packages?.uninstall ?? [],
328-
});
348+
try {
349+
await api.managePackages(env, {
350+
upgrade: false,
351+
install: packages?.install,
352+
uninstall: packages?.uninstall ?? [],
353+
});
354+
} catch (e) {
355+
// error occurred while installing packages
356+
result.pkgInstallationErr = e instanceof Error ? e.message : String(e);
357+
}
329358
}
330-
return env;
359+
result.environment = env;
331360
} catch (e) {
332361
log.error(`Failed to create virtual environment: ${e}`);
333-
showErrorMessage(VenvManagerStrings.venvCreateFailed);
334-
return;
362+
result.envCreationErr = `Failed to create virtual environment: ${e}`;
335363
}
364+
return result;
336365
},
337366
);
338367
}
@@ -365,7 +394,7 @@ export async function quickCreateVenv(
365394
baseEnv: PythonEnvironment,
366395
venvRoot: Uri,
367396
additionalPackages?: string[],
368-
): Promise<PythonEnvironment | undefined> {
397+
): Promise<CreateEnvironmentResult | undefined> {
369398
const project = api.getPythonProject(venvRoot);
370399

371400
sendTelemetryEvent(EventNames.VENV_CREATION, undefined, { creationType: 'quick' });
@@ -387,6 +416,7 @@ export async function quickCreateVenv(
387416
venvPath = `${venvPath}-${i}`;
388417
}
389418

419+
// createWithProgress handles building CreateEnvironmentResult and adding err msgs
390420
return await createWithProgress(nativeFinder, api, log, manager, baseEnv, venvRoot, venvPath, {
391421
install: allPackages,
392422
uninstall: [],
@@ -401,7 +431,7 @@ export async function createPythonVenv(
401431
basePythons: PythonEnvironment[],
402432
venvRoot: Uri,
403433
options: { showQuickAndCustomOptions: boolean; additionalPackages?: string[] },
404-
): Promise<PythonEnvironment | undefined> {
434+
): Promise<CreateEnvironmentResult | undefined> {
405435
const sortedEnvs = ensureGlobalEnv(basePythons, log);
406436

407437
let customize: boolean | undefined = true;
@@ -421,7 +451,9 @@ export async function createPythonVenv(
421451
const basePython = await pickEnvironmentFrom(sortedEnvs);
422452
if (!basePython || !basePython.execInfo) {
423453
log.error('No base python selected, cannot create virtual environment.');
424-
return;
454+
return {
455+
envCreationErr: 'No base python selected, cannot create virtual environment.',
456+
};
425457
}
426458

427459
const name = await showInputBox({
@@ -439,7 +471,9 @@ export async function createPythonVenv(
439471
});
440472
if (!name) {
441473
log.error('No name entered, cannot create virtual environment.');
442-
return;
474+
return {
475+
envCreationErr: 'No name entered, cannot create virtual environment.',
476+
};
443477
}
444478

445479
const envPath = path.join(venvRoot.fsPath, name);

0 commit comments

Comments
 (0)