This is a high-performance Rust-based tool for discovering Python environments and virtual environments. It operates as a JSONRPC server consumed by the VS Code Python extension to avoid spawning Python processes repeatedly.
- Locators: Modular environment discovery components implementing the
Locatortrait (crates/pet-core/src/lib.rs) - JSONRPC Server: Main communication interface (
crates/pet/src/jsonrpc.rs) with stdio/stdout protocol - Environment Types: 15+ supported Python installations (Conda, Poetry, PyEnv, Homebrew, Windows Store, etc.)
- Reporter Pattern: Asynchronous environment discovery reporting via the
Reportertrait
crates/pet/src/locators.rs- Ordered locator creation and fallback identification logiccrates/pet/src/find.rs- Multi-threaded environment discovery coordinationcrates/pet-core/src/lib.rs- Core traits and configuration structuresdocs/JSONRPC.md- Complete API specification with TypeScript interfaces
# Standard build
cargo build
# Release build (optimized for performance)
cargo build --release
# Run tests with specific CI features
cargo test --features ci
cargo test --features ci-poetry-global
# Run JSONRPC server
./target/debug/pet serverTests use feature flags for different environments:
ci- General CI environment testsci-jupyter-container- Jupyter container-specific testsci-homebrew-container- Homebrew container testsci-poetry-*- Poetry-specific test variants
When adding new environment types:
- Create new crate:
crates/pet-{name}/ - Implement Locator trait: Key methods are
try_from()(identification) andfind()(discovery) - Add to locator chain: Update
create_locators()incrates/pet/src/locators.rs- ORDER MATTERS - Platform-specific: Use
#[cfg(windows)],#[cfg(unix)],#[cfg(target_os = "macos")]
Example structure:
impl Locator for MyLocator {
fn get_kind(&self) -> LocatorKind { LocatorKind::MyType }
fn supported_categories(&self) -> Vec<PythonEnvironmentKind> { vec![PythonEnvironmentKind::MyType] }
fn try_from(&self, env: &PythonEnv) -> Option<PythonEnvironment> { /* identification logic */ }
fn find(&self, reporter: &dyn Reporter) { /* discovery logic */ }
}- Avoid spawning processes - Extract info from files/filesystem when possible
- Report immediately - Use Reporter pattern for async discovery
- Complete information - Gather all environment details in one pass, not incrementally
- Client sends
configurerequest (must be first) - Client sends
refreshrequest to discover environments - Server sends
environmentnotifications as discoveries happen - Optional:
resolverequest for individual Python executables
Tests validate discovered environments using 4 verification methods:
- Spawn Python to verify
sys.prefixandsys.version - Use
try_from()with executable to get same info - Test symlink identification
- Use
resolvemethod for consistency
- Supports detection from history files and conda-meta directories
- Manager detection via spawning conda executable in background threads
- Complex prefix/name relationships for base vs named environments
- Hash-based environment naming:
{project-name}-{hash}-py - Project-specific virtual environments in configured cache directories
- Configuration hierarchy: local poetry.toml → global config
- Windows: Registry + Windows Store detection, different path separators
- macOS: Xcode Command Line Tools, python.org, Homebrew paths
- Linux: Global system paths (
/usr/bin,/usr/local/bin)
- Locator order matters in
create_locators()- more specific before generic - Thread safety - Heavy use of Arc/Mutex for concurrent discovery
- Feature flags - Many tests only run with specific CI features enabled
- Path canonicalization - Symlink resolution varies by platform
- Caching - Optional cache directory for expensive operations (conda spawning)
docs/JSONRPC.md- Understanding the external APIcrates/pet/src/locators.rs- Core architecture patternscrates/pet-core/src/lib.rs- Essential traits and typescrates/pet/tests/ci_test.rs- Comprehensive testing patterns
- Use
cargo fetchto download all dependencies - Use
rustup component add clippyto install Clippy linter - Use
cargo fmt --allto format code in all packages - Use
cargo clippy --all-features -- -Dwarningsto check for linter issues - Use
cargo clippy --all-features --fix --allow-dirty -- -Dwarningsto automatically fix linter issues - Use
cargo buildto build the project - Use
cargo test --allto test all packages (this can take a few seconds) - Use
cargo test [TESTNAME]to test a specific test - Use
cargo test -p [SPEC]to test a specific package - Use
cargo test --allto test all packages