mirror of https://github.com/astral-sh/uv
Merge 54bdc410e2 into 4f6f56b070
This commit is contained in:
commit
b71e9d8ef0
|
|
@ -7,6 +7,8 @@ use std::{
|
|||
|
||||
use jiff::{Span, Timestamp, ToSpan, Unit, tz::TimeZone};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use serde::de::value::MapAccessDeserializer;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
|
|
@ -95,9 +97,9 @@ impl std::fmt::Display for ExcludeNewerChange {
|
|||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ExcludeNewerPackageChange {
|
||||
PackageAdded(PackageName, ExcludeNewerValue),
|
||||
PackageAdded(PackageName, PackageExcludeNewer),
|
||||
PackageRemoved(PackageName),
|
||||
PackageChanged(PackageName, ExcludeNewerValueChange),
|
||||
PackageChanged(PackageName, Box<PackageExcludeNewerChange>),
|
||||
}
|
||||
|
||||
impl ExcludeNewerPackageChange {
|
||||
|
|
@ -112,18 +114,23 @@ impl ExcludeNewerPackageChange {
|
|||
impl std::fmt::Display for ExcludeNewerPackageChange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::PackageAdded(name, value) => {
|
||||
Self::PackageAdded(name, PackageExcludeNewer::Enabled(value)) => {
|
||||
write!(
|
||||
f,
|
||||
"addition of exclude newer `{value}` for package `{name}`"
|
||||
"addition of exclude newer `{}` for package `{name}`",
|
||||
value.as_ref()
|
||||
)
|
||||
}
|
||||
Self::PackageAdded(name, PackageExcludeNewer::Disabled) => {
|
||||
write!(
|
||||
f,
|
||||
"addition of exclude newer exclusion for package `{name}`"
|
||||
)
|
||||
}
|
||||
Self::PackageRemoved(name) => {
|
||||
write!(f, "removal of exclude newer for package `{name}`")
|
||||
}
|
||||
Self::PackageChanged(name, change) => {
|
||||
write!(f, "{change} for package `{name}`")
|
||||
}
|
||||
Self::PackageChanged(name, change) => write!(f, "{change} for package `{name}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,7 +262,7 @@ impl ExcludeNewerValue {
|
|||
self.span.as_ref()
|
||||
}
|
||||
|
||||
/// Create a new [`ExcludeNewerTimestamp`].
|
||||
/// Create a new [`ExcludeNewerValue`].
|
||||
pub fn new(timestamp: Timestamp, span: Option<ExcludeNewerSpan>) -> Self {
|
||||
Self { timestamp, span }
|
||||
}
|
||||
|
|
@ -326,7 +333,7 @@ fn format_exclude_newer_error(
|
|||
impl FromStr for ExcludeNewerValue {
|
||||
type Err = String;
|
||||
|
||||
/// Parse an [`ExcludeNewerTimestamp`] from a string.
|
||||
/// Parse an [`ExcludeNewerValue`] from a string.
|
||||
///
|
||||
/// Accepts RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`), local dates in the same format
|
||||
/// (e.g., `2006-12-02`), "friendly" durations (e.g., `1 week`, `30 days`), and ISO 8601
|
||||
|
|
@ -425,47 +432,170 @@ impl std::fmt::Display for ExcludeNewerValue {
|
|||
}
|
||||
}
|
||||
|
||||
/// Per-package exclude-newer setting.
|
||||
///
|
||||
/// This enum represents whether exclude-newer should be disabled for a package,
|
||||
/// or if a specific cutoff (absolute or relative) should be used.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PackageExcludeNewer {
|
||||
/// Disable exclude-newer for this package (allow all versions regardless of upload date).
|
||||
Disabled,
|
||||
/// Enable exclude-newer with this cutoff for this package.
|
||||
Enabled(Box<ExcludeNewerValue>),
|
||||
}
|
||||
|
||||
/// A package-specific exclude-newer entry.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ExcludeNewerPackageEntry {
|
||||
pub package: PackageName,
|
||||
pub timestamp: ExcludeNewerValue,
|
||||
pub setting: PackageExcludeNewer,
|
||||
}
|
||||
|
||||
impl FromStr for ExcludeNewerPackageEntry {
|
||||
type Err = String;
|
||||
|
||||
/// Parses a [`ExcludeNewerPackageEntry`] from a string in the format `PACKAGE=DATE`.
|
||||
/// Parses a [`ExcludeNewerPackageEntry`] from a string in the format `PACKAGE=DATE` or `PACKAGE=false`.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((package, date)) = s.split_once('=') else {
|
||||
let Some((package, value)) = s.split_once('=') else {
|
||||
return Err(format!(
|
||||
"Invalid `exclude-newer-package` value `{s}`: expected format `PACKAGE=DATE`"
|
||||
"Invalid `exclude-newer-package` value `{s}`: expected format `PACKAGE=DATE` or `PACKAGE=false`"
|
||||
));
|
||||
};
|
||||
|
||||
let package = PackageName::from_str(package).map_err(|err| {
|
||||
format!("Invalid `exclude-newer-package` package name `{package}`: {err}")
|
||||
})?;
|
||||
let timestamp = ExcludeNewerValue::from_str(date)
|
||||
.map_err(|err| format!("Invalid `exclude-newer-package` timestamp `{date}`: {err}"))?;
|
||||
|
||||
Ok(Self { package, timestamp })
|
||||
let setting = if value == "false" {
|
||||
PackageExcludeNewer::Disabled
|
||||
} else {
|
||||
PackageExcludeNewer::Enabled(Box::new(ExcludeNewerValue::from_str(value).map_err(
|
||||
|err| format!("Invalid `exclude-newer-package` value `{value}`: {err}"),
|
||||
)?))
|
||||
};
|
||||
|
||||
Ok(Self { package, setting })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(PackageName, PackageExcludeNewer)> for ExcludeNewerPackageEntry {
|
||||
fn from((package, setting): (PackageName, PackageExcludeNewer)) -> Self {
|
||||
Self { package, setting }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(PackageName, ExcludeNewerValue)> for ExcludeNewerPackageEntry {
|
||||
fn from((package, timestamp): (PackageName, ExcludeNewerValue)) -> Self {
|
||||
Self { package, timestamp }
|
||||
Self {
|
||||
package,
|
||||
setting: PackageExcludeNewer::Enabled(Box::new(timestamp)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for PackageExcludeNewer {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = PackageExcludeNewer;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str(
|
||||
"a date/timestamp/duration string, false to disable exclude-newer, or a table \
|
||||
with timestamp/span",
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
ExcludeNewerValue::from_str(v)
|
||||
.map(|ts| PackageExcludeNewer::Enabled(Box::new(ts)))
|
||||
.map_err(|e| E::custom(format!("failed to parse exclude-newer value: {e}")))
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
if v {
|
||||
Err(E::custom(
|
||||
"expected false to disable exclude-newer, got true",
|
||||
))
|
||||
} else {
|
||||
Ok(PackageExcludeNewer::Disabled)
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: serde::de::MapAccess<'de>,
|
||||
{
|
||||
Ok(PackageExcludeNewer::Enabled(Box::new(
|
||||
ExcludeNewerValue::deserialize(MapAccessDeserializer::new(map))?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for PackageExcludeNewer {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Enabled(timestamp) => timestamp.to_string().serialize(serializer),
|
||||
Self::Disabled => serializer.serialize_bool(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PackageExcludeNewerChange {
|
||||
Disabled { was: ExcludeNewerValue },
|
||||
Enabled { now: ExcludeNewerValue },
|
||||
TimestampChanged(ExcludeNewerValueChange),
|
||||
}
|
||||
|
||||
impl PackageExcludeNewerChange {
|
||||
pub fn is_relative_timestamp_change(&self) -> bool {
|
||||
match self {
|
||||
Self::Disabled { .. } | Self::Enabled { .. } => false,
|
||||
Self::TimestampChanged(change) => change.is_relative_timestamp_change(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for PackageExcludeNewerChange {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Disabled { was } => {
|
||||
write!(f, "add exclude newer exclusion (was `{was}`)")
|
||||
}
|
||||
Self::Enabled { now } => {
|
||||
write!(f, "remove exclude newer exclusion (now `{now}`)")
|
||||
}
|
||||
Self::TimestampChanged(change) => write!(f, "{change}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ExcludeNewerPackage(FxHashMap<PackageName, ExcludeNewerValue>);
|
||||
pub struct ExcludeNewerPackage(FxHashMap<PackageName, PackageExcludeNewer>);
|
||||
|
||||
impl Deref for ExcludeNewerPackage {
|
||||
type Target = FxHashMap<PackageName, ExcludeNewerValue>;
|
||||
type Target = FxHashMap<PackageName, PackageExcludeNewer>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
|
|
@ -482,15 +612,15 @@ impl FromIterator<ExcludeNewerPackageEntry> for ExcludeNewerPackage {
|
|||
fn from_iter<T: IntoIterator<Item = ExcludeNewerPackageEntry>>(iter: T) -> Self {
|
||||
Self(
|
||||
iter.into_iter()
|
||||
.map(|entry| (entry.package, entry.timestamp))
|
||||
.map(|entry| (entry.package, entry.setting))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ExcludeNewerPackage {
|
||||
type Item = (PackageName, ExcludeNewerValue);
|
||||
type IntoIter = std::collections::hash_map::IntoIter<PackageName, ExcludeNewerValue>;
|
||||
type Item = (PackageName, PackageExcludeNewer);
|
||||
type IntoIter = std::collections::hash_map::IntoIter<PackageName, PackageExcludeNewer>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
|
|
@ -498,8 +628,8 @@ impl IntoIterator for ExcludeNewerPackage {
|
|||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ExcludeNewerPackage {
|
||||
type Item = (&'a PackageName, &'a ExcludeNewerValue);
|
||||
type IntoIter = std::collections::hash_map::Iter<'a, PackageName, ExcludeNewerValue>;
|
||||
type Item = (&'a PackageName, &'a PackageExcludeNewer);
|
||||
type IntoIter = std::collections::hash_map::Iter<'a, PackageName, PackageExcludeNewer>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
|
|
@ -508,20 +638,55 @@ impl<'a> IntoIterator for &'a ExcludeNewerPackage {
|
|||
|
||||
impl ExcludeNewerPackage {
|
||||
/// Convert to the inner `HashMap`.
|
||||
pub fn into_inner(self) -> FxHashMap<PackageName, ExcludeNewerValue> {
|
||||
pub fn into_inner(self) -> FxHashMap<PackageName, PackageExcludeNewer> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns true if this map is empty (no package-specific settings).
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn compare(&self, other: &Self) -> Option<ExcludeNewerPackageChange> {
|
||||
for (package, timestamp) in self {
|
||||
let Some(other_timestamp) = other.get(package) else {
|
||||
return Some(ExcludeNewerPackageChange::PackageRemoved(package.clone()));
|
||||
};
|
||||
if let Some(change) = timestamp.compare(other_timestamp) {
|
||||
return Some(ExcludeNewerPackageChange::PackageChanged(
|
||||
package.clone(),
|
||||
change,
|
||||
));
|
||||
for (package, setting) in self {
|
||||
match (setting, other.get(package)) {
|
||||
(
|
||||
PackageExcludeNewer::Enabled(self_timestamp),
|
||||
Some(PackageExcludeNewer::Enabled(other_timestamp)),
|
||||
) => {
|
||||
if let Some(change) = self_timestamp.compare(other_timestamp) {
|
||||
return Some(ExcludeNewerPackageChange::PackageChanged(
|
||||
package.clone(),
|
||||
Box::new(PackageExcludeNewerChange::TimestampChanged(change)),
|
||||
));
|
||||
}
|
||||
}
|
||||
(
|
||||
PackageExcludeNewer::Enabled(self_timestamp),
|
||||
Some(PackageExcludeNewer::Disabled),
|
||||
) => {
|
||||
return Some(ExcludeNewerPackageChange::PackageChanged(
|
||||
package.clone(),
|
||||
Box::new(PackageExcludeNewerChange::Disabled {
|
||||
was: self_timestamp.as_ref().clone(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
(
|
||||
PackageExcludeNewer::Disabled,
|
||||
Some(PackageExcludeNewer::Enabled(other_timestamp)),
|
||||
) => {
|
||||
return Some(ExcludeNewerPackageChange::PackageChanged(
|
||||
package.clone(),
|
||||
Box::new(PackageExcludeNewerChange::Enabled {
|
||||
now: other_timestamp.as_ref().clone(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
(PackageExcludeNewer::Disabled, Some(PackageExcludeNewer::Disabled)) => {}
|
||||
(_, None) => {
|
||||
return Some(ExcludeNewerPackageChange::PackageRemoved(package.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -574,12 +739,16 @@ impl ExcludeNewer {
|
|||
Self { global, package }
|
||||
}
|
||||
|
||||
/// Returns the timestamp for a specific package, falling back to the global timestamp if set.
|
||||
/// Returns the exclude-newer value for a specific package, returning `Some(value)` if the
|
||||
/// package has a package-specific setting or falls back to the global value if set, or `None`
|
||||
/// if exclude-newer is explicitly disabled for the package (set to `false`) or if no
|
||||
/// exclude-newer is configured.
|
||||
pub fn exclude_newer_package(&self, package_name: &PackageName) -> Option<ExcludeNewerValue> {
|
||||
self.package
|
||||
.get(package_name)
|
||||
.cloned()
|
||||
.or(self.global.clone())
|
||||
match self.package.get(package_name) {
|
||||
Some(PackageExcludeNewer::Enabled(timestamp)) => Some(timestamp.as_ref().clone()),
|
||||
Some(PackageExcludeNewer::Disabled) => None,
|
||||
None => self.global.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this has any configuration (global or per-package).
|
||||
|
|
@ -615,11 +784,18 @@ impl std::fmt::Display for ExcludeNewer {
|
|||
}
|
||||
}
|
||||
let mut first = true;
|
||||
for (name, timestamp) in &self.package {
|
||||
for (name, setting) in &self.package {
|
||||
if !first {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{name}: {timestamp}")?;
|
||||
match setting {
|
||||
PackageExcludeNewer::Enabled(timestamp) => {
|
||||
write!(f, "{name}: {}", timestamp.as_ref())?;
|
||||
}
|
||||
PackageExcludeNewer::Disabled => {
|
||||
write!(f, "{name}: disabled")?;
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ pub use dependency_mode::DependencyMode;
|
|||
pub use error::{ErrorTree, NoSolutionError, NoSolutionHeader, ResolveError, SentinelRange};
|
||||
pub use exclude_newer::{
|
||||
ExcludeNewer, ExcludeNewerChange, ExcludeNewerPackage, ExcludeNewerPackageChange,
|
||||
ExcludeNewerPackageEntry, ExcludeNewerValue, ExcludeNewerValueChange,
|
||||
ExcludeNewerPackageEntry, ExcludeNewerValue, ExcludeNewerValueChange, PackageExcludeNewer,
|
||||
PackageExcludeNewerChange,
|
||||
};
|
||||
pub use exclusions::Exclusions;
|
||||
pub use flat_index::{FlatDistributions, FlatIndex};
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
|
|||
use crate::universal_marker::{ConflictMarker, UniversalMarker};
|
||||
use crate::{
|
||||
ExcludeNewer, ExcludeNewerPackage, ExcludeNewerValue, InMemoryIndex, MetadataResponse,
|
||||
PrereleaseMode, ResolutionMode, ResolverOutput,
|
||||
PackageExcludeNewer, PrereleaseMode, ResolutionMode, ResolverOutput,
|
||||
};
|
||||
|
||||
mod export;
|
||||
|
|
@ -1071,20 +1071,29 @@ impl Lock {
|
|||
// Serialize package-specific exclusions as a separate field
|
||||
if !exclude_newer.package.is_empty() {
|
||||
let mut package_table = toml_edit::Table::new();
|
||||
for (name, exclude_newer_value) in &exclude_newer.package {
|
||||
if let Some(span) = exclude_newer_value.span() {
|
||||
// Serialize as inline table with timestamp and span
|
||||
let mut inline = toml_edit::InlineTable::new();
|
||||
inline.insert(
|
||||
"timestamp",
|
||||
exclude_newer_value.timestamp().to_string().into(),
|
||||
);
|
||||
inline.insert("span", span.to_string().into());
|
||||
package_table.insert(name.as_ref(), Item::Value(inline.into()));
|
||||
} else {
|
||||
// Serialize as simple string
|
||||
package_table
|
||||
.insert(name.as_ref(), value(exclude_newer_value.to_string()));
|
||||
for (name, setting) in &exclude_newer.package {
|
||||
match setting {
|
||||
PackageExcludeNewer::Enabled(exclude_newer_value) => {
|
||||
if let Some(span) = exclude_newer_value.span() {
|
||||
// Serialize as inline table with timestamp and span
|
||||
let mut inline = toml_edit::InlineTable::new();
|
||||
inline.insert(
|
||||
"timestamp",
|
||||
exclude_newer_value.timestamp().to_string().into(),
|
||||
);
|
||||
inline.insert("span", span.to_string().into());
|
||||
package_table.insert(name.as_ref(), Item::Value(inline.into()));
|
||||
} else {
|
||||
// Serialize as simple string
|
||||
package_table.insert(
|
||||
name.as_ref(),
|
||||
value(exclude_newer_value.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
PackageExcludeNewer::Disabled => {
|
||||
package_table.insert(name.as_ref(), value(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
options_table.insert("exclude-newer-package", Item::Table(package_table));
|
||||
|
|
|
|||
|
|
@ -228,9 +228,11 @@ impl Combine for ExcludeNewer {
|
|||
if self.package.is_empty() {
|
||||
self.package = other.package;
|
||||
} else {
|
||||
// Merge package-specific timestamps, with self taking precedence
|
||||
for (pkg, timestamp) in &other.package {
|
||||
self.package.entry(pkg.clone()).or_insert(timestamp.clone());
|
||||
// Merge package-specific settings, with self taking precedence
|
||||
for (pkg, setting) in &other.package {
|
||||
self.package
|
||||
.entry(pkg.clone())
|
||||
.or_insert_with(|| setting.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31179,6 +31179,94 @@ fn test_tilde_equals_python_version() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that exclude-newer-package can be disabled for specific packages using `false`.
|
||||
#[test]
|
||||
fn lock_exclude_newer_package_disable() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["idna", "iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Lock with global exclude-newer and disable it for idna
|
||||
uv_snapshot!(context.filters(), context
|
||||
.lock()
|
||||
.env_remove(EnvVars::UV_EXCLUDE_NEWER)
|
||||
.arg("--exclude-newer")
|
||||
.arg("2022-04-04T12:00:00Z")
|
||||
.arg("--exclude-newer-package")
|
||||
.arg("idna=false"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 3 packages in [TIME]
|
||||
"###);
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[options]
|
||||
exclude-newer = "2022-04-04T12:00:00Z"
|
||||
|
||||
[options.exclude-newer-package]
|
||||
idna = false
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/a2/97899f6bd0e873fed3a7e67ae8d3a08b21799430fb4da15cfedf10d6e2c2/iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32", size = 8104, upload-time = "2020-10-14T10:20:18.572Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/dd/b3c12c6d707058fa947864b67f0c4e0c39ef8610988d7baea9578f3c48f3/iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", size = 4990, upload-time = "2020-10-16T17:37:23.05Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "iniconfig" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "idna" },
|
||||
{ name = "iniconfig" },
|
||||
]
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that exclude-newer-package is properly serialized in the lockfile.
|
||||
#[test]
|
||||
fn lock_exclude_newer_package() -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -3565,7 +3565,7 @@ fn compile_exclude_newer_package_errors() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'tqdm' for '--exclude-newer-package <EXCLUDE_NEWER_PACKAGE>': Invalid `exclude-newer-package` value `tqdm`: expected format `PACKAGE=DATE`
|
||||
error: invalid value 'tqdm' for '--exclude-newer-package <EXCLUDE_NEWER_PACKAGE>': Invalid `exclude-newer-package` value `tqdm`: expected format `PACKAGE=DATE` or `PACKAGE=false`
|
||||
|
||||
For more information, try '--help'.
|
||||
"
|
||||
|
|
@ -3583,7 +3583,7 @@ fn compile_exclude_newer_package_errors() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'tqdm=invalid-date' for '--exclude-newer-package <EXCLUDE_NEWER_PACKAGE>': Invalid `exclude-newer-package` timestamp `invalid-date`: `invalid-date` could not be parsed as a valid exclude-newer value (expected a date like `2024-01-01`, a timestamp like `2024-01-01T00:00:00Z`, or a duration like `3 days` or `P3D`)
|
||||
error: invalid value 'tqdm=invalid-date' for '--exclude-newer-package <EXCLUDE_NEWER_PACKAGE>': Invalid `exclude-newer-package` value `invalid-date`: `invalid-date` could not be parsed as a valid exclude-newer value (expected a date like `2024-01-01`, a timestamp like `2024-01-01T00:00:00Z`, or a duration like `3 days` or `P3D`)
|
||||
|
||||
For more information, try '--help'.
|
||||
"
|
||||
|
|
|
|||
|
|
@ -658,8 +658,8 @@ configured time zone.
|
|||
|
||||
The package index must support the `upload-time` field as specified in
|
||||
[`PEP 700`](https://peps.python.org/pep-0700/). If the field is not present for a given
|
||||
distribution, the distribution will be treated as unavailable. PyPI provides `upload-time` for
|
||||
all packages.
|
||||
distribution, the distribution will be treated as unavailable unless the package is opted out
|
||||
via `--exclude-newer-package <package>=false`. PyPI provides `upload-time` for all packages.
|
||||
|
||||
To ensure reproducibility, messages for unsatisfiable resolutions will not mention that
|
||||
distributions were excluded due to the `--exclude-newer` flag — newer distributions will be treated
|
||||
|
|
@ -689,6 +689,9 @@ Values may also be specified for specific packages, e.g.,
|
|||
exclude-newer-package = { setuptools = "2006-12-02T02:07:43Z" }
|
||||
```
|
||||
|
||||
The same flag also accepts `<package>=false` to opt a package out of the `--exclude-newer`
|
||||
restriction, e.g., to allow resolving packages from an index that does not publish upload times.
|
||||
|
||||
Package-specific values will take precedence over global values.
|
||||
|
||||
## Dependency cooldowns
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@
|
|||
"ExcludeNewerPackage": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/ExcludeNewerTimestamp"
|
||||
"$ref": "#/definitions/PackageExcludeNewer"
|
||||
}
|
||||
},
|
||||
"ExcludeNewerTimestamp": {
|
||||
|
|
@ -1213,6 +1213,29 @@
|
|||
"$ref": "#/definitions/ConfigSettings"
|
||||
}
|
||||
},
|
||||
"PackageExcludeNewer": {
|
||||
"description": "Per-package exclude-newer setting.\n\nThis enum represents whether exclude-newer should be disabled for a package,\nor if a specific cutoff (absolute or relative) should be used.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Disable exclude-newer for this package (allow all versions regardless of upload date).",
|
||||
"type": "string",
|
||||
"const": "Disabled"
|
||||
},
|
||||
{
|
||||
"description": "Enable exclude-newer with this cutoff for this package.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Enabled": {
|
||||
"$ref": "#/definitions/ExcludeNewerTimestamp"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"Enabled"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"PackageName": {
|
||||
"description": "The normalized name of a package.\n\nConverts the name to lowercase and collapses runs of `-`, `_`, and `.` down to a single `-`.\nFor example, `---`, `.`, and `__` are all converted to a single `-`.\n\nSee: <https://packaging.python.org/en/latest/specifications/name-normalization/>",
|
||||
"type": "string"
|
||||
|
|
|
|||
Loading…
Reference in New Issue