add pip-compatible --group flag to uv pip install and uv pip compile (#11686)
This is a minimal redux of #10861 to be compatible with `uv pip`. This implements the interface described in: https://github.com/pypa/pip/pull/13065#issuecomment-2544000876 for `uv pip install` and `uv pip compile`. Namely `--group <[path:]name>`, where `path` when not defined defaults to `pyproject.toml`. In that interface they add `--group` to `pip install`, `pip download`, and `pip wheel`. Notably we do not define `uv pip download` and `uv pip wheel`, so for parity we only need to implement `uv pip install`. However, we also support `uv pip compile` which is not part of pip itself, and `--group` makes sense there too. ---- The behaviour of `--group` for `uv pip` commands makes sense for the cases upstream pip supports, but has confusing meanings in cases that only we support (because reading pyproject.tomls is New Tech to them but heavily supported by us). **Specifically case (h) below is a concerning footgun, and case (e) below may get complaints from people who aren't well-versed in dependency-groups-as-they-pertain-to-wheels.** ## Only Group Flags Group flags on their own work reasonably and uncontroversially, except perhaps that they don't do very clever automatic project discovery. a) `uv pip install --group path/to/pyproject.toml:mygroup` pulls up `path/to/project.toml` and installs all the packages listed by its `mygroup` dependency-group (essentially treating it like another kind of requirements.txt). In this regard it functions similarly to `--only-group` in the rest of uv's interface. b) `uv pip install --group mygroup` is just sugar for `uv pip install --group pyproject.toml:mygroup` (**note that no project discovery occurs**, upstream pip simply hardcodes the path "pyproject.toml" here and we reproduce that.) c) `uv pip install --group a/pyproject.toml:groupx --group b/pyproject.toml:groupy`, and any other instance of multiple `--group` flags, can be understood as completely independent requests for the given groups at the given files. ## Groups With Named Packages Groups being mixed with named packages also work in a fairly unsurprising way, especially if you understand that things like dependency-groups are not really supposed to exist on pypi, they're just for local development. d) `uv pip install mypackage --group path/to/pyproject.toml:mygroup` much like multiple instances of `--group` the two requests here are essentially completely independent: pleases install `mypackage`, and please also install `path/to/pyproject.toml:mygroup`. e) `uv pip install mypackage --group mygroup` is exactly the same, but this is where it becomes possible for someone to be a little confused, as you might think `mygroup` is supposed to refer to `mypackage` in some way (it can't). But no, it's sourcing `pyproject.toml:mygroup` from the current working directory. ## Groups With Requirements/Sourcetrees/Editables Requirements and sourcetrees are where I expect users to get confused. It behaves *exactly* the same as it does in the previous sections but you would absolutely be forgiven for expecting a different behaviour. *Especially* because `--group` with the rest of uv *does* do something different. f) `uv pip install -r a/pyproject.toml --group b/pyproject.toml:mygroup` is again just two independent requests (install `a/pyproject.toml`'s dependencies, and `b/pyproject.toml`'s `mygroup`). g) `uv pip install -r pyproject.toml --group mygroup` is exactly like the previous case but *incidentally* the two requests refer to the same file. What the user wanted to happen is almost certainly happening, but they are likely getting "lucky" here that they're requesting something simple. h) `uv pip install -r a/pyproject.toml --group mygroup` is again exactly the same but the user is likely to get surprised and upset as this invocation actually sources two different files (install `a/pyproject.toml`'s dependencies, and `pyproject.toml`'s `mygroup`)! I would expect most people to assume the `--group` flag here is covering all applicable requirements/sourcetrees/editables, but no, it continues to be a totally independent reference to a file with a hardcoded relative path. ------ Fixes https://github.com/astral-sh/uv/issues/8590 Fixes https://github.com/astral-sh/uv/issues/8969
This commit is contained in:
@@ -15,7 +15,7 @@ use uv_configuration::{
|
||||
ProjectBuildBackend, TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
|
||||
};
|
||||
use uv_distribution_types::{Index, IndexUrl, Origin, PipExtraIndex, PipFindLinks, PipIndex};
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName, PipGroupName};
|
||||
use uv_pep508::{MarkerTree, Requirement};
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
@@ -949,6 +949,7 @@ fn parse_maybe_string(input: &str) -> Result<Maybe<String>, String> {
|
||||
|
||||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[command(group = clap::ArgGroup::new("sources").required(true).multiple(true))]
|
||||
pub struct PipCompileArgs {
|
||||
/// Include all packages listed in the given `requirements.in` files.
|
||||
///
|
||||
@@ -959,7 +960,7 @@ pub struct PipCompileArgs {
|
||||
///
|
||||
/// The order of the requirements files and the requirements in them is used to determine
|
||||
/// priority during resolution.
|
||||
#[arg(required(true), value_parser = parse_file_path)]
|
||||
#[arg(group = "sources", value_parser = parse_file_path)]
|
||||
pub src_file: Vec<PathBuf>,
|
||||
|
||||
/// Constrain versions using the given requirements files.
|
||||
@@ -1022,6 +1023,14 @@ pub struct PipCompileArgs {
|
||||
#[arg(long, overrides_with("no_deps"), hide = true)]
|
||||
pub deps: bool,
|
||||
|
||||
/// Install the specified dependency group from a `pyproject.toml`.
|
||||
///
|
||||
/// If no path is provided, the `pyproject.toml` in the working directory is used.
|
||||
///
|
||||
/// May be provided multiple times.
|
||||
#[arg(long, group = "sources")]
|
||||
pub group: Vec<PipGroupName>,
|
||||
|
||||
/// Write the compiled requirements to the given `requirements.txt` file.
|
||||
///
|
||||
/// If the file already exists, the existing versions will be preferred when resolving
|
||||
@@ -1586,6 +1595,14 @@ pub struct PipInstallArgs {
|
||||
#[arg(long, overrides_with("no_deps"), hide = true)]
|
||||
pub deps: bool,
|
||||
|
||||
/// Install the specified dependency group from a `pyproject.toml`.
|
||||
///
|
||||
/// If no path is provided, the `pyproject.toml` in the working directory is used.
|
||||
///
|
||||
/// May be provided multiple times.
|
||||
#[arg(long, group = "sources")]
|
||||
pub group: Vec<PipGroupName>,
|
||||
|
||||
/// Require a matching hash for each requirement.
|
||||
///
|
||||
/// By default, uv will verify any available hashes in the requirements file, but will not
|
||||
|
||||
@@ -25,6 +25,9 @@ impl std::fmt::Display for SourceAnnotation {
|
||||
RequirementOrigin::Project(path, project_name) => {
|
||||
write!(f, "{project_name} ({})", path.portable_display())
|
||||
}
|
||||
RequirementOrigin::Group(path, project_name, group) => {
|
||||
write!(f, "{project_name} ({}:{group})", path.portable_display())
|
||||
}
|
||||
RequirementOrigin::Workspace => {
|
||||
write!(f, "(workspace)")
|
||||
}
|
||||
@@ -40,6 +43,14 @@ impl std::fmt::Display for SourceAnnotation {
|
||||
// Project is not used for override
|
||||
write!(f, "--override {project_name} ({})", path.portable_display())
|
||||
}
|
||||
RequirementOrigin::Group(path, project_name, group) => {
|
||||
// Group is not used for override
|
||||
write!(
|
||||
f,
|
||||
"--override {project_name} ({}:{group})",
|
||||
path.portable_display()
|
||||
)
|
||||
}
|
||||
RequirementOrigin::Workspace => {
|
||||
write!(f, "--override (workspace)")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
@@ -5,7 +7,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use uv_small_str::SmallString;
|
||||
|
||||
use crate::{validate_and_normalize_ref, InvalidNameError};
|
||||
use crate::{
|
||||
validate_and_normalize_ref, InvalidNameError, InvalidPipGroupError, InvalidPipGroupPathError,
|
||||
};
|
||||
|
||||
/// The normalized name of a dependency group.
|
||||
///
|
||||
@@ -82,6 +86,84 @@ impl AsRef<str> for GroupName {
|
||||
}
|
||||
}
|
||||
|
||||
/// The pip-compatible variant of a [`GroupName`].
|
||||
///
|
||||
/// Either <groupname> or <path>:<groupname>.
|
||||
/// If <path> is omitted it defaults to "pyproject.toml".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct PipGroupName {
|
||||
pub path: Option<PathBuf>,
|
||||
pub name: GroupName,
|
||||
}
|
||||
|
||||
impl PipGroupName {
|
||||
/// Gets the path to use, applying the default if it's missing
|
||||
pub fn path(&self) -> &Path {
|
||||
if let Some(path) = &self.path {
|
||||
path
|
||||
} else {
|
||||
Path::new("pyproject.toml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PipGroupName {
|
||||
type Err = InvalidPipGroupError;
|
||||
|
||||
fn from_str(path_and_name: &str) -> Result<Self, Self::Err> {
|
||||
// The syntax is `<path>:<name>`.
|
||||
//
|
||||
// `:` isn't valid as part of a dependency-group name, but it can appear in a path.
|
||||
// Therefore we look for the first `:` starting from the end to find the delimiter.
|
||||
// If there is no `:` then there's no path and we use the default one.
|
||||
if let Some((path, name)) = path_and_name.rsplit_once(':') {
|
||||
// pip hard errors if the path does not end with pyproject.toml
|
||||
if !path.ends_with("pyproject.toml") {
|
||||
Err(InvalidPipGroupPathError(path.to_owned()))?;
|
||||
}
|
||||
|
||||
let name = GroupName::from_str(name)?;
|
||||
let path = Some(PathBuf::from(path));
|
||||
Ok(Self { path, name })
|
||||
} else {
|
||||
let name = GroupName::from_str(path_and_name)?;
|
||||
let path = None;
|
||||
Ok(Self { path, name })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for PipGroupName {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Self::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for PipGroupName {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let string = self.to_string();
|
||||
string.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PipGroupName {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(path) = &self.path {
|
||||
write!(f, "{}:{}", path.display(), self.name)
|
||||
} else {
|
||||
self.name.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The name of the global `dev-dependencies` group.
|
||||
///
|
||||
/// Internally, we model dependency groups as a generic concept; but externally, we only expose the
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};
|
||||
|
||||
pub use dist_info_name::DistInfoName;
|
||||
pub use extra_name::ExtraName;
|
||||
pub use group_name::{GroupName, DEV_DEPENDENCIES};
|
||||
pub use group_name::{GroupName, PipGroupName, DEV_DEPENDENCIES};
|
||||
pub use package_name::PackageName;
|
||||
|
||||
use uv_small_str::SmallString;
|
||||
@@ -121,6 +121,55 @@ impl Display for InvalidNameError {
|
||||
|
||||
impl Error for InvalidNameError {}
|
||||
|
||||
/// Path didn't end with `pyproject.toml`
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InvalidPipGroupPathError(String);
|
||||
|
||||
impl InvalidPipGroupPathError {
|
||||
/// Returns the invalid path.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for InvalidPipGroupPathError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: {}",
|
||||
self.0,
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Error for InvalidPipGroupPathError {}
|
||||
|
||||
/// Possible errors from reading a [`PipGroupName`].
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum InvalidPipGroupError {
|
||||
Name(InvalidNameError),
|
||||
Path(InvalidPipGroupPathError),
|
||||
}
|
||||
|
||||
impl Display for InvalidPipGroupError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InvalidPipGroupError::Name(e) => e.fmt(f),
|
||||
InvalidPipGroupError::Path(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for InvalidPipGroupError {}
|
||||
impl From<InvalidNameError> for InvalidPipGroupError {
|
||||
fn from(value: InvalidNameError) -> Self {
|
||||
Self::Name(value)
|
||||
}
|
||||
}
|
||||
impl From<InvalidPipGroupPathError> for InvalidPipGroupError {
|
||||
fn from(value: InvalidPipGroupPathError) -> Self {
|
||||
Self::Path(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use uv_normalize::PackageName;
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
|
||||
/// The origin of a dependency, e.g., a `-r requirements.txt` file.
|
||||
#[derive(
|
||||
@@ -12,6 +12,8 @@ pub enum RequirementOrigin {
|
||||
File(PathBuf),
|
||||
/// The requirement was provided via a local project (e.g., a `pyproject.toml` file).
|
||||
Project(PathBuf, PackageName),
|
||||
/// The requirement was provided via a local project (e.g., a `pyproject.toml` file).
|
||||
Group(PathBuf, PackageName, GroupName),
|
||||
/// The requirement was provided via a workspace.
|
||||
Workspace,
|
||||
}
|
||||
@@ -22,6 +24,7 @@ impl RequirementOrigin {
|
||||
match self {
|
||||
RequirementOrigin::File(path) => path.as_path(),
|
||||
RequirementOrigin::Project(path, _) => path.as_path(),
|
||||
RequirementOrigin::Group(path, _, _) => path.as_path(),
|
||||
// Multiple toml are merged and difficult to track files where Requirement is defined. Returns a dummy path instead.
|
||||
RequirementOrigin::Workspace => Path::new("(workspace)"),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures::stream::FuturesOrdered;
|
||||
@@ -18,7 +18,7 @@ use uv_pep508::RequirementOrigin;
|
||||
use uv_pypi_types::Requirement;
|
||||
use uv_resolver::{InMemoryIndex, MetadataResponse};
|
||||
use uv_types::{BuildContext, HashStrategy};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceTreeResolution {
|
||||
/// The requirements sourced from the source trees.
|
||||
@@ -37,7 +37,7 @@ pub struct SourceTreeResolver<'a, Context: BuildContext> {
|
||||
/// The extras to include when resolving requirements.
|
||||
extras: &'a ExtrasSpecification,
|
||||
/// The groups to include when resolving requirements.
|
||||
groups: &'a DependencyGroups,
|
||||
groups: &'a BTreeMap<PathBuf, DependencyGroups>,
|
||||
/// The hash policy to enforce.
|
||||
hasher: &'a HashStrategy,
|
||||
/// The in-memory index for resolving dependencies.
|
||||
@@ -50,7 +50,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||
/// Instantiate a new [`SourceTreeResolver`] for a given set of `source_trees`.
|
||||
pub fn new(
|
||||
extras: &'a ExtrasSpecification,
|
||||
groups: &'a DependencyGroups,
|
||||
groups: &'a BTreeMap<PathBuf, DependencyGroups>,
|
||||
hasher: &'a HashStrategy,
|
||||
index: &'a InMemoryIndex,
|
||||
database: DistributionDatabase<'a, Context>,
|
||||
@@ -100,9 +100,13 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||
|
||||
let mut requirements = Vec::new();
|
||||
|
||||
// Resolve any groups associated with this path
|
||||
let default_groups = DependencyGroups::default();
|
||||
let groups = self.groups.get(path).unwrap_or(&default_groups);
|
||||
|
||||
// Flatten any transitive extras and include dependencies
|
||||
// (unless something like --only-group was passed)
|
||||
if self.groups.prod() {
|
||||
if groups.prod() {
|
||||
requirements.extend(
|
||||
FlatRequiresDist::from_requirements(metadata.requires_dist, &metadata.name)
|
||||
.into_iter()
|
||||
@@ -116,20 +120,24 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
|
||||
|
||||
// Apply dependency-groups
|
||||
for (group_name, group) in &metadata.dependency_groups {
|
||||
if self.groups.contains(group_name) {
|
||||
requirements.extend(group.iter().cloned());
|
||||
if groups.contains(group_name) {
|
||||
requirements.extend(group.iter().cloned().map(|group| Requirement {
|
||||
origin: Some(RequirementOrigin::Group(
|
||||
path.to_path_buf(),
|
||||
metadata.name.clone(),
|
||||
group_name.clone(),
|
||||
)),
|
||||
..group
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Complain if dependency groups are named that don't appear.
|
||||
// This is only a warning because *technically* we support passing in
|
||||
// multiple pyproject.tomls, but at this level of abstraction we can't see them all,
|
||||
// so hard erroring on "no pyproject.toml mentions this" is a bit difficult.
|
||||
for name in self.groups.explicit_names() {
|
||||
for name in groups.explicit_names() {
|
||||
if !metadata.dependency_groups.contains_key(name) {
|
||||
warn_user_once!(
|
||||
"The dependency-group '{name}' is not defined in {}",
|
||||
path.display()
|
||||
);
|
||||
return Err(anyhow::anyhow!(
|
||||
"The dependency group '{name}' was not found in the project: {}",
|
||||
path.user_display()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
//! * `setup.py` or `setup.cfg` instead of `pyproject.toml`: Directory is an entry in
|
||||
//! `source_trees`.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
@@ -34,13 +35,13 @@ use rustc_hash::FxHashSet;
|
||||
use tracing::instrument;
|
||||
use uv_cache_key::CanonicalUrl;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::{NoBinary, NoBuild};
|
||||
use uv_configuration::{DependencyGroups, NoBinary, NoBuild};
|
||||
use uv_distribution_types::{
|
||||
IndexUrl, NameRequirementSpecification, UnresolvedRequirement,
|
||||
UnresolvedRequirementSpecification,
|
||||
};
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||
use uv_pep508::{MarkerTree, UnnamedRequirement, UnnamedRequirementUrl};
|
||||
use uv_pypi_types::Requirement;
|
||||
use uv_pypi_types::VerbatimParsedUrl;
|
||||
@@ -62,6 +63,8 @@ pub struct RequirementsSpecification {
|
||||
pub overrides: Vec<UnresolvedRequirementSpecification>,
|
||||
/// The source trees from which to extract requirements.
|
||||
pub source_trees: Vec<PathBuf>,
|
||||
/// The groups to use for `source_trees`
|
||||
pub groups: BTreeMap<PathBuf, DependencyGroups>,
|
||||
/// The extras used to collect requirements.
|
||||
pub extras: FxHashSet<ExtraName>,
|
||||
/// The index URL to use for fetching packages.
|
||||
@@ -223,15 +226,75 @@ impl RequirementsSpecification {
|
||||
requirements: &[RequirementsSource],
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
groups: BTreeMap<PathBuf, Vec<GroupName>>,
|
||||
client_builder: &BaseClientBuilder<'_>,
|
||||
) -> Result<Self> {
|
||||
let mut spec = Self::default();
|
||||
|
||||
// Resolve sources into specifications so we know their `source_tree`s∂
|
||||
let mut requirement_sources = Vec::new();
|
||||
for source in requirements {
|
||||
let source = Self::from_source(source, client_builder).await?;
|
||||
requirement_sources.push(source);
|
||||
}
|
||||
|
||||
// pip `--group` flags specify their own sources, which we need to process here
|
||||
if !groups.is_empty() {
|
||||
let mut group_specs = BTreeMap::new();
|
||||
for (path, groups) in groups {
|
||||
// Conceptually pip `--group` flags just add the group referred to by the file.
|
||||
// In uv semantics this would be like `--only-group`, however if you do this:
|
||||
//
|
||||
// uv pip install -r pyproject.toml --group pyproject.toml:foo
|
||||
//
|
||||
// We don't want to discard the package listed by `-r` in the way `--only-group`
|
||||
// would. So we check to see if any other source wants to add this path, and use
|
||||
// that to determine if we're doing `--group` or `--only-group` semantics.
|
||||
//
|
||||
// Note that it's fine if a file gets referred to multiple times by
|
||||
// different-looking paths (like `./pyproject.toml` vs `pyproject.toml`). We're
|
||||
// specifically trying to disambiguate in situations where the `--group` *happens*
|
||||
// to match with an unrelated argument, and `--only-group` would be overzealous!
|
||||
let source_exists_without_group = requirement_sources
|
||||
.iter()
|
||||
.any(|source| source.source_trees.contains(&path));
|
||||
let (group, only_group) = if source_exists_without_group {
|
||||
(groups, Vec::new())
|
||||
} else {
|
||||
(Vec::new(), groups)
|
||||
};
|
||||
let group_spec = DependencyGroups::from_args(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
group,
|
||||
Vec::new(),
|
||||
false,
|
||||
only_group,
|
||||
false,
|
||||
);
|
||||
|
||||
// If we're doing `--only-group` semantics it's because only `--group` flags referred
|
||||
// to this file, and so we need to make sure to add it to the list of sources!
|
||||
if !source_exists_without_group {
|
||||
let source = Self::from_source(
|
||||
&RequirementsSource::PyprojectToml(path.clone()),
|
||||
client_builder,
|
||||
)
|
||||
.await?;
|
||||
requirement_sources.push(source);
|
||||
}
|
||||
|
||||
group_specs.insert(path, group_spec);
|
||||
}
|
||||
|
||||
spec.groups = group_specs;
|
||||
}
|
||||
|
||||
// Read all requirements, and keep track of all requirements _and_ constraints.
|
||||
// A `requirements.txt` can contain a `-c constraints.txt` directive within it, so reading
|
||||
// a requirements file can also add constraints.
|
||||
for source in requirements {
|
||||
let source = Self::from_source(source, client_builder).await?;
|
||||
for source in requirement_sources {
|
||||
spec.requirements.extend(source.requirements);
|
||||
spec.constraints.extend(source.constraints);
|
||||
spec.overrides.extend(source.overrides);
|
||||
@@ -337,7 +400,7 @@ impl RequirementsSpecification {
|
||||
requirements: &[RequirementsSource],
|
||||
client_builder: &BaseClientBuilder<'_>,
|
||||
) -> Result<Self> {
|
||||
Self::from_sources(requirements, &[], &[], client_builder).await
|
||||
Self::from_sources(requirements, &[], &[], BTreeMap::default(), client_builder).await
|
||||
}
|
||||
|
||||
/// Initialize a [`RequirementsSpecification`] from a list of [`Requirement`].
|
||||
|
||||
@@ -13,7 +13,7 @@ use uv_distribution_types::{
|
||||
};
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_macros::{CombineOptions, OptionsMetadata};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_normalize::{ExtraName, PackageName, PipGroupName};
|
||||
use uv_pep508::Requirement;
|
||||
use uv_pypi_types::{SupportedEnvironments, VerbatimParsedUrl};
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonVersion};
|
||||
@@ -1127,6 +1127,15 @@ pub struct PipOptions {
|
||||
"#
|
||||
)]
|
||||
pub no_deps: Option<bool>,
|
||||
/// Include the following dependency groups.
|
||||
#[option(
|
||||
default = "None",
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
group = ["dev", "docs"]
|
||||
"#
|
||||
)]
|
||||
pub group: Option<Vec<PipGroupName>>,
|
||||
/// Allow `uv pip sync` with empty requirements, which will clear the environment of all
|
||||
/// packages.
|
||||
#[option(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -13,8 +13,8 @@ use tracing::debug;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, ExtrasSpecification,
|
||||
IndexStrategy, NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, ExtrasSpecification, IndexStrategy,
|
||||
NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
};
|
||||
use uv_configuration::{KeyringProviderType, TargetTriple};
|
||||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
@@ -24,7 +24,7 @@ use uv_distribution_types::{
|
||||
};
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
use uv_pypi_types::{Conflicts, Requirement, SupportedEnvironments};
|
||||
use uv_python::{
|
||||
EnvironmentPreference, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest,
|
||||
@@ -60,7 +60,7 @@ pub(crate) async fn pip_compile(
|
||||
build_constraints_from_workspace: Vec<Requirement>,
|
||||
environments: SupportedEnvironments,
|
||||
extras: ExtrasSpecification,
|
||||
groups: DependencyGroups,
|
||||
groups: BTreeMap<PathBuf, Vec<GroupName>>,
|
||||
output_file: Option<&Path>,
|
||||
resolution_mode: ResolutionMode,
|
||||
prerelease_mode: PrereleaseMode,
|
||||
@@ -148,6 +148,7 @@ pub(crate) async fn pip_compile(
|
||||
constraints,
|
||||
overrides,
|
||||
source_trees,
|
||||
groups,
|
||||
extras: used_extras,
|
||||
index_url,
|
||||
extra_index_urls,
|
||||
@@ -159,6 +160,7 @@ pub(crate) async fn pip_compile(
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
groups,
|
||||
&client_builder,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
@@ -9,9 +10,8 @@ use tracing::{debug, enabled, Level};
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, DryRun,
|
||||
ExtrasSpecification, HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy,
|
||||
Upgrade,
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
|
||||
HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
};
|
||||
use uv_configuration::{KeyringProviderType, TargetTriple};
|
||||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
@@ -22,6 +22,7 @@ use uv_distribution_types::{
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
use uv_normalize::GroupName;
|
||||
use uv_pep508::PackageName;
|
||||
use uv_pypi_types::{Conflicts, Requirement};
|
||||
use uv_python::{
|
||||
@@ -55,7 +56,7 @@ pub(crate) async fn pip_install(
|
||||
overrides_from_workspace: Vec<Requirement>,
|
||||
build_constraints_from_workspace: Vec<Requirement>,
|
||||
extras: &ExtrasSpecification,
|
||||
groups: &DependencyGroups,
|
||||
groups: BTreeMap<PathBuf, Vec<GroupName>>,
|
||||
resolution_mode: ResolutionMode,
|
||||
prerelease_mode: PrereleaseMode,
|
||||
dependency_mode: DependencyMode,
|
||||
@@ -107,6 +108,7 @@ pub(crate) async fn pip_install(
|
||||
constraints,
|
||||
overrides,
|
||||
source_trees,
|
||||
groups,
|
||||
index_url,
|
||||
extra_index_urls,
|
||||
no_index,
|
||||
@@ -424,7 +426,7 @@ pub(crate) async fn pip_install(
|
||||
project,
|
||||
BTreeSet::default(),
|
||||
extras,
|
||||
groups,
|
||||
&groups,
|
||||
preferences,
|
||||
site_packages.clone(),
|
||||
&hasher,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use itertools::Itertools;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::collections::{BTreeSet, HashSet};
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -28,7 +28,7 @@ use uv_distribution_types::{
|
||||
use uv_fs::Simplified;
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_installer::{Plan, Planner, Preparer, SitePackages};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_normalize::{GroupName, PackageName};
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_pypi_types::{Conflicts, ResolverMarkerEnvironment};
|
||||
use uv_python::{PythonEnvironment, PythonInstallation};
|
||||
@@ -54,7 +54,7 @@ pub(crate) async fn read_requirements(
|
||||
constraints: &[RequirementsSource],
|
||||
overrides: &[RequirementsSource],
|
||||
extras: &ExtrasSpecification,
|
||||
groups: &DependencyGroups,
|
||||
groups: BTreeMap<PathBuf, Vec<GroupName>>,
|
||||
client_builder: &BaseClientBuilder<'_>,
|
||||
) -> Result<RequirementsSpecification, Error> {
|
||||
// If the user requests `extras` but does not provide a valid source (e.g., a `pyproject.toml`),
|
||||
@@ -75,19 +75,13 @@ pub(crate) async fn read_requirements(
|
||||
)
|
||||
.into());
|
||||
}
|
||||
if !groups.is_empty() && !requirements.iter().any(RequirementsSource::allows_groups) {
|
||||
let flags = groups.history().as_flags_pretty().join(" ");
|
||||
return Err(anyhow!(
|
||||
"Requesting groups requires a `pyproject.toml`. Requested via: {flags}"
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// Read all requirements from the provided sources.
|
||||
Ok(RequirementsSpecification::from_sources(
|
||||
requirements,
|
||||
constraints,
|
||||
overrides,
|
||||
groups,
|
||||
client_builder,
|
||||
)
|
||||
.await?)
|
||||
@@ -98,11 +92,15 @@ pub(crate) async fn read_constraints(
|
||||
constraints: &[RequirementsSource],
|
||||
client_builder: &BaseClientBuilder<'_>,
|
||||
) -> Result<Vec<NameRequirementSpecification>, Error> {
|
||||
Ok(
|
||||
RequirementsSpecification::from_sources(&[], constraints, &[], client_builder)
|
||||
.await?
|
||||
.constraints,
|
||||
Ok(RequirementsSpecification::from_sources(
|
||||
&[],
|
||||
constraints,
|
||||
&[],
|
||||
BTreeMap::default(),
|
||||
client_builder,
|
||||
)
|
||||
.await?
|
||||
.constraints)
|
||||
}
|
||||
|
||||
/// Resolve a set of requirements, similar to running `pip compile`.
|
||||
@@ -114,7 +112,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
|
||||
mut project: Option<PackageName>,
|
||||
workspace_members: BTreeSet<PackageName>,
|
||||
extras: &ExtrasSpecification,
|
||||
groups: &DependencyGroups,
|
||||
groups: &BTreeMap<PathBuf, DependencyGroups>,
|
||||
preferences: Vec<Preference>,
|
||||
installed_packages: InstalledPackages,
|
||||
hasher: &HashStrategy,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -9,9 +9,8 @@ use tracing::debug;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, DependencyGroups, DryRun,
|
||||
ExtrasSpecification, HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy,
|
||||
Upgrade,
|
||||
BuildOptions, Concurrency, ConfigSettings, Constraints, DryRun, ExtrasSpecification,
|
||||
HashCheckingMode, IndexStrategy, PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
};
|
||||
use uv_configuration::{KeyringProviderType, TargetTriple};
|
||||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
@@ -88,7 +87,7 @@ pub(crate) async fn pip_sync(
|
||||
// Initialize a few defaults.
|
||||
let overrides = &[];
|
||||
let extras = ExtrasSpecification::default();
|
||||
let groups = DependencyGroups::default();
|
||||
let groups = BTreeMap::default();
|
||||
let upgrade = Upgrade::default();
|
||||
let resolution_mode = ResolutionMode::default();
|
||||
let prerelease_mode = PrereleaseMode::default();
|
||||
@@ -101,6 +100,7 @@ pub(crate) async fn pip_sync(
|
||||
constraints,
|
||||
overrides,
|
||||
source_trees,
|
||||
groups,
|
||||
index_url,
|
||||
extra_index_urls,
|
||||
no_index,
|
||||
@@ -113,7 +113,7 @@ pub(crate) async fn pip_sync(
|
||||
constraints,
|
||||
overrides,
|
||||
&extras,
|
||||
&groups,
|
||||
groups,
|
||||
&client_builder,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -13,8 +13,7 @@ use uv_auth::UrlAuthPolicies;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
Concurrency, Constraints, DependencyGroups, DryRun, ExtrasSpecification, PreviewMode,
|
||||
Reinstall, Upgrade,
|
||||
Concurrency, Constraints, DryRun, ExtrasSpecification, PreviewMode, Reinstall, Upgrade,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
@@ -588,7 +587,7 @@ async fn do_lock(
|
||||
// optional on the downstream APIs.
|
||||
let build_hasher = HashStrategy::default();
|
||||
let extras = ExtrasSpecification::default();
|
||||
let groups = DependencyGroups::default();
|
||||
let groups = BTreeMap::new();
|
||||
|
||||
// Resolve the flat indexes from `--find-links`.
|
||||
let flat_index = {
|
||||
|
||||
@@ -13,8 +13,8 @@ use uv_cache::{Cache, CacheBucket};
|
||||
use uv_cache_key::cache_digest;
|
||||
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun,
|
||||
ExtrasSpecification, PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification,
|
||||
PreviewMode, Reinstall, SourceStrategy, Upgrade,
|
||||
};
|
||||
use uv_dispatch::{BuildDispatch, SharedState};
|
||||
use uv_distribution::{DistributionDatabase, LoweredRequirement};
|
||||
@@ -1727,7 +1727,7 @@ pub(crate) async fn resolve_environment(
|
||||
// TODO(charlie): These are all default values. We should consider whether we want to make them
|
||||
// optional on the downstream APIs.
|
||||
let extras = ExtrasSpecification::default();
|
||||
let groups = DependencyGroups::default();
|
||||
let groups = BTreeMap::new();
|
||||
let hasher = HashStrategy::default();
|
||||
let build_constraints = Constraints::default();
|
||||
let build_hasher = HashStrategy::default();
|
||||
@@ -2112,7 +2112,7 @@ pub(crate) async fn update_environment(
|
||||
let build_constraints = Constraints::default();
|
||||
let build_hasher = HashStrategy::default();
|
||||
let extras = ExtrasSpecification::default();
|
||||
let groups = DependencyGroups::default();
|
||||
let groups = BTreeMap::new();
|
||||
let hasher = HashStrategy::default();
|
||||
let preferences = Vec::default();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Write;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -234,9 +235,14 @@ pub(crate) async fn install(
|
||||
};
|
||||
|
||||
// Read the `--with` requirements.
|
||||
let spec =
|
||||
RequirementsSpecification::from_sources(with, constraints, overrides, &client_builder)
|
||||
.await?;
|
||||
let spec = RequirementsSpecification::from_sources(
|
||||
with,
|
||||
constraints,
|
||||
overrides,
|
||||
BTreeMap::default(),
|
||||
&client_builder,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Resolve the `--from` and `--with` requirements.
|
||||
let requirements = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
@@ -750,9 +751,14 @@ async fn get_or_create_environment(
|
||||
};
|
||||
|
||||
// Read the `--with` requirements.
|
||||
let spec =
|
||||
RequirementsSpecification::from_sources(with, constraints, overrides, &client_builder)
|
||||
.await?;
|
||||
let spec = RequirementsSpecification::from_sources(
|
||||
with,
|
||||
constraints,
|
||||
overrides,
|
||||
BTreeMap::default(),
|
||||
&client_builder,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Resolve the `--from` and `--with` requirements.
|
||||
let requirements = {
|
||||
|
||||
+31
-2
@@ -1,4 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::io::stdout;
|
||||
@@ -391,6 +392,20 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
.map(RequirementsSource::from_constraints_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut groups = BTreeMap::new();
|
||||
for group in args.settings.groups {
|
||||
// If there's no path provided, expect a pyproject.toml in the project-dir
|
||||
// (Which is typically the current working directory, matching pip's behaviour)
|
||||
let pyproject_path = group
|
||||
.path
|
||||
.clone()
|
||||
.unwrap_or_else(|| project_dir.join("pyproject.toml"));
|
||||
groups
|
||||
.entry(pyproject_path)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(group.name.clone());
|
||||
}
|
||||
|
||||
commands::pip_compile(
|
||||
&requirements,
|
||||
&constraints,
|
||||
@@ -401,7 +416,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
args.build_constraints_from_workspace,
|
||||
args.environments,
|
||||
args.settings.extras,
|
||||
args.settings.groups,
|
||||
groups,
|
||||
args.settings.output_file.as_deref(),
|
||||
args.settings.resolution,
|
||||
args.settings.prerelease,
|
||||
@@ -561,6 +576,20 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
.map(RequirementsSource::from_overrides_txt)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut groups = BTreeMap::new();
|
||||
for group in args.settings.groups {
|
||||
// If there's no path provided, expect a pyproject.toml in the project-dir
|
||||
// (Which is typically the current working directory, matching pip's behaviour)
|
||||
let pyproject_path = group
|
||||
.path
|
||||
.clone()
|
||||
.unwrap_or_else(|| project_dir.join("pyproject.toml"));
|
||||
groups
|
||||
.entry(pyproject_path)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(group.name.clone());
|
||||
}
|
||||
|
||||
commands::pip_install(
|
||||
&requirements,
|
||||
&constraints,
|
||||
@@ -570,7 +599,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
||||
args.overrides_from_workspace,
|
||||
args.build_constraints_from_workspace,
|
||||
&args.settings.extras,
|
||||
&args.settings.groups,
|
||||
groups,
|
||||
args.settings.resolution,
|
||||
args.settings.prerelease,
|
||||
args.settings.dependency_mode,
|
||||
|
||||
@@ -30,7 +30,7 @@ use uv_configuration::{
|
||||
};
|
||||
use uv_distribution_types::{DependencyMetadata, Index, IndexLocations, IndexUrl};
|
||||
use uv_install_wheel::LinkMode;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_normalize::{PackageName, PipGroupName};
|
||||
use uv_pep508::{ExtraName, MarkerTree, RequirementOrigin};
|
||||
use uv_pypi_types::{Requirement, SupportedEnvironments};
|
||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||
@@ -1605,6 +1605,7 @@ impl PipCompileSettings {
|
||||
refresh,
|
||||
no_deps,
|
||||
deps,
|
||||
group,
|
||||
output_file,
|
||||
no_strip_extras,
|
||||
strip_extras,
|
||||
@@ -1721,6 +1722,7 @@ impl PipCompileSettings {
|
||||
extra,
|
||||
all_extras: flag(all_extras, no_all_extras),
|
||||
no_deps: flag(no_deps, deps),
|
||||
group: Some(group),
|
||||
output_file,
|
||||
no_strip_extras: flag(no_strip_extras, strip_extras),
|
||||
no_strip_markers: flag(no_strip_markers, strip_markers),
|
||||
@@ -1867,6 +1869,7 @@ impl PipInstallSettings {
|
||||
refresh,
|
||||
no_deps,
|
||||
deps,
|
||||
group,
|
||||
require_hashes,
|
||||
no_require_hashes,
|
||||
verify_hashes,
|
||||
@@ -1973,6 +1976,7 @@ impl PipInstallSettings {
|
||||
strict: flag(strict, no_strict),
|
||||
extra,
|
||||
all_extras: flag(all_extras, no_all_extras),
|
||||
group: Some(group),
|
||||
no_deps: flag(no_deps, deps),
|
||||
python_version,
|
||||
python_platform,
|
||||
@@ -2592,7 +2596,7 @@ pub(crate) struct PipSettings {
|
||||
pub(crate) install_mirrors: PythonInstallMirrors,
|
||||
pub(crate) system: bool,
|
||||
pub(crate) extras: ExtrasSpecification,
|
||||
pub(crate) groups: DependencyGroups,
|
||||
pub(crate) groups: Vec<PipGroupName>,
|
||||
pub(crate) break_system_packages: bool,
|
||||
pub(crate) target: Option<Target>,
|
||||
pub(crate) prefix: Option<Prefix>,
|
||||
@@ -2669,6 +2673,7 @@ impl PipSettings {
|
||||
extra,
|
||||
all_extras,
|
||||
no_extra,
|
||||
group,
|
||||
no_deps,
|
||||
allow_empty_requirements,
|
||||
resolution,
|
||||
@@ -2786,16 +2791,7 @@ impl PipSettings {
|
||||
args.no_extra.combine(no_extra).unwrap_or_default(),
|
||||
args.extra.combine(extra).unwrap_or_default(),
|
||||
),
|
||||
groups: DependencyGroups::from_args(
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
false,
|
||||
Vec::new(),
|
||||
false,
|
||||
),
|
||||
groups: args.group.combine(group).unwrap_or_default(),
|
||||
dependency_mode: if args.no_deps.combine(no_deps).unwrap_or_default() {
|
||||
DependencyMode::Direct
|
||||
} else {
|
||||
|
||||
@@ -1591,7 +1591,7 @@ fn compile_python_conflicts() -> Result<()> {
|
||||
.arg("--python")
|
||||
.arg("3.12")
|
||||
.arg("-p")
|
||||
.arg("3.12"), @r###"
|
||||
.arg("3.12"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -1599,10 +1599,10 @@ fn compile_python_conflicts() -> Result<()> {
|
||||
----- stderr -----
|
||||
error: the argument '--python <PYTHON>' cannot be used multiple times
|
||||
|
||||
Usage: uv pip compile [OPTIONS] <SRC_FILE>...
|
||||
Usage: uv pip compile [OPTIONS] <SRC_FILE|--group <GROUP>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
"
|
||||
);
|
||||
|
||||
// `UV_PYTHON` should be usable with `-p`
|
||||
@@ -3263,7 +3263,7 @@ optional-dependencies.bar = [
|
||||
.arg("--all-extras")
|
||||
.arg("--extra")
|
||||
.arg("foo"),
|
||||
@r###"
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
@@ -3271,10 +3271,10 @@ optional-dependencies.bar = [
|
||||
----- stderr -----
|
||||
error: the argument '--all-extras' cannot be used with '--extra <EXTRA>'
|
||||
|
||||
Usage: uv pip compile --cache-dir [CACHE_DIR] --all-extras --exclude-newer <EXCLUDE_NEWER> <SRC_FILE>...
|
||||
Usage: uv pip compile --cache-dir [CACHE_DIR] --all-extras --exclude-newer <EXCLUDE_NEWER> <SRC_FILE|--group <GROUP>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -15261,6 +15261,733 @@ fn respect_index_preference() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_group() -> Result<()> {
|
||||
// uv pip compile --group tests, with a single pyproject.toml
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
bar = ["iniconfig"]
|
||||
dev = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// Passing --group should add just that group's contents from ./pyproject.toml
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// Passing a pyproject.toml and --group should include both its deps and the group
|
||||
// (This is a "try to confuse the internals" test, as this file is logically
|
||||
// imported twice, but with two different semantics.)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("pyproject.toml")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --group bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
typing-extensions==4.10.0
|
||||
# via myproject (pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// Another "try to confuse the internals" test with an absolute path
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg(context.temp_dir.child("pyproject.toml").path())
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] [TEMP_DIR]/pyproject.toml --group bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
typing-extensions==4.10.0
|
||||
# via myproject (pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// An explicit use of `<path>:<group>` syntax, here using what the default is anyway
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group pyproject.toml:bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// "try to confuse the internals" with an explicit path for the group
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("pyproject.toml")
|
||||
.arg("--group").arg("pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --group pyproject.toml:bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
typing-extensions==4.10.0
|
||||
# via myproject (pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// Let's check that the other group works fine individually
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group foo
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// Now let's do both of the groups together
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group foo --group bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// And finally put it all together
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("pyproject.toml")
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --group foo --group bar
|
||||
iniconfig==2.0.0
|
||||
# via myproject (pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
typing-extensions==4.10.0
|
||||
# via myproject (pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_pyproject_group() -> Result<()> {
|
||||
// uv pip compile --group tests, with multiple pyproject.tomls at once
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// Use the 'foo' group from the main toml
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group pyproject.toml:foo
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// Use the 'foo' group from the subtoml
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group subdir/pyproject.toml:foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// Now try both together, where they happen to define a group with the same name
|
||||
// (This does nothing special but the code shouldn't get confused.)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("pyproject.toml:foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group pyproject.toml:foo --group subdir/pyproject.toml:foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspicious_group() -> Result<()> {
|
||||
// uv pip compile --group tests, where the invocations are suspicious
|
||||
// and we might want to add warnings
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// Another variant of "both" but with the path sugar applied to the one in cwd
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group foo --group subdir/pyproject.toml:foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// Using the path sugar for "foo" but requesting "bar" for the subtoml
|
||||
// Although you would be forgiven for thinking "foo" should be used from
|
||||
// the subtoml, that's not what should happen.
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --group foo --group subdir/pyproject.toml:bar
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (subdir/pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// Using the path sugar to request pyproject.toml:foo
|
||||
// while also importing subdir/pyproject.toml's dependencies
|
||||
// Although you would be forgiven for thinking "foo" should be used from
|
||||
// the subtoml, that's not what should happen.
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("subdir/pyproject.toml")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] subdir/pyproject.toml --group foo
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
typing-extensions==4.10.0
|
||||
# via mysubproject (subdir/pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// An inversion of the previous -- this one isn't terribly ambiguous
|
||||
// but we should have it in the suite too in case it should be distinguished!
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("pyproject.toml")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] pyproject.toml --group subdir/pyproject.toml:foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
typing-extensions==4.10.0
|
||||
# via myproject (pyproject.toml)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_group() -> Result<()> {
|
||||
// uv pip compile --group tests, where the invocations should fail
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let context = new_context()?;
|
||||
|
||||
// Hey you passed a path and not a group!
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("subdir/"), @r#"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'subdir/' for '--group <GROUP>': Not a valid package or extra name: "subdir/". Names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters.
|
||||
|
||||
For more information, try '--help'.
|
||||
"#);
|
||||
|
||||
// Hey this path needs to end with "pyproject.toml"!
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("./:foo"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value './:foo' for '--group <GROUP>': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: ./
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
||||
// Hey this path needs to end with "pyproject.toml"!
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("subdir/:foo"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'subdir/:foo' for '--group <GROUP>': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: subdir/
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
||||
// Another invocation that Looks Weird but is asking for bar from two
|
||||
// different tomls. In this case the main one doesn't define it and
|
||||
// we should error!
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: The dependency group 'bar' was not found in the project: pyproject.toml
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_and_group() -> Result<()> {
|
||||
// Checking that --project is handled properly with --group
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'foo' from subtoml, by implicit-sugar + --project
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --project subdir --group foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
");
|
||||
|
||||
// 'foo' from subtoml, by implicit-sugar + --project
|
||||
// 'bar' from subtoml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --project subdir --group subdir/pyproject.toml:bar --group foo
|
||||
iniconfig==2.0.0
|
||||
# via mysubproject (subdir/pyproject.toml:foo)
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (subdir/pyproject.toml:bar)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by implicit-sugar + --project
|
||||
// 'foo' from main toml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --project subdir --group bar --group pyproject.toml:foo
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (subdir/pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from cwd
|
||||
// 'foo' from main toml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar")
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --project subdir --group subdir/pyproject.toml:bar --group pyproject.toml:foo
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (subdir/pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_and_group() -> Result<()> {
|
||||
// Checking that --directory is handled properly with --group
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'bar' from subtoml, by implicit-sugar + --directory
|
||||
// 'foo' from main toml, by explicit relpath from --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("../pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --directory subdir --group bar --group ../pyproject.toml:foo
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (../pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from --directory
|
||||
// 'foo' from main toml, by explicit relpath from --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--group").arg("pyproject.toml:bar")
|
||||
.arg("--group").arg("../pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --directory subdir --group pyproject.toml:bar --group ../pyproject.toml:foo
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject (../pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from --directory
|
||||
// 'foo' from main toml, by implicit path + --project + --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_compile()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--project").arg("../")
|
||||
.arg("--group").arg("pyproject.toml:bar")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile --cache-dir [CACHE_DIR] --directory subdir --project ../ --group pyproject.toml:bar --group foo
|
||||
sniffio==1.3.1
|
||||
# via mysubproject (pyproject.toml:bar)
|
||||
sortedcontainers==2.4.0
|
||||
# via myproject ([TEMP_DIR]/pyproject.toml:foo)
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// See: <https://github.com/astral-sh/uv/issues/10957>
|
||||
#[test]
|
||||
fn compile_preserve_requires_python_split() -> Result<()> {
|
||||
|
||||
@@ -9060,6 +9060,749 @@ fn direct_url_json_direct_url() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_group() -> Result<()> {
|
||||
// testing basic `uv pip install --group` functionality
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
bar = ["iniconfig"]
|
||||
dev = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
context.lock().assert().success();
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'bar' using path sugar
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
// 'bar' using path sugar
|
||||
// and also pulling in the same pyproject.toml with -r
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r").arg("pyproject.toml")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
// 'bar' with an explicit path
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
// 'bar' using explicit path
|
||||
// and also pulling in the same pyproject.toml with -r
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r").arg("pyproject.toml")
|
||||
.arg("--group").arg("pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
// 'bar' using path sugar
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// 'foo' using path sugar
|
||||
// 'bar' using path sugar
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// all together now!
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r").arg("pyproject.toml")
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ sortedcontainers==2.4.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_pyproject_group() -> Result<()> {
|
||||
// `uv pip install --group` tests with multiple projects
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
context.lock().assert().success();
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'foo' from main toml
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// 'foo' from subtoml
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
// 'foo' from main toml
|
||||
// 'foo' from sub toml
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("pyproject.toml:foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_sources_group() -> Result<()> {
|
||||
// `uv pip install --group` tests just slamming random other sources like -e and .
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'foo' from main toml
|
||||
// and install '.'
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg(".")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
Prepared 3 packages in [TIME]
|
||||
Installed 3 packages in [TIME]
|
||||
+ myproject==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sortedcontainers==2.4.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
// 'foo' from main toml
|
||||
// and install an editable
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-e").arg(context.workspace_root.join("scripts/packages/poetry_editable"))
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 5 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ idna==3.6
|
||||
+ poetry-editable==0.1.0 (from file://[WORKSPACE]/scripts/packages/poetry_editable)
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspicious_group() -> Result<()> {
|
||||
// uv pip compile --group tests, where the invocations are suspicious
|
||||
// and we might want to add warnings
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// Another variant of "both" but with the path sugar applied to the one in cwd
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// Using the path sugar for "foo" but requesting "bar" for the subtoml
|
||||
// Although you would be forgiven for thinking "foo" should be used from
|
||||
// the subtoml, that's not what should happen.
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("foo")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// Using the path sugar to request pyproject.toml:foo
|
||||
// while also importing subdir/pyproject.toml's dependencies
|
||||
// Although you would be forgiven for thinking "foo" should be used from
|
||||
// the subtoml, that's not what should happen.
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r").arg("subdir/pyproject.toml")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sortedcontainers==2.4.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
// An inversion of the previous -- this one isn't terribly ambiguous
|
||||
// but we should have it in the suite too in case it should be distinguished!
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("-r").arg("pyproject.toml")
|
||||
.arg("--group").arg("subdir/pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_group() -> Result<()> {
|
||||
// uv pip compile --group tests, where the invocations should fail
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let context = new_context()?;
|
||||
|
||||
// Hey you passed a path and not a group!
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("subdir/"), @r#"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'subdir/' for '--group <GROUP>': Not a valid package or extra name: "subdir/". Names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters.
|
||||
|
||||
For more information, try '--help'.
|
||||
"#);
|
||||
|
||||
// Hey this path needs to end with "pyproject.toml"!
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("./:foo"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value './:foo' for '--group <GROUP>': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: ./
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
||||
// Hey this path needs to end with "pyproject.toml"!
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("subdir/:foo"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'subdir/:foo' for '--group <GROUP>': The `--group` path is required to end in 'pyproject.toml' for compatibility with pip; got: subdir/
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
||||
// Another invocation that Looks Weird but is asking for bar from two
|
||||
// different tomls. In this case the main one doesn't define it and
|
||||
// we should error!
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: The dependency group 'bar' was not found in the project: pyproject.toml
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_and_group() -> Result<()> {
|
||||
// Checking that --project is handled properly with --group
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'foo' from subtoml, by implicit-sugar + --project
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
");
|
||||
|
||||
// 'foo' from subtoml, by implicit-sugar + --project
|
||||
// 'bar' from subtoml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ sniffio==1.3.1
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by implicit-sugar + --project
|
||||
// 'foo' from main toml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from cwd
|
||||
// 'foo' from main toml, by explicit relpath from cwd
|
||||
// (explicit relpaths are not affected by --project)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--project").arg("subdir")
|
||||
.arg("--group").arg("subdir/pyproject.toml:bar")
|
||||
.arg("--group").arg("pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn directory_and_group() -> Result<()> {
|
||||
// Checking that --directory is handled properly with --group
|
||||
fn new_context() -> Result<TestContext> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "myproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["typing-extensions"]
|
||||
[dependency-groups]
|
||||
foo = ["sortedcontainers"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let subdir = context.temp_dir.child("subdir");
|
||||
subdir.create_dir_all()?;
|
||||
let pyproject_toml2 = subdir.child("pyproject.toml");
|
||||
pyproject_toml2.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "mysubproject"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
[dependency-groups]
|
||||
foo = ["iniconfig"]
|
||||
bar = ["sniffio"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
let mut context;
|
||||
|
||||
// 'bar' from subtoml, by implicit-sugar + --directory
|
||||
// 'foo' from main toml, by explicit relpath from --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--group").arg("bar")
|
||||
.arg("--group").arg("../pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from --directory
|
||||
// 'foo' from main toml, by explicit relpath from --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--group").arg("pyproject.toml:bar")
|
||||
.arg("--group").arg("../pyproject.toml:foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
// 'bar' from subtoml, by explicit relpath from --directory
|
||||
// 'foo' from main toml, by implicit path + --project + --directory
|
||||
// (explicit relpaths ARE affected by --directory)
|
||||
context = new_context()?;
|
||||
uv_snapshot!(context.filters(), context.pip_install()
|
||||
.arg("--directory").arg("subdir")
|
||||
.arg("--project").arg("../")
|
||||
.arg("--group").arg("pyproject.toml:bar")
|
||||
.arg("--group").arg("foo"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Using Python 3.12.[X] environment at: [VENV]/
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ sniffio==1.3.1
|
||||
+ sortedcontainers==2.4.0
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test that we don't discover workspaces with `--no-sources`.
|
||||
///
|
||||
/// We have a workspace dependency shadowing a PyPI package and using this package's version to
|
||||
|
||||
@@ -148,24 +148,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -323,24 +306,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -499,24 +465,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -707,24 +656,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -853,24 +785,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -1039,24 +954,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -1269,24 +1167,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -1508,24 +1389,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -1708,24 +1572,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -1876,24 +1723,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2098,24 +1928,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2303,24 +2116,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2470,24 +2266,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2621,24 +2400,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2772,24 +2534,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -2925,24 +2670,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -3260,24 +2988,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -3470,24 +3181,7 @@ fn resolve_both() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -3770,24 +3464,7 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4015,24 +3692,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4169,24 +3829,7 @@ fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4342,24 +3985,7 @@ fn allow_insecure_host() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4570,24 +4196,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4777,24 +4386,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -4990,24 +4582,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -5198,24 +4773,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -5413,24 +4971,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -5621,24 +5162,7 @@ fn index_priority() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -5780,24 +5304,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -5925,24 +5432,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -6068,24 +5558,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -6213,24 +5686,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -6356,24 +5812,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
@@ -6500,24 +5939,7 @@ fn verify_hashes() -> anyhow::Result<()> {
|
||||
},
|
||||
system: false,
|
||||
extras: None,
|
||||
groups: DependencyGroups(
|
||||
DependencyGroupsInner {
|
||||
include: Some(
|
||||
[],
|
||||
),
|
||||
exclude: [],
|
||||
only_groups: false,
|
||||
history: DependencyGroupsHistory {
|
||||
dev_mode: None,
|
||||
group: [],
|
||||
only_group: [],
|
||||
no_group: [],
|
||||
all_groups: false,
|
||||
no_default_groups: false,
|
||||
defaults: [],
|
||||
},
|
||||
},
|
||||
),
|
||||
groups: [],
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
|
||||
@@ -60,6 +60,35 @@ $ uv pip compile pyproject.toml --all-extras
|
||||
|
||||
Note extras are not supported with the `requirements.in` format.
|
||||
|
||||
To lock a dependency group in the current project directory's `pyproject.toml`, for example the
|
||||
group `foo`:
|
||||
|
||||
```console
|
||||
$ uv pip compile --group foo
|
||||
```
|
||||
|
||||
!!! important
|
||||
|
||||
A `--group` flag has to be added to pip-tools' `pip compile`, [although they're considering it](https://github.com/jazzband/pip-tools/issues/2062). We expect to support whatever syntax and semantics they adopt.
|
||||
|
||||
To specify the project directory where groups should be sourced from:
|
||||
|
||||
```console
|
||||
$ uv pip compile --project some/path/ --group foo --group bar
|
||||
```
|
||||
|
||||
Alternatively, you can specify a path to a `pyproject.toml` for each group:
|
||||
|
||||
```console
|
||||
$ uv pip compile --group some/path/pyproject.toml:foo --group other/pyproject.toml:bar
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
`--group` flags do not apply to other specified sources. For instance,
|
||||
`uv pip compile some/path/pyproject.toml --group foo` sources `foo`
|
||||
from `./pyproject.toml` and **not** `some/path/pyproject.toml`.
|
||||
|
||||
## Upgrading requirements
|
||||
|
||||
When using an output file, uv will consider the versions pinned in an existing output file. If a
|
||||
|
||||
@@ -107,6 +107,31 @@ Install from a `pyproject.toml` file with all optional dependencies enabled:
|
||||
$ uv pip install -r pyproject.toml --all-extras
|
||||
```
|
||||
|
||||
To install dependency groups in the current project directory's `pyproject.toml`, for example the
|
||||
group `foo`:
|
||||
|
||||
```console
|
||||
$ uv pip install --group foo
|
||||
```
|
||||
|
||||
To specify the project directory where groups should be sourced from:
|
||||
|
||||
```console
|
||||
$ uv pip install --project some/path/ --group foo --group bar
|
||||
```
|
||||
|
||||
Alternatively, you can specify a path to a `pyproject.toml` for each group:
|
||||
|
||||
```console
|
||||
$ uv pip install --group some/path/pyproject.toml:foo --group other/pyproject.toml:bar
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
As in pip, `--group` flags do not apply to other sources specified with flags like `-r` or -e`.
|
||||
For instance, `uv pip install -r some/path/pyproject.toml --group foo` sources `foo`
|
||||
from `./pyproject.toml` and **not** `some/path/pyproject.toml`.
|
||||
|
||||
## Uninstalling a package
|
||||
|
||||
To uninstall a package, e.g., Flask:
|
||||
|
||||
+14
-2
@@ -5577,7 +5577,7 @@ Compile a `requirements.in` file to a `requirements.txt` file
|
||||
<h3 class="cli-reference">Usage</h3>
|
||||
|
||||
```
|
||||
uv pip compile [OPTIONS] <SRC_FILE>...
|
||||
uv pip compile [OPTIONS] <SRC_FILE|--group <GROUP>>
|
||||
```
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
@@ -5722,6 +5722,12 @@ uv pip compile [OPTIONS] <SRC_FILE>...
|
||||
</ul>
|
||||
</dd><dt id="uv-pip-compile--generate-hashes"><a href="#uv-pip-compile--generate-hashes"><code>--generate-hashes</code></a></dt><dd><p>Include distribution hashes in the output file</p>
|
||||
|
||||
</dd><dt id="uv-pip-compile--group"><a href="#uv-pip-compile--group"><code>--group</code></a> <i>group</i></dt><dd><p>Install the specified dependency group from a <code>pyproject.toml</code>.</p>
|
||||
|
||||
<p>If no path is provided, the <code>pyproject.toml</code> in the working directory is used.</p>
|
||||
|
||||
<p>May be provided multiple times.</p>
|
||||
|
||||
</dd><dt id="uv-pip-compile--help"><a href="#uv-pip-compile--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||
|
||||
</dd><dt id="uv-pip-compile--index"><a href="#uv-pip-compile--index"><code>--index</code></a> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
|
||||
@@ -6461,7 +6467,7 @@ Install packages into an environment
|
||||
<h3 class="cli-reference">Usage</h3>
|
||||
|
||||
```
|
||||
uv pip install [OPTIONS] <PACKAGE|--requirements <REQUIREMENTS>|--editable <EDITABLE>>
|
||||
uv pip install [OPTIONS] <PACKAGE|--requirements <REQUIREMENTS>|--editable <EDITABLE>|--group <GROUP>>
|
||||
```
|
||||
|
||||
<h3 class="cli-reference">Arguments</h3>
|
||||
@@ -6596,6 +6602,12 @@ uv pip install [OPTIONS] <PACKAGE|--requirements <REQUIREMENTS>|--editable <EDIT
|
||||
|
||||
<li><code>requires-python</code>: Optimize for selecting latest supported version of each package, for each supported Python version</li>
|
||||
</ul>
|
||||
</dd><dt id="uv-pip-install--group"><a href="#uv-pip-install--group"><code>--group</code></a> <i>group</i></dt><dd><p>Install the specified dependency group from a <code>pyproject.toml</code>.</p>
|
||||
|
||||
<p>If no path is provided, the <code>pyproject.toml</code> in the working directory is used.</p>
|
||||
|
||||
<p>May be provided multiple times.</p>
|
||||
|
||||
</dd><dt id="uv-pip-install--help"><a href="#uv-pip-install--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
|
||||
|
||||
</dd><dt id="uv-pip-install--index"><a href="#uv-pip-install--index"><code>--index</code></a> <i>index</i></dt><dd><p>The URLs to use when resolving dependencies, in addition to the default index.</p>
|
||||
|
||||
@@ -2308,6 +2308,32 @@ Include distribution hashes in the output file.
|
||||
|
||||
---
|
||||
|
||||
#### [`group`](#pip_group) {: #pip_group }
|
||||
<span id="group"></span>
|
||||
|
||||
Include the following dependency groups.
|
||||
|
||||
**Default value**: `None`
|
||||
|
||||
**Type**: `list[str]`
|
||||
|
||||
**Example usage**:
|
||||
|
||||
=== "pyproject.toml"
|
||||
|
||||
```toml
|
||||
[tool.uv.pip]
|
||||
group = ["dev", "docs"]
|
||||
```
|
||||
=== "uv.toml"
|
||||
|
||||
```toml
|
||||
[pip]
|
||||
group = ["dev", "docs"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### [`index-strategy`](#pip_index-strategy) {: #pip_index-strategy }
|
||||
<span id="index-strategy"></span>
|
||||
|
||||
|
||||
Generated
+28
@@ -880,6 +880,24 @@
|
||||
"type": "string",
|
||||
"pattern": "^(:none:|:all:|([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]))$"
|
||||
},
|
||||
"PipGroupName": {
|
||||
"description": "The pip-compatible variant of a [`GroupName`].\n\nEither <groupname> or <path>:<groupname>. If <path> is omitted it defaults to \"pyproject.toml\".",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"$ref": "#/definitions/GroupName"
|
||||
},
|
||||
"path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"PipOptions": {
|
||||
"description": "Settings that are specific to the `uv pip` command-line interface.\n\nThese values will be ignored when running commands outside the `uv pip` namespace (e.g., `uv lock`, `uvx`).",
|
||||
"type": "object",
|
||||
@@ -1045,6 +1063,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"group": {
|
||||
"description": "Include the following dependency groups.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"$ref": "#/definitions/PipGroupName"
|
||||
}
|
||||
},
|
||||
"index-strategy": {
|
||||
"description": "The strategy to use when resolving against multiple index URLs.\n\nBy default, uv will stop at the first index on which a given package is available, and limit resolutions to those present on that first index (`first-index`). This prevents \"dependency confusion\" attacks, whereby an attacker can upload a malicious package under the same name to an alternate index.",
|
||||
"anyOf": [
|
||||
|
||||
Reference in New Issue
Block a user