Skip to content

feat: add pet-hatch locator for Hatch environments (Fixes #450)#460

Draft
karthiknadig wants to merge 15 commits into
mainfrom
feature/issue-450
Draft

feat: add pet-hatch locator for Hatch environments (Fixes #450)#460
karthiknadig wants to merge 15 commits into
mainfrom
feature/issue-450

Conversation

@karthiknadig
Copy link
Copy Markdown
Member

Fixes #450.

Adds a new pet-hatch crate so Hatch-managed virtual environments are no longer misclassified as plain Venv by downstream consumers.

Implementation

Matches Hatch's actual storage layout from src/hatch/env/virtual.py:

  • Default storage: <data_dir>/env/virtual/<project_name>/<project_id>/<venv_name> (3 levels deep, not 2).
  • HATCH_DATA_DIR is honoured and never silently falls back to the platform default when set.
  • Project-local discovery via [tool.hatch.dirs.env].virtual in pyproject.toml or [dirs.env] in hatch.toml. Handles the virtual = ".hatch" example called out in the issue.
  • Hatch locator is inserted before Venv so it claims envs first.
  • LocatorKind::Hatch registered as RefreshStatePersistence::ConfiguredOnly (workspace-driven discovery).

Tests

15 unit tests covering:

  • Default-storage layout matches at exactly 3 levels deep; rejects 2-deep and 4-deep prefixes.
  • HATCH_DATA_DIR semantics — used when set, no fallback to platform default.
  • Project-local discovery via both pyproject.toml and hatch.toml; rejected without dirs.env.virtual config.
  • Per-platform platformdirs defaults (Linux/macOS/Windows).

Replaces #451

This PR replaces #451, which had a fundamental layout bug: it assumed envs are stored 2 levels deep under <data_dir>/env/virtual (<project-hash>/<env-name>). Per Hatch's source, the actual layout is 3 levels deep (<project_name>/<project_id>/<venv_name>), so #451 would fail to detect any real Hatch env in the default storage location.

Adds a new pet-hatch crate that detects Hatch-managed virtual environments so they are no longer misclassified as plain Venv by downstream consumers.

Implementation matches Hatch's actual storage layout from src/hatch/env/virtual.py:

- Default storage: <data_dir>/env/virtual/<project_name>/<project_id>/<venv_name> (3 levels deep)

- HATCH_DATA_DIR env var honoured; never silently falls back to platform default when set

\ example from the issue)

- Locator inserted before Venv so Hatch claims its envs first

- 15 unit tests covering layout depth (rejects 2 / 4 levels), HATCH_DATA_DIR semantics, project-local config, and platform defaults
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Performance Report (Linux) ➖

Metric PR (P50) PR (P95) Baseline (P50) Delta Change
Server Startup 1ms 1ms 1ms 0ms 0%
Full Refresh 66ms 512ms 50ms 16ms 30.0%

Results based on 10 iterations. P50 = median, P95 = 95th percentile.


Legend
  • 🚀 Significant speedup (>100ms faster)
  • ✅ Faster than baseline
  • ➖ No significant change
  • 🔺 Slower than baseline (>100ms)
  • ⚠️ Significant slowdown (>500ms)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Test Coverage Report (Linux)

Metric Value
Current Coverage 77.5%
Base Branch Coverage 76.5%
Delta 1.0% ✅

Coverage increased! Great work!

@karthiknadig karthiknadig marked this pull request as ready for review May 8, 2026 21:28
@karthiknadig karthiknadig requested a review from Copilot May 8, 2026 21:28
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Performance Report (Windows) ✅

Metric PR (P50) PR (P95) Baseline (P50) Delta Change
Server Startup 7ms 9ms 9ms -2ms -22.2%
Full Refresh 91ms 6074ms 148ms -57ms -38.5%

Results based on 10 iterations. P50 = median, P95 = 95th percentile.


Legend
  • 🚀 Significant speedup (>100ms faster)
  • ✅ Faster than baseline
  • ➖ No significant change
  • 🔺 Slower than baseline (>100ms)
  • ⚠️ Significant slowdown (>500ms)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Test Coverage Report (Windows)

Coverage analysis in progress...

This comment will be updated with results when the analysis completes.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Performance Report (macOS)

Metric PR (P50) PR (P95) Baseline (P50) Delta
Server Startup 94ms 647ms 71ms 23ms
Full Refresh 168ms 40790ms 147ms 21ms

Results based on 10 iterations. P50 = median, P95 = 95th percentile.


Legend
  • 🚀 Significant speedup (>100ms faster)
  • ✅ Faster than baseline
  • ➖ No significant change
  • 🔺 Slower than baseline (>100ms)
  • ⚠️ Significant slowdown (>500ms)

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds first-class Hatch environment detection to PET by introducing a new pet-hatch locator and wiring it into the existing locator chain so Hatch-managed venvs are classified as Hatch rather than the generic Venv.

Changes:

  • Add new pet-hatch crate implementing a Hatch locator with default-storage and workspace-configured discovery/identification.
  • Register LocatorKind::Hatch and PythonEnvironmentKind::Hatch, and insert Hatch into the locator chain before Venv.
  • Update refresh-state contract expectations to mark Hatch as RefreshStatePersistence::ConfiguredOnly.
Show a summary per file
File Description
crates/pet/src/locators.rs Adds the Hatch locator into the ordered locator chain before Venv.
crates/pet/src/jsonrpc.rs Updates the refresh-state contract test to include LocatorKind::Hatch.
crates/pet/Cargo.toml Adds pet-hatch as a dependency of the main pet crate.
crates/pet-hatch/src/lib.rs Implements Hatch detection (default storage + workspace-configured) and adds unit tests.
crates/pet-hatch/Cargo.toml Defines the new pet-hatch crate and its dependencies.
crates/pet-core/src/python_environment.rs Adds PythonEnvironmentKind::Hatch.
crates/pet-core/src/lib.rs Adds LocatorKind::Hatch.
Cargo.lock Records the new pet-hatch crate and dependency graph updates.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 2

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs
- Cache resolved virtual dirs in configure() so try_from() does not re-parse pyproject.toml / hatch.toml on every executable identification attempt.

- Expand ~ (and \C:\Users\kanadig/\) in configured dirs.env.virtual values via pet_fs::path::expand_path so values like '~/.virtualenvs' resolve against the user home.

- Build the new cache outside the workspace_virtual_dirs lock to keep disk I/O out of the critical section.

- Serialize env-var-mutating tests via a per-binary mutex so cargo's default multi-threaded harness cannot race.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 1

Comment thread crates/pet/src/locators.rs
dmitrivMS
dmitrivMS previously approved these changes May 8, 2026
A Hatch project can configure dirs.env.virtual = '~/.virtualenvs' (or any path that overlaps with WORKON_HOME). With Hatch placed after VirtualEnvWrapper, those envs would be claimed as VirtualEnvWrapper before Hatch ever saw them. Move Hatch ahead so it gets first claim on workspace-configured envs.
dmitrivMS
dmitrivMS previously approved these changes May 8, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 3

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
- get_default_virtual_dir(): no longer requires the path to exist on disk at construction time. The long-lived locator graph is built once at server startup; users may install Hatch (or create the first env) later in the same process. find() now re-checks existence at call time so newly-created envs are discovered without a restart.

- try_from(): do the cheap path-shape classification (default storage / configured workspace dirs) BEFORE reading pyvenv.cfg, so non-Hatch venvs flowing through the locator chain do not pay an extra filesystem read.

- try_from(): inspect the workspace cache under the lock and capture the match instead of cloning the entire cache on every identification attempt.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 3

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
- resolve_project_virtual_dirs(): cache configured paths regardless of whether the directory exists on disk yet. A user may configure 'virtual = .hatch' and create the env later in this process lifetime; we now recognise it without requiring the client to re-send 'configure'. find_envs_in_flat_dir() already handles missing dirs by returning empty.

- default_virtual_dir field doc: corrected to reflect that the path may not exist on disk; existence is rechecked at find() time.

- Module docs: aligned with implementation. All workspace-configured 'dirs.env.virtual' paths use the flat layout, regardless of whether they are relative, absolute, or use ~ expansion.
…irtual dirs (PR #460)

Also fix CI test for tilde expansion to compare paths component-wise (Windows mixed-separator).
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 2

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
Address Copilot review: configure() now parses pyproject.toml and hatch.toml once each per workspace via read_workspace_hatch_sections(), and the env-name allowlist is precomputed into EnvNameMatcher so the try_from() hot path avoids per-call to_lowercase()/format!() allocations.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Comments suppressed due to low confidence (2)

crates/pet-hatch/src/lib.rs:187

  • try_from() recovers from a poisoned workspace_virtual_dirs mutex via into_inner(). If a previous panic occurred while mutating the cache, continuing here can misclassify environments and makes the underlying issue harder to detect. Consider using .expect("workspace_virtual_dirs mutex poisoned") (consistent with other crates) unless there’s a documented reason to continue after poisoning.
            let cache = self
                .workspace_virtual_dirs
                .lock()
                .unwrap_or_else(|p| p.into_inner());
            'workspaces: for (workspace, virtual_dirs, matcher) in cache.iter() {

crates/pet-hatch/src/lib.rs:247

  • find() clones the workspace cache after recovering from a poisoned mutex via into_inner(). Similar to configure()/try_from(), this can hide panics and proceed with corrupted state. Align with the rest of the repo by using .expect("workspace_virtual_dirs mutex poisoned") (or add a comment explaining why poison recovery is safe here).
        let workspaces = self
            .workspace_virtual_dirs
            .lock()
            .unwrap_or_else(|p| p.into_inner())
            .clone();
  • Files reviewed: 7/8 changed files
  • Comments generated: 2

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 3

Comment on lines 13 to 23
pub enum PythonEnvironmentKind {
Conda,
Pixi,
Homebrew,
Pyenv,
GlobalPaths, // Python found in global locations like PATH, /usr/bin etc.
PyenvVirtualEnv, // Pyenv virtualenvs.
Pipenv,
Poetry,
Hatch,
MacPythonOrg,
Comment thread crates/pet-hatch/src/lib.rs
Comment thread crates/pet-hatch/src/lib.rs
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

Comments suppressed due to low confidence (2)

crates/pet-hatch/src/lib.rs:181

  • Here and elsewhere in this locator, .lock().unwrap() will panic without context if the mutex is poisoned. For consistency with other PET locators, prefer .lock().expect("workspace_virtual_dirs mutex poisoned") (or equivalent) so failures are actionable.
        if classification.is_none() {
            let cache = self.workspace_virtual_dirs.lock().unwrap();
            'workspaces: for (workspace, virtual_dirs, matcher) in cache.iter() {

crates/pet-hatch/src/lib.rs:237

  • find() clones the cached workspace state under a mutex using .lock().unwrap(). Consider using .lock().expect("workspace_virtual_dirs mutex poisoned") for a clearer panic message (matching the convention used across the repo).
        let workspaces = self.workspace_virtual_dirs.lock().unwrap().clone();
  • Files reviewed: 7/8 changed files
  • Comments generated: 1

Comment thread crates/pet-hatch/src/lib.rs Outdated
@karthiknadig karthiknadig requested a review from Copilot May 11, 2026 20:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 2

Comment thread crates/pet-hatch/src/lib.rs
Comment thread crates/pet-hatch/src/lib.rs Outdated
@karthiknadig karthiknadig requested a review from Copilot May 11, 2026 21:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 2

Comment thread crates/pet-hatch/src/lib.rs Outdated
Comment thread crates/pet-hatch/src/lib.rs
@karthiknadig karthiknadig requested a review from Copilot May 11, 2026 21:26
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 7/8 changed files
  • Comments generated: 2

*self
.workspace_virtual_dirs
.lock()
.expect("workspace_virtual_dirs mutex poisoned") = new_cache;
Comment on lines +727 to +734
PythonEnvironmentBuilder::new(Some(PythonEnvironmentKind::Hatch))
.name(env_name)
.executable(Some(executable))
.version(cfg.version)
.prefix(Some(prefix.to_path_buf()))
.symlinks(Some(find_executables(prefix)))
.project(project_path)
.build(),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Hatch environment detection (new pet-hatch locator)

4 participants