mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 05:03:46 +00:00
Add a common abstraction to discover PEP 723 script interpreters (#10132)
## Summary This logic is already repeated twice, and I'm on the verge of adding a third. (No behavioral changes.)
This commit is contained in:
parent
6ed7302432
commit
9279a125e9
10 changed files with 133 additions and 114 deletions
|
|
@ -229,6 +229,7 @@ impl PythonInstallation {
|
|||
&self.interpreter
|
||||
}
|
||||
|
||||
/// Consume the [`PythonInstallation`] and return the [`Interpreter`].
|
||||
pub fn into_interpreter(self) -> Interpreter {
|
||||
self.interpreter
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ use uv_git::{GitReference, GIT_STORE};
|
|||
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
||||
use uv_pep508::{ExtraName, Requirement, UnnamedRequirement, VersionOrUrl};
|
||||
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation,
|
||||
PythonPreference, PythonRequest,
|
||||
};
|
||||
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::{FlatIndex, InstallTarget};
|
||||
use uv_scripts::{Pep723Item, Pep723Script};
|
||||
|
|
@ -46,8 +43,7 @@ use crate::commands::pip::loggers::{
|
|||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::lock::LockMode;
|
||||
use crate::commands::project::{
|
||||
init_script_python_requirement, lock, validate_script_requires_python, ProjectError,
|
||||
ProjectInterpreter, ScriptPython,
|
||||
init_script_python_requirement, lock, ProjectError, ProjectInterpreter, ScriptInterpreter,
|
||||
};
|
||||
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
|
||||
use crate::commands::{diagnostics, project, ExitStatus};
|
||||
|
|
@ -144,7 +140,7 @@ pub(crate) async fn add(
|
|||
} else {
|
||||
let requires_python = init_script_python_requirement(
|
||||
python.as_deref(),
|
||||
install_mirrors.clone(),
|
||||
&install_mirrors,
|
||||
project_dir,
|
||||
false,
|
||||
python_preference,
|
||||
|
|
@ -158,42 +154,23 @@ pub(crate) async fn add(
|
|||
Pep723Script::init(&script, requires_python.specifiers()).await?
|
||||
};
|
||||
|
||||
let ScriptPython {
|
||||
source,
|
||||
python_request,
|
||||
requires_python,
|
||||
} = ScriptPython::from_request(
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
None,
|
||||
// Discover the interpreter.
|
||||
let interpreter = ScriptInterpreter::discover(
|
||||
&Pep723Item::Script(script.clone()),
|
||||
no_config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let interpreter = PythonInstallation::find_or_download(
|
||||
python_request.as_ref(),
|
||||
EnvironmentPreference::Any,
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
python_preference,
|
||||
python_downloads,
|
||||
&client_builder,
|
||||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
Some(&reporter),
|
||||
install_mirrors.python_install_mirror.as_deref(),
|
||||
install_mirrors.pypy_install_mirror.as_deref(),
|
||||
printer,
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
if let Some((requires_python, requires_python_source)) = requires_python {
|
||||
validate_script_requires_python(
|
||||
&interpreter,
|
||||
None,
|
||||
&requires_python,
|
||||
&requires_python_source,
|
||||
&source,
|
||||
)?;
|
||||
}
|
||||
|
||||
Target::Script(script, Box::new(interpreter))
|
||||
} else {
|
||||
// Find the project in the workspace.
|
||||
|
|
@ -234,7 +211,7 @@ pub(crate) async fn add(
|
|||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
install_mirrors.clone(),
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
printer,
|
||||
|
|
@ -248,7 +225,7 @@ pub(crate) async fn add(
|
|||
let venv = project::get_or_init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
install_mirrors.clone(),
|
||||
&install_mirrors,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
connectivity,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ pub(crate) async fn export(
|
|||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
printer,
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ async fn init_script(
|
|||
|
||||
let requires_python = init_script_python_requirement(
|
||||
python.as_deref(),
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
&CWD,
|
||||
no_pin_python,
|
||||
python_preference,
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ pub(crate) async fn lock(
|
|||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
printer,
|
||||
|
|
|
|||
|
|
@ -397,7 +397,7 @@ pub(crate) fn validate_requires_python(
|
|||
|
||||
/// Returns an error if the [`Interpreter`] does not satisfy script or workspace `requires-python`.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub(crate) fn validate_script_requires_python(
|
||||
fn validate_script_requires_python(
|
||||
interpreter: &Interpreter,
|
||||
workspace: Option<&Workspace>,
|
||||
requires_python: &RequiresPython,
|
||||
|
|
@ -406,35 +406,105 @@ pub(crate) fn validate_script_requires_python(
|
|||
) -> Result<(), ProjectError> {
|
||||
match requires_python_source {
|
||||
RequiresPythonSource::Project => {
|
||||
validate_requires_python(interpreter, workspace, requires_python, request_source)?;
|
||||
validate_requires_python(interpreter, workspace, requires_python, request_source)
|
||||
}
|
||||
RequiresPythonSource::Script => {}
|
||||
};
|
||||
RequiresPythonSource::Script => {
|
||||
if requires_python.contains(interpreter.python_version()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if requires_python.contains(interpreter.python_version()) {
|
||||
return Ok(());
|
||||
match request_source {
|
||||
PythonRequestSource::UserRequest => {
|
||||
Err(ProjectError::RequestedPythonScriptIncompatibility(
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
PythonRequestSource::DotPythonVersion(file) => {
|
||||
Err(ProjectError::DotPythonVersionScriptIncompatibility(
|
||||
file.file_name().to_string(),
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
PythonRequestSource::RequiresPython => {
|
||||
Err(ProjectError::RequiresPythonScriptIncompatibility(
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An interpreter suitable for a PEP 723 script.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ScriptInterpreter(Interpreter);
|
||||
|
||||
impl ScriptInterpreter {
|
||||
/// Discover the interpreter to use for the current [`Pep723Item`].
|
||||
pub(crate) async fn discover(
|
||||
script: &Pep723Item,
|
||||
python_request: Option<PythonRequest>,
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
allow_insecure_host: &[TrustedHost],
|
||||
install_mirrors: &PythonInstallMirrors,
|
||||
no_config: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
) -> Result<Self, ProjectError> {
|
||||
// For now, we assume that scripts are never evaluated in the context of a workspace.
|
||||
let workspace = None;
|
||||
|
||||
let ScriptPython {
|
||||
source,
|
||||
python_request,
|
||||
requires_python,
|
||||
} = ScriptPython::from_request(python_request, workspace, script, no_config).await?;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls)
|
||||
.allow_insecure_host(allow_insecure_host.to_vec());
|
||||
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
|
||||
let interpreter = PythonInstallation::find_or_download(
|
||||
python_request.as_ref(),
|
||||
EnvironmentPreference::Any,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
&client_builder,
|
||||
cache,
|
||||
Some(&reporter),
|
||||
install_mirrors.python_install_mirror.as_deref(),
|
||||
install_mirrors.pypy_install_mirror.as_deref(),
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
if let Some((requires_python, requires_python_source)) = requires_python {
|
||||
if let Err(err) = validate_script_requires_python(
|
||||
&interpreter,
|
||||
workspace,
|
||||
&requires_python,
|
||||
&requires_python_source,
|
||||
&source,
|
||||
) {
|
||||
warn_user!("{err}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(interpreter))
|
||||
}
|
||||
|
||||
match request_source {
|
||||
PythonRequestSource::UserRequest => {
|
||||
Err(ProjectError::RequestedPythonScriptIncompatibility(
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
PythonRequestSource::DotPythonVersion(file) => {
|
||||
Err(ProjectError::DotPythonVersionScriptIncompatibility(
|
||||
file.file_name().to_string(),
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
PythonRequestSource::RequiresPython => {
|
||||
Err(ProjectError::RequiresPythonScriptIncompatibility(
|
||||
interpreter.python_version().clone(),
|
||||
requires_python.clone(),
|
||||
))
|
||||
}
|
||||
/// Consume the [`PythonInstallation`] and return the [`Interpreter`].
|
||||
pub(crate) fn into_interpreter(self) -> Interpreter {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,7 +529,7 @@ impl ProjectInterpreter {
|
|||
connectivity: Connectivity,
|
||||
native_tls: bool,
|
||||
allow_insecure_host: &[TrustedHost],
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
install_mirrors: &PythonInstallMirrors,
|
||||
no_config: bool,
|
||||
cache: &Cache,
|
||||
printer: Printer,
|
||||
|
|
@ -547,7 +617,7 @@ impl ProjectInterpreter {
|
|||
|
||||
let reporter = PythonDownloadReporter::single(printer);
|
||||
|
||||
// Locate the Python interpreter to use in the environment
|
||||
// Locate the Python interpreter to use in the environment.
|
||||
let python = PythonInstallation::find_or_download(
|
||||
python_request.as_ref(),
|
||||
EnvironmentPreference::OnlySystem,
|
||||
|
|
@ -771,7 +841,7 @@ impl ScriptPython {
|
|||
pub(crate) async fn get_or_init_environment(
|
||||
workspace: &Workspace,
|
||||
python: Option<PythonRequest>,
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
install_mirrors: &PythonInstallMirrors,
|
||||
python_preference: PythonPreference,
|
||||
python_downloads: PythonDownloads,
|
||||
connectivity: Connectivity,
|
||||
|
|
@ -1598,7 +1668,7 @@ pub(crate) async fn update_environment(
|
|||
/// Determine the [`RequiresPython`] requirement for a new PEP 723 script.
|
||||
pub(crate) async fn init_script_python_requirement(
|
||||
python: Option<&str>,
|
||||
install_mirrors: PythonInstallMirrors,
|
||||
install_mirrors: &PythonInstallMirrors,
|
||||
directory: &Path,
|
||||
no_pin_python: bool,
|
||||
python_preference: PythonPreference,
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ pub(crate) async fn remove(
|
|||
let venv = project::get_or_init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
connectivity,
|
||||
|
|
|
|||
|
|
@ -45,8 +45,8 @@ use crate::commands::pip::operations::Modifications;
|
|||
use crate::commands::project::environment::CachedEnvironment;
|
||||
use crate::commands::project::lock::LockMode;
|
||||
use crate::commands::project::{
|
||||
default_dependency_groups, validate_requires_python, validate_script_requires_python,
|
||||
DependencyGroupsTarget, EnvironmentSpecification, ProjectError, ScriptPython, WorkspacePython,
|
||||
default_dependency_groups, validate_requires_python, DependencyGroupsTarget,
|
||||
EnvironmentSpecification, ProjectError, ScriptInterpreter, WorkspacePython,
|
||||
};
|
||||
use crate::commands::reporters::PythonDownloadReporter;
|
||||
use crate::commands::{diagnostics, project, ExitStatus};
|
||||
|
|
@ -181,52 +181,23 @@ pub(crate) async fn run(
|
|||
}
|
||||
}
|
||||
|
||||
let ScriptPython {
|
||||
source,
|
||||
python_request,
|
||||
requires_python,
|
||||
} = ScriptPython::from_request(
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
None,
|
||||
// Discover the interpreter for the script.
|
||||
let interpreter = ScriptInterpreter::discover(
|
||||
&script,
|
||||
no_config,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let client_builder = BaseClientBuilder::new()
|
||||
.connectivity(connectivity)
|
||||
.native_tls(native_tls)
|
||||
.allow_insecure_host(allow_insecure_host.to_vec());
|
||||
|
||||
let interpreter = PythonInstallation::find_or_download(
|
||||
python_request.as_ref(),
|
||||
EnvironmentPreference::Any,
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
python_preference,
|
||||
python_downloads,
|
||||
&client_builder,
|
||||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
Some(&download_reporter),
|
||||
install_mirrors.python_install_mirror.as_deref(),
|
||||
install_mirrors.pypy_install_mirror.as_deref(),
|
||||
printer,
|
||||
)
|
||||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
if let Some((requires_python, requires_python_source)) = requires_python {
|
||||
match validate_script_requires_python(
|
||||
&interpreter,
|
||||
None,
|
||||
&requires_python,
|
||||
&requires_python_source,
|
||||
&source,
|
||||
) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
warn_user!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the working directory for the script.
|
||||
let script_dir = match &script {
|
||||
Pep723Item::Script(script) => std::path::absolute(&script.path)?
|
||||
|
|
@ -592,7 +563,7 @@ pub(crate) async fn run(
|
|||
project::get_or_init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
connectivity,
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ pub(crate) async fn sync(
|
|||
let venv = project::get_or_init_environment(
|
||||
project.workspace(),
|
||||
python.as_deref().map(PythonRequest::parse),
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
python_preference,
|
||||
python_downloads,
|
||||
connectivity,
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub(crate) async fn tree(
|
|||
connectivity,
|
||||
native_tls,
|
||||
allow_insecure_host,
|
||||
install_mirrors,
|
||||
&install_mirrors,
|
||||
no_config,
|
||||
cache,
|
||||
printer,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue