Skip to content

Commit 8893c4a

Browse files
committed
fix error
1 parent ae335aa commit 8893c4a

File tree

1 file changed

+116
-6
lines changed

1 file changed

+116
-6
lines changed

crates/pet-pipenv/src/lib.rs

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,36 @@ fn get_pipenv_project(env: &PythonEnv) -> Option<PathBuf> {
2323
if let Some(project) = get_pipenv_project_from_prefix(prefix) {
2424
return Some(project);
2525
}
26+
// If there's no .project file, but the venv lives inside the project folder
27+
// (e.g., <project>/.venv or <project>/venv), then the project is the parent
28+
// directory of the venv. Detect that by checking for a Pipfile next to the venv.
29+
if let Some(parent) = prefix.parent() {
30+
let project_folder = parent;
31+
if project_folder.join("Pipfile").exists() {
32+
return Some(project_folder.to_path_buf());
33+
}
34+
}
2635
}
2736

2837
// We can also have a venv in the workspace that has pipenv installed in it.
2938
// In such cases, the project is the workspace folder containing the venv.
30-
if let Some(project) = &env.project {
31-
if project.join("Pipfile").exists() {
32-
return Some(project.clone());
39+
// Derive the project folder from the executable path when prefix isn't available.
40+
// Typical layout: <project>/.venv/{bin|Scripts}/python
41+
// So walk up to {bin|Scripts} -> venv dir -> project dir and check for Pipfile.
42+
if let Some(bin) = env.executable.parent() {
43+
let venv_dir = if bin.file_name().unwrap_or_default() == Path::new("bin")
44+
|| bin.file_name().unwrap_or_default() == Path::new("Scripts")
45+
{
46+
bin.parent()
47+
} else {
48+
Some(bin)
49+
};
50+
if let Some(venv_dir) = venv_dir {
51+
if let Some(project_dir) = venv_dir.parent() {
52+
if project_dir.join("Pipfile").exists() {
53+
return Some(project_dir.to_path_buf());
54+
}
55+
}
3356
}
3457
}
3558

@@ -59,9 +82,29 @@ fn get_pipenv_project_from_prefix(prefix: &Path) -> Option<PathBuf> {
5982
}
6083

6184
fn is_pipenv_from_project(env: &PythonEnv) -> bool {
62-
if let Some(project) = &env.project {
63-
if project.join("Pipfile").exists() {
64-
return true;
85+
// If the env prefix is inside a project folder, check that folder for a Pipfile.
86+
if let Some(prefix) = &env.prefix {
87+
if let Some(project_dir) = prefix.parent() {
88+
if project_dir.join("Pipfile").exists() {
89+
return true;
90+
}
91+
}
92+
}
93+
// Derive from the executable path as a fallback.
94+
if let Some(bin) = env.executable.parent() {
95+
let venv_dir = if bin.file_name().unwrap_or_default() == Path::new("bin")
96+
|| bin.file_name().unwrap_or_default() == Path::new("Scripts")
97+
{
98+
bin.parent()
99+
} else {
100+
Some(bin)
101+
};
102+
if let Some(venv_dir) = venv_dir {
103+
if let Some(project_dir) = venv_dir.parent() {
104+
if project_dir.join("Pipfile").exists() {
105+
return true;
106+
}
107+
}
65108
}
66109
}
67110
false
@@ -144,3 +187,70 @@ impl Locator for PipEnv {
144187
//
145188
}
146189
}
190+
191+
#[cfg(test)]
192+
mod tests {
193+
use super::*;
194+
use std::time::{SystemTime, UNIX_EPOCH};
195+
196+
fn unique_temp_dir() -> PathBuf {
197+
let mut dir = std::env::temp_dir();
198+
let nanos = SystemTime::now()
199+
.duration_since(UNIX_EPOCH)
200+
.unwrap()
201+
.as_nanos();
202+
dir.push(format!("pet_pipenv_test_{}", nanos));
203+
dir
204+
}
205+
206+
#[test]
207+
fn infer_project_for_venv_in_project() {
208+
let project_dir = unique_temp_dir();
209+
let venv_dir = project_dir.join(".venv");
210+
let bin_dir = if cfg!(windows) {
211+
venv_dir.join("Scripts")
212+
} else {
213+
venv_dir.join("bin")
214+
};
215+
let python_exe = if cfg!(windows) {
216+
bin_dir.join("python.exe")
217+
} else {
218+
bin_dir.join("python")
219+
};
220+
221+
// Create directories and files
222+
std::fs::create_dir_all(&bin_dir).unwrap();
223+
std::fs::write(project_dir.join("Pipfile"), b"[[source]]\n").unwrap();
224+
// Touch python exe file
225+
std::fs::write(&python_exe, b"").unwrap();
226+
// Touch pyvenv.cfg in venv root so PythonEnv::new logic would normally detect prefix
227+
std::fs::write(venv_dir.join("pyvenv.cfg"), b"version = 3.12.0\n").unwrap();
228+
229+
// Construct PythonEnv directly
230+
let env = PythonEnv {
231+
executable: norm_case(python_exe.clone()),
232+
prefix: Some(norm_case(venv_dir.clone())),
233+
version: None,
234+
symlinks: None,
235+
};
236+
237+
// Validate helper infers project
238+
let inferred = get_pipenv_project(&env).expect("expected project path");
239+
assert_eq!(inferred, norm_case(project_dir.clone()));
240+
241+
// Validate locator populates project
242+
let locator = PipEnv {
243+
env_vars: EnvVariables {
244+
pipenv_max_depth: 3,
245+
pipenv_pipfile: "Pipfile".to_string(),
246+
},
247+
};
248+
let result = locator
249+
.try_from(&env)
250+
.expect("expected locator to return environment");
251+
assert_eq!(result.project, Some(norm_case(project_dir.clone())));
252+
253+
// Cleanup
254+
std::fs::remove_dir_all(&project_dir).ok();
255+
}
256+
}

0 commit comments

Comments
 (0)