mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-28 02:40:11 +00:00
Implement --show-version-specifiers for tree (#5240)
## Summary resolves https://github.com/astral-sh/uv/issues/5217 ## Test Plan existing tests pass (should be perfectly backwards compatible) + added a few tests to cover the new functionality. in particular, in addition to the simple use of `--show-version-specifiers`, its interaction with `--invert` and `--package` flags are tested.
This commit is contained in:
parent
1b09cb26f5
commit
12518a01a4
8 changed files with 389 additions and 17 deletions
|
|
@ -567,6 +567,15 @@ pub enum VersionOrUrl<T: Pep508Url = VerbatimUrl> {
|
|||
Url(T),
|
||||
}
|
||||
|
||||
impl<T: Pep508Url> Display for VersionOrUrl<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::VersionSpecifier(version_specifier) => Display::fmt(version_specifier, f),
|
||||
Self::Url(url) => Display::fmt(url, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unowned version specifier or URL to install.
|
||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
||||
|
|
@ -576,6 +585,15 @@ pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
|||
Url(&'a T),
|
||||
}
|
||||
|
||||
impl<T: Pep508Url> Display for VersionOrUrlRef<'_, T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::VersionSpecifier(version_specifier) => Display::fmt(version_specifier, f),
|
||||
Self::Url(url) => Display::fmt(url, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a VersionOrUrl> for VersionOrUrlRef<'a> {
|
||||
fn from(value: &'a VersionOrUrl) -> Self {
|
||||
match value {
|
||||
|
|
|
|||
|
|
@ -392,6 +392,24 @@ impl RequirementSource {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert the source to a version specifier or URL.
|
||||
///
|
||||
/// If the source is a registry and the specifier is empty, it returns `None`.
|
||||
pub fn version_or_url(&self) -> Option<VersionOrUrl<VerbatimParsedUrl>> {
|
||||
match self {
|
||||
Self::Registry { specifier, .. } => {
|
||||
if specifier.len() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(VersionOrUrl::VersionSpecifier(specifier.clone()))
|
||||
}
|
||||
}
|
||||
Self::Url { .. } | Self::Git { .. } | Self::Path { .. } | Self::Directory { .. } => {
|
||||
Some(VersionOrUrl::Url(self.to_verbatim_parsed_url()?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the source is editable.
|
||||
pub fn is_editable(&self) -> bool {
|
||||
matches!(self, Self::Directory { editable: true, .. })
|
||||
|
|
|
|||
|
|
@ -2764,4 +2764,8 @@ pub struct DisplayTreeArgs {
|
|||
/// Show the reverse dependencies for the given package. This flag will invert the tree and display the packages that depend on the given package.
|
||||
#[arg(long, alias = "reverse")]
|
||||
pub invert: bool,
|
||||
|
||||
/// Show the version constraint(s) imposed on each package.
|
||||
#[arg(long)]
|
||||
pub show_version_specifiers: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use owo_colors::OwoColorize;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use tracing::debug;
|
||||
|
||||
use distribution_types::{Diagnostic, Name};
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use pypi_types::RequirementSource;
|
||||
use uv_cache::Cache;
|
||||
use uv_distribution::Metadata;
|
||||
use uv_fs::Simplified;
|
||||
|
|
@ -29,6 +29,7 @@ pub(crate) fn pip_tree(
|
|||
package: Vec<PackageName>,
|
||||
no_dedupe: bool,
|
||||
invert: bool,
|
||||
show_version_specifiers: bool,
|
||||
strict: bool,
|
||||
python: Option<&str>,
|
||||
system: bool,
|
||||
|
|
@ -66,6 +67,7 @@ pub(crate) fn pip_tree(
|
|||
package,
|
||||
no_dedupe,
|
||||
invert,
|
||||
show_version_specifiers,
|
||||
environment.interpreter().markers(),
|
||||
packages,
|
||||
)
|
||||
|
|
@ -74,7 +76,7 @@ pub(crate) fn pip_tree(
|
|||
|
||||
writeln!(printer.stdout(), "{rendered_tree}")?;
|
||||
|
||||
if rendered_tree.contains('*') {
|
||||
if rendered_tree.contains("(*)") {
|
||||
let message = if no_dedupe {
|
||||
"(*) Package tree is a cycle and cannot be shown".italic()
|
||||
} else {
|
||||
|
|
@ -113,7 +115,9 @@ pub(crate) struct DisplayDependencyGraph {
|
|||
/// Map from package name to its requirements.
|
||||
///
|
||||
/// If `--invert` is given the map is inverted.
|
||||
requirements: HashMap<PackageName, Vec<PackageName>>,
|
||||
requirements: FxHashMap<PackageName, Vec<PackageName>>,
|
||||
/// Map from requirement package name-to-parent-to-dependency metadata.
|
||||
dependencies: FxHashMap<PackageName, FxHashMap<PackageName, Dependency>>,
|
||||
}
|
||||
|
||||
impl DisplayDependencyGraph {
|
||||
|
|
@ -124,10 +128,13 @@ impl DisplayDependencyGraph {
|
|||
package: Vec<PackageName>,
|
||||
no_dedupe: bool,
|
||||
invert: bool,
|
||||
show_version_specifiers: bool,
|
||||
markers: &MarkerEnvironment,
|
||||
packages: IndexMap<PackageName, Vec<Metadata>>,
|
||||
) -> Self {
|
||||
let mut requirements: HashMap<_, Vec<_>> = HashMap::new();
|
||||
let mut requirements: FxHashMap<_, Vec<_>> = FxHashMap::default();
|
||||
let mut dependencies: FxHashMap<PackageName, FxHashMap<PackageName, Dependency>> =
|
||||
FxHashMap::default();
|
||||
|
||||
// Add all transitive requirements.
|
||||
for metadata in packages.values().flatten() {
|
||||
|
|
@ -138,20 +145,33 @@ impl DisplayDependencyGraph {
|
|||
.as_ref()
|
||||
.map_or(true, |m| m.evaluate(markers, &[]))
|
||||
}) {
|
||||
if invert {
|
||||
requirements
|
||||
.entry(required.name.clone())
|
||||
.or_default()
|
||||
.push(metadata.name.clone());
|
||||
let dependency = if invert {
|
||||
Dependency::Inverted(
|
||||
required.name.clone(),
|
||||
metadata.name.clone(),
|
||||
required.source.clone(),
|
||||
)
|
||||
} else {
|
||||
requirements
|
||||
.entry(metadata.name.clone())
|
||||
Dependency::Normal(
|
||||
metadata.name.clone(),
|
||||
required.name.clone(),
|
||||
required.source.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
requirements
|
||||
.entry(dependency.parent().clone())
|
||||
.or_default()
|
||||
.push(dependency.child().clone());
|
||||
|
||||
if show_version_specifiers {
|
||||
dependencies
|
||||
.entry(dependency.parent().clone())
|
||||
.or_default()
|
||||
.push(required.name.clone());
|
||||
.insert(dependency.child().clone(), dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
packages,
|
||||
depth,
|
||||
|
|
@ -159,6 +179,7 @@ impl DisplayDependencyGraph {
|
|||
package,
|
||||
no_dedupe,
|
||||
requirements,
|
||||
dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +196,19 @@ impl DisplayDependencyGraph {
|
|||
}
|
||||
|
||||
let package_name = &metadata.name;
|
||||
let line = format!("{} v{}", package_name, metadata.version);
|
||||
let mut line = format!("{} v{}", package_name, metadata.version);
|
||||
|
||||
// If the current package is not top-level (i.e., it has a parent), include the specifiers.
|
||||
if let Some(last) = path.last().copied() {
|
||||
if let Some(dependency) = self
|
||||
.dependencies
|
||||
.get(last)
|
||||
.and_then(|deps| deps.get(package_name))
|
||||
{
|
||||
line.push(' ');
|
||||
line.push_str(&format!("[{dependency}]"));
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the traversal if:
|
||||
// 1. The package is in the current traversal path (i.e., a dependency cycle).
|
||||
|
|
@ -261,7 +294,7 @@ impl DisplayDependencyGraph {
|
|||
|
||||
if self.package.is_empty() {
|
||||
// The root nodes are those that are not required by any other package.
|
||||
let children: HashSet<_> = self.requirements.values().flatten().collect();
|
||||
let children: FxHashSet<_> = self.requirements.values().flatten().collect();
|
||||
for package in self.packages.values().flatten() {
|
||||
// If the current package is not required by any other package, start the traversal
|
||||
// with the current package as the root.
|
||||
|
|
@ -286,3 +319,50 @@ impl DisplayDependencyGraph {
|
|||
lines
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Dependency {
|
||||
/// Show dependencies from parent to the child package that it requires.
|
||||
Normal(PackageName, PackageName, RequirementSource),
|
||||
/// Show dependencies from the child package to the parent that requires it.
|
||||
Inverted(PackageName, PackageName, RequirementSource),
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
/// Return the parent in the tree.
|
||||
fn parent(&self) -> &PackageName {
|
||||
match self {
|
||||
Self::Normal(parent, _, _) => parent,
|
||||
Self::Inverted(parent, _, _) => parent,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the child in the tree.
|
||||
fn child(&self) -> &PackageName {
|
||||
match self {
|
||||
Self::Normal(_, child, _) => child,
|
||||
Self::Inverted(_, child, _) => child,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Dependency {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Normal(_, _, source) => {
|
||||
let version = match source.version_or_url() {
|
||||
None => "*".to_string(),
|
||||
Some(version) => version.to_string(),
|
||||
};
|
||||
write!(f, "required: {version}")
|
||||
}
|
||||
Self::Inverted(parent, _, source) => {
|
||||
let version = match source.version_or_url() {
|
||||
None => "*".to_string(),
|
||||
Some(version) => version.to_string(),
|
||||
};
|
||||
write!(f, "requires: {parent} {version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ pub(crate) async fn tree(
|
|||
package: Vec<PackageName>,
|
||||
no_dedupe: bool,
|
||||
invert: bool,
|
||||
show_version_specifiers: bool,
|
||||
python: Option<String>,
|
||||
settings: ResolverSettings,
|
||||
python_preference: PythonPreference,
|
||||
|
|
@ -94,6 +95,7 @@ pub(crate) async fn tree(
|
|||
package,
|
||||
no_dedupe,
|
||||
invert,
|
||||
show_version_specifiers,
|
||||
interpreter.markers(),
|
||||
packages,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
args.package,
|
||||
args.no_dedupe,
|
||||
args.invert,
|
||||
args.show_version_specifiers,
|
||||
args.shared.strict,
|
||||
args.shared.python.as_deref(),
|
||||
args.shared.system,
|
||||
|
|
@ -999,6 +1000,7 @@ async fn run_project(
|
|||
args.package,
|
||||
args.no_dedupe,
|
||||
args.invert,
|
||||
args.show_version_specifiers,
|
||||
args.python,
|
||||
args.resolver,
|
||||
globals.python_preference,
|
||||
|
|
|
|||
|
|
@ -725,6 +725,7 @@ pub(crate) struct TreeSettings {
|
|||
pub(crate) package: Vec<PackageName>,
|
||||
pub(crate) no_dedupe: bool,
|
||||
pub(crate) invert: bool,
|
||||
pub(crate) show_version_specifiers: bool,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) resolver: ResolverSettings,
|
||||
}
|
||||
|
|
@ -749,6 +750,7 @@ impl TreeSettings {
|
|||
package: tree.package,
|
||||
no_dedupe: tree.no_dedupe,
|
||||
invert: tree.invert,
|
||||
show_version_specifiers: tree.show_version_specifiers,
|
||||
python,
|
||||
resolver: ResolverSettings::combine(resolver_options(resolver, build), filesystem),
|
||||
}
|
||||
|
|
@ -1265,6 +1267,7 @@ pub(crate) struct PipTreeSettings {
|
|||
pub(crate) package: Vec<PackageName>,
|
||||
pub(crate) no_dedupe: bool,
|
||||
pub(crate) invert: bool,
|
||||
pub(crate) show_version_specifiers: bool,
|
||||
// CLI-only settings.
|
||||
pub(crate) shared: PipSettings,
|
||||
}
|
||||
|
|
@ -1287,6 +1290,7 @@ impl PipTreeSettings {
|
|||
prune: tree.prune,
|
||||
no_dedupe: tree.no_dedupe,
|
||||
invert: tree.invert,
|
||||
show_version_specifiers: tree.show_version_specifiers,
|
||||
package: tree.package,
|
||||
// Shared settings.
|
||||
shared: PipSettings::combine(
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ fn single_package() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
// `pandas` requires `numpy` with markers on Python version.
|
||||
#[test]
|
||||
#[cfg(not(windows))]
|
||||
|
|
@ -1500,3 +1499,248 @@ fn package_flag() {
|
|||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_version_specifiers_simple() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("requests==2.31.0").unwrap();
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ certifi==2024.2.2
|
||||
+ charset-normalizer==3.3.2
|
||||
+ idna==3.6
|
||||
+ requests==2.31.0
|
||||
+ urllib3==2.2.1
|
||||
"###
|
||||
);
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_tree().arg("--show-version-specifiers"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
requests v2.31.0
|
||||
├── charset-normalizer v3.3.2 [required: <4, >=2]
|
||||
├── idna v3.6 [required: <4, >=2.5]
|
||||
├── urllib3 v2.2.1 [required: <3, >=1.21.1]
|
||||
└── certifi v2024.2.2 [required: >=2017.4.17]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn show_version_specifiers_complex() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt.write_str("packse").unwrap();
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 32 packages in [TIME]
|
||||
Prepared 32 packages in [TIME]
|
||||
Installed 32 packages in [TIME]
|
||||
+ certifi==2024.2.2
|
||||
+ charset-normalizer==3.3.2
|
||||
+ chevron-blue==0.2.1
|
||||
+ docutils==0.20.1
|
||||
+ hatchling==1.22.4
|
||||
+ idna==3.6
|
||||
+ importlib-metadata==7.1.0
|
||||
+ jaraco-classes==3.3.1
|
||||
+ jaraco-context==4.3.0
|
||||
+ jaraco-functools==4.0.0
|
||||
+ keyring==25.0.0
|
||||
+ markdown-it-py==3.0.0
|
||||
+ mdurl==0.1.2
|
||||
+ more-itertools==10.2.0
|
||||
+ msgspec==0.18.6
|
||||
+ nh3==0.2.15
|
||||
+ packaging==24.0
|
||||
+ packse==0.3.12
|
||||
+ pathspec==0.12.1
|
||||
+ pkginfo==1.10.0
|
||||
+ pluggy==1.4.0
|
||||
+ pygments==2.17.2
|
||||
+ readme-renderer==43.0
|
||||
+ requests==2.31.0
|
||||
+ requests-toolbelt==1.0.0
|
||||
+ rfc3986==2.0.0
|
||||
+ rich==13.7.1
|
||||
+ setuptools==69.2.0
|
||||
+ trove-classifiers==2024.3.3
|
||||
+ twine==4.0.2
|
||||
+ urllib3==2.2.1
|
||||
+ zipp==3.18.1
|
||||
"###
|
||||
);
|
||||
|
||||
uv_snapshot!(context.filters(), context.pip_tree().arg("--show-version-specifiers"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
packse v0.3.12
|
||||
├── chevron-blue v0.2.1 [required: >=0.2.1, <0.3.0]
|
||||
├── hatchling v1.22.4 [required: >=1.20.0, <2.0.0]
|
||||
│ ├── packaging v24.0 [required: >=21.3]
|
||||
│ ├── pathspec v0.12.1 [required: >=0.10.1]
|
||||
│ ├── pluggy v1.4.0 [required: >=1.0.0]
|
||||
│ └── trove-classifiers v2024.3.3 [required: *]
|
||||
├── msgspec v0.18.6 [required: >=0.18.4, <0.19.0]
|
||||
├── setuptools v69.2.0 [required: >=69.1.1, <70.0.0]
|
||||
└── twine v4.0.2 [required: >=4.0.2, <5.0.0]
|
||||
├── pkginfo v1.10.0 [required: >=1.8.1]
|
||||
├── readme-renderer v43.0 [required: >=35.0]
|
||||
│ ├── nh3 v0.2.15 [required: >=0.2.14]
|
||||
│ ├── docutils v0.20.1 [required: >=0.13.1]
|
||||
│ └── pygments v2.17.2 [required: >=2.5.1]
|
||||
├── requests v2.31.0 [required: >=2.20]
|
||||
│ ├── charset-normalizer v3.3.2 [required: <4, >=2]
|
||||
│ ├── idna v3.6 [required: <4, >=2.5]
|
||||
│ ├── urllib3 v2.2.1 [required: <3, >=1.21.1]
|
||||
│ └── certifi v2024.2.2 [required: >=2017.4.17]
|
||||
├── requests-toolbelt v1.0.0 [required: !=0.9.0, >=0.8.0]
|
||||
│ └── requests v2.31.0 [required: <3.0.0, >=2.0.1] (*)
|
||||
├── urllib3 v2.2.1 [required: >=1.26.0]
|
||||
├── importlib-metadata v7.1.0 [required: >=3.6]
|
||||
│ └── zipp v3.18.1 [required: >=0.5]
|
||||
├── keyring v25.0.0 [required: >=15.1]
|
||||
│ ├── jaraco-classes v3.3.1 [required: *]
|
||||
│ │ └── more-itertools v10.2.0 [required: *]
|
||||
│ ├── jaraco-functools v4.0.0 [required: *]
|
||||
│ │ └── more-itertools v10.2.0 [required: *]
|
||||
│ └── jaraco-context v4.3.0 [required: *]
|
||||
├── rfc3986 v2.0.0 [required: >=1.4.0]
|
||||
└── rich v13.7.1 [required: >=12.0.0]
|
||||
├── markdown-it-py v3.0.0 [required: >=2.2.0]
|
||||
│ └── mdurl v0.1.2 [required: ~=0.1]
|
||||
└── pygments v2.17.2 [required: >=2.13.0, <3.0.0]
|
||||
(*) Package tree already displayed
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_version_specifiers_with_invert() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt
|
||||
.write_str("scikit-learn==1.4.1.post1")
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ joblib==1.3.2
|
||||
+ numpy==1.26.4
|
||||
+ scikit-learn==1.4.1.post1
|
||||
+ scipy==1.12.0
|
||||
+ threadpoolctl==3.4.0
|
||||
"###
|
||||
);
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context.pip_tree()
|
||||
.arg("--show-version-specifiers")
|
||||
.arg("--invert"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
joblib v1.3.2
|
||||
└── scikit-learn v1.4.1.post1 [requires: joblib >=1.2.0]
|
||||
numpy v1.26.4
|
||||
├── scikit-learn v1.4.1.post1 [requires: numpy <2.0, >=1.19.5]
|
||||
└── scipy v1.12.0 [requires: numpy <1.29.0, >=1.22.4]
|
||||
└── scikit-learn v1.4.1.post1 [requires: scipy >=1.6.0]
|
||||
threadpoolctl v3.4.0
|
||||
└── scikit-learn v1.4.1.post1 [requires: threadpoolctl >=2.0.0]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_version_specifiers_with_package() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let requirements_txt = context.temp_dir.child("requirements.txt");
|
||||
requirements_txt
|
||||
.write_str("scikit-learn==1.4.1.post1")
|
||||
.unwrap();
|
||||
|
||||
uv_snapshot!(context
|
||||
.pip_install()
|
||||
.arg("-r")
|
||||
.arg("requirements.txt")
|
||||
.arg("--strict"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ joblib==1.3.2
|
||||
+ numpy==1.26.4
|
||||
+ scikit-learn==1.4.1.post1
|
||||
+ scipy==1.12.0
|
||||
+ threadpoolctl==3.4.0
|
||||
"###
|
||||
);
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context.pip_tree()
|
||||
.arg("--show-version-specifiers")
|
||||
.arg("--package")
|
||||
.arg("scipy"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
scipy v1.12.0
|
||||
└── numpy v1.26.4 [required: <1.29.0, >=1.22.4]
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue