From 21c383e8a771f15b483d9fe4945cb4370ca91de2 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Mon, 10 Jul 2023 15:45:22 -0700 Subject: [PATCH 1/4] Refactor api types and helper function into separate module --- src/client/api/main.ts | 400 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 400 insertions(+) create mode 100644 src/client/api/main.ts diff --git a/src/client/api/main.ts b/src/client/api/main.ts new file mode 100644 index 000000000000..f415af28eea0 --- /dev/null +++ b/src/client/api/main.ts @@ -0,0 +1,400 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { CancellationToken, Event, Uri, WorkspaceFolder, QuickPickItem, extensions } from 'vscode'; + +export interface PythonExtension { + /** + * Promise indicating whether all parts of the extension have completed loading or not. + * @type {Promise} + */ + ready: Promise; + jupyter: { + registerHooks(): void; + }; + debug: { + /** + * Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging. + * Users can append another array of strings of what they want to execute along with relevant arguments to Python. + * E.g `['/Users/..../pythonVSCode/pythonFiles/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']` + * @param {string} host + * @param {number} port + * @param {boolean} [waitUntilDebuggerAttaches=true] + * @returns {Promise} + */ + getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; + + /** + * Gets the path to the debugger package used by the extension. + * @returns {Promise} + */ + getDebuggerPackagePath(): Promise; + }; + + datascience: { + /** + * Launches Data Viewer component. + * @param {IDataViewerDataProvider} dataProvider Instance that will be used by the Data Viewer component to fetch data. + * @param {string} title Data Viewer title + */ + showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise; + /** + * Registers a remote server provider component that's used to pick remote jupyter server URIs + * @param serverProvider object called back when picking jupyter server URI + */ + registerRemoteServerProvider(serverProvider: IJupyterUriProvider): void; + }; + + /** + * 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. See + * https://github.com/microsoft/vscode-python/wiki/Python-Environment-APIs for usage examples and more. + */ + readonly environments: { + /** + * Returns the environment configured by user in settings. Note that this can be an invalid environment, use + * {@link resolveEnvironment} to get full details. + * @param 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. + */ + getActiveEnvironmentPath(resource?: Resource): EnvironmentPath; + /** + * Sets the active environment path for the python extension for the resource. Configuration target will always + * be the workspace folder. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + * @param resource : [optional] File or workspace to scope to a particular workspace folder. + */ + updateActiveEnvironmentPath( + environment: string | EnvironmentPath | Environment, + resource?: Resource, + ): Promise; + /** + * This event is triggered when the active environment setting changes. + */ + readonly onDidChangeActiveEnvironmentPath: Event; + /** + * 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. + * + * Only reports environments in the current workspace. + */ + readonly known: readonly Environment[]; + /** + * 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. + */ + readonly onDidChangeEnvironments: Event; + /** + * 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 + * {@link RefreshOptions.forceRefresh}. + * + * Note that if there is a refresh already going on then this returns the promise for that refresh. + * @param options Additional options for refresh. + * @param token A cancellation token that indicates a refresh is no longer needed. + */ + refreshEnvironments(options?: RefreshOptions, token?: CancellationToken): Promise; + /** + * Returns details for the given environment, or `undefined` if the env is invalid. + * @param environment : If string, it represents the full path to environment folder or python executable + * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. + */ + resolveEnvironment( + environment: Environment | EnvironmentPath | string, + ): Promise; + /** + * Returns the environment variables used by the extension for a resource, which includes the custom + * variables configured by user in `.env` files. + * @param 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. + */ + getEnvironmentVariables(resource?: Resource): EnvironmentVariables; + /** + * This event is fired when the environment variables for a resource change. Note it's currently not + * possible to detect if environment variables in the system change, so this only fires if custom + * environment variables are updated in `.env` files. + */ + readonly onDidEnvironmentVariablesChange: Event; + }; +} + +interface IJupyterServerUri { + baseUrl: string; + token: string; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + authorizationHeader: any; // JSON object for authorization header. + expiration?: Date; // Date/time when header expires and should be refreshed. + displayName: string; +} + +type JupyterServerUriHandle = string; + +export interface IJupyterUriProvider { + readonly id: string; // Should be a unique string (like a guid) + getQuickPickEntryItems(): QuickPickItem[]; + handleQuickPick(item: QuickPickItem, backEnabled: boolean): Promise; + getServerUri(handle: JupyterServerUriHandle): Promise; +} + +interface IDataFrameInfo { + columns?: { key: string; type: ColumnType }[]; + indexColumn?: string; + rowCount?: number; +} + +export interface IDataViewerDataProvider { + dispose(): void; + getDataFrameInfo(): Promise; + getAllRows(): Promise; + getRows(start: number, end: number): Promise; +} + +enum ColumnType { + String = 'string', + Number = 'number', + Bool = 'bool', +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type IRowsResponse = any[]; + +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; +}; + +/** + * Details about the environment. Note the environment folder, type and name never changes over time. + */ +export 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: WorkspaceFolder | undefined; + } + | undefined; + /** + * Carries Python version information known at this moment, carries `undefined` for envs without python. + */ + readonly version: + | (VersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string | undefined; + }) + | 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[]; +}; + +/** + * Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an + * {@link Environment} with complete information. + */ +export type ResolvedEnvironment = Environment & { + /** + * Carries complete 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 of the environment. + */ + readonly bitness: Bitness; + /** + * Value of `sys.prefix` in sys module. + */ + readonly sysPrefix: string; + }; + /** + * Carries complete Python version information, carries `undefined` for envs without python. + */ + readonly version: + | (ResolvedVersionInfo & { + /** + * Value of `sys.version` in sys module if known at this moment. + */ + readonly sysVersion: string; + }) + | undefined; +}; + +export 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 type ActiveEnvironmentPathChangeEvent = EnvironmentPath & { + /** + * Workspace folder the environment changed for. + */ + readonly resource: WorkspaceFolder | undefined; +}; + +/** + * Uri of a file inside a workspace or workspace folder itself. + */ +export type Resource = Uri | WorkspaceFolder; + +export type EnvironmentPath = { + /** + * The ID of the environment. + */ + 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. + */ +export 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. + */ +export 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. + */ +export 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. + */ +export type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown'; + +/** + * Carries bitness for an environment. + */ +export type Bitness = '64-bit' | '32-bit' | 'Unknown'; + +/** + * The possible Python release levels. + */ +export type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; + +/** + * Release information for a Python version. + */ +export type PythonVersionRelease = { + readonly level: PythonReleaseLevel; + readonly serial: number; +}; + +export type VersionInfo = { + readonly major: number | undefined; + readonly minor: number | undefined; + readonly micro: number | undefined; + readonly release: PythonVersionRelease | undefined; +}; + +export type ResolvedVersionInfo = { + readonly major: number; + readonly minor: number; + readonly micro: number; + readonly release: PythonVersionRelease; +}; + +/** + * A record containing readonly keys. + */ +export type EnvironmentVariables = { readonly [key: string]: string | undefined }; + +export type EnvironmentVariablesChangeEvent = { + /** + * Workspace folder the environment variables changed for. + */ + readonly resource: WorkspaceFolder | undefined; + /** + * Updated value of environment variables. + */ + readonly env: EnvironmentVariables; +}; + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PythonExtension { + export async function api(): Promise { + const extension = extensions.getExtension('ms-python.python'); + if (extension === undefined) { + throw new Error(`Python extension is not installed or is disabled`); + } + if (!extension.isActive) { + await extension.activate(); + } + const pythonApi: PythonExtension = extension.exports; + return pythonApi; + } +} From 543b575baaaaeccfdc776ef4f3d01ffbcbe3f7b7 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Mon, 10 Jul 2023 15:49:11 -0700 Subject: [PATCH 2/4] Move apiTypes.ts and rename --- src/client/api.ts | 8 ++++---- src/client/{ => api}/apiTypes.ts | 6 +++--- src/client/api/main.ts | 4 ++++ src/client/deprecatedProposedApiTypes.ts | 2 +- src/client/environmentApi.ts | 8 ++++---- src/client/extension.ts | 8 ++++---- .../pythonEnvironments/creation/proposed.createEnvApis.ts | 2 +- src/test/api.test.ts | 4 ++-- src/test/common.ts | 4 ++-- src/test/environmentApi.unit.test.ts | 6 +++--- src/test/initialize.ts | 4 ++-- 11 files changed, 30 insertions(+), 26 deletions(-) rename src/client/{ => api}/apiTypes.ts (98%) diff --git a/src/client/api.ts b/src/client/api.ts index 7bc3fc81373b..ba9572333be1 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -10,7 +10,7 @@ import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient import { LanguageClient } from 'vscode-languageclient/node'; import { PYLANCE_NAME } from './activation/node/languageClientFactory'; import { ILanguageServerOutputChannel } from './activation/types'; -import { IExtensionApi } from './apiTypes'; +import { PythonExtension } from './api/apiTypes'; import { isTestExecution, PYTHON_LANGUAGE } from './common/constants'; import { IConfigurationService, Resource } from './common/types'; import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers'; @@ -29,14 +29,14 @@ export function buildApi( serviceManager: IServiceManager, serviceContainer: IServiceContainer, discoveryApi: IDiscoveryAPI, -): IExtensionApi { +): PythonExtension { const configurationService = serviceContainer.get(IConfigurationService); const interpreterService = serviceContainer.get(IInterpreterService); serviceManager.addSingleton(JupyterExtensionIntegration, JupyterExtensionIntegration); const jupyterIntegration = serviceContainer.get(JupyterExtensionIntegration); const outputChannel = serviceContainer.get(ILanguageServerOutputChannel); - const api: IExtensionApi & { + const api: PythonExtension & { /** * @deprecated Temporarily exposed for Pylance until we expose this API generally. Will be removed in an * iteration or two. @@ -44,7 +44,7 @@ export function buildApi( pylance: ApiForPylance; } & { /** - * @deprecated Use IExtensionApi.environments API instead. + * @deprecated Use PythonExtension.environments API instead. * * Return internal settings within the extension which are stored in VSCode storage */ diff --git a/src/client/apiTypes.ts b/src/client/api/apiTypes.ts similarity index 98% rename from src/client/apiTypes.ts rename to src/client/api/apiTypes.ts index d30a81582a7e..ef1c9de9a2bb 100644 --- a/src/client/apiTypes.ts +++ b/src/client/api/apiTypes.ts @@ -2,18 +2,18 @@ // Licensed under the MIT License. import { CancellationToken, Event, Uri, WorkspaceFolder } from 'vscode'; -import { IDataViewerDataProvider, IJupyterUriProvider } from './jupyter/types'; +import { IDataViewerDataProvider, IJupyterUriProvider } from '../jupyter/types'; /* * Do not introduce any breaking changes to this API. * This is the public API for other extensions to interact with this extension. */ -export interface IExtensionApi { +export interface PythonExtension { /** * Promise indicating whether all parts of the extension have completed loading or not. * @type {Promise} - * @memberof IExtensionApi + * @memberof PythonExtension */ ready: Promise; jupyter: { diff --git a/src/client/api/main.ts b/src/client/api/main.ts index f415af28eea0..793ccd9e5d62 100644 --- a/src/client/api/main.ts +++ b/src/client/api/main.ts @@ -3,6 +3,10 @@ import { CancellationToken, Event, Uri, WorkspaceFolder, QuickPickItem, extensions } from 'vscode'; +/* + * Do not introduce any breaking changes to this API. + * This is the public API for other extensions to interact with this extension. + */ export interface PythonExtension { /** * Promise indicating whether all parts of the extension have completed loading or not. diff --git a/src/client/deprecatedProposedApiTypes.ts b/src/client/deprecatedProposedApiTypes.ts index 14cabe1d09ae..d3a7bae85f35 100644 --- a/src/client/deprecatedProposedApiTypes.ts +++ b/src/client/deprecatedProposedApiTypes.ts @@ -4,7 +4,7 @@ import { Uri, Event } from 'vscode'; import { PythonEnvKind, EnvPathType } from './pythonEnvironments/base/info'; import { ProgressNotificationEvent, GetRefreshEnvironmentsOptions } from './pythonEnvironments/base/locator'; -import { Resource } from './apiTypes'; +import { Resource } from './api/apiTypes'; export interface EnvironmentDetailsOptions { useCache: boolean; diff --git a/src/client/environmentApi.ts b/src/client/environmentApi.ts index 3e846eb2772f..e144a1fe3a1d 100644 --- a/src/client/environmentApi.ts +++ b/src/client/environmentApi.ts @@ -26,11 +26,11 @@ import { EnvironmentTools, EnvironmentType, EnvironmentVariablesChangeEvent, - IExtensionApi, + PythonExtension, RefreshOptions, ResolvedEnvironment, Resource, -} from './apiTypes'; +} from './api/apiTypes'; import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi'; type ActiveEnvironmentChangeEvent = { @@ -114,7 +114,7 @@ function filterUsingVSCodeContext(e: PythonEnvInfo) { export function buildEnvironmentApi( discoveryApi: IDiscoveryAPI, serviceContainer: IServiceContainer, -): IExtensionApi['environments'] { +): PythonExtension['environments'] { const interpreterPathService = serviceContainer.get(IInterpreterPathService); const configService = serviceContainer.get(IConfigurationService); const disposables = serviceContainer.get(IDisposableRegistry); @@ -180,7 +180,7 @@ export function buildEnvironmentApi( onEnvironmentVariablesChanged, ); - const environmentApi: IExtensionApi['environments'] = { + const environmentApi: PythonExtension['environments'] = { getEnvironmentVariables: (resource?: Resource) => { sendApiTelemetry('getEnvironmentVariables'); resource = resource && 'uri' in resource ? resource.uri : resource; diff --git a/src/client/extension.ts b/src/client/extension.ts index 5fcb63e2d322..61524a3640c8 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,7 +41,7 @@ import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry'; import { IStartupDurations } from './types'; import { runAfterActivation } from './common/utils/runAfterActivation'; import { IInterpreterService } from './interpreter/contracts'; -import { IExtensionApi } from './apiTypes'; +import { PythonExtension } from './api/apiTypes'; import { WorkspaceService } from './common/application/workspace'; import { disposeAll } from './common/utils/resourceLifecycle'; import { ProposedExtensionAPI } from './proposedApiTypes'; @@ -58,8 +58,8 @@ let activatedServiceContainer: IServiceContainer | undefined; ///////////////////////////// // public functions -export async function activate(context: IExtensionContext): Promise { - let api: IExtensionApi; +export async function activate(context: IExtensionContext): Promise { + let api: PythonExtension; let ready: Promise; let serviceContainer: IServiceContainer; try { @@ -103,7 +103,7 @@ async function activateUnsafe( context: IExtensionContext, startupStopWatch: StopWatch, startupDurations: IStartupDurations, -): Promise<[IExtensionApi & ProposedExtensionAPI, Promise, IServiceContainer]> { +): Promise<[PythonExtension & ProposedExtensionAPI, Promise, IServiceContainer]> { // Add anything that we got from initializing logs to dispose. context.subscriptions.push(...logDispose); diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts index 52209a5a31d0..8a03bd07ff20 100644 --- a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License import { Event, Disposable, WorkspaceFolder } from 'vscode'; -import { EnvironmentTools } from '../../apiTypes'; +import { EnvironmentTools } from '../../api/apiTypes'; export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; export type EnvironmentProviderId = string; diff --git a/src/test/api.test.ts b/src/test/api.test.ts index 24eb78c11bf0..c14855305d5c 100644 --- a/src/test/api.test.ts +++ b/src/test/api.test.ts @@ -2,12 +2,12 @@ // Licensed under the MIT License. import { expect } from 'chai'; -import { IExtensionApi } from '../client/apiTypes'; +import { PythonExtension } from '../client/api/apiTypes'; import { ProposedExtensionAPI } from '../client/proposedApiTypes'; import { initialize } from './initialize'; suite('Python API tests', () => { - let api: IExtensionApi & ProposedExtensionAPI; + let api: PythonExtension & ProposedExtensionAPI; suiteSetup(async () => { api = await initialize(); }); diff --git a/src/test/common.ts b/src/test/common.ts index 0a76c495830a..26cb5c08bcc2 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -10,7 +10,7 @@ import * as glob from 'glob'; import * as path from 'path'; import { coerce, SemVer } from 'semver'; import { ConfigurationTarget, Event, TextDocument, Uri } from 'vscode'; -import type { IExtensionApi } from '../client/apiTypes'; +import type { PythonExtension } from '../client/api/apiTypes'; import { IProcessService } from '../client/common/process/types'; import { IDisposable } from '../client/common/types'; import { IServiceContainer, IServiceManager } from '../client/ioc/types'; @@ -438,7 +438,7 @@ export async function isPythonVersion(...versions: string[]): Promise { } } -export interface IExtensionTestApi extends IExtensionApi, ProposedExtensionAPI { +export interface IExtensionTestApi extends PythonExtension, ProposedExtensionAPI { serviceContainer: IServiceContainer; serviceManager: IServiceManager; } diff --git a/src/test/environmentApi.unit.test.ts b/src/test/environmentApi.unit.test.ts index a4ea73fb6c92..084bc6f2eef1 100644 --- a/src/test/environmentApi.unit.test.ts +++ b/src/test/environmentApi.unit.test.ts @@ -36,8 +36,8 @@ import { ActiveEnvironmentPathChangeEvent, EnvironmentVariablesChangeEvent, EnvironmentsChangeEvent, - IExtensionApi, -} from '../client/apiTypes'; + PythonExtension, +} from '../client/api/apiTypes'; suite('Python Environment API', () => { const workspacePath = 'path/to/workspace'; @@ -57,7 +57,7 @@ suite('Python Environment API', () => { let onDidChangeEnvironments: EventEmitter; let onDidChangeEnvironmentVariables: EventEmitter; - let environmentApi: IExtensionApi['environments']; + let environmentApi: PythonExtension['environments']; setup(() => { serviceContainer = typemoq.Mock.ofType(); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index f4f37204da85..60c7fd55a35e 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import type { IExtensionApi } from '../client/apiTypes'; +import type { PythonExtension } from '../client/api/apiTypes'; import { clearPythonPathInWorkspaceFolder, IExtensionTestApi, @@ -42,7 +42,7 @@ export async function initialize(): Promise { return (api as any) as IExtensionTestApi; } export async function activateExtension() { - const extension = vscode.extensions.getExtension(PVSC_EXTENSION_ID_FOR_TESTS)!; + const extension = vscode.extensions.getExtension(PVSC_EXTENSION_ID_FOR_TESTS)!; const api = await extension.activate(); // Wait until its ready to use. await api.ready; From e5511db0f3c002e08b4c372d8e7e51544272a7d9 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Mon, 10 Jul 2023 16:00:40 -0700 Subject: [PATCH 3/4] Consolidate all types inside the module --- src/client/api.ts | 2 +- src/client/api/apiTypes.ts | 351 ------------------ src/client/api/main.ts | 4 +- src/client/deprecatedProposedApiTypes.ts | 2 +- src/client/environmentApi.ts | 2 +- src/client/extension.ts | 2 +- .../creation/proposed.createEnvApis.ts | 2 +- src/test/api.test.ts | 2 +- src/test/common.ts | 2 +- src/test/environmentApi.unit.test.ts | 2 +- src/test/initialize.ts | 2 +- 11 files changed, 12 insertions(+), 361 deletions(-) delete mode 100644 src/client/api/apiTypes.ts diff --git a/src/client/api.ts b/src/client/api.ts index ba9572333be1..7cf580d9f78f 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -10,7 +10,7 @@ import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient import { LanguageClient } from 'vscode-languageclient/node'; import { PYLANCE_NAME } from './activation/node/languageClientFactory'; import { ILanguageServerOutputChannel } from './activation/types'; -import { PythonExtension } from './api/apiTypes'; +import { PythonExtension } from './api/main'; import { isTestExecution, PYTHON_LANGUAGE } from './common/constants'; import { IConfigurationService, Resource } from './common/types'; import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers'; diff --git a/src/client/api/apiTypes.ts b/src/client/api/apiTypes.ts deleted file mode 100644 index ef1c9de9a2bb..000000000000 --- a/src/client/api/apiTypes.ts +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { CancellationToken, Event, Uri, WorkspaceFolder } from 'vscode'; -import { IDataViewerDataProvider, IJupyterUriProvider } from '../jupyter/types'; - -/* - * Do not introduce any breaking changes to this API. - * This is the public API for other extensions to interact with this extension. - */ - -export interface PythonExtension { - /** - * Promise indicating whether all parts of the extension have completed loading or not. - * @type {Promise} - * @memberof PythonExtension - */ - ready: Promise; - jupyter: { - registerHooks(): void; - }; - debug: { - /** - * Generate an array of strings for commands to pass to the Python executable to launch the debugger for remote debugging. - * Users can append another array of strings of what they want to execute along with relevant arguments to Python. - * E.g `['/Users/..../pythonVSCode/pythonFiles/lib/python/debugpy', '--listen', 'localhost:57039', '--wait-for-client']` - * @param {string} host - * @param {number} port - * @param {boolean} [waitUntilDebuggerAttaches=true] - * @returns {Promise} - */ - getRemoteLauncherCommand(host: string, port: number, waitUntilDebuggerAttaches: boolean): Promise; - - /** - * Gets the path to the debugger package used by the extension. - * @returns {Promise} - */ - getDebuggerPackagePath(): Promise; - }; - - datascience: { - /** - * Launches Data Viewer component. - * @param {IDataViewerDataProvider} dataProvider Instance that will be used by the Data Viewer component to fetch data. - * @param {string} title Data Viewer title - */ - showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise; - /** - * Registers a remote server provider component that's used to pick remote jupyter server URIs - * @param serverProvider object called back when picking jupyter server URI - */ - registerRemoteServerProvider(serverProvider: IJupyterUriProvider): void; - }; - - /** - * 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. See - * https://github.com/microsoft/vscode-python/wiki/Python-Environment-APIs for usage examples and more. - */ - readonly environments: { - /** - * Returns the environment configured by user in settings. Note that this can be an invalid environment, use - * {@link resolveEnvironment} to get full details. - * @param 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. - */ - getActiveEnvironmentPath(resource?: Resource): EnvironmentPath; - /** - * Sets the active environment path for the python extension for the resource. Configuration target will always - * be the workspace folder. - * @param environment : If string, it represents the full path to environment folder or python executable - * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. - * @param resource : [optional] File or workspace to scope to a particular workspace folder. - */ - updateActiveEnvironmentPath( - environment: string | EnvironmentPath | Environment, - resource?: Resource, - ): Promise; - /** - * This event is triggered when the active environment setting changes. - */ - readonly onDidChangeActiveEnvironmentPath: Event; - /** - * 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. - * - * Only reports environments in the current workspace. - */ - readonly known: readonly Environment[]; - /** - * 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. - */ - readonly onDidChangeEnvironments: Event; - /** - * 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 - * {@link RefreshOptions.forceRefresh}. - * - * Note that if there is a refresh already going on then this returns the promise for that refresh. - * @param options Additional options for refresh. - * @param token A cancellation token that indicates a refresh is no longer needed. - */ - refreshEnvironments(options?: RefreshOptions, token?: CancellationToken): Promise; - /** - * Returns details for the given environment, or `undefined` if the env is invalid. - * @param environment : If string, it represents the full path to environment folder or python executable - * for the environment. Otherwise it can be {@link Environment} or {@link EnvironmentPath} itself. - */ - resolveEnvironment( - environment: Environment | EnvironmentPath | string, - ): Promise; - /** - * Returns the environment variables used by the extension for a resource, which includes the custom - * variables configured by user in `.env` files. - * @param 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. - */ - getEnvironmentVariables(resource?: Resource): EnvironmentVariables; - /** - * This event is fired when the environment variables for a resource change. Note it's currently not - * possible to detect if environment variables in the system change, so this only fires if custom - * environment variables are updated in `.env` files. - */ - readonly onDidEnvironmentVariablesChange: Event; - }; -} - -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; -}; - -/** - * Details about the environment. Note the environment folder, type and name never changes over time. - */ -export 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: WorkspaceFolder | undefined; - } - | undefined; - /** - * Carries Python version information known at this moment, carries `undefined` for envs without python. - */ - readonly version: - | (VersionInfo & { - /** - * Value of `sys.version` in sys module if known at this moment. - */ - readonly sysVersion: string | undefined; - }) - | 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[]; -}; - -/** - * Derived form of {@link Environment} where certain properties can no longer be `undefined`. Meant to represent an - * {@link Environment} with complete information. - */ -export type ResolvedEnvironment = Environment & { - /** - * Carries complete 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 of the environment. - */ - readonly bitness: Bitness; - /** - * Value of `sys.prefix` in sys module. - */ - readonly sysPrefix: string; - }; - /** - * Carries complete Python version information, carries `undefined` for envs without python. - */ - readonly version: - | (ResolvedVersionInfo & { - /** - * Value of `sys.version` in sys module if known at this moment. - */ - readonly sysVersion: string; - }) - | undefined; -}; - -export 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 type ActiveEnvironmentPathChangeEvent = EnvironmentPath & { - /** - * Workspace folder the environment changed for. - */ - readonly resource: WorkspaceFolder | undefined; -}; - -/** - * Uri of a file inside a workspace or workspace folder itself. - */ -export type Resource = Uri | WorkspaceFolder; - -export type EnvironmentPath = { - /** - * The ID of the environment. - */ - 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. - */ -export 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. - */ -export 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. - */ -export 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. - */ -export type KnownEnvironmentTypes = 'VirtualEnvironment' | 'Conda' | 'Unknown'; - -/** - * Carries bitness for an environment. - */ -export type Bitness = '64-bit' | '32-bit' | 'Unknown'; - -/** - * The possible Python release levels. - */ -export type PythonReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; - -/** - * Release information for a Python version. - */ -export type PythonVersionRelease = { - readonly level: PythonReleaseLevel; - readonly serial: number; -}; - -export type VersionInfo = { - readonly major: number | undefined; - readonly minor: number | undefined; - readonly micro: number | undefined; - readonly release: PythonVersionRelease | undefined; -}; - -export type ResolvedVersionInfo = { - readonly major: number; - readonly minor: number; - readonly micro: number; - readonly release: PythonVersionRelease; -}; - -/** - * A record containing readonly keys. - */ -export type EnvironmentVariables = { readonly [key: string]: string | undefined }; - -export type EnvironmentVariablesChangeEvent = { - /** - * Workspace folder the environment variables changed for. - */ - readonly resource: WorkspaceFolder | undefined; - /** - * Updated value of environment variables. - */ - readonly env: EnvironmentVariables; -}; diff --git a/src/client/api/main.ts b/src/client/api/main.ts index 793ccd9e5d62..b9266a732826 100644 --- a/src/client/api/main.ts +++ b/src/client/api/main.ts @@ -388,10 +388,12 @@ export type EnvironmentVariablesChangeEvent = { readonly env: EnvironmentVariables; }; +export const PVSC_EXTENSION_ID = 'ms-python.python'; + // eslint-disable-next-line @typescript-eslint/no-namespace export namespace PythonExtension { export async function api(): Promise { - const extension = extensions.getExtension('ms-python.python'); + const extension = extensions.getExtension(PVSC_EXTENSION_ID); if (extension === undefined) { throw new Error(`Python extension is not installed or is disabled`); } diff --git a/src/client/deprecatedProposedApiTypes.ts b/src/client/deprecatedProposedApiTypes.ts index d3a7bae85f35..407cb1dab394 100644 --- a/src/client/deprecatedProposedApiTypes.ts +++ b/src/client/deprecatedProposedApiTypes.ts @@ -4,7 +4,7 @@ import { Uri, Event } from 'vscode'; import { PythonEnvKind, EnvPathType } from './pythonEnvironments/base/info'; import { ProgressNotificationEvent, GetRefreshEnvironmentsOptions } from './pythonEnvironments/base/locator'; -import { Resource } from './api/apiTypes'; +import { Resource } from './api/main'; export interface EnvironmentDetailsOptions { useCache: boolean; diff --git a/src/client/environmentApi.ts b/src/client/environmentApi.ts index e144a1fe3a1d..ee0b28ae2ff4 100644 --- a/src/client/environmentApi.ts +++ b/src/client/environmentApi.ts @@ -30,7 +30,7 @@ import { RefreshOptions, ResolvedEnvironment, Resource, -} from './api/apiTypes'; +} from './api/main'; import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi'; type ActiveEnvironmentChangeEvent = { diff --git a/src/client/extension.ts b/src/client/extension.ts index 61524a3640c8..89649d377c74 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -41,7 +41,7 @@ import { sendErrorTelemetry, sendStartupTelemetry } from './startupTelemetry'; import { IStartupDurations } from './types'; import { runAfterActivation } from './common/utils/runAfterActivation'; import { IInterpreterService } from './interpreter/contracts'; -import { PythonExtension } from './api/apiTypes'; +import { PythonExtension } from './api/main'; import { WorkspaceService } from './common/application/workspace'; import { disposeAll } from './common/utils/resourceLifecycle'; import { ProposedExtensionAPI } from './proposedApiTypes'; diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts index 8a03bd07ff20..e0e79134fc56 100644 --- a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License import { Event, Disposable, WorkspaceFolder } from 'vscode'; -import { EnvironmentTools } from '../../api/apiTypes'; +import { EnvironmentTools } from '../../api/main'; export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; export type EnvironmentProviderId = string; diff --git a/src/test/api.test.ts b/src/test/api.test.ts index c14855305d5c..488ce79073c2 100644 --- a/src/test/api.test.ts +++ b/src/test/api.test.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { expect } from 'chai'; -import { PythonExtension } from '../client/api/apiTypes'; +import { PythonExtension } from '../client/api/main'; import { ProposedExtensionAPI } from '../client/proposedApiTypes'; import { initialize } from './initialize'; diff --git a/src/test/common.ts b/src/test/common.ts index 26cb5c08bcc2..0168ee47fc37 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -10,7 +10,7 @@ import * as glob from 'glob'; import * as path from 'path'; import { coerce, SemVer } from 'semver'; import { ConfigurationTarget, Event, TextDocument, Uri } from 'vscode'; -import type { PythonExtension } from '../client/api/apiTypes'; +import type { PythonExtension } from '../client/api/main'; import { IProcessService } from '../client/common/process/types'; import { IDisposable } from '../client/common/types'; import { IServiceContainer, IServiceManager } from '../client/ioc/types'; diff --git a/src/test/environmentApi.unit.test.ts b/src/test/environmentApi.unit.test.ts index 084bc6f2eef1..76441247db49 100644 --- a/src/test/environmentApi.unit.test.ts +++ b/src/test/environmentApi.unit.test.ts @@ -37,7 +37,7 @@ import { EnvironmentVariablesChangeEvent, EnvironmentsChangeEvent, PythonExtension, -} from '../client/api/apiTypes'; +} from '../client/api/main'; suite('Python Environment API', () => { const workspacePath = 'path/to/workspace'; diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 60c7fd55a35e..8a7abca0d91c 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as vscode from 'vscode'; -import type { PythonExtension } from '../client/api/apiTypes'; +import type { PythonExtension } from '../client/api/main'; import { clearPythonPathInWorkspaceFolder, IExtensionTestApi, From 1c25b04bdcc0a3c26788804743311ab3ac503a38 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Mon, 10 Jul 2023 16:21:48 -0700 Subject: [PATCH 4/4] Add package json files --- src/client/api/package-lock.json | 63 ++++++++++++++++++++++++++++++++ src/client/api/package.json | 26 +++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/client/api/package-lock.json create mode 100644 src/client/api/package.json diff --git a/src/client/api/package-lock.json b/src/client/api/package-lock.json new file mode 100644 index 000000000000..137262c6523b --- /dev/null +++ b/src/client/api/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "@vscode/python-extension", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@vscode/python-extension", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/vscode": "^1.78.0" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "typescript": "^4.7.2" + } + }, + "node_modules/@types/node": { + "version": "16.18.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.27.tgz", + "integrity": "sha512-GFfndd/RINWD19W+xNJ9Qh/sOZ5ieTiOSagA86ER/12i/l+MEnQxsbldGRF23azWjRfe7zUlAldyrwN84a1E5w==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.78.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.0.tgz", + "integrity": "sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@types/node": { + "version": "16.18.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.27.tgz", + "integrity": "sha512-GFfndd/RINWD19W+xNJ9Qh/sOZ5ieTiOSagA86ER/12i/l+MEnQxsbldGRF23azWjRfe7zUlAldyrwN84a1E5w==", + "dev": true + }, + "@types/vscode": { + "version": "1.78.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.78.0.tgz", + "integrity": "sha512-LJZIJpPvKJ0HVQDqfOy6W4sNKUBBwyDu1Bs8chHBZOe9MNuKTJtidgZ2bqjhmmWpUb0TIIqv47BFUcVmAsgaVA==" + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } +} diff --git a/src/client/api/package.json b/src/client/api/package.json new file mode 100644 index 000000000000..901ab3f13cfd --- /dev/null +++ b/src/client/api/package.json @@ -0,0 +1,26 @@ +{ + "name": "@vscode/python-extension", + "description": "VSCode Python extension's public API", + "version": "1.0.0", + "publisher": "ms-python", + "author": { + "name": "Microsoft Corporation" + }, + "types": "./index.d.ts", + "license": "MIT", + "homepage": "https://github.com/Microsoft/vscode-python", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-python" + }, + "bugs": { + "url": "https://github.com/Microsoft/vscode-python/issues" + }, + "dependencies": { + "@types/vscode": "^1.78.0" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "typescript": "^4.7.2" + } +}