Skip to content

Commit

Permalink
Use new Python Environment API (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
monosans authored Apr 1, 2023
1 parent 2049fb1 commit c1016c1
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 75 deletions.
30 changes: 15 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"ruff"
],
"engines": {
"vscode": "^1.64.0"
"vscode": "^1.72.0"
},
"categories": [
"Programming Languages",
Expand Down Expand Up @@ -182,8 +182,8 @@
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/glob": "^8.0.0",
"@types/node": "14.x",
"@types/vscode": "1.64.0",
"@types/node": "16.x",
"@types/vscode": "1.71.0",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"@vscode/test-electron": "^2.2.1",
Expand Down
254 changes: 197 additions & 57 deletions src/common/python.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,210 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { commands, Disposable, Event, EventEmitter, extensions, Uri } from 'vscode';
import { commands, Disposable, Event, EventEmitter, extensions, Uri, WorkspaceFolder } from 'vscode';
import { traceError, traceLog } from './log/logging';

export enum PythonEnvKind {
Unknown = 'unknown',
// "global"
System = 'global-system',
WindowsStore = 'global-windows-store',
Pyenv = 'global-pyenv',
Poetry = 'poetry',
Custom = 'global-custom',
OtherGlobal = 'global-other',
// "virtual"
Venv = 'virt-venv',
VirtualEnv = 'virt-virtualenv',
VirtualEnvWrapper = 'virt-virtualenvwrapper',
Pipenv = 'virt-pipenv',
Conda = 'virt-conda',
OtherVirtual = 'virt-other',
}
type Environment = EnvironmentPath & {
/**
* Carries details about python executable.
*/
readonly executable: {
/**
* Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to
* the environment.
*/
readonly uri: Uri | undefined;
/**
* Bitness if known at this moment.
*/
readonly bitness: Bitness | undefined;
/**
* Value of `sys.prefix` in sys module if known at this moment.
*/
readonly sysPrefix: string | undefined;
};
/**
* Carries details if it is an environment, otherwise `undefined` in case of global interpreters and others.
*/
readonly environment:
| {
/**
* Type of the environment.
*/
readonly type: EnvironmentType;
/**
* Name to the environment if any.
*/
readonly name: string | undefined;
/**
* Uri of the environment folder.
*/
readonly folderUri: Uri;
/**
* Any specific workspace folder this environment is created for.
*/
readonly workspaceFolder: Uri | undefined;
}
| undefined;
/**
* Carries Python version information known at this moment.
*/
readonly version: VersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string | undefined;
};
/**
* Tools/plugins which created the environment or where it came from. First value in array corresponds
* to the primary tool which manages the environment, which never changes over time.
*
* Array is empty if no tool is responsible for creating/managing the environment. Usually the case for
* global interpreters.
*/
readonly tools: readonly EnvironmentTools[];
};

export interface EnvPathType {
/**
* Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an
* {@link Environment} with complete information.
*/
type ResolvedEnvironment = Environment & {
/**
* Path to environment folder or path to interpreter that uniquely identifies an environment.
* Virtual environments lacking an interpreter are identified by environment folder paths,
* whereas other envs can be identified using interpreter path.
* Carries complete details about python executable.
*/
path: string;
pathType: 'envFolderPath' | 'interpreterPath';
}
readonly executable: {
/**
* Uri of the python interpreter/executable. Carries `undefined` in case an executable does not belong to
* the environment.
*/
readonly uri: Uri | undefined;
/**
* Bitness of the environment.
*/
readonly bitness: Bitness;
/**
* Value of `sys.prefix` in sys module.
*/
readonly sysPrefix: string;
};
/**
* Carries complete Python version information.
*/
readonly version: ResolvedVersionInfo & {
/**
* Value of `sys.version` in sys module if known at this moment.
*/
readonly sysVersion: string;
};
};

export interface EnvironmentDetailsOptions {
useCache: boolean;
}
type EnvironmentsChangeEvent = {
readonly env: Environment;
/**
* * "add": New environment is added.
* * "remove": Existing environment in the list is removed.
* * "update": New information found about existing environment.
*/
readonly type: 'add' | 'remove' | 'update';
};

export interface EnvironmentDetails {
interpreterPath: string;
envFolderPath?: string;
version: string[];
environmentType: PythonEnvKind[];
metadata: Record<string, unknown>;
}
type ActiveEnvironmentPathChangeEvent = EnvironmentPath & {
/**
* Workspace folder the environment changed for.
*/
readonly resource: WorkspaceFolder | undefined;
};

/**
* Uri of a file inside a workspace or workspace folder itself.
*/
type Resource = Uri | WorkspaceFolder;

export interface ActiveEnvironmentChangedParams {
type EnvironmentPath = {
/**
* Path to environment folder or path to interpreter that uniquely identifies an environment.
* Virtual environments lacking an interpreter are identified by environment folder paths,
* whereas other envs can be identified using interpreter path.
* The ID of the environment.
*/
path: string;
resource?: Uri;
}
readonly id: string;
/**
* Path to environment folder or path to python executable that uniquely identifies an environment. Environments
* lacking a python executable are identified by environment folder paths, whereas other envs can be identified
* using python executable path.
*/
readonly path: string;
};

/**
* Tool/plugin where the environment came from. It can be {@link KnownEnvironmentTools} or custom string which
* was contributed.
*/
type EnvironmentTools = KnownEnvironmentTools | string;
/**
* Tools or plugins the Python extension currently has built-in support for. Note this list is expected to shrink
* once tools have their own separate extensions.
*/
type KnownEnvironmentTools =
| 'Conda'
| 'Pipenv'
| 'Poetry'
| 'VirtualEnv'
| 'Venv'
| 'VirtualEnvWrapper'
| 'Pyenv'
| 'Unknown';

/**
* Type of the environment. It can be {@link KnownEnvironmentTypes} or custom string which was contributed.
*/
type EnvironmentType = KnownEnvironmentTypes | string;
/**
* Environment types the Python extension is aware of. Note this list is expected to shrink once tools have their
* own separate extensions, in which case they're expected to provide the type themselves.
*/
type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown';

/**
* Carries bitness for an environment.
*/
type Bitness = '64-bit' | '32-bit' | 'Unknown';

/**
* The possible Python release levels.
*/
type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final';

/**
* Release information for a Python version.
*/
type PythonVersionRelease = {
readonly level: PythonReleaseLevel;
readonly serial: number;
};

type VersionInfo = {
readonly major: number | undefined;
readonly minor: number | undefined;
readonly micro: number | undefined;
readonly release: PythonVersionRelease | undefined;
};

type ResolvedVersionInfo = {
readonly major: number;
readonly minor: number;
readonly micro: number;
readonly release: PythonVersionRelease;
};

interface IExtensionApi {
ready: Promise<void>;
debug: {
getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise<string[]>;
getDebuggerPackagePath(): Promise<string | undefined>;
};
settings: {
readonly onDidChangeExecutionDetails: Event<Uri | undefined>;
getExecutionDetails(resource?: Uri | undefined): {
execCommand: string[] | undefined;
};
};
environment: {
getActiveEnvironmentPath(resource?: Uri | undefined): Promise<EnvPathType | undefined>;
onDidActiveEnvironmentChanged: Event<ActiveEnvironmentChangedParams>;
environments: {
getActiveEnvironmentPath(resource?: Resource): EnvironmentPath;
resolveEnvironment(
environment: Environment | EnvironmentPath | string,
): Promise<ResolvedEnvironment | undefined>;
readonly onDidChangeActiveEnvironmentPath: Event<ActiveEnvironmentPathChangeEvent>;
};
}

Expand Down Expand Up @@ -99,8 +237,8 @@ export async function initializePython(disposables: Disposable[]): Promise<void>

if (api) {
disposables.push(
api.environment.onDidActiveEnvironmentChanged((e) => {
onDidChangePythonInterpreterEvent.fire({ path: [e.path], resource: e.resource });
api.environments.onDidChangeActiveEnvironmentPath((e) => {
onDidChangePythonInterpreterEvent.fire({ path: [e.path], resource: e.resource?.uri });
}),
);

Expand All @@ -114,9 +252,11 @@ export async function initializePython(disposables: Disposable[]): Promise<void>

export async function getInterpreterDetails(resource?: Uri): Promise<IInterpreterDetails> {
const api = await getPythonExtensionAPI();
const interpreter = await api?.environment.getActiveEnvironmentPath(resource);
if (interpreter && interpreter.pathType === 'interpreterPath') {
return { path: [interpreter.path], resource };
const environment = await api?.environments.resolveEnvironment(
api?.environments.getActiveEnvironmentPath(resource),
);
if (environment?.executable.uri) {
return { path: [environment?.executable.uri.fsPath], resource };
}
return { path: undefined, resource };
}
Expand All @@ -128,5 +268,5 @@ export async function getDebuggerPath(): Promise<string | undefined> {

export async function runPythonExtensionCommand(command: string, ...rest: any[]) {
await activateExtension();
return commands.executeCommand(command, ...rest);
return await commands.executeCommand(command, ...rest);
}

0 comments on commit c1016c1

Please sign in to comment.