Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Python interpreter not found error #5793

Merged
merged 4 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ suite('Python Runtime Session', () => {
} as PythonEnvironment;

runtimeMetadata = {
extraRuntimeData: { pythonEnvironmentId: interpreter.id },
extraRuntimeData: { pythonPath: interpreter.path },
} as positron.LanguageRuntimeMetadata;

const interpreterService = {
getInterpreters: () => [interpreter],
getInterpreterDetails: (_pythonPath, _resource) => Promise.resolve(interpreter),
} as IInterpreterService;

const installer = ({
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
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,8 @@ export class RuntimeStartupService extends Disposable implements IRuntimeStartup
await this.restoreWorkspaceSessions(sessions);
}
} catch (err) {
this._logService.error(`Could not restore workspace sessions: ${err} ` +
`(data: ${storedSessions})`);
this._logService.error(`Could not restore workspace sessions: ${err?.stack ?? err} ` +
`(data: ${JSON.stringify(storedSessions)})`);
}
}

Expand Down
7 changes: 3 additions & 4 deletions test/e2e/areas/new-project-wizard/new-project-python.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ test.describe('Python - New Project Wizard', { tag: [tags.NEW_PROJECT_WIZARD] },
await app.workbench.quickaccess.runCommand('workbench.action.toggleAuxiliaryBar');
});

// Skip test due to https://github.com/posit-dev/positron/issues/5730. Both have to skipped as they depend o
test.skip('With ipykernel already installed [C609619]', {
test('With ipykernel already installed [C609619]', {
tag: [tags.WIN],
annotation: [{ type: 'issue', description: 'https://github.com/posit-dev/positron/issues/5730' }],
}, async function ({ app, page, python }) {
Expand Down Expand Up @@ -100,7 +99,7 @@ test.describe('Python - New Project Wizard', { tag: [tags.NEW_PROJECT_WIZARD] },
await expect(app.workbench.positronConsole.activeConsole.getByText('>>>')).toBeVisible({ timeout: 90000 });
});

test.skip('With ipykernel not already installed [C609617]', {
test('With ipykernel not already installed [C609617]', {
tag: [tags.WIN],
annotation: [{ type: 'issue', description: 'https://github.com/posit-dev/positron/issues/5730' }],
}, async function ({ app, page }) {
Expand All @@ -116,7 +115,7 @@ test.describe('Python - New Project Wizard', { tag: [tags.NEW_PROJECT_WIZARD] },
await app.workbench.positronInterpreterDropdown.closeInterpreterDropdown();
await app.workbench.positronConsole.typeToConsole('pip uninstall -y ipykernel');
await app.workbench.positronConsole.sendEnterKey();
await app.workbench.positronConsole.waitForConsoleContents('Successfully uninstalled ipykernel')
await app.workbench.positronConsole.waitForConsoleContents('Successfully uninstalled ipykernel');

// Create a new Python project and use the selected python interpreter
await pw.startNewProject(ProjectType.PYTHON_PROJECT);
Expand Down
Loading