Skip to content
Open
17 changes: 15 additions & 2 deletions api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,10 @@ export interface PackageManager {
/**
* Retrieves the list of packages for the specified Python environment.
* @param environment - The Python environment for which to retrieve packages.
* @param options - Optional settings for package retrieval.
* @returns An array of packages, or undefined if the packages could not be retrieved.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;

/**
* Event that is fired when packages change.
Expand Down Expand Up @@ -794,6 +795,17 @@ export interface DidChangePythonProjectsEventArgs {
removed: PythonProject[];
}

/**
* Options for retrieving packages from a package manager.
*/
export interface GetPackagesOptions {
/**
* When `true`, bypasses the cache and fetches the latest packages from the underlying tool.
* Defaults to `false`.
*/
skipCache?: boolean;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the point of going with options that then has the skipCache inside is just more flexibility down the line? Did you have anything else in mind you were going to put into this object?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for flexibility down the line. Perhaps adding an option for overriding the feed, or perhaps specifying some filter

}

export type PackageManagementOptions =
| {
/**
Expand Down Expand Up @@ -1025,9 +1037,10 @@ export interface PythonPackageGetterApi {
* Get the list of packages in a Python Environment.
*
* @param environment The Python Environment for which the list of packages is required.
* @param options Optional settings for package retrieval.
* @returns The list of packages in the Python Environment.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;

/**
* Event raised when the list of packages in a Python Environment changes.
Expand Down
31 changes: 22 additions & 9 deletions examples/sample1/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,10 @@ export interface PackageManager {
/**
* Retrieves the list of packages for the specified Python environment.
* @param environment - The Python environment for which to retrieve packages.
* @param options - Optional settings for package retrieval.
* @returns An array of packages, or undefined if the packages could not be retrieved.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;

/**
* Event that is fired when packages change.
Expand Down Expand Up @@ -714,6 +715,17 @@ export interface DidChangePythonProjectsEventArgs {
removed: PythonProject[];
}

/**
* Options for retrieving packages from a package manager.
*/
export interface GetPackagesOptions {
/**
* When `true`, bypasses the cache and fetches the latest packages from the underlying tool.
* Defaults to `false`.
*/
skipCache?: boolean;
}

/**
* Options for package management.
*/
Expand Down Expand Up @@ -915,7 +927,8 @@ export interface PythonProjectEnvironmentApi {
}

export interface PythonEnvironmentManagerApi
extends PythonEnvironmentManagerRegistrationApi,
extends
PythonEnvironmentManagerRegistrationApi,
PythonEnvironmentItemApi,
PythonEnvironmentManagementApi,
PythonEnvironmentsApi,
Expand Down Expand Up @@ -949,9 +962,10 @@ export interface PythonPackageGetterApi {
* Get the list of packages in a Python Environment.
*
* @param environment The Python Environment for which the list of packages is required.
* @param options Optional settings for package retrieval.
* @returns The list of packages in the Python Environment.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;

/**
* Event raised when the list of packages in a Python Environment changes.
Expand Down Expand Up @@ -984,7 +998,8 @@ export interface PythonPackageManagementApi {
}

export interface PythonPackageManagerApi
extends PythonPackageManagerRegistrationApi,
extends
PythonPackageManagerRegistrationApi,
PythonPackageGetterApi,
PythonPackageManagementApi,
PythonPackageItemApi {}
Expand Down Expand Up @@ -1203,10 +1218,7 @@ export interface PythonBackgroundRunApi {
}

export interface PythonExecutionApi
extends PythonTerminalCreateApi,
PythonTerminalRunApi,
PythonTaskRunApi,
PythonBackgroundRunApi {}
extends PythonTerminalCreateApi, PythonTerminalRunApi, PythonTaskRunApi, PythonBackgroundRunApi {}

/**
* Event arguments for when the monitored `.env` files or any other sources change.
Expand Down Expand Up @@ -1255,7 +1267,8 @@ export interface PythonEnvironmentVariablesApi {
* The API for interacting with Python environments, package managers, and projects.
*/
export interface PythonEnvironmentApi
extends PythonEnvironmentManagerApi,
extends
PythonEnvironmentManagerApi,
PythonPackageManagerApi,
PythonProjectApi,
PythonExecutionApi,
Expand Down
17 changes: 15 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,9 +671,10 @@ export interface PackageManager {
/**
* Retrieves the list of packages for the specified Python environment.
* @param environment - The Python environment for which to retrieve packages.
* @param options - Optional settings for package retrieval.
* @returns An array of packages, or undefined if the packages could not be retrieved.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;
Comment thread
edvilme marked this conversation as resolved.

/**
* Event that is fired when packages change.
Expand Down Expand Up @@ -788,6 +789,17 @@ export interface DidChangePythonProjectsEventArgs {
removed: PythonProject[];
}

/**
* Options for retrieving packages from a package manager.
*/
export interface GetPackagesOptions {
/**
* When `true`, bypasses the cache and fetches the latest packages from the underlying tool.
* Defaults to `false`.
*/
skipCache?: boolean;
}

export type PackageManagementOptions =
| {
/**
Expand Down Expand Up @@ -1019,9 +1031,10 @@ export interface PythonPackageGetterApi {
* Get the list of packages in a Python Environment.
*
* @param environment The Python Environment for which the list of packages is required.
* @param options Optional settings for package retrieval.
* @returns The list of packages in the Python Environment.
*/
getPackages(environment: PythonEnvironment): Promise<Package[] | undefined>;
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined>;

/**
* Event raised when the list of packages in a Python Environment changes.
Expand Down
11 changes: 6 additions & 5 deletions src/features/pythonApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
EnvironmentManager,
GetEnvironmentScope,
GetEnvironmentsScope,
GetPackagesOptions,
Package,
PackageId,
PackageInfo,
Expand Down Expand Up @@ -96,9 +97,9 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
// *selected* manager's changes propagate (refreshEnvironment checks
// getEnvironmentManager(scope) internally). It updates the cache and
// fires onDidChangeActiveEnvironment, which the Python API listens to.
this.envManagers.refreshEnvironment(e.uri).catch((err) =>
traceError('Failed to refresh environment on change:', err),
);
this.envManagers
.refreshEnvironment(e.uri)
.catch((err) => traceError('Failed to refresh environment on change:', err));
});
}),
);
Expand Down Expand Up @@ -257,13 +258,13 @@ class PythonEnvironmentApiImpl implements PythonEnvironmentApi {
}
return manager.refresh(context);
}
async getPackages(context: PythonEnvironment): Promise<Package[] | undefined> {
async getPackages(context: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined> {
await waitForEnvManagerId([context.envId.managerId]);
const manager = this.envManagers.getPackageManager(context);
if (!manager) {
return Promise.resolve(undefined);
}
return manager.getPackages(context);
return manager.getPackages(context, options);
}
onDidChangePackages: Event<DidChangePackagesEventArgs> = this._onDidChangePackages.event;
createPackageItem(info: PackageInfo, environment: PythonEnvironment, manager: PackageManager): Package {
Expand Down
5 changes: 3 additions & 2 deletions src/internal.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
EnvironmentManager,
GetEnvironmentScope,
GetEnvironmentsScope,
GetPackagesOptions,
IconPath,
Package,
PackageChangeKind,
Expand Down Expand Up @@ -367,8 +368,8 @@ export class InternalPackageManager implements PackageManager {
return this.manager.refresh(environment);
}

getPackages(environment: PythonEnvironment): Promise<Package[] | undefined> {
return this.manager.getPackages(environment);
getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined> {
return this.manager.getPackages(environment, options);
}

onDidChangePackages(handler: (e: DidChangePackagesEventArgs) => void): Disposable {
Expand Down
8 changes: 4 additions & 4 deletions src/managers/builtin/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createSimpleDebounce } from '../../common/utils/debounce';
import { createFileSystemWatcher, onDidDeleteFiles } from '../../common/workspace.apis';
import { getPythonApi } from '../../features/pythonApi';
import { NativePythonFinder } from '../common/nativePythonFinder';
import { PipPackageManager } from './pipManager';
import { PipPackageManager } from './pipPackageManager';
import { SysPythonManager } from './sysPythonManager';
import { VenvManager } from './venvManager';

Expand Down Expand Up @@ -60,10 +60,10 @@ export async function registerSystemPythonFeatures(
);
});
const packageWatcher = createFileSystemWatcher(
'**/site-packages/*.dist-info/METADATA',
'**/site-packages/*.dist-info/METADATA',
false, // don't ignore create events (pip install)
true, // ignore change events (content changes in METADATA don't affect package list)
false // don't ignore delete events (pip uninstall)
true, // ignore change events (content changes in METADATA don't affect package list)
false, // don't ignore delete events (pip uninstall)
);
disposables.push(
packageDebouncedRefresh,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,19 @@ import {
} from 'vscode';
import {
DidChangePackagesEventArgs,
GetPackagesOptions,
IconPath,
Package,
PackageChangeKind,
PackageManagementOptions,
PackageManager,
PythonEnvironment,
PythonEnvironmentApi,
} from '../../api';
import { updatePackagesAndNotify } from '../common/packageChanges';
import { getWorkspacePackagesToInstall } from './pipUtils';
import { managePackages, refreshPackages } from './utils';
import { managePackages, refreshPipPackages } from './utils';
import { VenvManager } from './venvManager';

function getChanges(before: Package[], after: Package[]): { kind: PackageChangeKind; pkg: Package }[] {
const changes: { kind: PackageChangeKind; pkg: Package }[] = [];
before.forEach((pkg) => {
changes.push({ kind: PackageChangeKind.remove, pkg });
});
after.forEach((pkg) => {
changes.push({ kind: PackageChangeKind.add, pkg });
});
return changes;
}

export class PipPackageManager implements PackageManager, Disposable {
private readonly _onDidChangePackages = new EventEmitter<DidChangePackagesEventArgs>();
onDidChangePackages: Event<DidChangePackagesEventArgs> = this._onDidChangePackages.event;
Expand Down Expand Up @@ -85,11 +75,15 @@ export class PipPackageManager implements PackageManager, Disposable {
},
async (_progress, token) => {
try {
const before = this.packages.get(environment.envId.id) ?? [];
const after = await managePackages(environment, manageOptions, this.api, this, token);
const changes = getChanges(before, after);
this.packages.set(environment.envId.id, after);
this._onDidChangePackages.fire({ environment, manager: this, changes });
await managePackages(environment, manageOptions, this, token);
await updatePackagesAndNotify(
this,
environment,
this.packages.get(environment.envId.id),
(changes) => {
this._onDidChangePackages.fire({ environment, manager: this, changes });
},
);
} catch (e) {
if (e instanceof CancellationError) {
throw e;
Expand All @@ -114,19 +108,19 @@ export class PipPackageManager implements PackageManager, Disposable {
title: 'Refreshing packages',
},
async () => {
const before = this.packages.get(environment.envId.id) ?? [];
const after = await refreshPackages(environment, this.api, this);
const changes = getChanges(before, after);
this.packages.set(environment.envId.id, after);
if (changes.length > 0) {
await updatePackagesAndNotify(this, environment, this.packages.get(environment.envId.id), (changes) => {
this._onDidChangePackages.fire({ environment, manager: this, changes });
}
});
},
);
}
async getPackages(environment: PythonEnvironment): Promise<Package[] | undefined> {
if (!this.packages.has(environment.envId.id)) {
await this.refresh(environment);

async getPackages(environment: PythonEnvironment, options?: GetPackagesOptions): Promise<Package[] | undefined> {
if (options?.skipCache || !this.packages.has(environment.envId.id)) {
const data = await refreshPipPackages(environment, this.log);
const packages = (data ?? []).map((pkg) => this.api.createPackageItem(pkg, environment, this));
this.packages.set(environment.envId.id, packages);
return packages;
}
return this.packages.get(environment.envId.id);
}
Expand Down
22 changes: 5 additions & 17 deletions src/managers/builtin/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,12 @@ export async function refreshPythons(

const PIP_LIST_TIMEOUT_MS = 30_000;

async function refreshPipPackagesRaw(environment: PythonEnvironment, log?: LogOutputChannel): Promise<string> {
async function execPipList(environment: PythonEnvironment, log?: LogOutputChannel, args?: string[]): Promise<string> {
// Use environmentPath directly for consistency with UV environment tracking
const useUv = await shouldUseUv(log, environment.environmentPath.fsPath);
if (useUv) {
return await runUV(
['pip', 'list', '--python', environment.execInfo.run.executable, '--format=json'],
['pip', 'list', '--python', environment.execInfo.run.executable, '--format=json', ...(args ?? [])],
undefined,
log,
undefined,
Expand Down Expand Up @@ -228,11 +228,11 @@ export async function refreshPipPackages(
location: ProgressLocation.Notification,
},
async () => {
return await refreshPipPackagesRaw(environment, log);
return await execPipList(environment, log);
},
);
} else {
data = await refreshPipPackagesRaw(environment, log);
data = await execPipList(environment, log);
}

return parsePipListJson(data);
Expand All @@ -243,22 +243,12 @@ export async function refreshPipPackages(
}
}

export async function refreshPackages(
environment: PythonEnvironment,
api: PythonEnvironmentApi,
manager: PackageManager,
): Promise<Package[]> {
const data = await refreshPipPackages(environment, manager.log);
return (data ?? []).map((pkg) => api.createPackageItem(pkg, environment, manager));
}

export async function managePackages(
environment: PythonEnvironment,
options: PackageManagementOptions,
api: PythonEnvironmentApi,
manager: PackageManager,
token?: CancellationToken,
): Promise<Package[]> {
): Promise<void> {
if (environment.version.startsWith('2.')) {
throw new Error('Python 2.* is not supported (deprecated)');
}
Expand Down Expand Up @@ -310,8 +300,6 @@ export async function managePackages(
);
}
}

return await refreshPackages(environment, api, manager);
}

/**
Expand Down
Loading
Loading