Skip to content

Commit

Permalink
Refactor API code in a separate module (#21604)
Browse files Browse the repository at this point in the history
For #20949

We plan to publish the "api" module as an npm package. Inspired from
https://insiders.vscode.dev/github/microsoft/vscode-wasi/blob/main/wasm-wasi/src/api/main.ts#L408.
  • Loading branch information
Kartik Raj authored Jul 10, 2023
1 parent 049ca8b commit d3b8985
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 28 deletions.
8 changes: 4 additions & 4 deletions src/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/main';
import { isTestExecution, PYTHON_LANGUAGE } from './common/constants';
import { IConfigurationService, Resource } from './common/types';
import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers';
Expand All @@ -29,22 +29,22 @@ export function buildApi(
serviceManager: IServiceManager,
serviceContainer: IServiceContainer,
discoveryApi: IDiscoveryAPI,
): IExtensionApi {
): PythonExtension {
const configurationService = serviceContainer.get<IConfigurationService>(IConfigurationService);
const interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);
serviceManager.addSingleton<JupyterExtensionIntegration>(JupyterExtensionIntegration, JupyterExtensionIntegration);
const jupyterIntegration = serviceContainer.get<JupyterExtensionIntegration>(JupyterExtensionIntegration);
const outputChannel = serviceContainer.get<ILanguageServerOutputChannel>(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.
*/
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
*/
Expand Down
65 changes: 60 additions & 5 deletions src/client/apiTypes.ts → src/client/api/main.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
// 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';
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 IExtensionApi {
export interface PythonExtension {
/**
* Promise indicating whether all parts of the extension have completed loading or not.
* @type {Promise<void>}
* @memberof IExtensionApi
*/
ready: Promise<void>;
jupyter: {
Expand Down Expand Up @@ -128,6 +125,47 @@ export interface IExtensionApi {
};
}

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<JupyterServerUriHandle | 'back' | undefined>;
getServerUri(handle: JupyterServerUriHandle): Promise<IJupyterServerUri>;
}

interface IDataFrameInfo {
columns?: { key: string; type: ColumnType }[];
indexColumn?: string;
rowCount?: number;
}

export interface IDataViewerDataProvider {
dispose(): void;
getDataFrameInfo(): Promise<IDataFrameInfo>;
getAllRows(): Promise<IRowsResponse>;
getRows(start: number, end: number): Promise<IRowsResponse>;
}

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
Expand Down Expand Up @@ -349,3 +387,20 @@ 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<PythonExtension> {
const extension = extensions.getExtension(PVSC_EXTENSION_ID);
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;
}
}
63 changes: 63 additions & 0 deletions src/client/api/package-lock.json

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

26 changes: 26 additions & 0 deletions src/client/api/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
2 changes: 1 addition & 1 deletion src/client/deprecatedProposedApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/main';

export interface EnvironmentDetailsOptions {
useCache: boolean;
Expand Down
8 changes: 4 additions & 4 deletions src/client/environmentApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import {
EnvironmentTools,
EnvironmentType,
EnvironmentVariablesChangeEvent,
IExtensionApi,
PythonExtension,
RefreshOptions,
ResolvedEnvironment,
Resource,
} from './apiTypes';
} from './api/main';
import { buildEnvironmentCreationApi } from './pythonEnvironments/creation/createEnvApi';

type ActiveEnvironmentChangeEvent = {
Expand Down Expand Up @@ -114,7 +114,7 @@ function filterUsingVSCodeContext(e: PythonEnvInfo) {
export function buildEnvironmentApi(
discoveryApi: IDiscoveryAPI,
serviceContainer: IServiceContainer,
): IExtensionApi['environments'] {
): PythonExtension['environments'] {
const interpreterPathService = serviceContainer.get<IInterpreterPathService>(IInterpreterPathService);
const configService = serviceContainer.get<IConfigurationService>(IConfigurationService);
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/main';
import { WorkspaceService } from './common/application/workspace';
import { disposeAll } from './common/utils/resourceLifecycle';
import { ProposedExtensionAPI } from './proposedApiTypes';
Expand All @@ -58,8 +58,8 @@ let activatedServiceContainer: IServiceContainer | undefined;
/////////////////////////////
// public functions

export async function activate(context: IExtensionContext): Promise<IExtensionApi> {
let api: IExtensionApi;
export async function activate(context: IExtensionContext): Promise<PythonExtension> {
let api: PythonExtension;
let ready: Promise<void>;
let serviceContainer: IServiceContainer;
try {
Expand Down Expand Up @@ -103,7 +103,7 @@ async function activateUnsafe(
context: IExtensionContext,
startupStopWatch: StopWatch,
startupDurations: IStartupDurations,
): Promise<[IExtensionApi & ProposedExtensionAPI, Promise<void>, IServiceContainer]> {
): Promise<[PythonExtension & ProposedExtensionAPI, Promise<void>, IServiceContainer]> {
// Add anything that we got from initializing logs to dispose.
context.subscriptions.push(...logDispose);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License

import { Event, Disposable, WorkspaceFolder } from 'vscode';
import { EnvironmentTools } from '../../apiTypes';
import { EnvironmentTools } from '../../api/main';

export type CreateEnvironmentUserActions = 'Back' | 'Cancel';
export type EnvironmentProviderId = string;
Expand Down
4 changes: 2 additions & 2 deletions src/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// Licensed under the MIT License.

import { expect } from 'chai';
import { IExtensionApi } from '../client/apiTypes';
import { PythonExtension } from '../client/api/main';
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();
});
Expand Down
4 changes: 2 additions & 2 deletions src/test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/main';
import { IProcessService } from '../client/common/process/types';
import { IDisposable } from '../client/common/types';
import { IServiceContainer, IServiceManager } from '../client/ioc/types';
Expand Down Expand Up @@ -438,7 +438,7 @@ export async function isPythonVersion(...versions: string[]): Promise<boolean> {
}
}

export interface IExtensionTestApi extends IExtensionApi, ProposedExtensionAPI {
export interface IExtensionTestApi extends PythonExtension, ProposedExtensionAPI {
serviceContainer: IServiceContainer;
serviceManager: IServiceManager;
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/environmentApi.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ import {
ActiveEnvironmentPathChangeEvent,
EnvironmentVariablesChangeEvent,
EnvironmentsChangeEvent,
IExtensionApi,
} from '../client/apiTypes';
PythonExtension,
} from '../client/api/main';

suite('Python Environment API', () => {
const workspacePath = 'path/to/workspace';
Expand All @@ -57,7 +57,7 @@ suite('Python Environment API', () => {
let onDidChangeEnvironments: EventEmitter<PythonEnvCollectionChangedEvent>;
let onDidChangeEnvironmentVariables: EventEmitter<Uri | undefined>;

let environmentApi: IExtensionApi['environments'];
let environmentApi: PythonExtension['environments'];

setup(() => {
serviceContainer = typemoq.Mock.ofType<IServiceContainer>();
Expand Down
4 changes: 2 additions & 2 deletions src/test/initialize.ts
Original file line number Diff line number Diff line change
@@ -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/main';
import {
clearPythonPathInWorkspaceFolder,
IExtensionTestApi,
Expand Down Expand Up @@ -42,7 +42,7 @@ export async function initialize(): Promise<IExtensionTestApi> {
return (api as any) as IExtensionTestApi;
}
export async function activateExtension() {
const extension = vscode.extensions.getExtension<IExtensionApi>(PVSC_EXTENSION_ID_FOR_TESTS)!;
const extension = vscode.extensions.getExtension<PythonExtension>(PVSC_EXTENSION_ID_FOR_TESTS)!;
const api = await extension.activate();
// Wait until its ready to use.
await api.ready;
Expand Down

0 comments on commit d3b8985

Please sign in to comment.