diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 32837df1b8bd..552e2f929276 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -12,6 +12,7 @@ export const KNOWN_PATH_SERVICE = 'KnownPathsService'; export const GLOBAL_VIRTUAL_ENV_SERVICE = 'VirtualEnvService'; export const WORKSPACE_VIRTUAL_ENV_SERVICE = 'WorkspaceVirtualEnvService'; export const PIPENV_SERVICE = 'PipEnvService'; +export const POETRY_ENV_SERVICE = 'PoetryEnvService'; export const IInterpreterVersionService = Symbol('IInterpreterVersionService'); export interface IInterpreterVersionService { getVersion(pythonPath: string, defaultValue: string): Promise; @@ -66,6 +67,7 @@ export interface ICondaService { export enum InterpreterType { Unknown = 'Unknown', Conda = 'Conda', + Poetry = 'Poetry', VirtualEnv = 'VirtualEnv', Pipenv = 'PipEnv', Pyenv = 'Pyenv', @@ -124,6 +126,9 @@ export interface IPipEnvService { isRelatedPipEnvironment(dir: string, pythonPath: string): Promise; } +export const IPoetryEnvService = Symbol('IPoetryEnvService'); +export interface IPoetryEnvService { +} export const IInterpreterLocatorHelper = Symbol('IInterpreterLocatorHelper'); export interface IInterpreterLocatorHelper { mergeInterpreters(interpreters: PythonInterpreter[]): Promise; diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index ac9c0c73147f..bfe24334298f 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -107,6 +107,9 @@ export class InterpreterHelper implements IInterpreterHelper { case InterpreterType.VirtualEnv: { return 'virtualenv'; } + case InterpreterType.Poetry: { + return 'poetry'; + } default: { return ''; } diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index 9bb0753cb343..d04cf3f6081e 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -17,7 +17,8 @@ import { PIPENV_SERVICE, PythonInterpreter, WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE + WORKSPACE_VIRTUAL_ENV_SERVICE, + POETRY_ENV_SERVICE } from '../contracts'; import { InterpreterFilter } from './services/interpreterFilter'; import { IInterpreterFilter } from './types'; @@ -107,6 +108,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi [CONDA_ENV_SERVICE, undefined], [CONDA_ENV_FILE_SERVICE, undefined], [PIPENV_SERVICE, undefined], + [POETRY_ENV_SERVICE, undefined], [GLOBAL_VIRTUAL_ENV_SERVICE, undefined], [WORKSPACE_VIRTUAL_ENV_SERVICE, undefined], [KNOWN_PATH_SERVICE, undefined], diff --git a/src/client/interpreter/locators/services/poetryEnvService.ts b/src/client/interpreter/locators/services/poetryEnvService.ts new file mode 100644 index 000000000000..6afb1e79df66 --- /dev/null +++ b/src/client/interpreter/locators/services/poetryEnvService.ts @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable, inject } from 'inversify'; +import { IServiceContainer } from '../../../ioc/types'; +import { CacheableLocatorService } from './cacheableLocatorService'; +import { POETRY_ENV_SERVICE, PythonInterpreter, IInterpreterHelper, InterpreterType } from '../../contracts'; +import { PoetryServce } from './poetryService'; +import { Resource } from '../../../common/types'; +import { traceError, traceDecorators } from '../../../common/logger'; +import { noop } from '../../../common/utils/misc'; + +/** + * Interpreter locator for Poetry. + * + * @export + * @class PoetryEnvService + * @extends {CacheableLocatorService} + */ +@injectable() +export class PoetryEnvService extends CacheableLocatorService { + constructor( + @inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(PoetryServce) private readonly poetryService: PoetryServce, + @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper + ) { + super(POETRY_ENV_SERVICE, serviceContainer, true); + } + public dispose(): void { + noop(); + } + protected async getInterpretersImplementation(resource: Resource): Promise { + return this.getPoetryInterpreters(resource).catch(ex => { + traceError('Failed to get Poetry Interpreters', ex); + return []; + }); + } + + @traceDecorators.error('Failed to get Poetry Interepters') + protected async getPoetryInterpreters(resource: Resource): Promise { + if (!(await this.poetryService.isInstalled(resource))) { + return []; + } + + const interpreterPaths = await this.poetryService.getEnvironments(resource); + const items = await Promise.all( + interpreterPaths.map(item => { + return this.helper + .getInterpreterInformation(item) + .then(info => { + if (!info) { + return; + } + return { + ...info, + type: InterpreterType.Poetry + }; + }) + .catch(ex => { + // Handle each error, we don't want everything to fail. + traceError(`Failed to get interpreter information for Poetry Interpreter, ${item}`, ex); + return; + }); + }) + ); + + return items.filter(item => !!item).map(item => item! as PythonInterpreter); + } +} diff --git a/src/client/interpreter/locators/services/poetryService.ts b/src/client/interpreter/locators/services/poetryService.ts new file mode 100644 index 000000000000..3d35fd0b5ae7 --- /dev/null +++ b/src/client/interpreter/locators/services/poetryService.ts @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable, inject } from 'inversify'; +import { IConfigurationService, IDisposable, IDisposableRegistry, Resource } from '../../../common/types'; +import { IProcessServiceFactory } from '../../../common/process/types'; +import { cache } from '../../../common/utils/decorators'; +import { traceError } from '../../../common/logger'; +import { IFileSystem } from '../../../common/platform/types'; +import { lookForInterpretersInDirectory } from '../helpers'; +const flatten = require('lodash/flatten') as typeof import('lodash/flatten'); + +const cacheEnvDuration = 10 * 60 * 1000; +const cacheIsInstalledDuration = 60 * 1000; + +@injectable() +export class PoetryServce implements IDisposable { + private readonly disposables: IDisposable[] = []; + constructor( + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, + @inject(IFileSystem) private readonly fs: IFileSystem, + @inject(IProcessServiceFactory) private readonly processFactory: IProcessServiceFactory + ) { + disposableRegistry.push(this); + } + + public dispose() { + this.disposables.forEach(d => d.dispose()); + } + @cache(cacheIsInstalledDuration) + public async isInstalled(resource: Resource): Promise { + const processService = await this.processFactory.create(resource); + return processService + .exec(this.configurationService.getSettings(resource).poetryPath, ['--version']) + .then(output => (output.stderr ? false : true)) + .catch(() => false); + } + + // @cache(cacheEnvDuration) + // public async getCurrentEnvironment(resource: Resource): Promise { + // const processService = await this.processFactory.create(resource); + // const dir = await processService + // .exec(this.configurationService.getSettings(resource).poetryPath, ['env', 'info', '--path']) + // .then(out => { + // if (out.stderr) { + // traceError('Failed to get current environment from Poetry', out.stderr); + // return ''; + // } + // return out.stdout.endsWith('%') ? out.stdout.substring(0, out.stdout.length - 1).trim() : out.stdout.trim(); + // }) + // .catch(ex => { + // traceError('Failed to get current environment from Poetry', ex); + // return ''; + // }); + + // const interpreters = await this.getInterpretersInDirectory(dir); + // return interpreters.length === 0 ? undefined : interpreters[0]; + // } + @cache(cacheEnvDuration) + public async getEnvironments(resource: Resource): Promise { + const processService = await this.processFactory.create(resource); + const output = await processService + .exec(this.configurationService.getSettings(resource).poetryPath, ['env', 'list', '--full-path']) + .then(out => { + if (out.stderr) { + traceError('Failed to get a list of environments from Poetry', out.stderr); + return ''; + } + return out.stdout; + }) + .catch(ex => { + traceError('Failed to get a list of environments from Poetry', ex); + return ''; + }); + + const interpreters = output + .splitLines({ trim: true, removeEmptyEntries: true }) + .map(line => { + if (line.endsWith('(Activated)')) { + return line.substring(0, line.length - '(Activated)'.length).trim(); + } + return line; + }) + .map(dir => this.getInterpretersInDirectory(dir)); + + return Promise.all(interpreters).then(listOfInterpreters => flatten(listOfInterpreters)); + } + + /** + * Return the interpreters in the given directory. + */ + private async getInterpretersInDirectory(dir: string) { + const exists = this.fs.directoryExists(dir); + if (exists) { + return lookForInterpretersInDirectory(dir, this.fs); + } + return []; + } +} diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index d1796e922717..4aaceb3df622 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -45,7 +45,8 @@ import { KNOWN_PATH_SERVICE, PIPENV_SERVICE, WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE + WORKSPACE_VIRTUAL_ENV_SERVICE, + POETRY_ENV_SERVICE } from './contracts'; import { InterpreterDisplay } from './display'; import { InterpreterSelectionTip } from './display/interpreterSelectionTip'; @@ -78,6 +79,7 @@ import { CondaInheritEnvPrompt } from './virtualEnvs/condaInheritEnvPrompt'; import { VirtualEnvironmentManager } from './virtualEnvs/index'; import { IVirtualEnvironmentManager } from './virtualEnvs/types'; import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; +import { PoetryEnvService } from './locators/services/poetryEnvService'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IKnownSearchPathsForInterpreters, KnownSearchPathsForInterpreters); @@ -102,6 +104,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE); serviceManager.addSingleton(IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE); serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); + serviceManager.addSingleton(IInterpreterLocatorService, PoetryEnvService, POETRY_ENV_SERVICE); serviceManager.addSingleton(IPipEnvService, PipEnvService); serviceManager.addSingleton(IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE); diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index ab9e0962e599..f000241f1ae1 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -222,7 +222,8 @@ import { PIPENV_SERVICE, PythonInterpreter, WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE + WORKSPACE_VIRTUAL_ENV_SERVICE, + POETRY_ENV_SERVICE } from '../../client/interpreter/contracts'; import { ShebangCodeLensProvider } from '../../client/interpreter/display/shebangCodeLensProvider'; import { InterpreterHelper } from '../../client/interpreter/helpers'; @@ -268,6 +269,7 @@ import { MockWorkspaceConfiguration } from './mockWorkspaceConfig'; import { blurWindow, createMessageEvent } from './reactHelpers'; import { TestInteractiveWindowProvider } from './testInteractiveWindowProvider'; import { TestNativeEditorProvider } from './testNativeEditorProvider'; +import { PoetryEnvService } from '../../client/interpreter/locators/services/poetryEnvService'; export class DataScienceIocContainer extends UnitTestIocContainer { public webPanelListener: IWebPanelMessageListener | undefined; @@ -632,6 +634,7 @@ export class DataScienceIocContainer extends UnitTestIocContainer { this.serviceManager.addSingleton(IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, PoetryEnvService, POETRY_ENV_SERVICE); this.serviceManager.addSingleton(IPipEnvService, PipEnvService); this.serviceManager.addSingleton(IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE); diff --git a/src/test/interpreters/locators/poetryEnvService.unit.test.ts b/src/test/interpreters/locators/poetryEnvService.unit.test.ts new file mode 100644 index 000000000000..2ce02cbedce8 --- /dev/null +++ b/src/test/interpreters/locators/poetryEnvService.unit.test.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { PoetryServce } from '../../../client/interpreter/locators/services/poetryService'; +import { Uri } from 'vscode'; +import { assert } from 'chai'; +import { when, mock, instance, verify } from 'ts-mockito'; +import { Resource } from '../../../client/common/types'; +import { PoetryEnvService } from '../../../client/interpreter/locators/services/poetryEnvService'; +import { IInterpreterHelper, InterpreterType } from '../../../client/interpreter/contracts'; +import { ServiceContainer } from '../../../client/ioc/container'; +import { InterpreterHelper } from '../../../client/interpreter/helpers'; +import { IServiceContainer } from '../../../client/ioc/types'; + +suite('Interpreters - PoetryService', () => { + class PoetryEnvServiceTest extends PoetryEnvService { + public getInterpretersImplementation(resource: Resource) { + return super.getInterpretersImplementation(resource); + } + } + let poetryService: PoetryServce; + let poetryEnvService: PoetryEnvServiceTest; + let svc: IServiceContainer; + let helper: IInterpreterHelper; + setup(() => { + poetryService = mock(PoetryServce); + svc = mock(ServiceContainer); + helper = mock(InterpreterHelper); + + poetryEnvService = new PoetryEnvServiceTest(instance(svc), instance(poetryService), instance(helper)); + }); + + [undefined, Uri.file('wow.py')].forEach(resource => { + suite(resource ? 'Without a resource' : 'With a resource', () => { + test('Returns an empty list of interpreters if poetry is not installed', async () => { + when(poetryService.isInstalled(resource)).thenResolve(false); + + const interpreters = await poetryEnvService.getInterpretersImplementation(resource); + + verify(poetryService.isInstalled(resource)).once(); + verify(poetryService.getEnvironments(resource)).never(); + assert.deepEqual(interpreters, []); + }); + test('Returns an empty list of interpreters if no environments are returned by PoetryService', async () => { + when(poetryService.isInstalled(resource)).thenResolve(true); + when(poetryService.getEnvironments(resource)).thenResolve([]); + + const interpreters = await poetryEnvService.getInterpretersImplementation(resource); + + verify(poetryService.isInstalled(resource)).once(); + verify(poetryService.getEnvironments(resource)).once(); + assert.deepEqual(interpreters, []); + }); + test('Returns an list of interpreters', async () => { + const envs = [path.join('one', 'wow.exe'), path.join('two', 'python')]; + when(poetryService.isInstalled(resource)).thenResolve(true); + when(poetryService.getEnvironments(resource)).thenResolve(envs); + when(helper.getInterpreterInformation(envs[0])).thenResolve({ path: envs[0] }); + when(helper.getInterpreterInformation(envs[1])).thenResolve({ path: envs[1] }); + + const interpreters = await poetryEnvService.getInterpretersImplementation(resource); + + verify(poetryService.isInstalled(resource)).once(); + verify(poetryService.getEnvironments(resource)).once(); + assert.deepEqual( + interpreters, + envs.map(item => ({ path: item, type: InterpreterType.Poetry })) + ); + }); + }); + }); +}); diff --git a/src/test/interpreters/locators/poetryService.unit.test.ts b/src/test/interpreters/locators/poetryService.unit.test.ts new file mode 100644 index 000000000000..8e4e9c296e2e --- /dev/null +++ b/src/test/interpreters/locators/poetryService.unit.test.ts @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { EOL } from 'os'; +import { PoetryServce } from '../../../client/interpreter/locators/services/poetryService'; +import { Uri, FileType } from 'vscode'; +import { assert } from 'chai'; +import { when, mock, anything, instance, deepEqual } from 'ts-mockito'; +import { IProcessService } from '../../../client/common/process/types'; +import { ProcessServiceFactory } from '../../../client/common/process/processFactory'; +import { ProcessService } from '../../../client/common/process/proc'; +import { IConfigurationService } from '../../../client/common/types'; +import { ConfigurationService } from '../../../client/common/configuration/service'; +import { IFileSystem } from '../../../client/common/platform/types'; +import { FileSystem } from '../../../client/common/platform/fileSystem'; +import { getOSType, OSType } from '../../common'; + +suite('Interpreters - PoetryService', () => { + let poetryService: PoetryServce; + let processService: IProcessService; + let configService: IConfigurationService; + let fs: IFileSystem; + setup(() => { + configService = mock(ConfigurationService); + const processFactory = mock(ProcessServiceFactory); + fs = mock(FileSystem); + processService = mock(ProcessService); + + poetryService = new PoetryServce(instance(configService), [] as any, instance(fs), instance(processFactory)); + + when((processService as any).then).thenReturn(undefined); + when(processFactory.create(anything())).thenResolve(instance(processService)); + }); + + [undefined, Uri.file('wow.py')].forEach(resource => { + suite(resource ? 'Without a resource' : 'With a resource', () => { + test('Should not be installed if process exec throws an error', async () => { + when(processService.exec('poetry path', deepEqual(['--version']))).thenReject(new Error('Kaboom')); + when(configService.getSettings(resource)).thenReturn({ poetryPath: 'poetry path' } as any); + + const installed = await poetryService.isInstalled(resource); + + assert.isFalse(installed); + }); + test('Should not be installed if process exec writes to stderr', async () => { + when(processService.exec('poetry path', deepEqual(['--version']))).thenResolve({ stdout: '', stderr: 'wow' }); + when(configService.getSettings(resource)).thenReturn({ poetryPath: 'poetry path' } as any); + + const installed = await poetryService.isInstalled(resource); + + assert.isFalse(installed); + }); + test('Should be installed', async () => { + when(processService.exec('poetry path', deepEqual(['--version']))).thenResolve({ stdout: 'wow', stderr: '' }); + when(configService.getSettings(resource)).thenReturn({ poetryPath: 'poetry path' } as any); + + const installed = await poetryService.isInstalled(resource); + + assert.isOk(installed); + }); + test('Returns an empty list of environments if process exec throws an error', async () => { + when(processService.exec('poetry path', deepEqual(['env', 'list', '--full-path']))).thenReject(new Error('Kaboom')); + when(configService.getSettings(resource)).thenReturn({ poetryPath: 'poetry path' } as any); + + const envs = await poetryService.getEnvironments(resource); + + assert.deepEqual(envs, []); + }); + test('Returns an list of environments', async () => { + const dirs = ['first env dir', '', ' ', 'corrupted env dir', 'second env dir (Activated)', ' '].join(EOL); + const executable = getOSType() === OSType.Windows ? 'python.exe' : 'python'; + + when(processService.exec('poetry path', deepEqual(['env', 'list', '--full-path']))).thenResolve({ stdout: dirs }); + when(fs.listdir('corrupted env dir')).thenResolve([]); + when(fs.directoryExists('corrupted env dir')).thenResolve(true); + when(fs.directoryExists('first env dir')).thenResolve(true); + when(fs.directoryExists('second env dir')).thenResolve(true); + when(fs.listdir('first env dir')).thenResolve([ + [path.join('first env dir', executable), FileType.File], + [path.join('first env dir', 'some other exec.exe'), FileType.File] + ]); + when(fs.listdir('second env dir')).thenResolve([ + [path.join('second env dir', executable), FileType.File], + [path.join('second env dir', 'some other exec.exe'), FileType.File] + ]); + when(configService.getSettings(resource)).thenReturn({ poetryPath: 'poetry path' } as any); + + const envs = await poetryService.getEnvironments(resource); + + assert.deepEqual(envs, [path.join('first env dir', executable), path.join('second env dir', executable)]); + }); + }); + }); +}); diff --git a/src/test/interpreters/serviceRegistry.unit.test.ts b/src/test/interpreters/serviceRegistry.unit.test.ts index b3ed9a91b5fd..ea081b51c2e9 100644 --- a/src/test/interpreters/serviceRegistry.unit.test.ts +++ b/src/test/interpreters/serviceRegistry.unit.test.ts @@ -52,7 +52,8 @@ import { KNOWN_PATH_SERVICE, PIPENV_SERVICE, WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE + WORKSPACE_VIRTUAL_ENV_SERVICE, + POETRY_ENV_SERVICE } from '../../client/interpreter/contracts'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { InterpreterSelectionTip } from '../../client/interpreter/display/interpreterSelectionTip'; @@ -86,6 +87,7 @@ import { CondaInheritEnvPrompt } from '../../client/interpreter/virtualEnvs/cond import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { VirtualEnvironmentPrompt } from '../../client/interpreter/virtualEnvs/virtualEnvPrompt'; import { ServiceManager } from '../../client/ioc/serviceManager'; +import { PoetryEnvService } from '../../client/interpreter/locators/services/poetryEnvService'; suite('Interpreters - Service Registry', () => { test('Registrations', () => { @@ -114,6 +116,7 @@ suite('Interpreters - Service Registry', () => { [IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE], [IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE], [IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE], + [IInterpreterLocatorService, PoetryEnvService, POETRY_ENV_SERVICE], [IPipEnvService, PipEnvService], [IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE], diff --git a/src/test/serviceRegistry.ts b/src/test/serviceRegistry.ts index cb8075ed9aaa..ecd7e64f317c 100644 --- a/src/test/serviceRegistry.ts +++ b/src/test/serviceRegistry.ts @@ -40,7 +40,8 @@ import { KNOWN_PATH_SERVICE, PIPENV_SERVICE, WINDOWS_REGISTRY_SERVICE, - WORKSPACE_VIRTUAL_ENV_SERVICE + WORKSPACE_VIRTUAL_ENV_SERVICE, + POETRY_ENV_SERVICE } from '../client/interpreter/contracts'; import { InterpreterService } from '../client/interpreter/interpreterService'; import { PythonInterpreterLocatorService } from '../client/interpreter/locators'; @@ -71,6 +72,7 @@ import { MockAutoSelectionService } from './mocks/autoSelector'; import { MockMemento } from './mocks/mementos'; import { MockProcessService } from './mocks/proc'; import { MockProcess } from './mocks/process'; +import { PoetryEnvService } from '../client/interpreter/locators/services/poetryEnvService'; export class IocContainer { public readonly serviceManager: IServiceManager; @@ -173,6 +175,7 @@ export class IocContainer { this.serviceManager.addSingleton(IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); + this.serviceManager.addSingleton(IInterpreterLocatorService, PoetryEnvService, POETRY_ENV_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, WindowsRegistryService, WINDOWS_REGISTRY_SERVICE); this.serviceManager.addSingleton(IInterpreterLocatorService, KnownPathsService, KNOWN_PATH_SERVICE); this.serviceManager.addSingleton(IPipEnvService, PipEnvService);