From 1f626bfc7300a700257348e1890af90837c740e6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 15 Apr 2024 16:24:08 -0400 Subject: [PATCH] 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. --- crates/uv-dev/src/resolve_cli.rs | 6 +-- crates/uv-resolver/src/exclude_newer.rs | 53 +++++++++++++++++++++ crates/uv-resolver/src/lib.rs | 2 + crates/uv-resolver/src/options.rs | 10 ++-- crates/uv-resolver/src/resolver/provider.rs | 7 ++- crates/uv-resolver/src/version_map.rs | 7 ++- crates/uv-resolver/tests/resolver.rs | 12 +++-- crates/uv/src/cli.rs | 34 ++++--------- crates/uv/src/commands/pip_compile.rs | 8 ++-- crates/uv/src/commands/pip_install.rs | 8 ++-- crates/uv/src/commands/venv.rs | 8 ++-- crates/uv/tests/pip_compile.rs | 2 +- 12 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 crates/uv-resolver/src/exclude_newer.rs diff --git a/crates/uv-dev/src/resolve_cli.rs b/crates/uv-dev/src/resolve_cli.rs index 52c171843..8ccaf0b7b 100644 --- a/crates/uv-dev/src/resolve_cli.rs +++ b/crates/uv-dev/src/resolve_cli.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use anstream::println; use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; + use clap::{Parser, ValueEnum}; use fs_err::File; use itertools::Itertools; @@ -17,7 +17,7 @@ use uv_configuration::{ConfigSettings, NoBinary, NoBuild, SetupPyStrategy}; use uv_dispatch::BuildDispatch; use uv_installer::SitePackages; 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}; #[derive(ValueEnum, Default, Clone)] @@ -42,7 +42,7 @@ pub(crate) struct ResolveCliArgs { #[command(flatten)] cache_args: CacheArgs, #[arg(long)] - exclude_newer: Option>, + exclude_newer: Option, #[clap(long, short, env = "UV_INDEX_URL")] index_url: Option, #[clap(long, env = "UV_EXTRA_INDEX_URL")] diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs new file mode 100644 index 000000000..260e202ad --- /dev/null +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -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); + +impl ExcludeNewer { + /// Returns the timestamp in milliseconds. + pub fn timestamp_millis(&self) -> i64 { + self.0.timestamp_millis() + } +} + +impl From> for ExcludeNewer { + fn from(datetime: DateTime) -> 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 { + 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) + } +} diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index c7fd1aea1..c695c9e4d 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -1,5 +1,6 @@ pub use dependency_mode::DependencyMode; pub use error::ResolveError; +pub use exclude_newer::ExcludeNewer; pub use exclusions::Exclusions; pub use flat_index::FlatIndex; pub use manifest::Manifest; @@ -24,6 +25,7 @@ mod dependency_mode; mod dependency_provider; mod editables; mod error; +mod exclude_newer; mod exclusions; mod flat_index; mod manifest; diff --git a/crates/uv-resolver/src/options.rs b/crates/uv-resolver/src/options.rs index 55e6e5f07..f6f4a4287 100644 --- a/crates/uv-resolver/src/options.rs +++ b/crates/uv-resolver/src/options.rs @@ -1,6 +1,4 @@ -use chrono::{DateTime, Utc}; - -use crate::{DependencyMode, PreReleaseMode, ResolutionMode}; +use crate::{DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode}; /// Options for resolving a manifest. #[derive(Debug, Default, Copy, Clone)] @@ -8,7 +6,7 @@ pub struct Options { pub resolution_mode: ResolutionMode, pub prerelease_mode: PreReleaseMode, pub dependency_mode: DependencyMode, - pub exclude_newer: Option>, + pub exclude_newer: Option, } /// Builder for [`Options`]. @@ -17,7 +15,7 @@ pub struct OptionsBuilder { resolution_mode: ResolutionMode, prerelease_mode: PreReleaseMode, dependency_mode: DependencyMode, - exclude_newer: Option>, + exclude_newer: Option, } impl OptionsBuilder { @@ -49,7 +47,7 @@ impl OptionsBuilder { /// Sets the exclusion date. #[must_use] - pub fn exclude_newer(mut self, exclude_newer: Option>) -> Self { + pub fn exclude_newer(mut self, exclude_newer: Option) -> Self { self.exclude_newer = exclude_newer; self } diff --git a/crates/uv-resolver/src/resolver/provider.rs b/crates/uv-resolver/src/resolver/provider.rs index 3479620ac..c362da33c 100644 --- a/crates/uv-resolver/src/resolver/provider.rs +++ b/crates/uv-resolver/src/resolver/provider.rs @@ -1,11 +1,9 @@ use std::future::Future; use anyhow::Result; -use chrono::{DateTime, Utc}; use distribution_types::{Dist, IndexLocations}; use platform_tags::Tags; - use uv_client::RegistryClient; use uv_configuration::{NoBinary, NoBuild}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; @@ -16,6 +14,7 @@ use crate::flat_index::FlatIndex; use crate::python_requirement::PythonRequirement; use crate::version_map::VersionMap; use crate::yanks::AllowedYanks; +use crate::ExcludeNewer; pub type PackageVersionsResult = Result; pub type WheelMetadataResult = Result; @@ -84,7 +83,7 @@ pub struct DefaultResolverProvider<'a, Context: BuildContext + Send + Sync> { python_requirement: PythonRequirement, allowed_yanks: AllowedYanks, hasher: HashStrategy, - exclude_newer: Option>, + exclude_newer: Option, no_binary: NoBinary, no_build: NoBuild, } @@ -100,7 +99,7 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex python_requirement: PythonRequirement, allowed_yanks: AllowedYanks, hasher: &'a HashStrategy, - exclude_newer: Option>, + exclude_newer: Option, no_binary: &'a NoBinary, no_build: &'a NoBuild, ) -> Self { diff --git a/crates/uv-resolver/src/version_map.rs b/crates/uv-resolver/src/version_map.rs index 91e41c4e2..afa60e04c 100644 --- a/crates/uv-resolver/src/version_map.rs +++ b/crates/uv-resolver/src/version_map.rs @@ -1,7 +1,6 @@ use std::collections::btree_map::{BTreeMap, Entry}; use std::sync::OnceLock; -use chrono::{DateTime, Utc}; use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize}; use rustc_hash::FxHashSet; use tracing::instrument; @@ -21,7 +20,7 @@ use uv_types::HashStrategy; use uv_warnings::warn_user_once; 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. #[derive(Debug)] @@ -49,7 +48,7 @@ impl VersionMap { python_requirement: &PythonRequirement, allowed_yanks: &AllowedYanks, hasher: &HashStrategy, - exclude_newer: Option<&DateTime>, + exclude_newer: Option<&ExcludeNewer>, flat_index: Option, no_binary: &NoBinary, no_build: &NoBuild, @@ -304,7 +303,7 @@ struct VersionMapLazy { /// exists) is satisfied or not. python_requirement: PythonRequirement, /// Whether files newer than this timestamp should be excluded or not. - exclude_newer: Option>, + exclude_newer: Option, /// Which yanked versions are allowed allowed_yanks: FxHashSet, /// The hashes of allowed distributions. diff --git a/crates/uv-resolver/tests/resolver.rs b/crates/uv-resolver/tests/resolver.rs index f440ebae2..b595714f0 100644 --- a/crates/uv-resolver/tests/resolver.rs +++ b/crates/uv-resolver/tests/resolver.rs @@ -18,7 +18,7 @@ use uv_client::RegistryClientBuilder; use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy}; use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment}; use uv_resolver::{ - DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, + DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, }; use uv_types::{ @@ -26,10 +26,12 @@ use uv_types::{ }; // Exclude any packages uploaded after this date. -static EXCLUDE_NEWER: Lazy> = Lazy::new(|| { - DateTime::parse_from_rfc3339("2023-11-18T12:00:00Z") - .unwrap() - .with_timezone(&Utc) +static EXCLUDE_NEWER: Lazy = Lazy::new(|| { + ExcludeNewer::from( + DateTime::parse_from_rfc3339("2023-11-18T12:00:00Z") + .unwrap() + .with_timezone(&Utc), + ) }); struct DummyContext { diff --git a/crates/uv/src/cli.rs b/crates/uv/src/cli.rs index cfdb719d3..d3ac8bb1a 100644 --- a/crates/uv/src/cli.rs +++ b/crates/uv/src/cli.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use std::str::FromStr; use anyhow::Result; -use chrono::{DateTime, Days, NaiveDate, NaiveTime, Utc}; + use clap::{Args, Parser, Subcommand}; use distribution_types::{FlatIndexLocation, IndexUrl}; @@ -11,7 +11,7 @@ use uv_cache::CacheArgs; use uv_configuration::IndexStrategy; use uv_configuration::{ConfigSettingEntry, PackageNameSpecifier}; use uv_normalize::{ExtraName, PackageName}; -use uv_resolver::{AnnotationStyle, PreReleaseMode, ResolutionMode}; +use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode}; use uv_toolchain::PythonVersion; use crate::commands::{extra_name_with_clap_error, ListFormat, VersionFormat}; @@ -178,24 +178,6 @@ pub(crate) enum PipCommand { Check(PipCheckArgs), } -/// Clap parser for the union of date and datetime -fn date_or_datetime(input: &str) -> Result, 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 /// [`parse_index_url`]. #[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 /// format (e.g., `2006-12-02`). - #[arg(long, value_parser = date_or_datetime)] - pub(crate) exclude_newer: Option>, + #[arg(long)] + pub(crate) exclude_newer: Option, /// 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. @@ -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 /// format (e.g., `2006-12-02`). - #[arg(long, value_parser = date_or_datetime)] - pub(crate) exclude_newer: Option>, + #[arg(long)] + pub(crate) exclude_newer: Option, /// Perform a dry run, i.e., don't actually install anything but resolve the dependencies and /// 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 /// format (e.g., `2006-12-02`). - #[arg(long, value_parser = date_or_datetime)] - pub(crate) exclude_newer: Option>, + #[arg(long)] + pub(crate) exclude_newer: Option, #[command(flatten)] pub(crate) compat_args: compat::VenvCompatArgs, diff --git a/crates/uv/src/commands/pip_compile.rs b/crates/uv/src/commands/pip_compile.rs index 2f5b82bb0..26c55d0b0 100644 --- a/crates/uv/src/commands/pip_compile.rs +++ b/crates/uv/src/commands/pip_compile.rs @@ -8,7 +8,6 @@ use std::str::FromStr; use anstream::{eprint, AutoStream, StripStream}; use anyhow::{anyhow, Context, Result}; -use chrono::{DateTime, Utc}; use itertools::Itertools; use owo_colors::OwoColorize; use tempfile::tempdir_in; @@ -35,8 +34,9 @@ use uv_requirements::{ RequirementsSource, RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ - AnnotationStyle, DependencyMode, DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, - Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, Resolver, + AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, + InMemoryIndex, Manifest, OptionsBuilder, PreReleaseMode, PythonRequirement, ResolutionMode, + Resolver, }; use uv_toolchain::PythonVersion; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -77,7 +77,7 @@ pub(crate) async fn pip_compile( no_build_isolation: bool, no_build: NoBuild, python_version: Option, - exclude_newer: Option>, + exclude_newer: Option, annotation_style: AnnotationStyle, native_tls: bool, quiet: bool, diff --git a/crates/uv/src/commands/pip_install.rs b/crates/uv/src/commands/pip_install.rs index 4e30b7c42..8deaaa076 100644 --- a/crates/uv/src/commands/pip_install.rs +++ b/crates/uv/src/commands/pip_install.rs @@ -4,7 +4,7 @@ use std::path::Path; use anstream::eprint; use anyhow::{anyhow, Context, Result}; -use chrono::{DateTime, Utc}; + use itertools::Itertools; use owo_colors::OwoColorize; use tempfile::tempdir_in; @@ -38,8 +38,8 @@ use uv_requirements::{ RequirementsSpecification, SourceTreeResolver, }; use uv_resolver::{ - DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, OptionsBuilder, - PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, + DependencyMode, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, + OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver, }; use uv_types::{BuildIsolation, HashStrategy, InFlight}; use uv_warnings::warn_user; @@ -75,7 +75,7 @@ pub(crate) async fn pip_install( no_build: NoBuild, no_binary: NoBinary, strict: bool, - exclude_newer: Option>, + exclude_newer: Option, python: Option, system: bool, break_system_packages: bool, diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 72674689b..82a2ac3c5 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -5,7 +5,7 @@ use std::vec; use anstream::eprint; use anyhow::Result; -use chrono::{DateTime, Utc}; + use itertools::Itertools; use miette::{Diagnostic, IntoDiagnostic}; use owo_colors::OwoColorize; @@ -21,7 +21,7 @@ use uv_configuration::{ConfigSettings, IndexStrategy, NoBinary, NoBuild, SetupPy use uv_dispatch::BuildDispatch; use uv_fs::Simplified; 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 crate::commands::ExitStatus; @@ -41,7 +41,7 @@ pub(crate) async fn venv( system_site_packages: bool, connectivity: Connectivity, seed: bool, - exclude_newer: Option>, + exclude_newer: Option, native_tls: bool, cache: &Cache, printer: Printer, @@ -104,7 +104,7 @@ async fn venv_impl( system_site_packages: bool, connectivity: Connectivity, seed: bool, - exclude_newer: Option>, + exclude_newer: Option, native_tls: bool, cache: &Cache, printer: Printer, diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 22a04aeb6..8ad13348c 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -2253,7 +2253,7 @@ fn compile_exclude_newer() -> Result<()> { ----- stdout ----- ----- stderr ----- - error: invalid value '2022-04-04+02:00' for '--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 ': `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'. "###