Skip to content

Python Environment APIs

Kartik Raj edited this page Mar 17, 2023 · 11 revisions

These APIs provide a way for extensions to work with by python environments available in the user's machine as found by the Python extension.

Functions

  • getActiveEnvironmentPath(resource?: Resource): EnvironmentPath;

    Returns the path to the python binary selected by the user or as in the settings. This is just the path to the python binary, this does not provide activation or any other activation command. The resource if provided will be used to determine the python binary in a multi-root scenario. If resource is undefined then the API returns what ever is set for the workspace.

    Params:

    • resource : Uri of a file or workspace folder. This is used to determine the env in a multi-root scenario. If undefined, then the API returns what ever is set for the workspace.
  • updateActiveEnvironmentPath(
        environment: string | EnvironmentPath | Environment,
        resource?: Resource,
    ): Promise<void>;

    Sets the active environment path for the python extension for the resource. Configuration target will always be the workspace folder.

    Params:

    • environment : Full path to environment folder or python executable for the environment. Can also pass the environment itself.
    • resource : [optional] File or workspace to scope to a particular workspace folder.
  • readonly known: Environment[];

    Carries environments known to the extension at the time of fetching the property. Note this may not contain all environments in the system as a refresh might be going on.

  • resolveEnvironment(
        environment: Environment | EnvironmentPath | string,
    ): Promise<ResolvedEnvironment | undefined>;

    Returns details for the given environment, or undefined if the env is invalid.

    Params:

    • environment: Full path to environment folder or python executable for the environment. Can also pass the environment id or the environment itself.
  • refreshEnvironments(options?: RefreshOptions, token?: CancellationToken): Promise<void>;

    This API will trigger environment discovery, but only if it has not already happened in this VSCode session. Useful for making sure env list is up-to-date when the caller needs it for the first time.

    To force trigger a refresh regardless of whether a refresh was already triggered, see option RefreshOptions.forceRefresh.

    Params:

    • options: RefreshOptions
      • forceRefresh: When true, force trigger a refresh regardless of whether a refresh was already triggered. Note this can be expensive so it's best to only use it if user manually triggers a refresh.

Events

onDidChangeEnvironments

This event is triggered when the known environment list changes, like when a environment is found, existing environment is removed, or some details changed on an environment.

Events:

  • "add": New environment is added.
  • "remove": Existing environment in the list is removed.
  • "update": New information found about existing environment.

This event addresses the API request from Jupyter extension https://github.com/microsoft/vscode-python/issues/17004

Event Type: EnvironmentsChangeEvent

onDidChangeActiveEnvironmentPath

Event triggered when either user selects an interpreter or updateActiveEnvironmentPath results in a change.

Event Type: ActiveEnvironmentPathChangeEvent

Interfaces

EnvPathType

export type EnvironmentPath = {
    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;
};

EnvironmentsChangeEvent

export type EnvironmentsChangeEvent = {
    readonly env: Environment;
    readonly type: 'add' | 'remove' | 'update';
};

ActiveEnvironmentPathChangeEvent

export type ActiveEnvironmentPathChangeEvent = EnvironmentPath & {
    /**
     * Workspace folder the environment changed for.
     */
    readonly resource: WorkspaceFolder | undefined;
};

RefreshOptions

export type RefreshOptions = {
    /**
     * When `true`, force trigger a refresh regardless of whether a refresh was already triggered. Note this can be expensive so
     * it's best to only use it if user manually triggers a refresh.
     */
    forceRefresh?: boolean;
};

Extension API Usage

Get the python extension api

Copy over contents of https://github.com/microsoft/vscode-python/blob/main/src/client/apiTypes.ts as needed.

const extension = extensions.getExtension('ms-python.python');
if (extension) {
    if (!extension.isActive) {
        await extension.activate();
    }
    const pythonApi: IExtensionAPI = extension.exports as IExtensionAPI;
}

Get the active environment to run some script

// This will return something like /usr/bin/python
const environmentPath = pythonApi.environments.getActiveEnvironmentPath();

// `environmentPath.path` carries the value of the setting. Note that this path may point to a folder and not the
// python binary. Depends entirely on how the env was created.
// E.g., `conda create -n myenv python` ensures the env has a python binary
// `conda create -n myenv` does not include a python binary.
// Also, the path specified may not be valid, use the following to get complete details for this environment if
// need be.

const environment = await pythonApi.environments.resolveEnvironment(environmentPath);
if (environment) {
    // run your script here.
}

Change the current active environment

await pythonApi.environments.updateActiveEnvironment('/bin/usr/python');

Get notified when active environment is changed

let currentActivePython = undefined;
extContext.subscriptions.push(
    pythonApi.environments.onDidChangeActiveEnvironment((e: ActiveEnvironmentPathChangeEvent) => {
        currentActivePython = e.path;
    }),
);
// Get currently selected environment.
currentActivePython = pythonApi.environments.getActiveEnvironmentPath()?.path;

Get current known environments regardless of refresh state

// Use this way if you don't really want to wait for the extension to fully load info for all environments.
const environments = pythonApi.environments.known;

Detect new environments or changes to the interpreters list

const environments: string[] | undefined;
extContext.subscriptions.push(
    pythonApi.environments.onDidEnvironmentsChanged((e: EnvironmentsChangeEvent) => {
        if (environments) {
            // handle changes here based on add, remove, update
        }
    }),
);

// Get the current list of environments.
environments = pythonApi.environments.known;

Get all details for a particular environment

const detail = pythonApi.environments.resolveEnvironment(`usr/bin/python`);

Get all details for all environments in the machine

// Trigger a full refresh and wait for it to complete.
await pythonApi.environments.refreshEnvironments();
const environments = pythonApi.environments.known;

Look for a particular environment and set it

const envs = pythonApi.environments.known;
const foundEnv = envs.find(p => p.name === 'envName');
if (foundEnv) {
    await pythonApi.environments.updateActiveEnvironment(foundEnv);
} else {
    // Could not find the path, maybe a refresh is needed?
    await pythonApi.environments.refreshEnvironments();
    const foundEnv = envs.find(p => p.name === 'envName');
    if (foundEnv) {
        await pythonApi.environments.updateActiveEnvironment(foundEnv);
    } else {
        // Environment not found in the system.
    }
}

Icebox following requests

Allow to register a provider

registerEnvironmentProvider(
    environmentProvider: IEnvironmentProvider,
    metadata: EnvironmentProviderMetadata,
): Promise<Disposable>;

// TODO: Figure out whether to return a promise or not

Request for API on how to run environments

run: {
    // Functions would only require the arguments. The env provider can internally decide on the commands.
    exec: Function;
    shellExec: Function; // Only for backwards compatibility.
    execObservable: Function;
    /**
     * Uses a VSCode terminal.
     * */
    terminalExec: () => void;
    /**
     * Any environment variables that can be used to activate the environment, if supported.
     * If not provided, Python extension itself uses the other execution APIs to calculate it.
     */
    env?: { [key: string]: string | null | undefined };
};

Request for getEnvironmentVariables and onDidChangeGetEnvironmentVariables

https://github.com/microsoft/vscode-python/issues/15112 This can be problematic, although the request asks for PYTHONPATH, in a activated environment scenario we will have to provide activated environment variables, and somehow also detect environment variable changes when new packages are introduced to the environment which can make those environment changes. At this point we provide the detail about the environment type, it is up to the consumer to get the variables as they are needed.

We could revisit this when we have better activation story for ourselves.

Clone this wiki locally