Skip to content

Commit af38958

Browse files
authored
add check for already set project environments and add documentation (#545)
fixes #507 introduce `setEnvironmentsIfUnset` so that newer environments when created do not override existing settings
1 parent 6a29d04 commit af38958

File tree

3 files changed

+89
-32
lines changed

3 files changed

+89
-32
lines changed

src/features/envCommands.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ export async function refreshPackagesCommand(context: unknown, managers?: Enviro
7474
}
7575
}
7676

77+
/**
78+
* Creates a Python environment using the manager implied by the context (no user prompt).
79+
*/
7780
export async function createEnvironmentCommand(
7881
context: unknown,
7982
em: EnvironmentManagers,
@@ -94,7 +97,7 @@ export async function createEnvironmentCommand(
9497
const scope = selected.length === 0 ? 'global' : selected.map((p) => p.uri);
9598
const env = await manager.create(scope, undefined);
9699
if (env) {
97-
await em.setEnvironments(scope, env);
100+
await em.setEnvironmentsIfUnset(scope, env);
98101
}
99102
return env;
100103
} else {
@@ -114,6 +117,9 @@ export async function createEnvironmentCommand(
114117
}
115118
}
116119

120+
/**
121+
* Prompts the user to pick the environment manager and project(s) for environment creation.
122+
*/
117123
export async function createAnyEnvironmentCommand(
118124
em: EnvironmentManagers,
119125
pm: PythonProjectManager,

src/features/envManagers.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Disposable, EventEmitter, Uri, workspace, ConfigurationTarget, Event } from 'vscode';
1+
import { ConfigurationTarget, Disposable, Event, EventEmitter, Uri, workspace } from 'vscode';
22
import {
33
DidChangeEnvironmentEventArgs,
44
DidChangeEnvironmentsEventArgs,
@@ -10,13 +10,14 @@ import {
1010
PythonProject,
1111
SetEnvironmentScope,
1212
} from '../api';
13-
import { traceError, traceVerbose } from '../common/logging';
1413
import {
15-
EditAllManagerSettings,
16-
getDefaultEnvManagerSetting,
17-
getDefaultPkgManagerSetting,
18-
setAllManagerSettings,
19-
} from './settings/settingHelpers';
14+
EnvironmentManagerAlreadyRegisteredError,
15+
PackageManagerAlreadyRegisteredError,
16+
} from '../common/errors/AlreadyRegisteredError';
17+
import { traceError, traceVerbose } from '../common/logging';
18+
import { EventNames } from '../common/telemetry/constants';
19+
import { sendTelemetryEvent } from '../common/telemetry/sender';
20+
import { getCallingExtension } from '../common/utils/frameUtils';
2021
import {
2122
DidChangeEnvironmentManagerEventArgs,
2223
DidChangePackageManagerEventArgs,
@@ -30,13 +31,12 @@ import {
3031
PythonProjectManager,
3132
PythonProjectSettings,
3233
} from '../internal.api';
33-
import { getCallingExtension } from '../common/utils/frameUtils';
3434
import {
35-
EnvironmentManagerAlreadyRegisteredError,
36-
PackageManagerAlreadyRegisteredError,
37-
} from '../common/errors/AlreadyRegisteredError';
38-
import { sendTelemetryEvent } from '../common/telemetry/sender';
39-
import { EventNames } from '../common/telemetry/constants';
35+
EditAllManagerSettings,
36+
getDefaultEnvManagerSetting,
37+
getDefaultPkgManagerSetting,
38+
setAllManagerSettings,
39+
} from './settings/settingHelpers';
4040

4141
function generateId(name: string): string {
4242
const newName = name.toLowerCase().replace(/[^a-zA-Z0-9-_]/g, '_');
@@ -165,6 +165,10 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
165165
this._onDidChangePackages.dispose();
166166
}
167167

168+
/**
169+
* Returns the environment manager for the given context.
170+
* Uses the default from settings if context is undefined or a Uri; otherwise uses the id or environment's managerId passed in via context.
171+
*/
168172
public getEnvironmentManager(context: EnvironmentManagerScope): InternalEnvironmentManager | undefined {
169173
if (this._environmentManagers.size === 0) {
170174
traceError('No environment managers registered');
@@ -256,6 +260,10 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
256260
}
257261
}
258262

263+
/**
264+
* Sets the environment for a single scope, scope of undefined checks 'global'.
265+
* If given an array of scopes, delegates to setEnvironments for batch setting.
266+
*/
259267
public async setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void> {
260268
if (Array.isArray(scope)) {
261269
return this.setEnvironments(scope, environment);
@@ -299,6 +307,10 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
299307
}
300308
}
301309

310+
/**
311+
* Sets the given environment for the specified project URIs or globally.
312+
* If a list of URIs is provided, sets the environment for each project; if 'global', sets it as the global environment.
313+
*/
302314
public async setEnvironments(scope: Uri[] | string, environment?: PythonEnvironment): Promise<void> {
303315
if (environment) {
304316
const manager = this.managers.find((m) => m.id === environment.envId.managerId);
@@ -403,6 +415,44 @@ export class PythonEnvironmentManagers implements EnvironmentManagers {
403415
}
404416
}
405417

418+
/**
419+
* Sets the environment for the given scopes, but only if the scope is not already set (i.e., is global or undefined).
420+
* Existing environments for a scope are not overwritten.
421+
*
422+
*/
423+
public async setEnvironmentsIfUnset(scope: Uri[] | string, environment?: PythonEnvironment): Promise<void> {
424+
if (!environment) {
425+
return;
426+
}
427+
if (typeof scope === 'string' && scope === 'global') {
428+
const current = await this.getEnvironment(undefined);
429+
if (!current) {
430+
await this.setEnvironments('global', environment);
431+
}
432+
} else if (Array.isArray(scope)) {
433+
const urisToSet: Uri[] = [];
434+
for (const uri of scope) {
435+
const current = await this.getEnvironment(uri);
436+
if (!current || current.envId.managerId === 'ms-python.python:system') {
437+
// If the current environment is not set or is the system environment, set the new environment.
438+
urisToSet.push(uri);
439+
}
440+
}
441+
if (urisToSet.length > 0) {
442+
await this.setEnvironments(urisToSet, environment);
443+
}
444+
}
445+
}
446+
447+
/**
448+
* Gets the current Python environment for the given scope URI or undefined for 'global'.
449+
*
450+
* This method queries the appropriate environment manager for the latest environment for the scope.
451+
* It also updates the internal cache and fires an event if the environment has changed since last check.
452+
*
453+
* @param scope The scope to get the environment.
454+
* @returns The current PythonEnvironment for the scope, or undefined if none is set.
455+
*/
406456
async getEnvironment(scope: GetEnvironmentScope): Promise<PythonEnvironment | undefined> {
407457
const manager = this.getEnvironmentManager(scope);
408458
if (!manager) {

src/internal.api.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
11
import { CancellationError, Disposable, Event, LogOutputChannel, MarkdownString, Uri } from 'vscode';
22
import {
3-
PythonEnvironment,
4-
EnvironmentManager,
5-
PackageManager,
6-
Package,
7-
IconPath,
3+
CreateEnvironmentOptions,
4+
CreateEnvironmentScope,
85
DidChangeEnvironmentEventArgs,
96
DidChangeEnvironmentsEventArgs,
107
DidChangePackagesEventArgs,
11-
PythonProject,
12-
RefreshEnvironmentsScope,
13-
GetEnvironmentsScope,
14-
CreateEnvironmentScope,
15-
SetEnvironmentScope,
8+
EnvironmentGroupInfo,
9+
EnvironmentManager,
1610
GetEnvironmentScope,
17-
PythonEnvironmentId,
18-
PythonEnvironmentExecutionInfo,
19-
PythonEnvironmentInfo,
11+
GetEnvironmentsScope,
12+
IconPath,
13+
Package,
2014
PackageChangeKind,
2115
PackageId,
2216
PackageInfo,
23-
PythonProjectCreator,
24-
ResolveEnvironmentContext,
2517
PackageManagementOptions,
26-
EnvironmentGroupInfo,
18+
PackageManager,
19+
PythonEnvironment,
20+
PythonEnvironmentExecutionInfo,
21+
PythonEnvironmentId,
22+
PythonEnvironmentInfo,
23+
PythonProject,
24+
PythonProjectCreator,
2725
QuickCreateConfig,
28-
CreateEnvironmentOptions,
26+
RefreshEnvironmentsScope,
27+
ResolveEnvironmentContext,
28+
SetEnvironmentScope,
2929
} from './api';
3030
import { CreateEnvironmentNotSupported, RemoveEnvironmentNotSupported } from './common/errors/NotSupportedError';
31-
import { sendTelemetryEvent } from './common/telemetry/sender';
3231
import { EventNames } from './common/telemetry/constants';
32+
import { sendTelemetryEvent } from './common/telemetry/sender';
3333

3434
export type EnvironmentManagerScope = undefined | string | Uri | PythonEnvironment;
3535
export type PackageManagerScope = undefined | string | Uri | PythonEnvironment | Package;
@@ -111,6 +111,7 @@ export interface EnvironmentManagers extends Disposable {
111111

112112
setEnvironment(scope: SetEnvironmentScope, environment?: PythonEnvironment): Promise<void>;
113113
setEnvironments(scope: Uri[] | string, environment?: PythonEnvironment): Promise<void>;
114+
setEnvironmentsIfUnset(scope: Uri[] | string, environment?: PythonEnvironment): Promise<void>;
114115
getEnvironment(scope: GetEnvironmentScope): Promise<PythonEnvironment | undefined>;
115116

116117
getProjectEnvManagers(uris: Uri[]): InternalEnvironmentManager[];

0 commit comments

Comments
 (0)