Skip to content

Commit ca62066

Browse files
authored
Revert "Allow --with-requirements to load extensionless inline-metadata scripts" (#16861)
Reverts #16805 / #16744 This also invalidates - #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.
1 parent 4d747f6 commit ca62066

File tree

4 files changed

+46
-132
lines changed

4 files changed

+46
-132
lines changed

crates/uv-requirements/src/sources.rs

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use console::Term;
66

77
use uv_fs::{CWD, Simplified};
88
use uv_requirements_txt::RequirementsTxtRequirement;
9-
use uv_scripts::Pep723Script;
109

1110
#[derive(Debug, Clone)]
1211
pub enum RequirementsSource {
@@ -15,7 +14,7 @@ pub enum RequirementsSource {
1514
/// An editable path was provided on the command line (e.g., `pip install -e ../flask`).
1615
Editable(RequirementsTxtRequirement),
1716
/// Dependencies were provided via a PEP 723 script.
18-
Pep723Script(Box<Pep723ScriptSource>),
17+
Pep723Script(PathBuf),
1918
/// Dependencies were provided via a `pylock.toml` file.
2019
PylockToml(PathBuf),
2120
/// Dependencies were provided via a `requirements.txt` file (e.g., `pip install -r requirements.txt`).
@@ -51,7 +50,8 @@ impl RequirementsSource {
5150
.extension()
5251
.is_some_and(|ext| ext.eq_ignore_ascii_case("py") || ext.eq_ignore_ascii_case("pyw"))
5352
{
54-
Ok(Self::Pep723Script(Pep723ScriptSource::new(path)))
53+
// TODO(blueraft): Support scripts without an extension.
54+
Ok(Self::Pep723Script(path))
5555
} else if path
5656
.extension()
5757
.is_some_and(|ext| ext.eq_ignore_ascii_case("toml"))
@@ -60,25 +60,6 @@ impl RequirementsSource {
6060
"`{}` 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`)",
6161
path.user_display(),
6262
))
63-
} else if path
64-
.extension()
65-
.is_some_and(|ext| ext.eq_ignore_ascii_case("txt") || ext.eq_ignore_ascii_case("in"))
66-
{
67-
Ok(Self::RequirementsTxt(path))
68-
} else if path.extension().is_none() && path.is_file() {
69-
// If we don't have an extension, attempt to detect a PEP 723 script, and
70-
// fall back to `requirements.txt` format if not. (If the path isn't a file,
71-
// we assume it's a readable file-like object in `requirements.txt` format, e.g.,
72-
// `-r <( cat requirements.lock | grep -v nvidia | grep -v torch==)` or similar, in
73-
// which case, reading the input would consume the stream, and only `requirements.txt`
74-
// format is supported anyway.)
75-
match Pep723Script::read_sync(&path) {
76-
Ok(Some(script)) => Ok(Self::Pep723Script(Pep723ScriptSource::with_script(
77-
path, script,
78-
))),
79-
Ok(None) => Ok(Self::RequirementsTxt(path)),
80-
Err(err) => Err(err.into()),
81-
}
8263
} else {
8364
Ok(Self::RequirementsTxt(path))
8465
}
@@ -310,43 +291,14 @@ impl RequirementsSource {
310291
}
311292
}
312293

313-
#[derive(Debug, Clone)]
314-
pub struct Pep723ScriptSource {
315-
path: PathBuf,
316-
script: Option<Pep723Script>,
317-
}
318-
319-
impl Pep723ScriptSource {
320-
fn new(path: PathBuf) -> Box<Self> {
321-
Box::new(Self { path, script: None })
322-
}
323-
324-
fn with_script(path: PathBuf, script: Pep723Script) -> Box<Self> {
325-
Box::new(Self {
326-
path,
327-
script: Some(script),
328-
})
329-
}
330-
331-
pub fn path(&self) -> &Path {
332-
&self.path
333-
}
334-
335-
pub fn script(&self) -> Option<&Pep723Script> {
336-
self.script.as_ref()
337-
}
338-
}
339-
340294
impl std::fmt::Display for RequirementsSource {
341295
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342296
match self {
343297
Self::Package(package) => write!(f, "{package:?}"),
344298
Self::Editable(path) => write!(f, "-e {path:?}"),
345-
Self::Pep723Script(source) => {
346-
write!(f, "{}", source.path().simplified_display())
347-
}
348299
Self::PylockToml(path)
349300
| Self::RequirementsTxt(path)
301+
| Self::Pep723Script(path)
350302
| Self::PyprojectToml(path)
351303
| Self::SetupPy(path)
352304
| Self::SetupCfg(path)

crates/uv-requirements/src/specification.rs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use uv_fs::{CWD, Simplified};
4646
use uv_normalize::{ExtraName, PackageName, PipGroupName};
4747
use uv_pypi_types::PyProjectToml;
4848
use uv_requirements_txt::{RequirementsTxt, RequirementsTxtRequirement};
49-
use uv_scripts::{Pep723Item, Pep723Script};
49+
use uv_scripts::{Pep723Error, Pep723Item, Pep723Script};
5050
use uv_warnings::warn_user;
5151

5252
use crate::{RequirementsSource, SourceTree};
@@ -184,20 +184,22 @@ impl RequirementsSpecification {
184184
..Self::default()
185185
}
186186
}
187-
RequirementsSource::Pep723Script(source) => {
188-
let script = if let Some(script) = source.script() {
189-
Pep723Item::Script(script.clone())
190-
} else {
191-
match Pep723Script::read(source.path()).await {
192-
Ok(Some(script)) => Pep723Item::Script(script),
193-
Ok(None) => {
194-
return Err(anyhow::anyhow!(
195-
"`{}` does not contain inline script metadata",
196-
source.path().user_display(),
197-
));
198-
}
199-
Err(err) => return Err(err.into()),
187+
RequirementsSource::Pep723Script(path) => {
188+
let script = match Pep723Script::read(&path).await {
189+
Ok(Some(script)) => Pep723Item::Script(script),
190+
Ok(None) => {
191+
return Err(anyhow::anyhow!(
192+
"`{}` does not contain inline script metadata",
193+
path.user_display(),
194+
));
195+
}
196+
Err(Pep723Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
197+
return Err(anyhow::anyhow!(
198+
"Failed to read `{}` (not found)",
199+
path.user_display(),
200+
));
200201
}
202+
Err(err) => return Err(err.into()),
201203
};
202204

203205
let metadata = script.metadata();

crates/uv-scripts/src/lib.rs

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,28 @@ impl Pep723Script {
175175
///
176176
/// See: <https://peps.python.org/pep-0723/>
177177
pub async fn read(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
178-
let file = file.as_ref();
179-
let contents = fs_err::tokio::read(file).await?;
180-
Self::from_contents(file, &contents)
181-
}
178+
let contents = fs_err::tokio::read(&file).await?;
182179

183-
/// Read the PEP 723 `script` metadata from a Python file using blocking I/O.
184-
pub fn read_sync(file: impl AsRef<Path>) -> Result<Option<Self>, Pep723Error> {
185-
let file = file.as_ref();
186-
let contents = fs_err::read(file)?;
187-
Self::from_contents(file, &contents)
180+
// Extract the `script` tag.
181+
let ScriptTag {
182+
prelude,
183+
metadata,
184+
postlude,
185+
} = match ScriptTag::parse(&contents) {
186+
Ok(Some(tag)) => tag,
187+
Ok(None) => return Ok(None),
188+
Err(err) => return Err(err),
189+
};
190+
191+
// Parse the metadata.
192+
let metadata = Pep723Metadata::from_str(&metadata)?;
193+
194+
Ok(Some(Self {
195+
path: std::path::absolute(file)?,
196+
metadata,
197+
prelude,
198+
postlude,
199+
}))
188200
}
189201

190202
/// Reads a Python script and generates a default PEP 723 metadata table.
@@ -337,29 +349,6 @@ impl Pep723Script {
337349
.and_then(|uv| uv.sources.as_ref())
338350
.unwrap_or(&EMPTY)
339351
}
340-
341-
fn from_contents(path: &Path, contents: &[u8]) -> Result<Option<Self>, Pep723Error> {
342-
let script_tag = match ScriptTag::parse(contents) {
343-
Ok(Some(tag)) => tag,
344-
Ok(None) => return Ok(None),
345-
Err(err) => return Err(err),
346-
};
347-
348-
let ScriptTag {
349-
prelude,
350-
metadata,
351-
postlude,
352-
} = script_tag;
353-
354-
let metadata = Pep723Metadata::from_str(&metadata)?;
355-
356-
Ok(Some(Self {
357-
path: std::path::absolute(path)?,
358-
metadata,
359-
prelude,
360-
postlude,
361-
}))
362-
}
363352
}
364353

365354
/// PEP 723 metadata as parsed from a `script` comment block.

crates/uv/tests/it/tool_run.rs

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2646,7 +2646,8 @@ fn tool_run_with_incompatible_build_constraints() -> Result<()> {
26462646
fn tool_run_with_dependencies_from_script() -> Result<()> {
26472647
let context = TestContext::new("3.12").with_filtered_counts();
26482648

2649-
let script_contents = indoc! {r#"
2649+
let script = context.temp_dir.child("script.py");
2650+
script.write_str(indoc! {r#"
26502651
# /// script
26512652
# requires-python = ">=3.11"
26522653
# dependencies = [
@@ -2655,13 +2656,7 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
26552656
# ///
26562657
26572658
import anyio
2658-
"#};
2659-
2660-
let script = context.temp_dir.child("script.py");
2661-
script.write_str(script_contents)?;
2662-
2663-
let script_without_extension = context.temp_dir.child("script-no-ext");
2664-
script_without_extension.write_str(script_contents)?;
2659+
"#})?;
26652660

26662661
// script dependencies (anyio) are now installed.
26672662
uv_snapshot!(context.filters(), context.tool_run()
@@ -2689,20 +2684,6 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
26892684
+ sniffio==1.3.1
26902685
");
26912686

2692-
uv_snapshot!(context.filters(), context.tool_run()
2693-
.arg("--with-requirements")
2694-
.arg("script-no-ext")
2695-
.arg("black")
2696-
.arg("script-no-ext")
2697-
.arg("-q"), @r"
2698-
success: true
2699-
exit_code: 0
2700-
----- stdout -----
2701-
2702-
----- stderr -----
2703-
Resolved [N] packages in [TIME]
2704-
");
2705-
27062687
// Error when the script is not a valid PEP723 script.
27072688
let script = context.temp_dir.child("not_pep723_script.py");
27082689
script.write_str("import anyio")?;
@@ -2719,18 +2700,8 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
27192700
error: `not_pep723_script.py` does not contain inline script metadata
27202701
");
27212702

2722-
let filters = context
2723-
.filters()
2724-
.into_iter()
2725-
.chain([(
2726-
// The error message is different on Windows.
2727-
"The system cannot find the file specified.",
2728-
"No such file or directory",
2729-
)])
2730-
.collect::<Vec<_>>();
2731-
27322703
// Error when the script doesn't exist.
2733-
uv_snapshot!(filters, context.tool_run()
2704+
uv_snapshot!(context.filters(), context.tool_run()
27342705
.arg("--with-requirements")
27352706
.arg("missing_file.py")
27362707
.arg("black"), @r"
@@ -2739,7 +2710,7 @@ fn tool_run_with_dependencies_from_script() -> Result<()> {
27392710
----- stdout -----
27402711
27412712
----- stderr -----
2742-
error: failed to read from file `missing_file.py`: No such file or directory (os error 2)
2713+
error: Failed to read `missing_file.py` (not found)
27432714
");
27442715

27452716
Ok(())

0 commit comments

Comments
 (0)