Move ExcludeNewer into its own type (#3041)

## Summary

This makes it easier to add (e.g.) JSON Schema derivations to the type.

If we have support for other dates in the future, we can generalize it
to a `UserDate` or similar.
This commit is contained in:
Charlie Marsh 2024-04-15 16:24:08 -04:00 committed by GitHub
parent 37a43f4b48
commit 1f626bfc73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 96 additions and 61 deletions

View file

@ -3,7 +3,7 @@ use std::path::PathBuf;
use anstream::println; use anstream::println;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use fs_err::File; use fs_err::File;
use itertools::Itertools; use itertools::Itertools;
@ -17,7 +17,7 @@ use uv_configuration::{ConfigSettings, NoBinary, NoBuild, SetupPyStrategy};
use uv_dispatch::BuildDispatch; use uv_dispatch::BuildDispatch;
use uv_installer::SitePackages; use uv_installer::SitePackages;
use uv_interpreter::PythonEnvironment; use uv_interpreter::PythonEnvironment;
use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, Resolver}; use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, Options, Resolver};
use uv_types::{BuildIsolation, HashStrategy, InFlight}; use uv_types::{BuildIsolation, HashStrategy, InFlight};
#[derive(ValueEnum, Default, Clone)] #[derive(ValueEnum, Default, Clone)]
@ -42,7 +42,7 @@ pub(crate) struct ResolveCliArgs {
#[command(flatten)] #[command(flatten)]
cache_args: CacheArgs, cache_args: CacheArgs,
#[arg(long)] #[arg(long)]
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
#[clap(long, short, env = "UV_INDEX_URL")] #[clap(long, short, env = "UV_INDEX_URL")]
index_url: Option<IndexUrl>, index_url: Option<IndexUrl>,
#[clap(long, env = "UV_EXTRA_INDEX_URL")] #[clap(long, env = "UV_EXTRA_INDEX_URL")]

View file

@ -0,0 +1,53 @@
use std::str::FromStr;
use chrono::{DateTime, Days, NaiveDate, NaiveTime, Utc};
/// A timestamp that excludes files newer than it.
#[derive(Debug, Copy, Clone)]
pub struct ExcludeNewer(DateTime<Utc>);
impl ExcludeNewer {
/// Returns the timestamp in milliseconds.
pub fn timestamp_millis(&self) -> i64 {
self.0.timestamp_millis()
}
}
impl From<DateTime<Utc>> for ExcludeNewer {
fn from(datetime: DateTime<Utc>) -> Self {
Self(datetime)
}
}
impl FromStr for ExcludeNewer {
type Err = String;
/// Parse an [`ExcludeNewer`] from a string.
///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`).
fn from_str(input: &str) -> Result<Self, Self::Err> {
let date_err = match NaiveDate::from_str(input) {
Ok(date) => {
// Midnight that day is 00:00:00 the next day
return Ok(Self(
(date + Days::new(1)).and_time(NaiveTime::MIN).and_utc(),
));
}
Err(err) => err,
};
let datetime_err = match DateTime::parse_from_rfc3339(input) {
Ok(datetime) => return Ok(Self(datetime.with_timezone(&Utc))),
Err(err) => err,
};
Err(format!(
"`{input}` is neither a valid date ({date_err}) nor a valid datetime ({datetime_err})"
))
}
}
impl std::fmt::Display for ExcludeNewer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

View file

@ -1,5 +1,6 @@
pub use dependency_mode::DependencyMode; pub use dependency_mode::DependencyMode;
pub use error::ResolveError; pub use error::ResolveError;
pub use exclude_newer::ExcludeNewer;
pub use exclusions::Exclusions; pub use exclusions::Exclusions;
pub use flat_index::FlatIndex; pub use flat_index::FlatIndex;
pub use manifest::Manifest; pub use manifest::Manifest;
@ -24,6 +25,7 @@ mod dependency_mode;
mod dependency_provider; mod dependency_provider;
mod editables; mod editables;
mod error; mod error;
mod exclude_newer;
mod exclusions; mod exclusions;
mod flat_index; mod flat_index;
mod manifest; mod manifest;

View file

@ -1,6 +1,4 @@
use chrono::{DateTime, Utc}; use crate::{DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode};
use crate::{DependencyMode, PreReleaseMode, ResolutionMode};
/// Options for resolving a manifest. /// Options for resolving a manifest.
#[derive(Debug, Default, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
@ -8,7 +6,7 @@ pub struct Options {
pub resolution_mode: ResolutionMode, pub resolution_mode: ResolutionMode,
pub prerelease_mode: PreReleaseMode, pub prerelease_mode: PreReleaseMode,
pub dependency_mode: DependencyMode, pub dependency_mode: DependencyMode,
pub exclude_newer: Option<DateTime<Utc>>, pub exclude_newer: Option<ExcludeNewer>,
} }
/// Builder for [`Options`]. /// Builder for [`Options`].
@ -17,7 +15,7 @@ pub struct OptionsBuilder {
resolution_mode: ResolutionMode, resolution_mode: ResolutionMode,
prerelease_mode: PreReleaseMode, prerelease_mode: PreReleaseMode,
dependency_mode: DependencyMode, dependency_mode: DependencyMode,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
} }
impl OptionsBuilder { impl OptionsBuilder {
@ -49,7 +47,7 @@ impl OptionsBuilder {
/// Sets the exclusion date. /// Sets the exclusion date.
#[must_use] #[must_use]
pub fn exclude_newer(mut self, exclude_newer: Option<DateTime<Utc>>) -> Self { pub fn exclude_newer(mut self, exclude_newer: Option<ExcludeNewer>) -> Self {
self.exclude_newer = exclude_newer; self.exclude_newer = exclude_newer;
self self
} }

View file

@ -1,11 +1,9 @@
use std::future::Future; use std::future::Future;
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Utc};
use distribution_types::{Dist, IndexLocations}; use distribution_types::{Dist, IndexLocations};
use platform_tags::Tags; use platform_tags::Tags;
use uv_client::RegistryClient; use uv_client::RegistryClient;
use uv_configuration::{NoBinary, NoBuild}; use uv_configuration::{NoBinary, NoBuild};
use uv_distribution::{ArchiveMetadata, DistributionDatabase}; use uv_distribution::{ArchiveMetadata, DistributionDatabase};
@ -16,6 +14,7 @@ use crate::flat_index::FlatIndex;
use crate::python_requirement::PythonRequirement; use crate::python_requirement::PythonRequirement;
use crate::version_map::VersionMap; use crate::version_map::VersionMap;
use crate::yanks::AllowedYanks; use crate::yanks::AllowedYanks;
use crate::ExcludeNewer;
pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>; pub type PackageVersionsResult = Result<VersionsResponse, uv_client::Error>;
pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>; pub type WheelMetadataResult = Result<MetadataResponse, uv_distribution::Error>;
@ -84,7 +83,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext + Send + Sync> {
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: HashStrategy, hasher: HashStrategy,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
no_binary: NoBinary, no_binary: NoBinary,
no_build: NoBuild, no_build: NoBuild,
} }
@ -100,7 +99,7 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
allowed_yanks: AllowedYanks, allowed_yanks: AllowedYanks,
hasher: &'a HashStrategy, hasher: &'a HashStrategy,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
no_binary: &'a NoBinary, no_binary: &'a NoBinary,
no_build: &'a NoBuild, no_build: &'a NoBuild,
) -> Self { ) -> Self {

View file

@ -1,7 +1,6 @@
use std::collections::btree_map::{BTreeMap, Entry}; use std::collections::btree_map::{BTreeMap, Entry};
use std::sync::OnceLock; use std::sync::OnceLock;
use chrono::{DateTime, Utc};
use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize}; use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use tracing::instrument; use tracing::instrument;
@ -21,7 +20,7 @@ use uv_types::HashStrategy;
use uv_warnings::warn_user_once; use uv_warnings::warn_user_once;
use crate::flat_index::FlatDistributions; use crate::flat_index::FlatDistributions;
use crate::{python_requirement::PythonRequirement, yanks::AllowedYanks}; use crate::{python_requirement::PythonRequirement, yanks::AllowedYanks, ExcludeNewer};
/// A map from versions to distributions. /// A map from versions to distributions.
#[derive(Debug)] #[derive(Debug)]
@ -49,7 +48,7 @@ impl VersionMap {
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
allowed_yanks: &AllowedYanks, allowed_yanks: &AllowedYanks,
hasher: &HashStrategy, hasher: &HashStrategy,
exclude_newer: Option<&DateTime<Utc>>, exclude_newer: Option<&ExcludeNewer>,
flat_index: Option<FlatDistributions>, flat_index: Option<FlatDistributions>,
no_binary: &NoBinary, no_binary: &NoBinary,
no_build: &NoBuild, no_build: &NoBuild,
@ -304,7 +303,7 @@ struct VersionMapLazy {
/// exists) is satisfied or not. /// exists) is satisfied or not.
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
/// Whether files newer than this timestamp should be excluded or not. /// Whether files newer than this timestamp should be excluded or not.
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
/// Which yanked versions are allowed /// Which yanked versions are allowed
allowed_yanks: FxHashSet<Version>, allowed_yanks: FxHashSet<Version>,
/// The hashes of allowed distributions. /// The hashes of allowed distributions.

View file

@ -18,7 +18,7 @@ use uv_client::RegistryClientBuilder;
use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy}; use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy};
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment}; use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
use uv_resolver::{ use uv_resolver::{
DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options,
OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
}; };
use uv_types::{ use uv_types::{
@ -26,10 +26,12 @@ use uv_types::{
}; };
// Exclude any packages uploaded after this date. // Exclude any packages uploaded after this date.
static EXCLUDE_NEWER: Lazy<DateTime<Utc>> = Lazy::new(|| { static EXCLUDE_NEWER: Lazy<ExcludeNewer> = Lazy::new(|| {
DateTime::parse_from_rfc3339("2023-11-18T12:00:00Z") ExcludeNewer::from(
.unwrap() DateTime::parse_from_rfc3339("2023-11-18T12:00:00Z")
.with_timezone(&Utc) .unwrap()
.with_timezone(&Utc),
)
}); });
struct DummyContext { struct DummyContext {

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Days, NaiveDate, NaiveTime, Utc};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use distribution_types::{FlatIndexLocation, IndexUrl}; use distribution_types::{FlatIndexLocation, IndexUrl};
@ -11,7 +11,7 @@ use uv_cache::CacheArgs;
use uv_configuration::IndexStrategy; use uv_configuration::IndexStrategy;
use uv_configuration::{ConfigSettingEntry, PackageNameSpecifier}; use uv_configuration::{ConfigSettingEntry, PackageNameSpecifier};
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{AnnotationStyle, PreReleaseMode, ResolutionMode}; use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::PythonVersion; use uv_toolchain::PythonVersion;
use crate::commands::{extra_name_with_clap_error, ListFormat, VersionFormat}; use crate::commands::{extra_name_with_clap_error, ListFormat, VersionFormat};
@ -178,24 +178,6 @@ pub(crate) enum PipCommand {
Check(PipCheckArgs), Check(PipCheckArgs),
} }
/// Clap parser for the union of date and datetime
fn date_or_datetime(input: &str) -> Result<DateTime<Utc>, String> {
let date_err = match NaiveDate::from_str(input) {
Ok(date) => {
// Midnight that day is 00:00:00 the next day
return Ok((date + Days::new(1)).and_time(NaiveTime::MIN).and_utc());
}
Err(err) => err,
};
let datetime_err = match DateTime::parse_from_rfc3339(input) {
Ok(datetime) => return Ok(datetime.with_timezone(&Utc)),
Err(err) => err,
};
Err(format!(
"Neither a valid date ({date_err}) not a valid datetime ({datetime_err})"
))
}
/// A re-implementation of `Option`, used to avoid Clap's automatic `Option` flattening in /// A re-implementation of `Option`, used to avoid Clap's automatic `Option` flattening in
/// [`parse_index_url`]. /// [`parse_index_url`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -446,8 +428,8 @@ pub(crate) struct PipCompileArgs {
/// ///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`). /// format (e.g., `2006-12-02`).
#[arg(long, value_parser = date_or_datetime)] #[arg(long)]
pub(crate) exclude_newer: Option<DateTime<Utc>>, pub(crate) exclude_newer: Option<ExcludeNewer>,
/// Specify a package to omit from the output resolution. Its dependencies will still be /// Specify a package to omit from the output resolution. Its dependencies will still be
/// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option. /// included in the resolution. Equivalent to pip-compile's `--unsafe-package` option.
@ -965,8 +947,8 @@ pub(crate) struct PipInstallArgs {
/// ///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`). /// format (e.g., `2006-12-02`).
#[arg(long, value_parser = date_or_datetime)] #[arg(long)]
pub(crate) exclude_newer: Option<DateTime<Utc>>, pub(crate) exclude_newer: Option<ExcludeNewer>,
/// Perform a dry run, i.e., don't actually install anything but resolve the dependencies and /// Perform a dry run, i.e., don't actually install anything but resolve the dependencies and
/// print the resulting plan. /// print the resulting plan.
@ -1309,8 +1291,8 @@ pub(crate) struct VenvArgs {
/// ///
/// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same /// Accepts both RFC 3339 timestamps (e.g., `2006-12-02T02:07:43Z`) and UTC dates in the same
/// format (e.g., `2006-12-02`). /// format (e.g., `2006-12-02`).
#[arg(long, value_parser = date_or_datetime)] #[arg(long)]
pub(crate) exclude_newer: Option<DateTime<Utc>>, pub(crate) exclude_newer: Option<ExcludeNewer>,
#[command(flatten)] #[command(flatten)]
pub(crate) compat_args: compat::VenvCompatArgs, pub(crate) compat_args: compat::VenvCompatArgs,

View file

@ -8,7 +8,6 @@ use std::str::FromStr;
use anstream::{eprint, AutoStream, StripStream}; use anstream::{eprint, AutoStream, StripStream};
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use tempfile::tempdir_in; use tempfile::tempdir_in;
@ -35,8 +34,9 @@ use uv_requirements::{
RequirementsSource, RequirementsSpecification, SourceTreeResolver, RequirementsSource, RequirementsSpecification, SourceTreeResolver,
}; };
use uv_resolver::{ use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex,
Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver, InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode,
Resolver,
}; };
use uv_toolchain::PythonVersion; use uv_toolchain::PythonVersion;
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
@ -77,7 +77,7 @@ pub(crate) async fn pip_compile(
no_build_isolation: bool, no_build_isolation: bool,
no_build: NoBuild, no_build: NoBuild,
python_version: Option<PythonVersion>, python_version: Option<PythonVersion>,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
annotation_style: AnnotationStyle, annotation_style: AnnotationStyle,
native_tls: bool, native_tls: bool,
quiet: bool, quiet: bool,

View file

@ -4,7 +4,7 @@ use std::path::Path;
use anstream::eprint; use anstream::eprint;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use tempfile::tempdir_in; use tempfile::tempdir_in;
@ -38,8 +38,8 @@ use uv_requirements::{
RequirementsSpecification, SourceTreeResolver, RequirementsSpecification, SourceTreeResolver,
}; };
use uv_resolver::{ use uv_resolver::{
DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, OptionsBuilder, DependencyMode, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options,
PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
}; };
use uv_types::{BuildIsolation, HashStrategy, InFlight}; use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -75,7 +75,7 @@ pub(crate) async fn pip_install(
no_build: NoBuild, no_build: NoBuild,
no_binary: NoBinary, no_binary: NoBinary,
strict: bool, strict: bool,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
python: Option<String>, python: Option<String>,
system: bool, system: bool,
break_system_packages: bool, break_system_packages: bool,

View file

@ -5,7 +5,7 @@ use std::vec;
use anstream::eprint; use anstream::eprint;
use anyhow::Result; use anyhow::Result;
use chrono::{DateTime, Utc};
use itertools::Itertools; use itertools::Itertools;
use miette::{Diagnostic, IntoDiagnostic}; use miette::{Diagnostic, IntoDiagnostic};
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
@ -21,7 +21,7 @@ use uv_configuration::{ConfigSettings, IndexStrategy, NoBinary, NoBuild, SetupPy
use uv_dispatch::BuildDispatch; use uv_dispatch::BuildDispatch;
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_interpreter::{find_default_python, find_requested_python, Error}; use uv_interpreter::{find_default_python, find_requested_python, Error};
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder}; use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder};
use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight}; use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight};
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
@ -41,7 +41,7 @@ pub(crate) async fn venv(
system_site_packages: bool, system_site_packages: bool,
connectivity: Connectivity, connectivity: Connectivity,
seed: bool, seed: bool,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
native_tls: bool, native_tls: bool,
cache: &Cache, cache: &Cache,
printer: Printer, printer: Printer,
@ -104,7 +104,7 @@ async fn venv_impl(
system_site_packages: bool, system_site_packages: bool,
connectivity: Connectivity, connectivity: Connectivity,
seed: bool, seed: bool,
exclude_newer: Option<DateTime<Utc>>, exclude_newer: Option<ExcludeNewer>,
native_tls: bool, native_tls: bool,
cache: &Cache, cache: &Cache,
printer: Printer, printer: Printer,

View file

@ -2253,7 +2253,7 @@ fn compile_exclude_newer() -> Result<()> {
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
error: invalid value '2022-04-04+02:00' for '--exclude-newer <EXCLUDE_NEWER>': Neither a valid date (trailing input) not a valid datetime (input contains invalid characters) error: invalid value '2022-04-04+02:00' for '--exclude-newer <EXCLUDE_NEWER>': `2022-04-04+02:00` is neither a valid date (trailing input) nor a valid datetime (input contains invalid characters)
For more information, try '--help'. For more information, try '--help'.
"### "###