mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-30 08:33:49 +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),
|
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.
|
/// Unowned version specifier or URL to install.
|
||||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||||
pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
||||||
|
@ -576,6 +585,15 @@ pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
|
||||||
Url(&'a T),
|
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> {
|
impl<'a> From<&'a VersionOrUrl> for VersionOrUrlRef<'a> {
|
||||||
fn from(value: &'a VersionOrUrl) -> Self {
|
fn from(value: &'a VersionOrUrl) -> Self {
|
||||||
match value {
|
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.
|
/// Returns `true` if the source is editable.
|
||||||
pub fn is_editable(&self) -> bool {
|
pub fn is_editable(&self) -> bool {
|
||||||
matches!(self, Self::Directory { editable: true, .. })
|
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.
|
/// 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")]
|
#[arg(long, alias = "reverse")]
|
||||||
pub invert: bool,
|
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 std::fmt::Write;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use owo_colors::OwoColorize;
|
use owo_colors::OwoColorize;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{Diagnostic, Name};
|
use distribution_types::{Diagnostic, Name};
|
||||||
use pep508_rs::MarkerEnvironment;
|
use pep508_rs::MarkerEnvironment;
|
||||||
|
use pypi_types::RequirementSource;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_distribution::Metadata;
|
use uv_distribution::Metadata;
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
|
@ -29,6 +29,7 @@ pub(crate) fn pip_tree(
|
||||||
package: Vec<PackageName>,
|
package: Vec<PackageName>,
|
||||||
no_dedupe: bool,
|
no_dedupe: bool,
|
||||||
invert: bool,
|
invert: bool,
|
||||||
|
show_version_specifiers: bool,
|
||||||
strict: bool,
|
strict: bool,
|
||||||
python: Option<&str>,
|
python: Option<&str>,
|
||||||
system: bool,
|
system: bool,
|
||||||
|
@ -66,6 +67,7 @@ pub(crate) fn pip_tree(
|
||||||
package,
|
package,
|
||||||
no_dedupe,
|
no_dedupe,
|
||||||
invert,
|
invert,
|
||||||
|
show_version_specifiers,
|
||||||
environment.interpreter().markers(),
|
environment.interpreter().markers(),
|
||||||
packages,
|
packages,
|
||||||
)
|
)
|
||||||
|
@ -74,7 +76,7 @@ pub(crate) fn pip_tree(
|
||||||
|
|
||||||
writeln!(printer.stdout(), "{rendered_tree}")?;
|
writeln!(printer.stdout(), "{rendered_tree}")?;
|
||||||
|
|
||||||
if rendered_tree.contains('*') {
|
if rendered_tree.contains("(*)") {
|
||||||
let message = if no_dedupe {
|
let message = if no_dedupe {
|
||||||
"(*) Package tree is a cycle and cannot be shown".italic()
|
"(*) Package tree is a cycle and cannot be shown".italic()
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,7 +115,9 @@ pub(crate) struct DisplayDependencyGraph {
|
||||||
/// Map from package name to its requirements.
|
/// Map from package name to its requirements.
|
||||||
///
|
///
|
||||||
/// If `--invert` is given the map is inverted.
|
/// 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 {
|
impl DisplayDependencyGraph {
|
||||||
|
@ -124,10 +128,13 @@ impl DisplayDependencyGraph {
|
||||||
package: Vec<PackageName>,
|
package: Vec<PackageName>,
|
||||||
no_dedupe: bool,
|
no_dedupe: bool,
|
||||||
invert: bool,
|
invert: bool,
|
||||||
|
show_version_specifiers: bool,
|
||||||
markers: &MarkerEnvironment,
|
markers: &MarkerEnvironment,
|
||||||
packages: IndexMap<PackageName, Vec<Metadata>>,
|
packages: IndexMap<PackageName, Vec<Metadata>>,
|
||||||
) -> Self {
|
) -> 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.
|
// Add all transitive requirements.
|
||||||
for metadata in packages.values().flatten() {
|
for metadata in packages.values().flatten() {
|
||||||
|
@ -138,20 +145,33 @@ impl DisplayDependencyGraph {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(true, |m| m.evaluate(markers, &[]))
|
.map_or(true, |m| m.evaluate(markers, &[]))
|
||||||
}) {
|
}) {
|
||||||
if invert {
|
let dependency = if invert {
|
||||||
requirements
|
Dependency::Inverted(
|
||||||
.entry(required.name.clone())
|
required.name.clone(),
|
||||||
.or_default()
|
metadata.name.clone(),
|
||||||
.push(metadata.name.clone());
|
required.source.clone(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
requirements
|
Dependency::Normal(
|
||||||
.entry(metadata.name.clone())
|
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()
|
.or_default()
|
||||||
.push(required.name.clone());
|
.insert(dependency.child().clone(), dependency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
packages,
|
packages,
|
||||||
depth,
|
depth,
|
||||||
|
@ -159,6 +179,7 @@ impl DisplayDependencyGraph {
|
||||||
package,
|
package,
|
||||||
no_dedupe,
|
no_dedupe,
|
||||||
requirements,
|
requirements,
|
||||||
|
dependencies,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +196,19 @@ impl DisplayDependencyGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
let package_name = &metadata.name;
|
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:
|
// Skip the traversal if:
|
||||||
// 1. The package is in the current traversal path (i.e., a dependency cycle).
|
// 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() {
|
if self.package.is_empty() {
|
||||||
// The root nodes are those that are not required by any other package.
|
// 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() {
|
for package in self.packages.values().flatten() {
|
||||||
// If the current package is not required by any other package, start the traversal
|
// If the current package is not required by any other package, start the traversal
|
||||||
// with the current package as the root.
|
// with the current package as the root.
|
||||||
|
@ -286,3 +319,50 @@ impl DisplayDependencyGraph {
|
||||||
lines
|
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>,
|
package: Vec<PackageName>,
|
||||||
no_dedupe: bool,
|
no_dedupe: bool,
|
||||||
invert: bool,
|
invert: bool,
|
||||||
|
show_version_specifiers: bool,
|
||||||
python: Option<String>,
|
python: Option<String>,
|
||||||
settings: ResolverSettings,
|
settings: ResolverSettings,
|
||||||
python_preference: PythonPreference,
|
python_preference: PythonPreference,
|
||||||
|
@ -94,6 +95,7 @@ pub(crate) async fn tree(
|
||||||
package,
|
package,
|
||||||
no_dedupe,
|
no_dedupe,
|
||||||
invert,
|
invert,
|
||||||
|
show_version_specifiers,
|
||||||
interpreter.markers(),
|
interpreter.markers(),
|
||||||
packages,
|
packages,
|
||||||
)
|
)
|
||||||
|
|
|
@ -501,6 +501,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
||||||
args.package,
|
args.package,
|
||||||
args.no_dedupe,
|
args.no_dedupe,
|
||||||
args.invert,
|
args.invert,
|
||||||
|
args.show_version_specifiers,
|
||||||
args.shared.strict,
|
args.shared.strict,
|
||||||
args.shared.python.as_deref(),
|
args.shared.python.as_deref(),
|
||||||
args.shared.system,
|
args.shared.system,
|
||||||
|
@ -999,6 +1000,7 @@ async fn run_project(
|
||||||
args.package,
|
args.package,
|
||||||
args.no_dedupe,
|
args.no_dedupe,
|
||||||
args.invert,
|
args.invert,
|
||||||
|
args.show_version_specifiers,
|
||||||
args.python,
|
args.python,
|
||||||
args.resolver,
|
args.resolver,
|
||||||
globals.python_preference,
|
globals.python_preference,
|
||||||
|
|
|
@ -725,6 +725,7 @@ pub(crate) struct TreeSettings {
|
||||||
pub(crate) package: Vec<PackageName>,
|
pub(crate) package: Vec<PackageName>,
|
||||||
pub(crate) no_dedupe: bool,
|
pub(crate) no_dedupe: bool,
|
||||||
pub(crate) invert: bool,
|
pub(crate) invert: bool,
|
||||||
|
pub(crate) show_version_specifiers: bool,
|
||||||
pub(crate) python: Option<String>,
|
pub(crate) python: Option<String>,
|
||||||
pub(crate) resolver: ResolverSettings,
|
pub(crate) resolver: ResolverSettings,
|
||||||
}
|
}
|
||||||
|
@ -749,6 +750,7 @@ impl TreeSettings {
|
||||||
package: tree.package,
|
package: tree.package,
|
||||||
no_dedupe: tree.no_dedupe,
|
no_dedupe: tree.no_dedupe,
|
||||||
invert: tree.invert,
|
invert: tree.invert,
|
||||||
|
show_version_specifiers: tree.show_version_specifiers,
|
||||||
python,
|
python,
|
||||||
resolver: ResolverSettings::combine(resolver_options(resolver, build), filesystem),
|
resolver: ResolverSettings::combine(resolver_options(resolver, build), filesystem),
|
||||||
}
|
}
|
||||||
|
@ -1265,6 +1267,7 @@ pub(crate) struct PipTreeSettings {
|
||||||
pub(crate) package: Vec<PackageName>,
|
pub(crate) package: Vec<PackageName>,
|
||||||
pub(crate) no_dedupe: bool,
|
pub(crate) no_dedupe: bool,
|
||||||
pub(crate) invert: bool,
|
pub(crate) invert: bool,
|
||||||
|
pub(crate) show_version_specifiers: bool,
|
||||||
// CLI-only settings.
|
// CLI-only settings.
|
||||||
pub(crate) shared: PipSettings,
|
pub(crate) shared: PipSettings,
|
||||||
}
|
}
|
||||||
|
@ -1287,6 +1290,7 @@ impl PipTreeSettings {
|
||||||
prune: tree.prune,
|
prune: tree.prune,
|
||||||
no_dedupe: tree.no_dedupe,
|
no_dedupe: tree.no_dedupe,
|
||||||
invert: tree.invert,
|
invert: tree.invert,
|
||||||
|
show_version_specifiers: tree.show_version_specifiers,
|
||||||
package: tree.package,
|
package: tree.package,
|
||||||
// Shared settings.
|
// Shared settings.
|
||||||
shared: PipSettings::combine(
|
shared: PipSettings::combine(
|
||||||
|
|
|
@ -110,7 +110,6 @@ fn single_package() {
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `pandas` requires `numpy` with markers on Python version.
|
// `pandas` requires `numpy` with markers on Python version.
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(not(windows))]
|
#[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