python-manager-discovery
bởi microsoft
Các mẫu phát hiện và vấn đề đã biết dành riêng cho trình quản lý môi trường. Sử dụng khi làm việc hoặc xem xét mã phát hiện môi trường cho conda, poetry, pipenv, pyenv,…
npx skills add https://github.com/microsoft/vscode-python-environments --skill python-manager-discoveryEnvironment Manager Discovery Patterns
This skill documents manager-specific discovery patterns, environment variable precedence, and known issues.
Manager Quick Reference
| Manager | Config Files | Cache Location | Key Env Vars |
|---|---|---|---|
| Poetry | poetry.toml, pyproject.toml, config.toml | Platform-specific | POETRY_VIRTUALENVS_IN_PROJECT, POETRY_CACHE_DIR |
| Pipenv | Pipfile, Pipfile.lock | XDG or WORKON_HOME | WORKON_HOME, XDG_DATA_HOME |
| Pyenv | .python-version, versions/ | ~/.pyenv/ or pyenv-win | PYENV_ROOT, PYENV_VERSION |
| Conda | environment.yml, conda-meta/ | Registries + paths | CONDA_PREFIX, CONDA_DEFAULT_ENV |
| venv | pyvenv.cfg | In-project | None |
Poetry
Discovery Locations
Virtualenvs cache (default):
- Windows:
%LOCALAPPDATA%\pypoetry\Cache\virtualenvs - macOS:
~/Library/Caches/pypoetry/virtualenvs - Linux:
~/.cache/pypoetry/virtualenvs
In-project (when enabled):
.venv/in project root
Config Precedence (highest to lowest)
- Local config:
poetry.tomlin project root - Environment variables:
POETRY_VIRTUALENVS_* - Global config:
~/.config/pypoetry/config.toml
Known Issues
| Issue | Description | Fix |
|---|---|---|
{cache-dir} placeholder | Not resolved in paths from config | Resolve placeholder before use |
| Wrong default path | Windows/macOS differ from Linux | Use platform-specific defaults |
| In-project detection | POETRY_VIRTUALENVS_IN_PROJECT must be checked | Check env var first, then config |
Code Pattern
async function getPoetryVirtualenvsPath(): Promise<string> {
// 1. Check environment variable first
const envVar = process.env.POETRY_VIRTUALENVS_PATH;
if (envVar) return envVar;
// 2. Check local poetry.toml
const localConfig = await readPoetryToml(projectRoot);
if (localConfig?.virtualenvs?.path) {
return resolvePoetryPath(localConfig.virtualenvs.path);
}
// 3. Use platform-specific default
return getDefaultPoetryCache();
}
function resolvePoetryPath(configPath: string): string {
// Handle {cache-dir} placeholder
if (configPath.includes('{cache-dir}')) {
const cacheDir = getDefaultPoetryCache();
return configPath.replace('{cache-dir}', cacheDir);
}
return configPath;
}
Pipenv
Discovery Locations
Default:
- Linux:
~/.local/share/virtualenvs/(XDG_DATA_HOME) - macOS:
~/.local/share/virtualenvs/ - Windows:
~\.virtualenvs\
When WORKON_HOME is set:
- Use
$WORKON_HOME/directly
Environment Variables
| Var | Purpose |
|---|---|
WORKON_HOME | Override virtualenv location |
XDG_DATA_HOME | Base for Linux default |
PIPENV_VENV_IN_PROJECT | Create .venv/ in project |
Known Issues
| Issue | Description | Fix |
|---|---|---|
| Missing WORKON_HOME support | Env var not checked | Read env var before defaults |
| Missing XDG_DATA_HOME support | Not used on Linux | Check XDG spec |
Code Pattern
function getPipenvVirtualenvsPath(): string {
// Check WORKON_HOME first
if (process.env.WORKON_HOME) {
return process.env.WORKON_HOME;
}
// Check XDG_DATA_HOME on Linux
if (process.platform === 'linux') {
const xdgData = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
return path.join(xdgData, 'virtualenvs');
}
// Windows/macOS defaults
return path.join(os.homedir(), '.virtualenvs');
}
PyEnv
Discovery Locations
Unix:
~/.pyenv/versions/(default)$PYENV_ROOT/versions/(if PYENV_ROOT set)
Windows (pyenv-win):
%USERPROFILE%\.pyenv\pyenv-win\versions\- Different directory structure than Unix!
Key Differences: Unix vs Windows
| Aspect | Unix | Windows (pyenv-win) |
|---|---|---|
| Command | pyenv | pyenv.bat |
| Root | ~/.pyenv/ | %USERPROFILE%\.pyenv\pyenv-win\ |
| Shims | ~/.pyenv/shims/ | %USERPROFILE%\.pyenv\pyenv-win\shims\ |
Known Issues
| Issue | Description | Fix |
|---|---|---|
| path.normalize() vs path.resolve() | Windows drive letter missing | Use path.resolve() on both sides |
| Wrong command on Windows | Looking for pyenv instead of pyenv.bat | Check for .bat extension |
Code Pattern
function getPyenvRoot(): string {
if (process.env.PYENV_ROOT) {
return process.env.PYENV_ROOT;
}
if (process.platform === 'win32') {
// pyenv-win uses different structure
return path.join(os.homedir(), '.pyenv', 'pyenv-win');
}
return path.join(os.homedir(), '.pyenv');
}
function getPyenvVersionsPath(): string {
const root = getPyenvRoot();
return path.join(root, 'versions');
}
// Use path.resolve() for comparisons!
function comparePyenvPaths(pathA: string, pathB: string): boolean {
return path.resolve(pathA) === path.resolve(pathB);
}
Conda
Discovery Locations
Environment locations:
- Base install
envs/directory ~/.conda/envs/- Paths in
~/.condarcenvs_dirs
Windows Registry:
HKCU\Software\Python\ContinuumAnalytics\HKLM\SOFTWARE\Python\ContinuumAnalytics\
Shell Activation
| Shell | Activation Command |
|---|---|
| bash, zsh | source activate envname |
| fish | conda activate envname (NOT source!) |
| PowerShell | conda activate envname |
| cmd | activate.bat envname |
Known Issues
| Issue | Description | Fix |
|---|---|---|
| Fish shell activation | Uses bash-style command | Use fish-compatible syntax |
| Registry paths | May be stale/invalid | Verify paths exist |
| Base vs named envs | Different activation | Check if activating base |
Code Pattern
function getCondaActivationCommand(shell: ShellType, envName: string): string {
switch (shell) {
case 'fish':
// Fish uses different syntax!
return `conda activate ${envName}`;
case 'cmd':
return `activate.bat ${envName}`;
case 'powershell':
return `conda activate ${envName}`;
default:
// bash, zsh
return `source activate ${envName}`;
}
}
venv
Discovery
Identification:
- Look for
pyvenv.cfgfile in directory - Contains
homeand optionallyversionkeys
Version Extraction Priority
versionfield inpyvenv.cfg- Parse from
homepath (e.g.,Python311) - Spawn Python executable (last resort)
Code Pattern
async function getVenvVersion(venvPath: string): Promise<string | undefined> {
const cfgPath = path.join(venvPath, 'pyvenv.cfg');
try {
const content = await fs.readFile(cfgPath, 'utf-8');
const lines = content.split('\n');
for (const line of lines) {
const [key, value] = line.split('=').map((s) => s.trim());
if (key === 'version') {
return value;
}
}
// Fall back to parsing home path
const homeLine = lines.find((l) => l.startsWith('home'));
if (homeLine) {
const home = homeLine.split('=')[1].trim();
const match = home.match(/(\d+)\.(\d+)/);
if (match) {
return `${match[1]}.${match[2]}`;
}
}
} catch {
// Config file not found or unreadable
}
return undefined;
}
PET Server (Native Finder)
JSON-RPC Communication
The PET server is a Rust-based locator that communicates via JSON-RPC over stdio.
Known Issues
| Issue | Description | Fix |
|---|---|---|
| No timeout | JSON-RPC can hang forever | Add Promise.race with timeout |
| Silent spawn errors | Extension continues without envs | Surface spawn errors to user |
| Resource leaks | Worker pool not cleaned up | Dispose on deactivation |
| Type guard missing | Response types not validated | Add runtime type checks |
| Cache key collision | Paths normalize to same key | Use consistent normalization |
Code Pattern
async function fetchFromPET<T>(method: string, params: unknown): Promise<T> {
const timeout = 30000; // 30 seconds
const result = await Promise.race([
this.client.request(method, params),
new Promise<never>((_, reject) => setTimeout(() => reject(new Error('PET server timeout')), timeout)),
]);
// Validate response type
if (!isValidResponse<T>(result)) {
throw new Error(`Invalid response from PET: ${JSON.stringify(result)}`);
}
return result;
}