Add forks to lockfile, don't read them yet (#5480)

Add the forks to the lockfile, without using them yet, which we'll add
in the next PR.

Please review commit-by-commit

Part of
https://github.com/astral-sh/uv/issues/5180#issuecomment-2247696198
This commit is contained in:
konsti 2024-07-30 13:11:18 +02:00 committed by GitHub
parent 228a803fde
commit dedd913603
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 533 additions and 76 deletions

View file

@ -131,7 +131,7 @@ pub struct NoSolutionError {
impl NoSolutionError {
pub fn header(&self) -> String {
match &self.markers {
ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => {
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
"No solution found when resolving dependencies:".to_string()
}
ResolverMarkers::Fork(markers) => {

View file

@ -40,7 +40,8 @@ impl ForkUrls {
];
conflicting_url.sort();
return match fork_markers {
ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => {
ResolverMarkers::Universal { .. }
| ResolverMarkers::SpecificEnvironment(_) => {
Err(ResolveError::ConflictingUrlsUniversal(
package_name.clone(),
conflicting_url,

View file

@ -20,10 +20,10 @@ use cache_key::RepositoryUrl;
use distribution_filename::WheelFilename;
use distribution_types::{
BuiltDist, DirectUrlBuiltDist, DirectUrlSourceDist, DirectorySourceDist, Dist,
DistributionMetadata, FileLocation, GitSourceDist, HashComparison, IndexUrl, PathBuiltDist,
PathSourceDist, PrioritizedDist, RegistryBuiltDist, RegistryBuiltWheel, RegistrySourceDist,
RemoteSource, Resolution, ResolvedDist, SourceDistCompatibility, ToUrlError, UrlString,
VersionId, WheelCompatibility,
DistributionMetadata, FileLocation, GitSourceDist, HashComparison, IndexUrl, Name,
PathBuiltDist, PathSourceDist, PrioritizedDist, RegistryBuiltDist, RegistryBuiltWheel,
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, SourceDistCompatibility,
ToUrlError, UrlString, VersionId, WheelCompatibility,
};
use pep440_rs::{Version, VersionSpecifier};
use pep508_rs::{
@ -53,7 +53,10 @@ const VERSION: u32 = 1;
#[serde(try_from = "LockWire")]
pub struct Lock {
version: u32,
distributions: Vec<Distribution>,
/// If this lockfile was built from a forking resolution with non-identical forks, store the
/// forks in the lockfile so we can recreate them in subsequent resolutions.
#[serde(rename = "environment-markers")]
fork_markers: Option<BTreeSet<MarkerTree>>,
/// The range of supported Python versions.
requires_python: Option<RequiresPython>,
/// The [`ResolutionMode`] used to generate this lock.
@ -62,6 +65,8 @@ pub struct Lock {
prerelease_mode: PreReleaseMode,
/// The [`ExcludeNewer`] used to generate this lock.
exclude_newer: Option<ExcludeNewer>,
/// The actual locked version and their metadata.
distributions: Vec<Distribution>,
/// A map from distribution ID to index in `distributions`.
///
/// This can be used to quickly lookup the full distribution for any ID
@ -87,7 +92,12 @@ impl Lock {
continue;
};
if dist.is_base() {
let mut locked_dist = Distribution::from_annotated_dist(dist)?;
let fork_markers = graph
.fork_markers(dist.name(), &dist.version, dist.dist.version_or_url().url())
.cloned();
let mut locked_dist = Distribution::from_annotated_dist(dist, fork_markers)?;
// Add all dependencies
for edge in graph.petgraph.edges(node_index) {
let ResolutionGraphNode::Dist(dependency_dist) = &graph.petgraph[edge.target()]
else {
@ -159,6 +169,7 @@ impl Lock {
options.resolution_mode,
options.prerelease_mode,
options.exclude_newer,
graph.fork_markers.clone(),
)?;
Ok(lock)
}
@ -171,6 +182,7 @@ impl Lock {
resolution_mode: ResolutionMode,
prerelease_mode: PreReleaseMode,
exclude_newer: Option<ExcludeNewer>,
fork_markers: Option<BTreeSet<MarkerTree>>,
) -> Result<Self, LockError> {
// Put all dependencies for each distribution in a canonical order and
// check for duplicates.
@ -324,11 +336,12 @@ impl Lock {
}
Ok(Self {
version,
distributions,
fork_markers,
requires_python,
resolution_mode,
prerelease_mode,
exclude_newer,
distributions,
by_id,
})
}
@ -363,6 +376,12 @@ impl Lock {
self.exclude_newer
}
/// If this lockfile was built from a forking resolution with non-identical forks, return the
/// markers of those forks, otherwise `None`.
pub fn fork_markers(&self) -> &Option<BTreeSet<MarkerTree>> {
&self.fork_markers
}
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
pub fn to_resolution(
&self,
@ -451,6 +470,11 @@ impl Lock {
if let Some(ref requires_python) = self.requires_python {
doc.insert("requires-python", value(requires_python.to_string()));
}
if let Some(ref fork_markers) = self.fork_markers {
let fork_markers =
each_element_on_its_line_array(fork_markers.iter().map(ToString::to_string));
doc.insert("environment-markers", value(fork_markers));
}
// Write the settings that were used to generate the resolution.
// This enables us to invalidate the lockfile if the user changes
@ -571,31 +595,36 @@ impl Lock {
#[serde(rename_all = "kebab-case")]
struct LockWire {
version: u32,
#[serde(rename = "distribution", default)]
distributions: Vec<DistributionWire>,
#[serde(default)]
requires_python: Option<RequiresPython>,
/// If this lockfile was built from a forking resolution with non-identical forks, store the
/// forks in the lockfile so we can recreate them in subsequent resolutions.
#[serde(rename = "environment-markers")]
fork_markers: Option<BTreeSet<MarkerTree>>,
#[serde(default)]
resolution_mode: ResolutionMode,
#[serde(default)]
prerelease_mode: PreReleaseMode,
#[serde(default)]
exclude_newer: Option<ExcludeNewer>,
#[serde(rename = "distribution", default)]
distributions: Vec<DistributionWire>,
}
impl From<Lock> for LockWire {
fn from(lock: Lock) -> LockWire {
LockWire {
version: lock.version,
requires_python: lock.requires_python,
fork_markers: lock.fork_markers,
resolution_mode: lock.resolution_mode,
prerelease_mode: lock.prerelease_mode,
exclude_newer: lock.exclude_newer,
distributions: lock
.distributions
.into_iter()
.map(DistributionWire::from)
.collect(),
requires_python: lock.requires_python,
resolution_mode: lock.resolution_mode,
prerelease_mode: lock.prerelease_mode,
exclude_newer: lock.exclude_newer,
}
}
}
@ -633,6 +662,7 @@ impl TryFrom<LockWire> for Lock {
wire.resolution_mode,
wire.prerelease_mode,
wire.exclude_newer,
wire.fork_markers,
)
}
}
@ -642,13 +672,22 @@ pub struct Distribution {
pub(crate) id: DistributionId,
sdist: Option<SourceDist>,
wheels: Vec<Wheel>,
/// If there are multiple distributions for the same package name, we add the markers of the
/// fork(s) that contained this distribution, so we can set the correct preferences in the next
/// resolution.
///
/// Named `environment-markers` in `uv.lock`.
fork_markers: Option<BTreeSet<MarkerTree>>,
dependencies: Vec<Dependency>,
optional_dependencies: BTreeMap<ExtraName, Vec<Dependency>>,
dev_dependencies: BTreeMap<GroupName, Vec<Dependency>>,
}
impl Distribution {
fn from_annotated_dist(annotated_dist: &AnnotatedDist) -> Result<Self, LockError> {
fn from_annotated_dist(
annotated_dist: &AnnotatedDist,
fork_markers: Option<BTreeSet<MarkerTree>>,
) -> Result<Self, LockError> {
let id = DistributionId::from_annotated_dist(annotated_dist);
let sdist = SourceDist::from_annotated_dist(&id, annotated_dist)?;
let wheels = Wheel::from_annotated_dist(annotated_dist)?;
@ -656,6 +695,7 @@ impl Distribution {
id,
sdist,
wheels,
fork_markers,
dependencies: vec![],
optional_dependencies: BTreeMap::default(),
dev_dependencies: BTreeMap::default(),
@ -1005,6 +1045,12 @@ impl Distribution {
self.id.to_toml(None, &mut table);
if let Some(ref fork_markers) = self.fork_markers {
let wheels =
each_element_on_its_line_array(fork_markers.iter().map(ToString::to_string));
table.insert("environment-markers", value(wheels));
}
if !self.dependencies.is_empty() {
let deps = each_element_on_its_line_array(
self.dependencies
@ -1145,6 +1191,8 @@ struct DistributionWire {
sdist: Option<SourceDist>,
#[serde(default)]
wheels: Vec<Wheel>,
#[serde(default, rename = "environment-markers")]
fork_markers: BTreeSet<MarkerTree>,
#[serde(default)]
dependencies: Vec<DependencyWire>,
#[serde(default)]
@ -1167,6 +1215,7 @@ impl DistributionWire {
id: self.id,
sdist: self.sdist,
wheels: self.wheels,
fork_markers: (!self.fork_markers.is_empty()).then_some(self.fork_markers),
dependencies: unwire_deps(self.dependencies)?,
optional_dependencies: self
.optional_dependencies
@ -1191,6 +1240,7 @@ impl From<Distribution> for DistributionWire {
id: dist.id,
sdist: dist.sdist,
wheels: dist.wheels,
fork_markers: dist.fork_markers.unwrap_or_default(),
dependencies: wire_deps(dist.dependencies),
optional_dependencies: dist
.optional_dependencies
@ -2546,12 +2596,13 @@ impl std::fmt::Display for HashParseError {
/// { name = "sniffio" },
/// ]
/// ```
fn each_element_on_its_line_array(elements: impl Iterator<Item = InlineTable>) -> Array {
fn each_element_on_its_line_array(elements: impl Iterator<Item = impl Into<Value>>) -> Array {
let mut array = elements
.map(|mut inline_table| {
.map(|item| {
let mut value = item.into();
// Each dependency is on its own line and indented.
inline_table.decor_mut().set_prefix("\n ");
inline_table
value.decor_mut().set_prefix("\n ");
value
})
.collect::<Array>();
// With a trailing comma, inserting another entry doesn't change the preceding line,

View file

@ -13,7 +13,9 @@ use uv_normalize::PackageName;
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode};
use crate::{marker, ResolutionGraph, ResolverMarkers};
static UNIVERSAL_MARKERS: ResolverMarkers = ResolverMarkers::Universal;
static UNIVERSAL_MARKERS: ResolverMarkers = ResolverMarkers::Universal {
fork_preferences: None,
};
/// A [`std::fmt::Display`] implementation for the resolution graph.
#[derive(Debug)]

View file

@ -1,3 +1,5 @@
use std::collections::BTreeSet;
use indexmap::IndexSet;
use petgraph::{
graph::{Graph, NodeIndex},
@ -10,7 +12,7 @@ use distribution_types::{
VersionOrUrlRef,
};
use pep440_rs::{Version, VersionSpecifier};
use pep508_rs::{MarkerEnvironment, MarkerTree};
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl};
use pypi_types::{HashDigest, ParsedUrlError, Requirement, VerbatimParsedUrl, Yanked};
use uv_configuration::{Constraints, Overrides};
use uv_distribution::Metadata;
@ -25,9 +27,12 @@ use crate::resolution::AnnotatedDist;
use crate::resolver::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
use crate::{
InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError,
VersionsResponse,
ResolverMarkers, VersionsResponse,
};
pub(crate) type MarkersForDistribution =
FxHashMap<(Version, Option<VerbatimUrl>), BTreeSet<MarkerTree>>;
/// A complete resolution graph in which every node represents a pinned package and every edge
/// represents a dependency between two pinned packages.
#[derive(Debug)]
@ -36,6 +41,9 @@ pub struct ResolutionGraph {
pub(crate) petgraph: Graph<ResolutionGraphNode, Option<MarkerTree>, Directed>,
/// The range of supported Python versions.
pub(crate) requires_python: Option<RequiresPython>,
/// If the resolution had non-identical forks, store the forks in the lockfile so we can
/// recreate them in subsequent resolutions.
pub(crate) fork_markers: Option<BTreeSet<MarkerTree>>,
/// Any diagnostics that were encountered while building the graph.
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
/// The requirements that were used to build the graph.
@ -46,6 +54,9 @@ pub struct ResolutionGraph {
pub(crate) overrides: Overrides,
/// The options that were used to build the graph.
pub(crate) options: Options,
/// If there are multiple options for a package, track which fork they belong to so we
/// can write that to the lockfile and later get the correct preference per fork back.
pub(crate) package_markers: FxHashMap<PackageName, MarkersForDistribution>,
}
#[derive(Debug)]
@ -86,10 +97,28 @@ impl ResolutionGraph {
// Add the root node.
let root_index = petgraph.add_node(ResolutionGraphNode::Root);
let mut package_markers: FxHashMap<PackageName, MarkersForDistribution> =
FxHashMap::default();
let mut seen = FxHashSet::default();
for resolution in resolutions {
// Add every package to the graph.
for (package, version) in &resolution.nodes {
if package.is_base() {
// For packages with diverging versions, store which version comes from which
// fork.
if let Some(markers) = resolution.markers.fork_markers() {
let entry = package_markers
.entry(package.name.clone())
.or_default()
.entry((version.clone(), package.url.clone().map(|url| url.verbatim)))
.or_default();
if !entry.contains(markers) {
entry.insert(markers.clone());
}
}
}
if !seen.insert((package, version)) {
// Insert each node only once.
continue;
@ -138,14 +167,38 @@ impl ResolutionGraph {
}
}
let fork_markers = if let [resolution] = resolutions {
match resolution.markers {
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => None,
ResolverMarkers::Fork(_) => {
panic!("A single fork must be universal");
}
}
} else {
Some(
resolutions
.iter()
.map(|resolution| {
resolution
.markers
.fork_markers()
.expect("A non-forking resolution exists in forking mode")
.clone()
})
.collect(),
)
};
Ok(Self {
petgraph,
requires_python,
package_markers,
diagnostics,
requirements: requirements.to_vec(),
constraints: constraints.clone(),
overrides: overrides.clone(),
options,
fork_markers,
})
}
@ -549,6 +602,22 @@ impl ResolutionGraph {
}
Ok(MarkerTree::And(conjuncts))
}
/// If there are multiple distributions for the same package name, return the markers of the
/// fork(s) that contained this distribution, otherwise return `None`.
pub fn fork_markers(
&self,
package_name: &PackageName,
version: &Version,
url: Option<&VerbatimUrl>,
) -> Option<&BTreeSet<MarkerTree>> {
let package_markers = &self.package_markers.get(package_name)?;
if package_markers.len() == 1 {
None
} else {
Some(&package_markers[&(version.clone(), url.cloned())])
}
}
}
impl From<ResolutionGraph> for distribution_types::Resolution {

View file

@ -78,7 +78,7 @@ impl<T> ForkMap<T> {
.collect(),
// If we haven't forked yet, all values are potentially compatible.
ResolverMarkers::Universal => values.iter().map(|entry| &entry.value).collect(),
ResolverMarkers::Universal { .. } => values.iter().map(|entry| &entry.value).collect(),
}
}
}

View file

@ -403,7 +403,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
.clone());
existing_resolution.markers = normalize(new_markers, None)
.map(ResolverMarkers::Fork)
.unwrap_or(ResolverMarkers::Universal);
.unwrap_or(ResolverMarkers::universal(None));
continue 'FORK;
}
@ -1155,7 +1155,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Dependencies::Available(deps) => ForkedDependencies::Unforked(deps),
Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
}),
ResolverMarkers::Universal | ResolverMarkers::Fork(_) => Ok(result?.fork()),
ResolverMarkers::Universal { .. } | ResolverMarkers::Fork(_) => Ok(result?.fork()),
}
}
@ -1770,7 +1770,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self.exclusions,
// We don't have access to the fork state when prefetching, so assume that
// pre-release versions are allowed.
&ResolverMarkers::Universal,
&ResolverMarkers::universal(None),
) else {
return Ok(None);
};
@ -2479,6 +2479,12 @@ impl Resolution {
}
}
impl ResolutionPackage {
pub(crate) fn is_base(&self) -> bool {
self.extra.is_none() && self.dev.is_none()
}
}
/// Fetch the metadata for an item
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]

View file

@ -1,6 +1,8 @@
use pep508_rs::{MarkerEnvironment, MarkerTree};
use std::collections::BTreeSet;
use std::fmt::{Display, Formatter};
use pep508_rs::{MarkerEnvironment, MarkerTree};
#[derive(Debug, Clone)]
/// Whether we're solving for a specific environment, universally or for a specific fork.
pub enum ResolverMarkers {
@ -8,16 +10,29 @@ pub enum ResolverMarkers {
SpecificEnvironment(MarkerEnvironment),
/// We're doing a universal resolution for all environments (a python version
/// constraint is expressed separately).
Universal,
Universal {
/// Start the resolution with these forks.
fork_preferences: Option<BTreeSet<MarkerTree>>,
},
/// We're in a fork of the universal resolution solving only for specific markers.
Fork(MarkerTree),
}
impl ResolverMarkers {
/// Set the resolver to perform a resolution for a specific environment.
pub fn specific_environment(markers: MarkerEnvironment) -> Self {
Self::SpecificEnvironment(markers)
}
/// Set the resolver to perform a universal resolution.
pub fn universal(fork_preferences: Option<BTreeSet<MarkerTree>>) -> Self {
Self::Universal { fork_preferences }
}
/// Add the markers of an initial or subsequent fork to the current markers.
pub(crate) fn and(self, other: MarkerTree) -> MarkerTree {
match self {
ResolverMarkers::Universal => other,
ResolverMarkers::Universal { .. } => other,
ResolverMarkers::Fork(mut current) => {
current.and(other);
current
@ -31,14 +46,15 @@ impl ResolverMarkers {
/// If solving for a specific environment, return this environment.
pub fn marker_environment(&self) -> Option<&MarkerEnvironment> {
match self {
ResolverMarkers::Universal | ResolverMarkers::Fork(_) => None,
ResolverMarkers::Universal { .. } | ResolverMarkers::Fork(_) => None,
ResolverMarkers::SpecificEnvironment(env) => Some(env),
}
}
/// If solving a fork, return that fork's markers.
pub fn fork_markers(&self) -> Option<&MarkerTree> {
match self {
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal => None,
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => None,
ResolverMarkers::Fork(markers) => Some(markers),
}
}
@ -47,7 +63,7 @@ impl ResolverMarkers {
impl Display for ResolverMarkers {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResolverMarkers::Universal => f.write_str("universal"),
ResolverMarkers::Universal { .. } => f.write_str("universal"),
ResolverMarkers::SpecificEnvironment(_) => f.write_str("specific environment"),
ResolverMarkers::Fork(markers) => {
write!(f, "({markers})")

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -56,15 +61,12 @@ Ok(
},
},
],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -63,15 +68,12 @@ Ok(
},
},
],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -49,15 +54,12 @@ Ok(
},
},
],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -51,6 +56,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
@ -100,6 +106,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [
Dependency {
distribution_id: DistributionId {
@ -133,10 +140,6 @@ Ok(
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -51,6 +56,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
@ -100,6 +106,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [
Dependency {
distribution_id: DistributionId {
@ -133,10 +140,6 @@ Ok(
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -51,6 +56,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
@ -100,6 +106,7 @@ Ok(
},
),
wheels: [],
fork_markers: None,
dependencies: [
Dependency {
distribution_id: DistributionId {
@ -133,10 +140,6 @@ Ok(
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -37,15 +42,12 @@ Ok(
},
sdist: None,
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -35,15 +40,12 @@ Ok(
},
sdist: None,
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -18,15 +23,12 @@ Ok(
},
sdist: None,
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(

View file

@ -5,6 +5,11 @@ expression: result
Ok(
Lock {
version: 1,
fork_markers: None,
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
distributions: [
Distribution {
id: DistributionId {
@ -18,15 +23,12 @@ Ok(
},
sdist: None,
wheels: [],
fork_markers: None,
dependencies: [],
optional_dependencies: {},
dev_dependencies: {},
},
],
requires_python: None,
resolution_mode: Highest,
prerelease_mode: IfNecessaryOrExplicit,
exclude_newer: None,
by_id: {
DistributionId {
name: PackageName(