forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Environment info cache class #14065
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
Merged
Merged
Environment info cache class #14065
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
c77eb0f
Add persistent storage external deps
kimadeline 4f92800
PythonEnvInfoCache class + tests
kimadeline a85dcff
Instantiate & initialize cache class in createAPI
kimadeline 0af4e8d
Add extra test for flush() and initialize()
kimadeline 1eba02b
Env cache fixes: storage key + find() result check
kimadeline b830ac4
Merge branch 'main' into environments-cache
kimadeline cd7a19c
Update src/client/pythonEnvironments/common/externalDependencies.ts
kimadeline 87efe9b
Use areSameEnvironment in getEnv
kimadeline d7fe660
Don't ping persistent storage for every initialize
kimadeline bacc11e
No need to export CompleteEnvInfoFunction
kimadeline 057f8be
PythonEnvInfoCache doc comment
kimadeline 92a01a4
Rename createGlobalPersistentStoreStub to get...
kimadeline 93075c0
Preemptively drop id key (#14051)
kimadeline d36d75d
Return deep copies
kimadeline d7e9f7d
IPersistentStore wrapper around IPersistentState
kimadeline 97d5251
Use correct areSameEnvironment + fix stub
kimadeline 3c251c3
Remove obsolete comment
kimadeline fab5344
Merge branch 'main' into environments-cache
kimadeline 9e438bc
getEnv -> filterEnvs
kimadeline 31abb9b
Remove stubbing of areSameEnvironment
kimadeline 33d30c9
Merge branch 'main' into environments-cache
kimadeline 85adf80
Update areSameEnv
kimadeline edc6ce5
Move IPersistentStateFactory registration to registerForIOC
kimadeline a8d8317
Revert "Move IPersistentStateFactory registration to registerForIOC"
kimadeline d67a118
Don't instantiate nor initialize cache for now
kimadeline File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { cloneDeep } from 'lodash'; | ||
import { IPersistentState } from '../../common/types'; | ||
import { createGlobalPersistentStore } from '../common/externalDependencies'; | ||
import { PythonEnvInfo } from './info'; | ||
|
||
/** | ||
* Represents the environment info cache to be used by the cache locator. | ||
*/ | ||
export interface IEnvsCache { | ||
/** | ||
* Initialization logic to be done outside of the constructor, for example reading from persistent storage. | ||
*/ | ||
initialize(): void; | ||
|
||
/** | ||
* Return all environment info currently in memory for this session. | ||
* | ||
* @return An array of cached environment info, or `undefined` if there are none. | ||
*/ | ||
getAllEnvs(): PythonEnvInfo[] | undefined; | ||
|
||
/** | ||
* Replace all environment info currently in memory for this session. | ||
* | ||
* @param envs The array of environment info to store in the in-memory cache. | ||
*/ | ||
setAllEnvs(envs: PythonEnvInfo[]): void; | ||
|
||
/** | ||
* Return a specific environmnent info object. | ||
* | ||
* @param env The environment info data that will be used to look for an environment info object in the cache. | ||
* This object may contain incomplete environment info. | ||
* @return The environment info object that matches all non-undefined keys from the `env` param, | ||
* `undefined` otherwise. | ||
*/ | ||
getEnv(env: Partial<PythonEnvInfo>): PythonEnvInfo | undefined; | ||
|
||
/** | ||
* Writes the content of the in-memory cache to persistent storage. | ||
*/ | ||
flush(): Promise<void>; | ||
} | ||
|
||
export type CompleteEnvInfoFunction = (envInfo: PythonEnvInfo) => boolean; | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
export class PythonEnvInfoCache implements IEnvsCache { | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private envsList: PythonEnvInfo[] | undefined; | ||
|
||
private persistentStorage: IPersistentState<PythonEnvInfo[]> | undefined; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
constructor(private readonly isComplete: CompleteEnvInfoFunction) {} | ||
|
||
public initialize(): void { | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.persistentStorage = createGlobalPersistentStore<PythonEnvInfo[]>('PYTHON_ENV_INFO_CACHE'); | ||
ericsnowcurrently marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.envsList = this.persistentStorage?.value; | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public getAllEnvs(): PythonEnvInfo[] | undefined { | ||
return this.envsList; | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public setAllEnvs(envs: PythonEnvInfo[]): void { | ||
this.envsList = cloneDeep(envs); | ||
} | ||
|
||
public getEnv(env: Partial<PythonEnvInfo>): PythonEnvInfo | undefined { | ||
// Retrieve all keys with non-undefined values. | ||
type EnvParamKeys = keyof typeof env; | ||
const keys = (Object.keys(env) as unknown as EnvParamKeys[]).filter((key) => env[key] !== undefined); | ||
|
||
// Return the first object where the values match env's. | ||
return this.envsList?.find((info) => { | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Check if there is any mismatch between the values of the in-memory info and env. | ||
const mismatch = keys.some((key) => info[key] !== env[key]); | ||
|
||
return !mismatch; | ||
}); | ||
} | ||
karrtikr marked this conversation as resolved.
Show resolved
Hide resolved
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public async flush(): Promise<void> { | ||
const completeEnvs = this.envsList?.filter(this.isComplete); | ||
|
||
if (completeEnvs?.length) { | ||
await this.persistentStorage?.updateValue(completeEnvs); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
src/test/pythonEnvironments/base/envsCache.unit.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { CompleteEnvInfoFunction, PythonEnvInfoCache } from '../../../client/pythonEnvironments/base/envsCache'; | ||
import { PythonEnvInfo, PythonEnvKind } from '../../../client/pythonEnvironments/base/info'; | ||
import * as externalDeps from '../../../client/pythonEnvironments/common/externalDependencies'; | ||
|
||
suite('Environment Info cache', () => { | ||
let createGlobalPersistentStoreStub: sinon.SinonStub; | ||
let updatedValues: PythonEnvInfo[] | undefined; | ||
|
||
const allEnvsComplete: CompleteEnvInfoFunction = () => true; | ||
const envInfoArray = [ | ||
{ | ||
id: 'someid1', kind: PythonEnvKind.Conda, name: 'my-conda-env', defaultDisplayName: 'env-one', | ||
}, | ||
{ | ||
id: 'someid2', kind: PythonEnvKind.Venv, name: 'my-venv-env', defaultDisplayName: 'env-two', | ||
}, | ||
{ | ||
id: 'someid3', kind: PythonEnvKind.Pyenv, name: 'my-pyenv-env', defaultDisplayName: 'env-three', | ||
kimadeline marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
] as PythonEnvInfo[]; | ||
|
||
setup(() => { | ||
createGlobalPersistentStoreStub = sinon.stub(externalDeps, 'createGlobalPersistentStore'); | ||
createGlobalPersistentStoreStub.returns({ | ||
value: envInfoArray, | ||
updateValue: async (envs: PythonEnvInfo[]) => { | ||
updatedValues = envs; | ||
return Promise.resolve(); | ||
}, | ||
}); | ||
}); | ||
|
||
teardown(() => { | ||
createGlobalPersistentStoreStub.restore(); | ||
updatedValues = undefined; | ||
}); | ||
|
||
test('`initialize` reads from persistent storage', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
|
||
envsCache.initialize(); | ||
|
||
assert.ok(createGlobalPersistentStoreStub.calledOnce); | ||
}); | ||
|
||
test('The in-memory env info array is undefined if there is no value in persistent storage when initializing the cache', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
|
||
createGlobalPersistentStoreStub.returns({ value: undefined }); | ||
envsCache.initialize(); | ||
const result = envsCache.getAllEnvs(); | ||
|
||
assert.strictEqual(result, undefined); | ||
}); | ||
|
||
test('`getAllEnvs` should return undefined if nothing has been set', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
|
||
const envs = envsCache.getAllEnvs(); | ||
|
||
assert.deepStrictEqual(envs, undefined); | ||
}); | ||
|
||
test('`setAllEnvs` should clone the environment info array passed as a parameter', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
|
||
envsCache.setAllEnvs(envInfoArray); | ||
const envs = envsCache.getAllEnvs(); | ||
|
||
assert.deepStrictEqual(envs, envInfoArray); | ||
assert.strictEqual(envs === envInfoArray, false); | ||
}); | ||
|
||
test('`getEnv` should return an environment that matches all non-undefined properties of its argument', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
envsCache.initialize(); | ||
|
||
const result = envsCache.getEnv({ name: 'my-venv-env' }); | ||
|
||
assert.deepStrictEqual(result, { | ||
id: 'someid2', kind: PythonEnvKind.Venv, name: 'my-venv-env', defaultDisplayName: 'env-two', | ||
}); | ||
}); | ||
|
||
test('`getEnv` should return undefined if no environment matches the properties of its argument', () => { | ||
const envsCache = new PythonEnvInfoCache(allEnvsComplete); | ||
envsCache.initialize(); | ||
|
||
const result = envsCache.getEnv({ name: 'my-nonexistent-env' }); | ||
|
||
assert.strictEqual(result, undefined); | ||
}); | ||
|
||
test('`flush` should write complete environment info objects to persistent storage', async () => { | ||
const otherEnv = { | ||
id: 'someid5', | ||
kind: PythonEnvKind.OtherGlobal, | ||
name: 'my-other-env', | ||
defaultDisplayName: 'env-five', | ||
}; | ||
const updatedEnvInfoArray = [ | ||
otherEnv, { id: 'someid4', kind: PythonEnvKind.System, name: 'my-system-env' }, | ||
] as PythonEnvInfo[]; | ||
const expected = [ | ||
otherEnv, | ||
]; | ||
const envsCache = new PythonEnvInfoCache((env) => env.defaultDisplayName !== undefined); | ||
|
||
envsCache.initialize(); | ||
envsCache.setAllEnvs(updatedEnvInfoArray); | ||
await envsCache.flush(); | ||
|
||
assert.deepStrictEqual(updatedValues, expected); | ||
}); | ||
|
||
test('`flush` should not write to persistent storage if there are no environment info objects in-memory', async () => { | ||
const envsCache = new PythonEnvInfoCache((env) => env.kind === PythonEnvKind.MacDefault); | ||
|
||
await envsCache.flush(); | ||
|
||
assert.strictEqual(updatedValues, undefined); | ||
}); | ||
|
||
test('`flush` should not write to persistent storage if there are no complete environment info objects', async () => { | ||
const envsCache = new PythonEnvInfoCache((env) => env.kind === PythonEnvKind.MacDefault); | ||
|
||
envsCache.initialize(); | ||
await envsCache.flush(); | ||
|
||
assert.strictEqual(updatedValues, undefined); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.