mirror of https://github.com/astral-sh/uv
Move `DependencyGroups` to uv-pypi-types so it can be imported there (#12037)
This PR is in support of #12005, where we need to import `DependencyGroups` in the `uv-pypi-types` crate without a circular dependency on `uv-workspace`.
This commit is contained in:
parent
7a56aef7d1
commit
1ab1945dd9
|
|
@ -32,7 +32,7 @@ mailparse = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
rkyv = { workspace = true }
|
rkyv = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true, optional = true }
|
||||||
serde-untagged = { workspace = true }
|
serde-untagged = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use uv_normalize::GroupName;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct DependencyGroups(BTreeMap<GroupName, Vec<DependencyGroupSpecifier>>);
|
||||||
|
|
||||||
|
impl DependencyGroups {
|
||||||
|
/// Returns the names of the dependency groups.
|
||||||
|
pub fn keys(&self) -> impl Iterator<Item = &GroupName> {
|
||||||
|
self.0.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the dependency group with the given name.
|
||||||
|
pub fn get(&self, group: &GroupName) -> Option<&Vec<DependencyGroupSpecifier>> {
|
||||||
|
self.0.get(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the dependency group is in the list.
|
||||||
|
pub fn contains_key(&self, group: &GroupName) -> bool {
|
||||||
|
self.0.contains_key(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the dependency groups.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&GroupName, &Vec<DependencyGroupSpecifier>)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a DependencyGroups {
|
||||||
|
type Item = (&'a GroupName, &'a Vec<DependencyGroupSpecifier>);
|
||||||
|
type IntoIter = std::collections::btree_map::Iter<'a, GroupName, Vec<DependencyGroupSpecifier>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that all keys in the TOML table are unique.
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for DependencyGroups {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct GroupVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for GroupVisitor {
|
||||||
|
type Value = DependencyGroups;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a table with unique dependency group names")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut sources = BTreeMap::new();
|
||||||
|
while let Some((key, value)) =
|
||||||
|
access.next_entry::<GroupName, Vec<DependencyGroupSpecifier>>()?
|
||||||
|
{
|
||||||
|
match sources.entry(key) {
|
||||||
|
std::collections::btree_map::Entry::Occupied(entry) => {
|
||||||
|
return Err(serde::de::Error::custom(format!(
|
||||||
|
"duplicate dependency group: `{}`",
|
||||||
|
entry.key()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
std::collections::btree_map::Entry::Vacant(entry) => {
|
||||||
|
entry.insert(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(DependencyGroups(sources))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_map(GroupVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A specifier item in a [PEP 735](https://peps.python.org/pep-0735/) Dependency Group.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
pub enum DependencyGroupSpecifier {
|
||||||
|
/// A PEP 508-compatible requirement string.
|
||||||
|
Requirement(String),
|
||||||
|
/// A reference to another dependency group.
|
||||||
|
IncludeGroup {
|
||||||
|
/// The name of the group to include.
|
||||||
|
include_group: GroupName,
|
||||||
|
},
|
||||||
|
/// A Dependency Object Specifier.
|
||||||
|
Object(BTreeMap<String, String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for DependencyGroupSpecifier {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct Visitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||||
|
type Value = DependencyGroupSpecifier;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a string or a map with the `include-group` key")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
Ok(DependencyGroupSpecifier::Requirement(value.to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: serde::de::MapAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut map_data = BTreeMap::new();
|
||||||
|
while let Some((key, value)) = map.next_entry()? {
|
||||||
|
map_data.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if map_data.is_empty() {
|
||||||
|
return Err(serde::de::Error::custom("missing field `include-group`"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(include_group) = map_data
|
||||||
|
.get("include-group")
|
||||||
|
.map(String::as_str)
|
||||||
|
.map(GroupName::from_str)
|
||||||
|
.transpose()
|
||||||
|
.map_err(serde::de::Error::custom)?
|
||||||
|
{
|
||||||
|
Ok(DependencyGroupSpecifier::IncludeGroup { include_group })
|
||||||
|
} else {
|
||||||
|
Ok(DependencyGroupSpecifier::Object(map_data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(Visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub use base_url::*;
|
pub use base_url::*;
|
||||||
pub use conflicts::*;
|
pub use conflicts::*;
|
||||||
|
pub use dependency_groups::*;
|
||||||
pub use direct_url::*;
|
pub use direct_url::*;
|
||||||
pub use lenient_requirement::*;
|
pub use lenient_requirement::*;
|
||||||
pub use marker_environment::*;
|
pub use marker_environment::*;
|
||||||
|
|
@ -12,6 +13,7 @@ pub use supported_environments::*;
|
||||||
|
|
||||||
mod base_url;
|
mod base_url;
|
||||||
mod conflicts;
|
mod conflicts;
|
||||||
|
mod dependency_groups;
|
||||||
mod direct_url;
|
mod direct_url;
|
||||||
mod lenient_requirement;
|
mod lenient_requirement;
|
||||||
mod marker_environment;
|
mod marker_environment;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ uv-normalize = { workspace = true }
|
||||||
uv-options-metadata = { workspace = true }
|
uv-options-metadata = { workspace = true }
|
||||||
uv-pep440 = { workspace = true }
|
uv-pep440 = { workspace = true }
|
||||||
uv-pep508 = { workspace = true }
|
uv-pep508 = { workspace = true }
|
||||||
uv-pypi-types = { workspace = true }
|
uv-pypi-types = { workspace = true, features = ["serde"] }
|
||||||
uv-static = { workspace = true }
|
uv-static = { workspace = true }
|
||||||
uv-warnings = { workspace = true }
|
uv-warnings = { workspace = true }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ use tracing::warn;
|
||||||
|
|
||||||
use uv_normalize::{GroupName, DEV_DEPENDENCIES};
|
use uv_normalize::{GroupName, DEV_DEPENDENCIES};
|
||||||
use uv_pep508::Pep508Error;
|
use uv_pep508::Pep508Error;
|
||||||
use uv_pypi_types::VerbatimParsedUrl;
|
use uv_pypi_types::{DependencyGroupSpecifier, VerbatimParsedUrl};
|
||||||
|
|
||||||
use crate::pyproject::DependencyGroupSpecifier;
|
|
||||||
|
|
||||||
/// PEP 735 dependency groups, with any `include-group` entries resolved.
|
/// PEP 735 dependency groups, with any `include-group` entries resolved.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
|
||||||
use uv_pep440::{Version, VersionSpecifiers};
|
use uv_pep440::{Version, VersionSpecifiers};
|
||||||
use uv_pep508::MarkerTree;
|
use uv_pep508::MarkerTree;
|
||||||
use uv_pypi_types::{
|
use uv_pypi_types::{
|
||||||
Conflicts, RequirementSource, SchemaConflicts, SupportedEnvironments, VerbatimParsedUrl,
|
Conflicts, DependencyGroups, RequirementSource, SchemaConflicts, SupportedEnvironments,
|
||||||
|
VerbatimParsedUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -143,73 +144,6 @@ impl AsRef<[u8]> for PyProjectToml {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A specifier item in a [PEP 735](https://peps.python.org/pep-0735/) Dependency Group.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
#[cfg_attr(test, derive(Serialize))]
|
|
||||||
pub enum DependencyGroupSpecifier {
|
|
||||||
/// A PEP 508-compatible requirement string.
|
|
||||||
Requirement(String),
|
|
||||||
/// A reference to another dependency group.
|
|
||||||
IncludeGroup {
|
|
||||||
/// The name of the group to include.
|
|
||||||
include_group: GroupName,
|
|
||||||
},
|
|
||||||
/// A Dependency Object Specifier.
|
|
||||||
Object(BTreeMap<String, String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for DependencyGroupSpecifier {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct Visitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
|
||||||
type Value = DependencyGroupSpecifier;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a string or a map with the `include-group` key")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
Ok(DependencyGroupSpecifier::Requirement(value.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
|
||||||
where
|
|
||||||
M: serde::de::MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut map_data = BTreeMap::new();
|
|
||||||
while let Some((key, value)) = map.next_entry()? {
|
|
||||||
map_data.insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if map_data.is_empty() {
|
|
||||||
return Err(serde::de::Error::custom("missing field `include-group`"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(include_group) = map_data
|
|
||||||
.get("include-group")
|
|
||||||
.map(String::as_str)
|
|
||||||
.map(GroupName::from_str)
|
|
||||||
.transpose()
|
|
||||||
.map_err(serde::de::Error::custom)?
|
|
||||||
{
|
|
||||||
Ok(DependencyGroupSpecifier::IncludeGroup { include_group })
|
|
||||||
} else {
|
|
||||||
Ok(DependencyGroupSpecifier::Object(map_data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(Visitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// PEP 621 project metadata (`project`).
|
/// PEP 621 project metadata (`project`).
|
||||||
///
|
///
|
||||||
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
|
/// See <https://packaging.python.org/en/latest/specifications/pyproject-toml>.
|
||||||
|
|
@ -797,84 +731,6 @@ impl Deref for SerdePattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(test, derive(Serialize))]
|
|
||||||
pub struct DependencyGroups(BTreeMap<GroupName, Vec<DependencyGroupSpecifier>>);
|
|
||||||
|
|
||||||
impl DependencyGroups {
|
|
||||||
/// Returns the names of the dependency groups.
|
|
||||||
pub fn keys(&self) -> impl Iterator<Item = &GroupName> {
|
|
||||||
self.0.keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the dependency group with the given name.
|
|
||||||
pub fn get(&self, group: &GroupName) -> Option<&Vec<DependencyGroupSpecifier>> {
|
|
||||||
self.0.get(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the dependency group is in the list.
|
|
||||||
pub fn contains_key(&self, group: &GroupName) -> bool {
|
|
||||||
self.0.contains_key(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over the dependency groups.
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&GroupName, &Vec<DependencyGroupSpecifier>)> {
|
|
||||||
self.0.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a DependencyGroups {
|
|
||||||
type Item = (&'a GroupName, &'a Vec<DependencyGroupSpecifier>);
|
|
||||||
type IntoIter = std::collections::btree_map::Iter<'a, GroupName, Vec<DependencyGroupSpecifier>>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
self.0.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure that all keys in the TOML table are unique.
|
|
||||||
impl<'de> serde::de::Deserialize<'de> for DependencyGroups {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
struct GroupVisitor;
|
|
||||||
|
|
||||||
impl<'de> serde::de::Visitor<'de> for GroupVisitor {
|
|
||||||
type Value = DependencyGroups;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
formatter.write_str("a table with unique dependency group names")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
|
||||||
where
|
|
||||||
M: serde::de::MapAccess<'de>,
|
|
||||||
{
|
|
||||||
let mut sources = BTreeMap::new();
|
|
||||||
while let Some((key, value)) =
|
|
||||||
access.next_entry::<GroupName, Vec<DependencyGroupSpecifier>>()?
|
|
||||||
{
|
|
||||||
match sources.entry(key) {
|
|
||||||
std::collections::btree_map::Entry::Occupied(entry) => {
|
|
||||||
return Err(serde::de::Error::custom(format!(
|
|
||||||
"duplicate dependency group: `{}`",
|
|
||||||
entry.key()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
std::collections::btree_map::Entry::Vacant(entry) => {
|
|
||||||
entry.insert(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(DependencyGroups(sources))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_map(GroupVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
#[serde(rename_all = "kebab-case", try_from = "SourcesWire")]
|
#[serde(rename_all = "kebab-case", try_from = "SourcesWire")]
|
||||||
|
|
|
||||||
|
|
@ -1500,8 +1500,9 @@ mod tests {
|
||||||
use insta::{assert_json_snapshot, assert_snapshot};
|
use insta::{assert_json_snapshot, assert_snapshot};
|
||||||
|
|
||||||
use uv_normalize::GroupName;
|
use uv_normalize::GroupName;
|
||||||
|
use uv_pypi_types::DependencyGroupSpecifier;
|
||||||
|
|
||||||
use crate::pyproject::{DependencyGroupSpecifier, PyProjectToml};
|
use crate::pyproject::PyProjectToml;
|
||||||
use crate::workspace::{DiscoveryOptions, ProjectWorkspace};
|
use crate::workspace::{DiscoveryOptions, ProjectWorkspace};
|
||||||
use crate::WorkspaceError;
|
use crate::WorkspaceError;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ use rustc_hash::FxHashSet;
|
||||||
use uv_configuration::{DependencyGroupsWithDefaults, ExtrasSpecification};
|
use uv_configuration::{DependencyGroupsWithDefaults, ExtrasSpecification};
|
||||||
use uv_distribution_types::Index;
|
use uv_distribution_types::Index;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_pypi_types::{LenientRequirement, VerbatimParsedUrl};
|
use uv_pypi_types::{DependencyGroupSpecifier, LenientRequirement, VerbatimParsedUrl};
|
||||||
use uv_resolver::{Installable, Lock, Package};
|
use uv_resolver::{Installable, Lock, Package};
|
||||||
use uv_scripts::Pep723Script;
|
use uv_scripts::Pep723Script;
|
||||||
use uv_workspace::pyproject::{DependencyGroupSpecifier, Source, Sources, ToolUvSources};
|
use uv_workspace::pyproject::{Source, Sources, ToolUvSources};
|
||||||
use uv_workspace::Workspace;
|
use uv_workspace::Workspace;
|
||||||
|
|
||||||
use crate::commands::project::ProjectError;
|
use crate::commands::project::ProjectError;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue