diff --git a/extension/resources/walkthrough/install-dvc.md b/extension/resources/walkthrough/install-dvc.md
index 1a2caff988..f881c531ce 100644
--- a/extension/resources/walkthrough/install-dvc.md
+++ b/extension/resources/walkthrough/install-dvc.md
@@ -22,3 +22,6 @@ right environment:
+
+> **Note**: The correct Python interpreter must be set for the current workspace
+> when relying on the Python extension for auto environment activation.
diff --git a/extension/src/extensions/python.ts b/extension/src/extensions/python.ts
index 7c72c56afb..7c310345d1 100644
--- a/extension/src/extensions/python.ts
+++ b/extension/src/extensions/python.ts
@@ -1,4 +1,4 @@
-import { Event, Uri } from 'vscode'
+import { commands, Event, Uri } from 'vscode'
import { executeProcess } from '../processExecution'
import { getExtensionAPI, isInstalled } from '../vscode/extensions'
@@ -54,3 +54,7 @@ export const getOnDidChangePythonExecutionDetails = async () => {
}
export const isPythonExtensionInstalled = () => isInstalled(PYTHON_EXTENSION_ID)
+
+export const selectPythonInterpreter = () => {
+ commands.executeCommand('python.setInterpreter')
+}
diff --git a/extension/src/setup.test.ts b/extension/src/setup.test.ts
index edfc6fb86f..987b1351a1 100644
--- a/extension/src/setup.test.ts
+++ b/extension/src/setup.test.ts
@@ -1,5 +1,5 @@
-import { resolve } from 'path'
-import { extensions, Extension } from 'vscode'
+import { join, resolve } from 'path'
+import { extensions, Extension, commands } from 'vscode'
import { setup, setupWorkspace } from './setup'
import { flushPromises } from './test/util/jest'
import {
@@ -16,6 +16,8 @@ import {
import { getFirstWorkspaceFolder } from './vscode/workspaceFolders'
import { Toast } from './vscode/toast'
import { Response } from './vscode/response'
+import { VscodePython } from './extensions/python'
+import { executeProcess } from './processExecution'
jest.mock('vscode')
jest.mock('./vscode/config')
@@ -23,8 +25,34 @@ jest.mock('./vscode/resourcePicker')
jest.mock('./vscode/quickPick')
jest.mock('./vscode/toast')
jest.mock('./vscode/workspaceFolders')
+jest.mock('./processExecution')
const mockedExtensions = jest.mocked(extensions)
+const mockedCommands = jest.mocked(commands)
+const mockedExecuteCommand = jest.fn()
+mockedCommands.executeCommand = mockedExecuteCommand
+
+const mockedExecuteProcess = jest.mocked(executeProcess)
+
+const mockedGetExtension = jest.fn()
+mockedExtensions.getExtension = mockedGetExtension
+
+const mockedReady = jest.fn()
+
+const mockedSettings = {
+ getExecutionDetails: () => ({
+ execCommand: [join('some', 'bin', 'path')]
+ })
+}
+
+const mockedVscodePythonAPI = {
+ ready: mockedReady,
+ settings: mockedSettings
+} as unknown as VscodePython
+
+const mockedVscodePython = {
+ activate: () => Promise.resolve(mockedVscodePythonAPI)
+}
const mockedCanRunCli = jest.fn()
const mockedHasRoots = jest.fn()
@@ -256,8 +284,14 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(undefined)
+ mockedExecuteProcess.mockImplementation(({ executable }) =>
+ Promise.resolve(executable)
+ )
+ mockedReady.mockResolvedValue(true)
+ mockedGetExtension.mockReturnValue(mockedVscodePython)
await setup(extension)
+ await flushPromises()
expect(mockedSetRoots).toBeCalledTimes(1)
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
@@ -270,6 +304,11 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(Response.SETUP_WORKSPACE)
+ mockedExecuteProcess.mockImplementation(({ executable }) =>
+ Promise.resolve(executable)
+ )
+ mockedReady.mockResolvedValue(true)
+ mockedGetExtension.mockReturnValue(mockedVscodePython)
await setup(extension)
await flushPromises()
@@ -277,6 +316,30 @@ describe('setup', () => {
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
expect(mockedSetupWorkspace).toBeCalledTimes(1)
+ expect(mockedExecuteCommand).not.toBeCalled()
+ expect(mockedSetUserConfigValue).not.toBeCalled()
+ expect(mockedResetMembers).toBeCalledTimes(1)
+ expect(mockedInitialize).not.toBeCalled()
+ })
+
+ it('should try to select the python interpreter if the workspace contains a DVC project, the cli cannot be found and the user decides to select the python interpreter', async () => {
+ mockedGetFirstWorkspaceFolder.mockReturnValueOnce(mockedCwd)
+ mockedHasRoots.mockReturnValueOnce(true)
+ mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
+ mockedWarnWithOptions.mockResolvedValueOnce(Response.SELECT_INTERPRETER)
+ mockedExecuteProcess.mockImplementation(({ executable }) =>
+ Promise.resolve(executable)
+ )
+ mockedReady.mockResolvedValue(true)
+ mockedGetExtension.mockReturnValue(mockedVscodePython)
+
+ await setup(extension)
+ await flushPromises()
+ expect(mockedSetRoots).toBeCalledTimes(1)
+ expect(mockedGetConfigValue).toBeCalledTimes(1)
+ expect(mockedWarnWithOptions).toBeCalledTimes(1)
+ expect(mockedSetupWorkspace).toBeCalledTimes(0)
+ expect(mockedExecuteCommand).toBeCalledTimes(1)
expect(mockedSetUserConfigValue).not.toBeCalled()
expect(mockedResetMembers).toBeCalledTimes(1)
expect(mockedInitialize).not.toBeCalled()
@@ -287,6 +350,11 @@ describe('setup', () => {
mockedHasRoots.mockReturnValueOnce(true)
mockedCanRunCli.mockRejectedValueOnce(new Error('command not found: dvc'))
mockedWarnWithOptions.mockResolvedValueOnce(Response.NEVER)
+ mockedExecuteProcess.mockImplementation(({ executable }) =>
+ Promise.resolve(executable)
+ )
+ mockedReady.mockResolvedValue(true)
+ mockedGetExtension.mockReturnValue(mockedVscodePython)
await setup(extension)
await flushPromises()
@@ -294,6 +362,7 @@ describe('setup', () => {
expect(mockedGetConfigValue).toBeCalledTimes(1)
expect(mockedWarnWithOptions).toBeCalledTimes(1)
expect(mockedSetupWorkspace).not.toBeCalled()
+ expect(mockedExecuteCommand).not.toBeCalled()
expect(mockedSetUserConfigValue).toBeCalledTimes(1)
expect(mockedResetMembers).toBeCalledTimes(1)
expect(mockedInitialize).not.toBeCalled()
diff --git a/extension/src/setup.ts b/extension/src/setup.ts
index e6c998476e..638f837915 100644
--- a/extension/src/setup.ts
+++ b/extension/src/setup.ts
@@ -15,7 +15,11 @@ import { getFirstWorkspaceFolder } from './vscode/workspaceFolders'
import { Response } from './vscode/response'
import { getSelectTitle, Title } from './vscode/title'
import { Toast } from './vscode/toast'
-import { isPythonExtensionInstalled } from './extensions/python'
+import {
+ getPythonBinPath,
+ isPythonExtensionInstalled,
+ selectPythonInterpreter
+} from './extensions/python'
const setConfigPath = async (
option: ConfigKey,
@@ -159,22 +163,49 @@ export const setupWorkspace = async (): Promise => {
return pickCliPath()
}
+const getToastText = async (
+ isPythonExtensionInstalled: boolean
+): Promise => {
+ const text = 'An error was thrown when trying to access the CLI.'
+ if (!isPythonExtensionInstalled) {
+ return text
+ }
+ const binPath = await getPythonBinPath()
+
+ return (
+ text +
+ ` For auto Python environment activation ensure the correct interpreter is set. Active Python interpreter: ${binPath}. `
+ )
+}
+
+const getToastOptions = (isPythonExtensionInstalled: boolean): Response[] => {
+ return isPythonExtensionInstalled
+ ? [Response.SETUP_WORKSPACE, Response.SELECT_INTERPRETER, Response.NEVER]
+ : [Response.SETUP_WORKSPACE, Response.NEVER]
+}
+
const warnUserCLIInaccessible = async (
extension: IExtension
): Promise => {
if (getConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE)) {
return
}
+
+ const isMsPythonInstalled = isPythonExtensionInstalled()
+ const warningText = await getToastText(isMsPythonInstalled)
+
const response = await Toast.warnWithOptions(
- 'An error was thrown when trying to access the CLI.',
- Response.SETUP_WORKSPACE,
- Response.NEVER
+ warningText,
+ ...getToastOptions(isMsPythonInstalled)
)
- if (response === Response.SETUP_WORKSPACE) {
- extension.setupWorkspace()
- }
- if (response === Response.NEVER) {
- setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
+
+ switch (response) {
+ case Response.SELECT_INTERPRETER:
+ return selectPythonInterpreter()
+ case Response.SETUP_WORKSPACE:
+ return extension.setupWorkspace()
+ case Response.NEVER:
+ return setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
}
}
diff --git a/extension/src/vscode/response.ts b/extension/src/vscode/response.ts
index 2769d77327..37b2b9da1d 100644
--- a/extension/src/vscode/response.ts
+++ b/extension/src/vscode/response.ts
@@ -7,6 +7,7 @@ export enum Response {
NEVER = "Don't Show Again",
NO = 'No',
SELECT_MOST_RECENT = 'Select Most Recent',
+ SELECT_INTERPRETER = 'Select Python Interpreter',
SETUP_WORKSPACE = 'Setup The Workspace',
SHOW = 'Show',
TURN_OFF = 'Turn Off',