дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
feat(sdk): create Python environments with uv and Poetry in Add Interpreter (#1888)
* feat(sdk): create Python environments with uv and Poetry in Add Interpreter Add a VenvTool parameter to sdk_python_create_venv so the Add Python Interpreter dialog can build a new environment with virtualenv, uv or Poetry, matching what the environment discovery already finds. Poetry keeps envs in its own cache, so the dialog disables the location field for it and resolves the interpreter via 'poetry env info --path'. Fixes l0ng-ai/openidea#1776 * fix(i18n): update text catalogs for the new env-tool strings The Add Interpreter dialog gained uv/Poetry labels and per-tool notes, and its spinner text changed from 'Creating virtual environment' to 'Creating environment'. Add the new zh entries and rename the stale key in every catalog so the text-catalog test passes.
Цей коміт міститься в:
1 джерело
d018771a8d
коміт
88ecaf9ccd
14 files changed
+338
-38
No files matched your search
@@ -16,25 +16,105 @@ pub struct PythonPackage {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
/// Creates a virtualenv at `location` with `base -m venv` and returns its interpreter.
|
||||
/// The tool that creates a new Python environment in "Add Python Interpreter".
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum VenvTool {
|
||||
/// `base -m venv <location>`.
|
||||
Venv,
|
||||
/// `uv venv <location> --python <base>` (uv must be on PATH).
|
||||
Uv,
|
||||
/// `poetry env use <base>` in the project, then `poetry env info --path`.
|
||||
Poetry,
|
||||
}
|
||||
|
||||
/// Creates a virtualenv at `location` with `tool` and returns its interpreter.
|
||||
/// `base` is the interpreter uv / venv build on, or the one poetry's env uses.
|
||||
pub fn create_venv(
|
||||
tool: VenvTool,
|
||||
base: &Path,
|
||||
location: &Path,
|
||||
cancel: &CancellationToken,
|
||||
on_line: &dyn Fn(String),
|
||||
) -> IdeResult<Sdk> {
|
||||
if location.join("pyvenv.cfg").is_file() {
|
||||
return Err(IdeError::new(
|
||||
ide_core::error::ErrorKind::AlreadyExists,
|
||||
format!("{} already contains a virtual environment", location.display()),
|
||||
));
|
||||
create_venv_with(tool, base, location, which, cancel, on_line)
|
||||
}
|
||||
|
||||
/// [`create_venv`] with the tool lookup injectable (tests fake `uv` / `poetry`).
|
||||
pub(crate) fn create_venv_with(
|
||||
tool: VenvTool,
|
||||
base: &Path,
|
||||
location: &Path,
|
||||
which: impl Fn(&str) -> Option<PathBuf>,
|
||||
cancel: &CancellationToken,
|
||||
on_line: &dyn Fn(String),
|
||||
) -> IdeResult<Sdk> {
|
||||
match tool {
|
||||
VenvTool::Venv | VenvTool::Uv => {
|
||||
if location.join("pyvenv.cfg").is_file() {
|
||||
return Err(IdeError::new(
|
||||
ide_core::error::ErrorKind::AlreadyExists,
|
||||
format!("{} already contains a virtual environment", location.display()),
|
||||
));
|
||||
}
|
||||
if let Some(parent) = location.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
let (program, args) = match tool {
|
||||
VenvTool::Uv => {
|
||||
let uv = which("uv").ok_or_else(|| {
|
||||
IdeError::unavailable("uv is not installed (install it from https://docs.astral.sh/uv)")
|
||||
})?;
|
||||
(
|
||||
uv,
|
||||
vec![
|
||||
"venv".to_owned(),
|
||||
location.to_string_lossy().into_owned(),
|
||||
"--python".to_owned(),
|
||||
base.to_string_lossy().into_owned(),
|
||||
],
|
||||
)
|
||||
}
|
||||
_ => (base.to_path_buf(), venv_args(location)),
|
||||
};
|
||||
stream(&program, &args, None, &[], cancel, on_line)?;
|
||||
let python = location.join(bin_dir()).join(exe("python"));
|
||||
crate::discover::python_sdk(&python, "venv", location.parent().and_then(Path::parent))
|
||||
.ok_or_else(|| IdeError::internal(format!("{} was not created", python.display())))
|
||||
}
|
||||
VenvTool::Poetry => create_poetry_env(base, location, &which, cancel, on_line),
|
||||
}
|
||||
if let Some(parent) = location.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
stream(base, &venv_args(location), None, &[], cancel, on_line)?;
|
||||
let python = location.join(bin_dir()).join(exe("python"));
|
||||
crate::discover::python_sdk(&python, "venv", location.parent().and_then(Path::parent))
|
||||
}
|
||||
|
||||
/// `poetry env use <base>` in the project directory (the parent of `location`, which the
|
||||
/// dialog fills with `<root>/.venv` only as a hint — poetry keeps its envs under its own
|
||||
/// cache), then `poetry env info --path` for the interpreter it made or reused.
|
||||
fn create_poetry_env(
|
||||
base: &Path,
|
||||
location: &Path,
|
||||
which: &dyn Fn(&str) -> Option<PathBuf>,
|
||||
cancel: &CancellationToken,
|
||||
on_line: &dyn Fn(String),
|
||||
) -> IdeResult<Sdk> {
|
||||
let poetry = which("poetry")
|
||||
.ok_or_else(|| IdeError::unavailable("Poetry is not installed (install it from https://python-poetry.org)"))?;
|
||||
let project = location
|
||||
.parent()
|
||||
.filter(|p| p.is_dir())
|
||||
.ok_or_else(|| IdeError::invalid("Poetry environments need an open project directory"))?;
|
||||
stream(
|
||||
&poetry,
|
||||
&["env".to_owned(), "use".to_owned(), base.to_string_lossy().into_owned()],
|
||||
Some(project),
|
||||
&[],
|
||||
cancel,
|
||||
on_line,
|
||||
)?;
|
||||
let home = query_in(&poetry, &["env", "info", "--path"], Some(project), &[], Duration::from_secs(30))
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| IdeError::internal("poetry created no environment".to_owned()))?;
|
||||
let python = home.join(bin_dir()).join(exe("python"));
|
||||
crate::discover::python_sdk(&python, "venv", Some(project))
|
||||
.ok_or_else(|| IdeError::internal(format!("{} was not created", python.display())))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Заблокувати користувача