Skip to content

Commit ab795f5

Browse files
Move code related to Python version to the py-envs component tree. (#12236)
This mostly involves moving code, and only minor refactoring (to pull the component code out of the DI-based classes).
1 parent 2a5d899 commit ab795f5

File tree

22 files changed

+247
-192
lines changed

22 files changed

+247
-192
lines changed

pythonFiles/interpreterInfo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66

77
obj = {}
8-
obj["versionInfo"] = sys.version_info[:4]
8+
obj["versionInfo"] = tuple(sys.version_info)
99
obj["sysPrefix"] = sys.prefix
1010
obj["version"] = sys.version
1111
obj["is64Bit"] = sys.maxsize > 2 ** 32

pythonFiles/vscode_datascience_helpers/daemon/daemon_python.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def m_get_executable(self):
198198

199199
def m_get_interpreter_information(self):
200200
return {
201-
"versionInfo": sys.version_info[:4],
201+
"versionInfo": tuple(sys.version_info),
202202
"sysPrefix": sys.prefix,
203203
"version": sys.version,
204204
"is64Bit": sys.maxsize > 2 ** 32,

src/client/common/process/internal/scripts/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the MIT License.
33

44
import * as path from 'path';
5-
import { PythonVersionInfo } from '../../../../pythonEnvironments/discovery/types';
65
import { EXTENSION_ROOT_DIR } from '../../../constants';
76

87
// It is simpler to hard-code it instead of using vscode.ExtensionContext.extensionPath.
@@ -41,7 +40,9 @@ export * as vscode_datascience_helpers from './vscode_datascience_helpers';
4140
//============================
4241
// interpreterInfo.py
4342

44-
type PythonEnvInfo = {
43+
type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final';
44+
type PythonVersionInfo = [number, number, number, ReleaseLevel, number];
45+
export type PythonEnvInfo = {
4546
versionInfo: PythonVersionInfo;
4647
sysPrefix: string;
4748
sysVersion: string;

src/client/common/process/pythonDaemon.ts

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55

66
import { ChildProcess } from 'child_process';
77
import { MessageConnection, RequestType, RequestType0 } from 'vscode-jsonrpc';
8-
import { InterpreterInformation, PythonVersionInfo } from '../../pythonEnvironments/discovery/types';
8+
import { InterpreterInformation } from '../../pythonEnvironments/discovery/types';
9+
import { PythonExecInfo } from '../../pythonEnvironments/exec';
10+
import { extractInterpreterInfo } from '../../pythonEnvironments/info';
911
import { traceWarning } from '../logger';
10-
import { Architecture } from '../utils/platform';
11-
import { parsePythonVersion } from '../utils/version';
1212
import { BasePythonDaemon } from './baseDaemon';
13+
import { PythonEnvInfo } from './internal/scripts';
1314
import {
1415
IPythonDaemonExecutionService,
1516
IPythonExecutionService,
1617
ObservableExecutionResult,
17-
PythonExecutionInfo,
1818
SpawnOptions
1919
} from './types';
2020

@@ -43,25 +43,12 @@ export class PythonDaemonExecutionService extends BasePythonDaemon implements IP
4343
public async getInterpreterInformation(): Promise<InterpreterInformation | undefined> {
4444
try {
4545
this.throwIfRPCConnectionIsDead();
46-
type InterpreterInfoResponse = ErrorResponse & {
47-
versionInfo: PythonVersionInfo;
48-
sysPrefix: string;
49-
sysVersion: string;
50-
is64Bit: boolean;
51-
};
52-
const request = new RequestType0<InterpreterInfoResponse, void, void>('get_interpreter_information');
46+
const request = new RequestType0<PythonEnvInfo & ErrorResponse, void, void>('get_interpreter_information');
5347
const response = await this.sendRequestWithoutArgs(request);
54-
const versionValue =
55-
response.versionInfo.length === 4
56-
? `${response.versionInfo.slice(0, 3).join('.')}-${response.versionInfo[3]}`
57-
: response.versionInfo.join('.');
58-
return {
59-
architecture: response.is64Bit ? Architecture.x64 : Architecture.x86,
60-
path: this.pythonPath,
61-
version: parsePythonVersion(versionValue),
62-
sysVersion: response.sysVersion,
63-
sysPrefix: response.sysPrefix
64-
};
48+
if (response.error) {
49+
throw Error(response.error);
50+
}
51+
return extractInterpreterInfo(this.pythonPath, response);
6552
} catch (ex) {
6653
traceWarning('Falling back to Python Execution Service due to failure in daemon', ex);
6754
return this.pythonExecutionService.getInterpreterInformation();
@@ -82,7 +69,7 @@ export class PythonDaemonExecutionService extends BasePythonDaemon implements IP
8269
return this.pythonExecutionService.getExecutablePath();
8370
}
8471
}
85-
public getExecutionInfo(pythonArgs?: string[]): PythonExecutionInfo {
72+
public getExecutionInfo(pythonArgs?: string[]): PythonExecInfo {
8673
return this.pythonExecutionService.getExecutionInfo(pythonArgs);
8774
}
8875
public async isModuleInstalled(moduleName: string): Promise<boolean> {

src/client/common/process/pythonDaemonPool.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { InterpreterInformation } from '../../pythonEnvironments/discovery/types';
5+
import { PythonExecInfo } from '../../pythonEnvironments/exec';
56
import { IDisposableRegistry } from '../types';
67
import { sleep } from '../utils/async';
78
import { noop } from '../utils/misc';
@@ -17,7 +18,6 @@ import {
1718
isDaemonPoolCreationOption,
1819
ObservableExecutionResult,
1920
PooledDaemonExecutionFactoryCreationOptions,
20-
PythonExecutionInfo,
2121
SpawnOptions
2222
} from './types';
2323

@@ -68,7 +68,7 @@ export class PythonDaemonExecutionServicePool extends PythonDaemonFactory implem
6868
const msg = { args: ['getExecutablePath'] };
6969
return this.wrapCall((daemon) => daemon.getExecutablePath(), msg);
7070
}
71-
public getExecutionInfo(pythonArgs?: string[]): PythonExecutionInfo {
71+
public getExecutionInfo(pythonArgs?: string[]): PythonExecInfo {
7272
return this.pythonExecutionService.getExecutionInfo(pythonArgs);
7373
}
7474
public async isModuleInstalled(moduleName: string): Promise<boolean> {

src/client/common/process/pythonEnvironment.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,13 @@
33

44
import { CondaEnvironmentInfo } from '../../pythonEnvironments/discovery/locators/services/conda';
55
import { InterpreterInformation } from '../../pythonEnvironments/discovery/types';
6+
import { getPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec';
7+
import { extractInterpreterInfo } from '../../pythonEnvironments/info';
68
import { traceError, traceInfo } from '../logger';
79
import { IFileSystem } from '../platform/types';
8-
import { Architecture } from '../utils/platform';
9-
import { parsePythonVersion } from '../utils/version';
1010
import * as internalPython from './internal/python';
1111
import * as internalScripts from './internal/scripts';
12-
import { ExecutionResult, IProcessService, PythonExecutionInfo, ShellOptions, SpawnOptions } from './types';
13-
14-
function getExecutionInfo(python: string[], pythonArgs: string[]): PythonExecutionInfo {
15-
const args = python.slice(1);
16-
args.push(...pythonArgs);
17-
return { command: python[0], args, python };
18-
}
12+
import { ExecutionResult, IProcessService, ShellOptions, SpawnOptions } from './types';
1913

2014
class PythonEnvironment {
2115
private cachedInterpreterInformation: InterpreterInformation | undefined | null = null;
@@ -33,13 +27,13 @@ class PythonEnvironment {
3327
}
3428
) {}
3529

36-
public getExecutionInfo(pythonArgs: string[] = []): PythonExecutionInfo {
30+
public getExecutionInfo(pythonArgs: string[] = []): PythonExecInfo {
3731
const python = this.deps.getPythonArgv(this.pythonPath);
38-
return getExecutionInfo(python, pythonArgs);
32+
return getPythonExecInfo(python, pythonArgs);
3933
}
40-
public getExecutionObservableInfo(pythonArgs: string[] = []): PythonExecutionInfo {
34+
public getExecutionObservableInfo(pythonArgs: string[] = []): PythonExecInfo {
4135
const python = this.deps.getObservablePythonArgv(this.pythonPath);
42-
return getExecutionInfo(python, pythonArgs);
36+
return getPythonExecInfo(python, pythonArgs);
4337
}
4438

4539
public async getInterpreterInformation(): Promise<InterpreterInformation | undefined> {
@@ -96,17 +90,7 @@ class PythonEnvironment {
9690
}
9791
const json = parse(result.stdout);
9892
traceInfo(`Found interpreter for ${argv}`);
99-
const versionValue =
100-
json.versionInfo.length === 4
101-
? `${json.versionInfo.slice(0, 3).join('.')}-${json.versionInfo[3]}`
102-
: json.versionInfo.join('.');
103-
return {
104-
architecture: json.is64Bit ? Architecture.x64 : Architecture.x86,
105-
path: this.pythonPath,
106-
version: parsePythonVersion(versionValue),
107-
sysVersion: json.sysVersion,
108-
sysPrefix: json.sysPrefix
109-
};
93+
return extractInterpreterInfo(this.pythonPath, json);
11094
} catch (ex) {
11195
traceError(`Failed to get interpreter information for '${this.pythonPath}'`, ex);
11296
}

src/client/common/process/pythonProcess.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import { PythonExecInfo } from '../../pythonEnvironments/exec';
45
import { ErrorUtils } from '../errors/errorUtils';
56
import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError';
67
import * as internalPython from './internal/python';
7-
import {
8-
ExecutionResult,
9-
IProcessService,
10-
ObservableExecutionResult,
11-
PythonExecutionInfo,
12-
SpawnOptions
13-
} from './types';
8+
import { ExecutionResult, IProcessService, ObservableExecutionResult, SpawnOptions } from './types';
149

1510
class PythonProcessService {
1611
constructor(
1712
// This is the externally defined functionality used by the class.
1813
private readonly deps: {
1914
// from PythonEnvironment:
2015
isModuleInstalled(moduleName: string): Promise<boolean>;
21-
getExecutionInfo(pythonArgs?: string[]): PythonExecutionInfo;
22-
getExecutionObservableInfo(pythonArgs?: string[]): PythonExecutionInfo;
16+
getExecutionInfo(pythonArgs?: string[]): PythonExecInfo;
17+
getExecutionObservableInfo(pythonArgs?: string[]): PythonExecInfo;
2318
// from ProcessService:
2419
exec(file: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
2520
execObservable(file: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
@@ -75,8 +70,8 @@ export function createPythonProcessService(
7570
procs: IProcessService,
7671
// from PythonEnvironment:
7772
env: {
78-
getExecutionInfo(pythonArgs?: string[]): PythonExecutionInfo;
79-
getExecutionObservableInfo(pythonArgs?: string[]): PythonExecutionInfo;
73+
getExecutionInfo(pythonArgs?: string[]): PythonExecInfo;
74+
getExecutionObservableInfo(pythonArgs?: string[]): PythonExecInfo;
8075
isModuleInstalled(moduleName: string): Promise<boolean>;
8176
}
8277
) {

src/client/common/process/types.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { CancellationToken, Uri } from 'vscode';
77

88
import { Newable } from '../../ioc/types';
99
import { InterpreterInformation, PythonInterpreter } from '../../pythonEnvironments/discovery/types';
10+
import { PythonExecInfo } from '../../pythonEnvironments/exec';
1011
import { ExecutionInfo, IDisposable } from '../types';
1112
import { EnvironmentVariables } from '../variables/types';
1213

@@ -167,7 +168,7 @@ export interface IPythonExecutionService {
167168
getInterpreterInformation(): Promise<InterpreterInformation | undefined>;
168169
getExecutablePath(): Promise<string>;
169170
isModuleInstalled(moduleName: string): Promise<boolean>;
170-
getExecutionInfo(pythonArgs?: string[]): PythonExecutionInfo;
171+
getExecutionInfo(pythonArgs?: string[]): PythonExecInfo;
171172

172173
execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
173174
execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult<string>;
@@ -176,12 +177,6 @@ export interface IPythonExecutionService {
176177
execModule(moduleName: string, args: string[], options: SpawnOptions): Promise<ExecutionResult<string>>;
177178
}
178179

179-
export type PythonExecutionInfo = {
180-
command: string;
181-
args: string[];
182-
183-
python: string[];
184-
};
185180
/**
186181
* Identical to the PythonExecutionService, but with a `dispose` method.
187182
* This is a daemon process that lives on until it is disposed, hence the `IDisposable`.

src/client/common/utils/version.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
'use strict';
55

66
import * as semver from 'semver';
7-
import { Version } from '../types';
87

98
export function parseVersion(raw: string): semver.SemVer {
109
raw = raw.replace(/\.00*(?=[1-9]|0\.)/, '.');
@@ -16,32 +15,3 @@ export function parseVersion(raw: string): semver.SemVer {
1615
}
1716
return ver;
1817
}
19-
export function parsePythonVersion(version: string): Version | undefined {
20-
if (!version || version.trim().length === 0) {
21-
return;
22-
}
23-
const versionParts = (version || '')
24-
.split('.')
25-
.map((item) => item.trim())
26-
.filter((item) => item.length > 0)
27-
.filter((_, index) => index < 4);
28-
29-
if (versionParts.length > 0 && versionParts[versionParts.length - 1].indexOf('-') > 0) {
30-
const lastPart = versionParts[versionParts.length - 1];
31-
versionParts[versionParts.length - 1] = lastPart.split('-')[0].trim();
32-
versionParts.push(lastPart.split('-')[1].trim());
33-
}
34-
while (versionParts.length < 4) {
35-
versionParts.push('');
36-
}
37-
// Exclude PII from `version_info` to ensure we don't send this up via telemetry.
38-
for (let index = 0; index < 3; index += 1) {
39-
versionParts[index] = /^\d+$/.test(versionParts[index]) ? versionParts[index] : '0';
40-
}
41-
if (['alpha', 'beta', 'candidate', 'final'].indexOf(versionParts[3]) === -1) {
42-
versionParts.pop();
43-
}
44-
const numberParts = `${versionParts[0]}.${versionParts[1]}.${versionParts[2]}`;
45-
const rawVersion = versionParts.length === 4 ? `${numberParts}-${versionParts[3]}` : numberParts;
46-
return new semver.SemVer(rawVersion);
47-
}

src/client/interpreter/interpreterVersion.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@ import { inject, injectable } from 'inversify';
22
import '../common/extensions';
33
import * as internalPython from '../common/process/internal/python';
44
import { IProcessServiceFactory } from '../common/process/types';
5+
import { getPythonVersion } from '../pythonEnvironments/pythonVersion';
56
import { IInterpreterVersionService } from './contracts';
67

78
export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)?';
89

910
@injectable()
1011
export class InterpreterVersionService implements IInterpreterVersionService {
1112
constructor(@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory) {}
13+
1214
public async getVersion(pythonPath: string, defaultValue: string): Promise<string> {
13-
const [args, parse] = internalPython.getVersion();
1415
const processService = await this.processServiceFactory.create();
15-
return processService
16-
.exec(pythonPath, args, { mergeStdOutErr: true })
17-
.then((output) => parse(output.stdout).splitLines()[0])
18-
.then((version) => (version.length === 0 ? defaultValue : version))
19-
.catch(() => defaultValue);
16+
return getPythonVersion(pythonPath, defaultValue, (cmd, args) =>
17+
processService.exec(cmd, args, { mergeStdOutErr: true })
18+
);
2019
}
20+
2121
public async getPipVersion(pythonPath: string): Promise<string> {
2222
const [args, parse] = internalPython.getModuleVersion('pip');
2323
const processService = await this.processServiceFactory.create();

src/client/pythonEnvironments/discovery/locators/services/windowsRegistryService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { traceError } from '../../../../common/logger';
77
import { IFileSystem, IPlatformService, IRegistry, RegistryHive } from '../../../../common/platform/types';
88
import { IPathUtils } from '../../../../common/types';
99
import { Architecture } from '../../../../common/utils/platform';
10-
import { parsePythonVersion } from '../../../../common/utils/version';
1110
import { IInterpreterHelper } from '../../../../interpreter/contracts';
1211
import { IWindowsStoreInterpreter } from '../../../../interpreter/locators/types';
1312
import { IServiceContainer } from '../../../../ioc/types';
13+
import { parsePythonVersion } from '../../../pythonVersion';
1414
import { InterpreterType, PythonInterpreter } from '../../types';
1515
import { CacheableLocatorService } from './cacheableLocatorService';
1616
import { AnacondaCompanyName, AnacondaCompanyNames } from './conda';

src/client/pythonEnvironments/exec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
export type PythonExecInfo = {
5+
command: string;
6+
args: string[];
7+
8+
python: string[];
9+
};
10+
11+
export function getPythonExecInfo(python: string | string[], pythonArgs?: string[]): PythonExecInfo {
12+
if (Array.isArray(python)) {
13+
const args = python.slice(1);
14+
if (pythonArgs) {
15+
args.push(...pythonArgs);
16+
}
17+
return { command: python[0], args, python };
18+
} else {
19+
return { command: python, args: pythonArgs || [], python: [python] };
20+
}
21+
}

src/client/pythonEnvironments/info.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { PythonEnvInfo } from '../common/process/internal/scripts';
5+
import { Architecture } from '../common/utils/platform';
6+
import { InterpreterInformation } from './discovery/types';
7+
import { parsePythonVersion } from './pythonVersion';
8+
9+
export function extractInterpreterInfo(python: string, raw: PythonEnvInfo): InterpreterInformation {
10+
const rawVersion = `${raw.versionInfo.slice(0, 3).join('.')}-${raw.versionInfo[3]}`;
11+
return {
12+
architecture: raw.is64Bit ? Architecture.x64 : Architecture.x86,
13+
path: python,
14+
version: parsePythonVersion(rawVersion),
15+
sysVersion: raw.sysVersion,
16+
sysPrefix: raw.sysPrefix
17+
};
18+
}

0 commit comments

Comments
 (0)