Skip to content

Commit

Permalink
resolve the runtime interpreter on start
Browse files Browse the repository at this point in the history
  • Loading branch information
seeM committed Dec 18, 2024
1 parent ae95182 commit bff9047
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 39 deletions.
58 changes: 29 additions & 29 deletions extensions/positron-python/src/client/positron/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
/** The current state of the runtime */
private _state: positron.RuntimeState = positron.RuntimeState.Uninitialized;

/** The service for getting the Python extension interpreter path */
/** The service for managing Python environments */
private _interpreterService: IInterpreterService;

/** The service for managing the active Python interpreter path */
private _interpreterPathService: IInterpreterPathService;

/**
Expand All @@ -82,37 +85,28 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
*/
private _parentIdsByOutputCommId = new Map<string, string>();

dynState: positron.LanguageRuntimeDynState;
/** The Python interpreter executable path */
private _pythonPath: string;

private readonly interpreter: PythonEnvironment;
dynState: positron.LanguageRuntimeDynState;

constructor(
readonly runtimeMetadata: positron.LanguageRuntimeMetadata,
readonly metadata: positron.RuntimeSessionMetadata,
readonly serviceContainer: IServiceContainer,
readonly kernelSpec?: JupyterKernelSpec | undefined,
) {
const interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);

this.onDidReceiveRuntimeMessage = this._messageEmitter.event;
this.onDidChangeRuntimeState = this._stateEmitter.event;
this.onDidEndSession = this._exitEmitter.event;

// Extract the extra data from the runtime metadata; it contains the
// environment ID that was saved when the metadata was created.
// Python path that was saved when the metadata was created.
const extraData: PythonRuntimeExtraData = runtimeMetadata.extraRuntimeData as PythonRuntimeExtraData;
if (!extraData || !extraData.pythonEnvironmentId) {
throw new Error(`Runtime metadata missing Python environment ID: ${JSON.stringify(runtimeMetadata)}`);
if (!extraData || !extraData.pythonPath) {
throw new Error(`Runtime metadata missing Python path: ${JSON.stringify(runtimeMetadata)}`);
}

const interpreter = interpreterService.getInterpreters().find((i) => i.id === extraData.pythonEnvironmentId);
if (!interpreter) {
const interpreterIds = interpreterService.getInterpreters().map((i) => `\n- ${i.id}`);
throw new Error(
`Interpreter ${extraData.pythonEnvironmentId} (path: ${extraData.pythonPath}) not found in available Python interpreters: ${interpreterIds}`,
);
}
this.interpreter = interpreter;
this._pythonPath = extraData.pythonPath;

this._queue = new PQueue({ concurrency: 1 });

Expand All @@ -125,6 +119,7 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
this.onStateChange(state);
});

this._interpreterService = serviceContainer.get<IInterpreterService>(IInterpreterService);
this._interpreterPathService = serviceContainer.get<IInterpreterPathService>(IInterpreterPathService);
}

Expand Down Expand Up @@ -234,7 +229,7 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
}
}

private async _installIpykernel(): Promise<void> {
private async _installIpykernel(interpreter: PythonEnvironment): Promise<void> {
// Get the installer service
const installer = this.serviceContainer.get<IInstaller>(IInstaller);

Expand All @@ -246,16 +241,16 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
const hasCompatibleKernel = await installer.isProductVersionCompatible(
Product.ipykernel,
IPYKERNEL_VERSION,
this.interpreter,
interpreter,
);

if (hasCompatibleKernel !== ProductInstallStatus.Installed) {
// Check if sqlite3 if installed before attempting to install ipykernel
// https://github.com/posit-dev/positron/issues/4698
const hasSqlite3 = await installer.isInstalled(Product.sqlite3, this.interpreter);
const hasSqlite3 = await installer.isInstalled(Product.sqlite3, interpreter);
if (!hasSqlite3) {
throw new Error(
`The Python sqlite3 extension is required but not installed for interpreter: ${this.interpreter?.displayName}. Missing the system library for SQLite?`,
`The Python sqlite3 extension is required but not installed for interpreter: ${interpreter?.displayName}. Missing the system library for SQLite?`,
);
}

Expand All @@ -280,7 +275,7 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs

const response = await installer.promptToInstall(
product,
this.interpreter,
interpreter,
installerToken,
undefined,
installOptions,
Expand All @@ -289,12 +284,12 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs

switch (response) {
case InstallerResponse.Installed:
traceInfo(`Successfully installed ipykernel for ${this.interpreter?.displayName}`);
traceInfo(`Successfully installed ipykernel for ${interpreter?.displayName}`);
break;
case InstallerResponse.Ignore:
case InstallerResponse.Disabled:
throw new Error(
`Could not start runtime: failed to install ipykernel for ${this.interpreter?.displayName}.`,
`Could not start runtime: failed to install ipykernel for ${interpreter?.displayName}.`,
);
default:
throw new Error(`Unknown installer response type: ${response}`);
Expand All @@ -303,9 +298,14 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
}

async start(): Promise<positron.LanguageRuntimeInfo> {
const interpreter = await this._interpreterService.getInterpreterDetails(this._pythonPath);
if (!interpreter) {
throw new Error(`Could not start runtime: failed to resolve interpreter ${this._pythonPath}`);
}

// Ensure the LSP client instance is created
if (!this._lsp) {
await this.createLsp();
await this.createLsp(interpreter);
}

// Ensure the Jupyter kernel instance is created
Expand All @@ -314,14 +314,14 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
}

// Ensure that the ipykernel module is installed for the interpreter.
await this._installIpykernel();
await this._installIpykernel(interpreter);

if (this.metadata.sessionMode === positron.LanguageRuntimeSessionMode.Console) {
// Update the active environment in the Python extension.
this._interpreterPathService.update(
undefined,
vscode.ConfigurationTarget.WorkspaceFolder,
this.interpreter.path,
interpreter.path,
);
}

Expand Down Expand Up @@ -371,7 +371,7 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
// Keep track of LSP init to avoid stopping in the middle of startup
private _lspStarting: Thenable<void> = Promise.resolve();

private async createLsp() {
private async createLsp(interpreter: PythonEnvironment): Promise<void> {
traceInfo(`createPythonSession: resolving LSP services`);
const environmentService = this.serviceContainer.get<IEnvironmentVariablesProvider>(
IEnvironmentVariablesProvider,
Expand All @@ -389,7 +389,7 @@ export class PythonRuntimeSession implements positron.LanguageRuntimeSession, vs
);

const resource = workspaceService.workspaceFolders?.[0].uri;
await analysisOptions.initialize(resource, this.interpreter);
await analysisOptions.initialize(resource, interpreter);
const languageClientOptions = await analysisOptions.getAnalysisOptions();

this._lsp = new PythonLsp(
Expand Down
11 changes: 1 addition & 10 deletions extensions/positron-reticulate/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,15 +311,6 @@ class ReticulateRuntimeSession implements positron.LanguageRuntimeSession {

const output = runtimeMetadata;
output.runtimePath = interpreterPath;

// Uses the Positron Python extension API to query the environment id
// given it's path.
const api = vscode.extensions.getExtension('ms-python.python');
if (!api) {
throw new Error('Failed to find the Positron Python extension API.');
}
const env = await api.exports.environments.resolveEnvironment(interpreterPath);
output.extraRuntimeData.pythonEnvironmentId = env.id;
output.extraRuntimeData.pythonPath = interpreterPath;

return output;
Expand Down Expand Up @@ -620,7 +611,7 @@ async function getRSession_(): Promise<positron.LanguageRuntimeSession> {

class ReticulateRuntimeMetadata implements positron.LanguageRuntimeMetadata {
extraRuntimeData: any = {
pythonEnvironmentId: 'reticulate',
pythonPath: 'Managed by the reticulate package',
};
base64EncodedIconSvg: string | undefined;
constructor() {
Expand Down

0 comments on commit bff9047

Please sign in to comment.