Skip to content

Commit 2a22d4e

Browse files
committed
Keep lockless PDM projects out of the uv probe
A PDM project with no lock and no saved interpreter ([tool.pdm] or .pdm.toml) is still PDM's, so an ambient UV_PROJECT_ENVIRONMENT no longer claims it before the PEP 582 fallback; uv.lock still wins. A [tool.pdm.build] table alone (the pdm-backend build backend) no longer marks a project as PDM-managed. Found in review. Assisted-by: Claude Code:claude-opus-5-5
1 parent 5ecac79 commit 2a22d4e

1 file changed

Lines changed: 60 additions & 4 deletions

File tree

‎crates/socket-patch-core/src/crawlers/python_crawler.rs‎

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,9 @@ async fn pdm_saved_interpreter(cwd: &Path) -> Option<PathBuf> {
494494
}
495495

496496
/// Whether `cwd` is a PDM project: `pdm.lock`, `.pdm.toml`, or a
497-
/// `[tool.pdm]` table in `pyproject.toml`.
497+
/// `[tool.pdm]` table in `pyproject.toml` with settings beyond `build` (a
498+
/// `[tool.pdm.build]` table alone only configures the pdm-backend build
499+
/// backend, which projects driven by other managers use too).
498500
async fn is_pdm_project(cwd: &Path) -> bool {
499501
if cwd.join("pdm.lock").is_file() || cwd.join(".pdm.toml").is_file() {
500502
return true;
@@ -504,7 +506,10 @@ async fn is_pdm_project(cwd: &Path) -> bool {
504506
};
505507
text.parse::<toml_edit::DocumentMut>()
506508
.ok()
507-
.and_then(|doc| doc.get("tool")?.get("pdm").map(|_| ()))
509+
.and_then(|doc| {
510+
let pdm = doc.get("tool")?.get("pdm")?.as_table_like()?;
511+
pdm.iter().any(|(key, _)| key != "build").then_some(())
512+
})
508513
.is_some()
509514
}
510515

@@ -537,8 +542,9 @@ async fn uv_project_environment_site_packages(
537542
]
538543
.iter()
539544
.any(|marker| cwd.join(marker).exists());
540-
// A lockless Poetry project is still Poetry's (`[tool.poetry]`).
545+
// A lockless Poetry (`[tool.poetry]`) or PDM project is still theirs.
541546
let other_manager = other_lock
547+
|| is_pdm_project(cwd).await
542548
|| read_regular_to_string(&cwd.join("pyproject.toml"))
543549
.await
544550
.is_ok_and(|text| text.contains("[tool.poetry"));
@@ -2411,6 +2417,30 @@ mod tests {
24112417
std::fs::remove_file(project.join(".pdm-python")).unwrap();
24122418
std::fs::remove_dir_all(project.join(".venv")).unwrap();
24132419

2420+
// A lockless PDM project's `__pypackages__` is not handed to an
2421+
// ambient UV_PROJECT_ENVIRONMENT.
2422+
std::fs::remove_file(project.join("pdm.lock")).unwrap();
2423+
std::fs::write(
2424+
project.join("pyproject.toml"),
2425+
"[project]\nname = \"demo\"\n[tool.pdm]\ndistribution = false\n",
2426+
)
2427+
.unwrap();
2428+
fake_venv(&tmp.path().join("uv-env"), "venv");
2429+
let uv_env = env_of(&[(
2430+
"UV_PROJECT_ENVIRONMENT",
2431+
tmp.path().join("uv-env").join("venv").to_string_lossy().into_owned(),
2432+
)]);
2433+
assert_eq!(
2434+
find_local_venv_site_packages_with(&project, &uv_env).await,
2435+
vec![lib.clone()]
2436+
);
2437+
std::fs::write(
2438+
project.join("pyproject.toml"),
2439+
"[project]\nname = \"demo\"\n",
2440+
)
2441+
.unwrap();
2442+
std::fs::write(project.join("pdm.lock"), "[metadata]\n").unwrap();
2443+
24142444
// A uv project with a leftover PDM lock is uv's, not PEP 582.
24152445
std::fs::write(project.join("uv.lock"), "version = 1\n").unwrap();
24162446
assert!(find_local_venv_site_packages_with(&project, &no_env)
@@ -2469,7 +2499,7 @@ mod tests {
24692499
std::fs::remove_file(project.join("uv.lock")).unwrap();
24702500
assert_eq!(
24712501
find_local_venv_site_packages_with(&project, &abs_env).await,
2472-
vec![abs_site]
2502+
vec![abs_site.clone()]
24732503
);
24742504

24752505
// A lockless Poetry project (`[tool.poetry]`, or `poetry.toml`) is
@@ -2495,6 +2525,32 @@ mod tests {
24952525
);
24962526
std::fs::remove_file(project.join("poetry.toml")).unwrap();
24972527

2528+
// A lockless PDM project is PDM's, while a `[tool.pdm.build]` table
2529+
// alone (the pdm-backend build backend) does not make one.
2530+
std::fs::write(
2531+
project.join("pyproject.toml"),
2532+
"[project]\nname = \"app\"\n[tool.pdm]\ndistribution = false\n",
2533+
)
2534+
.unwrap();
2535+
assert_eq!(
2536+
find_local_venv_site_packages_with(&project, &abs_env).await,
2537+
vec![stray.clone()]
2538+
);
2539+
std::fs::write(
2540+
project.join("pyproject.toml"),
2541+
"[project]\nname = \"app\"\n[tool.pdm.build]\nincludes = [\"app\"]\n",
2542+
)
2543+
.unwrap();
2544+
assert_eq!(
2545+
find_local_venv_site_packages_with(&project, &abs_env).await,
2546+
vec![abs_site.clone()]
2547+
);
2548+
std::fs::write(
2549+
project.join("pyproject.toml"),
2550+
"[project]\nname = \"app\"\n",
2551+
)
2552+
.unwrap();
2553+
24982554
// ...but not a project another manager drives, nor a non-project.
24992555
std::fs::write(project.join("poetry.lock"), "").unwrap();
25002556
assert_eq!(

0 commit comments

Comments
 (0)