mirror of https://github.com/astral-sh/uv
Revert "Allow `--with-requirements` to load extensionless inline-metadata scripts" (#16861)
Reverts https://github.com/astral-sh/uv/pull/16805 / https://github.com/astral-sh/uv/pull/16744 This also invalidates - https://github.com/astral-sh/uv/pull/16855 - #16857 There's probably a way we can make this work, but detecting whether a file is safe to read repeatedly is non-trivial, `is_file` returns `true` for `/dev/stdin` on macOS so the approach from #16857 is not sufficient. I spent a while trying to add `is_char_device` detection for macOS but unfortunately that didn't work.
This commit is contained in:
parent
4d747f6e86
commit
ca62066194
|
|
@ -6,7 +6,6 @@ use console::Term;
|
||||||
|
|
||||||
use uv_fs::{CWD, Simplified};
|
use uv_fs::{CWD, Simplified};
|
||||||
use uv_requirements_txt::RequirementsTxtRequirement;
|
use uv_requirements_txt::RequirementsTxtRequirement;
|
||||||
use uv_scripts::Pep723Script;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RequirementsSource {
|
pub enum RequirementsSource {
|
||||||
|
|
@ -15,7 +14,7 @@ pub enum RequirementsSource {
|
||||||
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
|
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
|
||||||
Editable(RequirementsTxtRequirement),
|
Editable(RequirementsTxtRequirement),
|
||||||
/// Dependencies were provided via a PEP 723 script.
|
/// Dependencies were provided via a PEP 723 script.
|
||||||
Pep723Script(Box<Pep723ScriptSource>),
|
Pep723Script(PathBuf),
|
||||||
/// Dependencies were provided via a `pylock.toml` file.
|
/// Dependencies were provided via a `pylock.toml` file.
|
||||||
PylockToml(PathBuf),
|
PylockToml(PathBuf),
|
||||||
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
|
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
|
||||||
|
|
@ -51,7 +50,8 @@ impl RequirementsSource {
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("py") || ext.eq_ignore_ascii_case("pyw"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("py") || ext.eq_ignore_ascii_case("pyw"))
|
||||||
{
|
{
|
||||||
Ok(Self::Pep723Script(Pep723ScriptSource::new(path)))
|
// TODO(blueraft): Support scripts without an extension.
|
||||||
|
Ok(Self::Pep723Script(path))
|
||||||
} else if path
|
} else if path
|
||||||
.extension()
|
.extension()
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
|
||||||
|
|
@ -60,25 +60,6 @@ impl RequirementsSource {
|
||||||
"`{}` is not a valid PEP 751 filename: expected TOML file to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`)",
|
"`{}` is not a valid PEP 751 filename: expected TOML file to start with `pylock.` and end with `.toml` (e.g., `pylock.toml`, `pylock.dev.toml`)",
|
||||||
path.user_display(),
|
path.user_display(),
|
||||||
))
|
))
|
||||||
} else if path
|
|
||||||
.extension()
|
|
||||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("txt") || ext.eq_ignore_ascii_case("in"))
|
|
||||||
{
|
|
||||||
Ok(Self::RequirementsTxt(path))
|
|
||||||
} else if path.extension().is_none() && path.is_file() {
|
|
||||||
// If we don't have an extension, attempt to detect a PEP 723 script, and
|
|
||||||
// fall back to `requirements.txt` format if not. (If the path isn't a file,
|
|
||||||
// we assume it's a readable file-like object in `requirements.txt` format, e.g.,
|
|
||||||
// `-r <( cat requirements.lock | grep -v nvidia | grep -v torch==)` or similar, in
|
|
||||||
// which case, reading the input would consume the stream, and only `requirements.txt`
|
|
||||||
// format is supported anyway.)
|
|
||||||
match Pep723Script::read_sync(&path) {
|
|
||||||
Ok(Some(script)) => Ok(Self::Pep723Script(Pep723ScriptSource::with_script(
|
|
||||||
path, script,
|
|
||||||
))),
|
|
||||||
Ok(None) => Ok(Self::RequirementsTxt(path)),
|
|
||||||
Err(err) => Err(err.into()),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Self::RequirementsTxt(path))
|
Ok(Self::RequirementsTxt(path))
|
||||||
}
|
}
|
||||||
|
|
@ -310,43 +291,14 @@ impl RequirementsSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Pep723ScriptSource {
|
|
||||||
path: PathBuf,
|
|
||||||
script: Option<Pep723Script>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pep723ScriptSource {
|
|
||||||
fn new(path: PathBuf) -> Box<Self> {
|
|
||||||
Box::new(Self { path, script: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_script(path: PathBuf, script: Pep723Script) -> Box<Self> {
|
|
||||||
Box::new(Self {
|
|
||||||
path,
|
|
||||||
script: Some(script),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn script(&self) -> Option<&Pep723Script> {
|
|
||||||
self.script.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for RequirementsSource {
|
impl std::fmt::Display for RequirementsSource {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Package(package) => write!(f, "{package:?}"),
|
Self::Package(package) => write!(f, "{package:?}"),
|
||||||
Self::Editable(path) => write!(f, "-e {path:?}"),
|
Self::Editable(path) => write!(f, "-e {path:?}"),
|
||||||
Self::Pep723Script(source) => {
|
|
||||||
write!(f, "{}", source.path().simplified_display())
|
|
||||||
}
|
|
||||||
Self::PylockToml(path)
|
Self::PylockToml(path)
|
||||||
| Self::RequirementsTxt(path)
|
| Self::RequirementsTxt(path)
|
||||||
|
| Self::Pep723Script(path)
|
||||||
| Self::PyprojectToml(path)
|
| Self::PyprojectToml(path)
|
||||||
| Self::SetupPy(path)
|
| Self::SetupPy(path)
|
||||||
| Self::SetupCfg(path)
|
| Self::SetupCfg(path)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ use uv_fs::{CWD, Simplified};
|
||||||
use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
||||||
use uv_pypi_types::PyProjectToml;
|
use uv_pypi_types::PyProjectToml;
|
||||||
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
|
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
|
||||||
use uv_scripts::{Pep723Item, Pep723Script};
|
use uv_scripts::{Pep723Error, Pep723Item, Pep723Script};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
use crate::{RequirementsSource, SourceTree};
|
use crate::{RequirementsSource, SourceTree};
|
||||||
|
|
@ -184,20 +184,22 @@ impl RequirementsSpecification {
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RequirementsSource::Pep723Script(source) => {
|
RequirementsSource::Pep723Script(path) => {
|
||||||
let script = if let Some(script) = source.script() {
|
let script = match Pep723Script::read(&path).await {
|
||||||
Pep723Item::Script(script.clone())
|
Ok(Some(script)) => Pep723Item::Script(script),
|
||||||
} else {
|
Ok(None) => {
|
||||||
match Pep723Script::read(source.path()).await {
|
return Err(anyhow::anyhow!(
|
||||||
Ok(Some(script)) => Pep723Item::Script(script),
|
"`{}` does not contain inline script metadata",
|
||||||
Ok(None) => {
|
path.user_display(),
|
||||||
return Err(anyhow::anyhow!(
|
));
|
||||||
"`{}` does not contain inline script metadata",
|
|
||||||
source.path().user_display(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Err(err) => return Err(err.into()),
|
|
||||||
}
|
}
|
||||||
|
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Failed to read `{}` (not found)",
|
||||||
|
path.user_display(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadata = script.metadata();
|
let metadata = script.metadata();
|
||||||
|
|
|
||||||
|
|
@ -175,16 +175,28 @@ impl Pep723Script {
|
||||||
///
|
///
|
||||||
/// See: <https://peps.python.org/pep-0723/>
|
/// See: <https://peps.python.org/pep-0723/>
|
||||||
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
||||||
let file = file.as_ref();
|
let contents = fs_err::tokio::read(&file).await?;
|
||||||
let contents = fs_err::tokio::read(file).await?;
|
|
||||||
Self::from_contents(file, &contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read the PEP 723 `script` metadata from a Python file using blocking I/O.
|
// Extract the `script` tag.
|
||||||
pub fn read_sync(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
|
let ScriptTag {
|
||||||
let file = file.as_ref();
|
prelude,
|
||||||
let contents = fs_err::read(file)?;
|
metadata,
|
||||||
Self::from_contents(file, &contents)
|
postlude,
|
||||||
|
} = match ScriptTag::parse(&contents) {
|
||||||
|
Ok(Some(tag)) => tag,
|
||||||
|
Ok(None) => return Ok(None),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse the metadata.
|
||||||
|
let metadata = Pep723Metadata::from_str(&metadata)?;
|
||||||
|
|
||||||
|
Ok(Some(Self {
|
||||||
|
path: std::path::absolute(file)?,
|
||||||
|
metadata,
|
||||||
|
prelude,
|
||||||
|
postlude,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads a Python script and generates a default PEP 723 metadata table.
|
/// Reads a Python script and generates a default PEP 723 metadata table.
|
||||||
|
|
@ -337,29 +349,6 @@ impl Pep723Script {
|
||||||
.and_then(|uv| uv.sources.as_ref())
|
.and_then(|uv| uv.sources.as_ref())
|
||||||
.unwrap_or(&EMPTY)
|
.unwrap_or(&EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_contents(path: &Path, contents: &[u8]) -> Result<Option<Self>, Pep723Error> {
|
|
||||||
let script_tag = match ScriptTag::parse(contents) {
|
|
||||||
Ok(Some(tag)) => tag,
|
|
||||||
Ok(None) => return Ok(None),
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let ScriptTag {
|
|
||||||
prelude,
|
|
||||||
metadata,
|
|
||||||
postlude,
|
|
||||||
} = script_tag;
|
|
||||||
|
|
||||||
let metadata = Pep723Metadata::from_str(&metadata)?;
|
|
||||||
|
|
||||||
Ok(Some(Self {
|
|
||||||
path: std::path::absolute(path)?,
|
|
||||||
metadata,
|
|
||||||
prelude,
|
|
||||||
postlude,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PEP 723 metadata as parsed from a `script` comment block.
|
/// PEP 723 metadata as parsed from a `script` comment block.
|
||||||
|
|
|
||||||
|
|
@ -2646,7 +2646,8 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> {
|
||||||
fn tool_run_with_dependencies_from_script() -> Result<()> {
|
fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
let context = TestContext::new("3.12").with_filtered_counts();
|
let context = TestContext::new("3.12").with_filtered_counts();
|
||||||
|
|
||||||
let script_contents = indoc! {r#"
|
let script = context.temp_dir.child("script.py");
|
||||||
|
script.write_str(indoc! {r#"
|
||||||
# /// script
|
# /// script
|
||||||
# requires-python = ">=3.11"
|
# requires-python = ">=3.11"
|
||||||
# dependencies = [
|
# dependencies = [
|
||||||
|
|
@ -2655,13 +2656,7 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
# ///
|
# ///
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
"#};
|
"#})?;
|
||||||
|
|
||||||
let script = context.temp_dir.child("script.py");
|
|
||||||
script.write_str(script_contents)?;
|
|
||||||
|
|
||||||
let script_without_extension = context.temp_dir.child("script-no-ext");
|
|
||||||
script_without_extension.write_str(script_contents)?;
|
|
||||||
|
|
||||||
// script dependencies (anyio) are now installed.
|
// script dependencies (anyio) are now installed.
|
||||||
uv_snapshot!(context.filters(), context.tool_run()
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
|
|
@ -2689,20 +2684,6 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
");
|
");
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.tool_run()
|
|
||||||
.arg("--with-requirements")
|
|
||||||
.arg("script-no-ext")
|
|
||||||
.arg("black")
|
|
||||||
.arg("script-no-ext")
|
|
||||||
.arg("-q"), @r"
|
|
||||||
success: true
|
|
||||||
exit_code: 0
|
|
||||||
----- stdout -----
|
|
||||||
|
|
||||||
----- stderr -----
|
|
||||||
Resolved [N] packages in [TIME]
|
|
||||||
");
|
|
||||||
|
|
||||||
// Error when the script is not a valid PEP723 script.
|
// Error when the script is not a valid PEP723 script.
|
||||||
let script = context.temp_dir.child("not_pep723_script.py");
|
let script = context.temp_dir.child("not_pep723_script.py");
|
||||||
script.write_str("import anyio")?;
|
script.write_str("import anyio")?;
|
||||||
|
|
@ -2719,18 +2700,8 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
error: `not_pep723_script.py` does not contain inline script metadata
|
error: `not_pep723_script.py` does not contain inline script metadata
|
||||||
");
|
");
|
||||||
|
|
||||||
let filters = context
|
|
||||||
.filters()
|
|
||||||
.into_iter()
|
|
||||||
.chain([(
|
|
||||||
// The error message is different on Windows.
|
|
||||||
"The system cannot find the file specified.",
|
|
||||||
"No such file or directory",
|
|
||||||
)])
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Error when the script doesn't exist.
|
// Error when the script doesn't exist.
|
||||||
uv_snapshot!(filters, context.tool_run()
|
uv_snapshot!(context.filters(), context.tool_run()
|
||||||
.arg("--with-requirements")
|
.arg("--with-requirements")
|
||||||
.arg("missing_file.py")
|
.arg("missing_file.py")
|
||||||
.arg("black"), @r"
|
.arg("black"), @r"
|
||||||
|
|
@ -2739,7 +2710,7 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: failed to read from file `missing_file.py`: No such file or directory (os error 2)
|
error: Failed to read `missing_file.py` (not found)
|
||||||
");
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue