Skip to content

Commit d33381b

Browse files
Copilotkarthiknadig
andcommitted
Refactor venv-uv implementation to extend existing venv locator instead of creating new crate
Co-authored-by: karthiknadig <3840081+karthiknadig@users.noreply.github.com>
1 parent b3faf31 commit d33381b

File tree

7 files changed

+135
-184
lines changed

7 files changed

+135
-184
lines changed

Cargo.lock

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/pet-core/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ pub enum LocatorKind {
5151
Poetry,
5252
PyEnv,
5353
Venv,
54-
VenvUv,
5554
VirtualEnv,
5655
VirtualEnvWrapper,
5756
WindowsRegistry,

crates/pet-venv-uv/Cargo.toml

Lines changed: 0 additions & 13 deletions
This file was deleted.

crates/pet-venv-uv/src/lib.rs

Lines changed: 0 additions & 152 deletions
This file was deleted.

crates/pet-venv/src/lib.rs

Lines changed: 134 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ pub fn is_venv(env: &PythonEnv) -> bool {
2626
pub fn is_venv_dir(path: &Path) -> bool {
2727
PyVenvCfg::find(path).is_some()
2828
}
29+
30+
pub fn is_venv_uv(env: &PythonEnv) -> bool {
31+
if let Some(cfg) = PyVenvCfg::find(env.executable.parent().unwrap_or(Path::new(""))) {
32+
return cfg.is_uv();
33+
}
34+
if let Some(ref prefix) = env.prefix {
35+
if let Some(cfg) = PyVenvCfg::find(prefix) {
36+
return cfg.is_uv();
37+
}
38+
}
39+
false
40+
}
41+
42+
pub fn is_venv_uv_dir(path: &Path) -> bool {
43+
if let Some(cfg) = PyVenvCfg::find(path) {
44+
cfg.is_uv()
45+
} else {
46+
false
47+
}
48+
}
2949
pub struct Venv {}
3050

3151
impl Venv {
@@ -43,7 +63,7 @@ impl Locator for Venv {
4363
LocatorKind::Venv
4464
}
4565
fn supported_categories(&self) -> Vec<PythonEnvironmentKind> {
46-
vec![PythonEnvironmentKind::Venv]
66+
vec![PythonEnvironmentKind::Venv, PythonEnvironmentKind::VenvUv]
4767
}
4868

4969
fn try_from(&self, env: &PythonEnv) -> Option<PythonEnvironment> {
@@ -67,10 +87,17 @@ impl Locator for Venv {
6787
// Get the name from the prefix if it exists.
6888
let cfg = PyVenvCfg::find(env.executable.parent()?)
6989
.or_else(|| PyVenvCfg::find(&env.prefix.clone()?));
70-
let name = cfg.and_then(|cfg| cfg.prompt);
90+
let name = cfg.as_ref().and_then(|cfg| cfg.prompt.clone());
91+
92+
// Determine the environment kind based on whether it was created with UV
93+
let kind = if cfg.as_ref().map_or(false, |c| c.is_uv()) {
94+
PythonEnvironmentKind::VenvUv
95+
} else {
96+
PythonEnvironmentKind::Venv
97+
};
7198

7299
Some(
73-
PythonEnvironmentBuilder::new(Some(PythonEnvironmentKind::Venv))
100+
PythonEnvironmentBuilder::new(Some(kind))
74101
.name(name)
75102
.executable(Some(env.executable.clone()))
76103
.version(version)
@@ -88,3 +115,107 @@ impl Locator for Venv {
88115
// We expect the user of this class to call `is_compatible`
89116
}
90117
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
use std::path::PathBuf;
123+
124+
#[test]
125+
fn test_is_venv_uv_dir_detects_uv_environment() {
126+
use std::fs;
127+
let test_dir = PathBuf::from("/tmp/test_uv_env_venv");
128+
fs::create_dir_all(&test_dir).unwrap();
129+
let pyvenv_cfg = test_dir.join("pyvenv.cfg");
130+
let contents = "home = /usr/bin/python3.12\nimplementation = CPython\nuv = 0.8.14\nversion_info = 3.12.11\ninclude-system-site-packages = false\nprompt = test-uv-env\n";
131+
fs::write(&pyvenv_cfg, contents).unwrap();
132+
133+
assert!(is_venv_uv_dir(&test_dir), "Should detect UV environment");
134+
135+
fs::remove_dir_all(&test_dir).ok();
136+
}
137+
138+
#[test]
139+
fn test_is_venv_uv_dir_does_not_detect_regular_environment() {
140+
use std::fs;
141+
let test_dir = PathBuf::from("/tmp/test_regular_env_venv");
142+
fs::create_dir_all(&test_dir).unwrap();
143+
let pyvenv_cfg = test_dir.join("pyvenv.cfg");
144+
let contents = "home = /usr/bin/python3.12\ninclude-system-site-packages = false\nversion = 3.13.5\nexecutable = /usr/bin/python3.12\ncommand = python -m venv /path/to/env\n";
145+
fs::write(&pyvenv_cfg, contents).unwrap();
146+
147+
assert!(
148+
!is_venv_uv_dir(&test_dir),
149+
"Should not detect regular venv as UV environment"
150+
);
151+
152+
fs::remove_dir_all(&test_dir).ok();
153+
}
154+
155+
#[test]
156+
fn test_is_venv_uv_dir_handles_nonexistent_environment() {
157+
let nonexistent_path = PathBuf::from("/tmp/nonexistent_env");
158+
assert!(
159+
!is_venv_uv_dir(&nonexistent_path),
160+
"Should not detect non-existent environment as UV"
161+
);
162+
}
163+
164+
#[test]
165+
fn test_venv_locator_detects_uv_kind() {
166+
use pet_core::env::PythonEnv;
167+
use std::fs;
168+
169+
// Create a test UV environment
170+
let test_dir = PathBuf::from("/tmp/test_locator_uv");
171+
let bin_dir = test_dir.join("bin");
172+
fs::create_dir_all(&bin_dir).unwrap();
173+
174+
let pyvenv_cfg = test_dir.join("pyvenv.cfg");
175+
let contents = "home = /usr/bin/python3.12\nimplementation = CPython\nuv = 0.8.14\nversion_info = 3.12.11\ninclude-system-site-packages = false\nprompt = test-uv-env\n";
176+
fs::write(&pyvenv_cfg, contents).unwrap();
177+
178+
let python_exe = bin_dir.join("python");
179+
fs::write(&python_exe, "").unwrap(); // Create dummy python executable
180+
181+
let env = PythonEnv::new(python_exe.clone(), Some(test_dir.clone()), Some("3.12.11".to_string()));
182+
let locator = Venv::new();
183+
184+
if let Some(result) = locator.try_from(&env) {
185+
assert_eq!(result.kind, Some(PythonEnvironmentKind::VenvUv), "UV environment should be detected as VenvUv");
186+
} else {
187+
panic!("Locator should detect UV environment");
188+
}
189+
190+
fs::remove_dir_all(&test_dir).ok();
191+
}
192+
193+
#[test]
194+
fn test_venv_locator_detects_regular_venv_kind() {
195+
use pet_core::env::PythonEnv;
196+
use std::fs;
197+
198+
// Create a test regular venv environment
199+
let test_dir = PathBuf::from("/tmp/test_locator_regular");
200+
let bin_dir = test_dir.join("bin");
201+
fs::create_dir_all(&bin_dir).unwrap();
202+
203+
let pyvenv_cfg = test_dir.join("pyvenv.cfg");
204+
let contents = "home = /usr/bin/python3.12\ninclude-system-site-packages = false\nversion = 3.13.5\nexecutable = /usr/bin/python3.12\ncommand = python -m venv /path/to/env\n";
205+
fs::write(&pyvenv_cfg, contents).unwrap();
206+
207+
let python_exe = bin_dir.join("python");
208+
fs::write(&python_exe, "").unwrap(); // Create dummy python executable
209+
210+
let env = PythonEnv::new(python_exe.clone(), Some(test_dir.clone()), Some("3.13.5".to_string()));
211+
let locator = Venv::new();
212+
213+
if let Some(result) = locator.try_from(&env) {
214+
assert_eq!(result.kind, Some(PythonEnvironmentKind::Venv), "Regular venv should be detected as Venv");
215+
} else {
216+
panic!("Locator should detect regular venv environment");
217+
}
218+
219+
fs::remove_dir_all(&test_dir).ok();
220+
}
221+
}

crates/pet/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ pet-linux-global-python = { path = "../pet-linux-global-python" }
3131
pet-mac-xcode = { path = "../pet-mac-xcode" }
3232
pet-mac-python-org = { path = "../pet-mac-python-org" }
3333
pet-venv = { path = "../pet-venv" }
34-
pet-venv-uv = { path = "../pet-venv-uv" }
3534
pet-virtualenv = { path = "../pet-virtualenv" }
3635
pet-pipenv = { path = "../pet-pipenv" }
3736
pet-telemetry = { path = "../pet-telemetry" }

crates/pet/src/locators.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use pet_poetry::Poetry;
2020
use pet_pyenv::PyEnv;
2121
use pet_python_utils::env::ResolvedPythonEnv;
2222
use pet_venv::Venv;
23-
use pet_venv_uv::VenvUv;
2423
use pet_virtualenv::VirtualEnv;
2524
use pet_virtualenvwrapper::VirtualEnvWrapper;
2625
use std::path::PathBuf;
@@ -58,11 +57,10 @@ pub fn create_locators(
5857

5958
// 6. Support for Virtual Envs
6059
// The order of these matter.
61-
// Basically PipEnv is a superset of VirtualEnvWrapper, which is a superset of VenvUv, which is a superset of Venv, which is a superset of VirtualEnv.
60+
// Basically PipEnv is a superset of VirtualEnvWrapper, which is a superset of Venv, which is a superset of VirtualEnv.
6261
locators.push(poetry_locator);
6362
locators.push(Arc::new(PipEnv::from(environment)));
6463
locators.push(Arc::new(VirtualEnvWrapper::from(environment)));
65-
locators.push(Arc::new(VenvUv::new()));
6664
locators.push(Arc::new(Venv::new()));
6765
// VirtualEnv is the most generic, hence should be the last.
6866
locators.push(Arc::new(VirtualEnv::new()));

0 commit comments

Comments
 (0)