diff --git a/Cargo.lock b/Cargo.lock index db6a24ce5e..1180113d52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4502,7 +4502,6 @@ dependencies = [ "rattler_conda_types", "rattler_networking", "rattler_repodata_gateway", - "reqwest-middleware", "rstest", "serde", "serde_ignored", @@ -4941,6 +4940,7 @@ name = "pypi_mapping" version = "0.1.0" dependencies = [ "async-once-cell", + "dashmap", "fs-err", "futures", "http-cache-reqwest", @@ -4957,7 +4957,9 @@ dependencies = [ "reqwest-retry", "serde", "serde_json", + "thiserror 2.0.11", "tokio", + "tracing", "url", ] @@ -5154,9 +5156,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.33.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454e5091f529a7c24f0b568b7c00b3c978ffb9c4020db8bb1e10dd6e73c9aaec" +checksum = "1f2f6a77d473028c29adf77cbc29d651d6566ca551bad3434e2314d992c6c1f9" dependencies = [ "anyhow", "clap", @@ -5321,9 +5323,9 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82434c783daf87bdcfee820db93fe12119cf469ca5abc174c8c45963d48aff2" +checksum = "cd9d126c14a5f5a50027c9f05992e7caf2122acb7ce528b9009ee92d475dd329" dependencies = [ "chrono", "configparser", @@ -5423,9 +5425,9 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6073881466bbd0e726061451e20341df1c4331220d5b489b09549388856d5da" +checksum = "ba15cb481b9d065194160fddff2fd21d15cac723622a1acddc8fae6b65ce0c7c" dependencies = [ "anyhow", "async-compression", diff --git a/Cargo.toml b/Cargo.toml index d9e7a20b3c..b5e6251743 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,18 +114,18 @@ which = "7.0.2" # Rattler crates file_url = "0.2.3" -rattler = { version = "0.33.1", default-features = false } -rattler_cache = { version = "0.3.15", default-features = false } -rattler_conda_types = { version = "0.31.5", default-features = false, features = [ +rattler = { version = "0.33.3", default-features = false } +rattler_cache = { version = "0.3.14", default-features = false } +rattler_conda_types = { version = "0.31.4", default-features = false, features = [ "rayon", ] } rattler_digest = { version = "1.0.8", default-features = false } rattler_lock = { version = "0.22.48", default-features = false } -rattler_menuinst = { version = "0.2.4", default-features = false } +rattler_menuinst = { version = "0.2.5", default-features = false } rattler_networking = { version = "0.22.10", default-features = false, features = [ "google-cloud-auth", ] } -rattler_repodata_gateway = { version = "0.22.2", default-features = false } +rattler_repodata_gateway = { version = "0.22.3", default-features = false } rattler_shell = { version = "0.22.24", default-features = false } rattler_solve = { version = "1.4.2", default-features = false } rattler_virtual_packages = { version = "2.0.8", default-features = false } diff --git a/crates/pixi_build_frontend/src/tool/cache.rs b/crates/pixi_build_frontend/src/tool/cache.rs index 6c9acb7d8e..e85a74050b 100644 --- a/crates/pixi_build_frontend/src/tool/cache.rs +++ b/crates/pixi_build_frontend/src/tool/cache.rs @@ -6,6 +6,7 @@ use std::{ }; use dashmap::{DashMap, Entry}; +use fs_err::tokio as tokio_fs; use itertools::Itertools; use miette::{Context, IntoDiagnostic}; use rattler_conda_types::{ChannelConfig, Matches, Platform, PrefixRecord}; @@ -13,8 +14,6 @@ use rattler_shell::{ activation::{ActivationVariables, Activator}, shell::ShellEnum, }; - -use fs_err::tokio as tokio_fs; use tokio::sync::broadcast; use super::{installer::ToolInstaller, IsolatedTool}; @@ -40,7 +39,8 @@ pub struct ToolCache { cache: DashMap>>, } -/// Finds the `PrefixRecord`s from `conda-meta` directory which starts with `Matchspec` names. +/// Finds the `PrefixRecord`s from `conda-meta` directory which starts with +/// `Matchspec` names. pub(crate) async fn find_spec_records( conda_meta: &Path, name_to_match: Vec, @@ -312,8 +312,8 @@ mod tests { const BAT_META_JSON: &str = "bat-0.24.0-h3bba108_1.json"; - /// A test helper to create a temporary directory and write conda meta files. - /// This is used to simulate already installed tools. + /// A test helper to create a temporary directory and write conda meta + /// files. This is used to simulate already installed tools. struct CondaMetaWriter { pub tmp_dir: PathBuf, } @@ -328,7 +328,8 @@ mod tests { } /// Write a meta-json file to the conda-meta directory. - /// If `override_name` is provided, the file will be written with that name. + /// If `override_name` is provided, the file will be written with that + /// name. async fn write_meta_json( &self, meta_json: &str, @@ -391,7 +392,7 @@ mod tests { let tool_context = ToolContext::for_tests() .with_platform(compatible_target_platform()) .with_client(auth_client.clone()) - .with_gateway(config.gateway(auth_client)) + .with_gateway(config.gateway().with_client(auth_client).finish()) .build(); let tool_spec = IsolatedToolSpec { @@ -491,8 +492,8 @@ mod tests { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_handle_a_failure() { // This test verifies that during the installation of a tool, if an error occurs - // the tool is not cached and the next request will try to install the tool again. - // A test installer that will fail on the first request. + // the tool is not cached and the next request will try to install the tool + // again. A test installer that will fail on the first request. #[derive(Default, Clone)] struct TestInstaller { count: Arc>>, diff --git a/crates/pixi_config/Cargo.toml b/crates/pixi_config/Cargo.toml index 81f49d5ea0..9807543473 100644 --- a/crates/pixi_config/Cargo.toml +++ b/crates/pixi_config/Cargo.toml @@ -21,7 +21,6 @@ rattler = { workspace = true } rattler_conda_types = { workspace = true } rattler_networking = { workspace = true, features = ["s3"] } rattler_repodata_gateway = { workspace = true, features = ["gateway"] } -reqwest-middleware = { workspace = true } serde = { workspace = true } serde_ignored = { workspace = true } serde_json = { workspace = true } diff --git a/crates/pixi_config/src/lib.rs b/crates/pixi_config/src/lib.rs index 55b012b1d8..ecf98e667c 100644 --- a/crates/pixi_config/src/lib.rs +++ b/crates/pixi_config/src/lib.rs @@ -1,3 +1,10 @@ +use std::{ + collections::{BTreeSet as Set, HashMap}, + path::{Path, PathBuf}, + process::{Command, Stdio}, + str::FromStr, +}; + use clap::{ArgAction, Parser}; use itertools::Itertools; use miette::{miette, Context, IntoDiagnostic}; @@ -7,15 +14,8 @@ use rattler_conda_types::{ ChannelConfig, NamedChannelOrUrl, Version, VersionBumpType, VersionSpec, }; use rattler_networking::s3_middleware; -use rattler_repodata_gateway::{Gateway, SourceConfig}; -use reqwest_middleware::ClientWithMiddleware; +use rattler_repodata_gateway::{Gateway, GatewayBuilder, SourceConfig}; use serde::{de::IntoDeserializer, Deserialize, Serialize}; -use std::{ - collections::{BTreeSet as Set, HashMap}, - path::{Path, PathBuf}, - process::{Command, Stdio}, - str::FromStr, -}; use url::Url; const EXPERIMENTAL: &str = "experimental"; @@ -182,7 +182,8 @@ impl RepodataConfig { #[derive(Parser, Debug, Default, Clone)] pub struct ConfigCliActivation { - /// Do not use the environment activation cache. (default: true except in experimental mode) + /// Do not use the environment activation cache. (default: true except in + /// experimental mode) #[arg(long, help_heading = consts::CLAP_CONFIG_OPTIONS)] force_activate: bool, @@ -339,7 +340,8 @@ impl Default for DetachedEnvironments { #[serde(rename_all = "kebab-case")] pub struct ExperimentalConfig { /// The option to opt into the environment activation cache feature. - /// This is an experimental feature and may be removed in the future or made default. + /// This is an experimental feature and may be removed in the future or made + /// default. #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub use_environment_activation_cache: Option, @@ -362,7 +364,8 @@ impl ExperimentalConfig { } } -// Making the default values part of pixi_config to allow for printing the default settings in the future. +// Making the default values part of pixi_config to allow for printing the +// default settings in the future. /// The default maximum number of concurrent solves that can be run at once. /// Defaulting to the number of CPUs available. fn default_max_concurrent_solves() -> usize { @@ -380,12 +383,14 @@ fn default_max_concurrent_downloads() -> usize { #[serde(rename_all = "kebab-case")] pub struct ConcurrencyConfig { /// The maximum number of concurrent solves that can be run at once. - // Needing to set this default next to the default of the full struct to avoid serde defaulting to 0 of partial struct was omitted. + // Needing to set this default next to the default of the full struct to avoid serde defaulting + // to 0 of partial struct was omitted. #[serde(default = "default_max_concurrent_solves")] pub solves: usize, /// The maximum number of concurrent HTTP requests to make. - // Needing to set this default next to the default of the full struct to avoid serde defaulting to 0 of partial struct was omitted. + // Needing to set this default next to the default of the full struct to avoid serde defaulting + // to 0 of partial struct was omitted. #[serde(default = "default_max_concurrent_downloads")] pub downloads: usize, } @@ -1444,8 +1449,8 @@ impl Config { .wrap_err(format!("failed to write config to '{}'", to.display())) } - /// Constructs a [`Gateway`] using a [`ClientWithMiddleware`] - pub fn gateway(&self, client: ClientWithMiddleware) -> Gateway { + /// Constructs a [`GatewayBuilder`] with preconfigured settings. + pub fn gateway(&self) -> GatewayBuilder { // Determine the cache directory and fall back to sane defaults otherwise. let cache_dir = get_cache_dir().unwrap_or_else(|e| { tracing::error!("failed to determine repodata cache directory: {e}"); @@ -1454,11 +1459,9 @@ impl Config { // Construct the gateway Gateway::builder() - .with_client(client) .with_cache_dir(cache_dir.join(consts::CONDA_REPODATA_CACHE_DIR)) .with_channel_config(self.into()) .with_max_concurrent_requests(self.max_concurrent_downloads()) - .finish() } pub fn compute_s3_config(&self) -> HashMap { diff --git a/crates/pypi_mapping/Cargo.toml b/crates/pypi_mapping/Cargo.toml index b2bc69e14d..d226f18b6c 100644 --- a/crates/pypi_mapping/Cargo.toml +++ b/crates/pypi_mapping/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" [dependencies] async-once-cell = { workspace = true } +dashmap = { workspace = true } fs-err = { workspace = true } futures = { workspace = true } http-cache-reqwest = { workspace = true } @@ -27,5 +28,7 @@ reqwest-middleware = { workspace = true } reqwest-retry = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } url = { workspace = true } diff --git a/crates/pypi_mapping/src/custom_mapping.rs b/crates/pypi_mapping/src/custom_mapping.rs new file mode 100644 index 0000000000..c8cc731856 --- /dev/null +++ b/crates/pypi_mapping/src/custom_mapping.rs @@ -0,0 +1,182 @@ +use async_once_cell::OnceCell as AsyncCell; +use miette::{IntoDiagnostic, WrapErr}; +use rattler_conda_types::{PackageUrl, RepoDataRecord}; +use reqwest_middleware::ClientWithMiddleware; +use std::path::Path; +use url::Url; + +use crate::{ + CacheMetrics, CompressedMapping, DerivePurls, MappingByChannel, MappingError, MappingLocation, + MappingMap, PurlSource, +}; + +/// Struct with a mapping of channel names to their respective mapping locations +/// location could be a remote url or local file. +/// +/// This struct caches the mapping internally. +#[derive(Debug)] +pub struct CustomMapping { + pub mapping: MappingMap, + mapping_value: AsyncCell, +} + +impl CustomMapping { + /// Create a new `CustomMapping` with the specified mapping. + pub fn new(mapping: MappingMap) -> Self { + Self { + mapping, + mapping_value: Default::default(), + } + } + + /// Fetch the custom mapping from the server or load from the local + pub async fn fetch_custom_mapping( + &self, + client: &ClientWithMiddleware, + ) -> miette::Result { + self.mapping_value + .get_or_try_init(async { + let mut mapping_url_to_name: MappingByChannel = Default::default(); + + for (name, url) in self.mapping.iter() { + // Fetch the mapping from the server or from the local + + match url { + MappingLocation::Url(url) => { + let mapping_by_name = match url.scheme() { + "file" => { + let file_path = url.to_file_path().map_err(|_| { + miette::miette!("{} is not a valid file url", url) + })?; + fetch_mapping_from_path(&file_path)? + } + _ => fetch_mapping_from_url(client, url).await?, + }; + + mapping_url_to_name.insert(name.to_string(), mapping_by_name); + } + MappingLocation::Path(path) => { + let mapping_by_name = fetch_mapping_from_path(path)?; + + mapping_url_to_name.insert(name.to_string(), mapping_by_name); + } + MappingLocation::Memory(mapping) => { + mapping_url_to_name.insert(name.to_string(), mapping.clone()); + } + } + } + + Ok(mapping_url_to_name) + }) + .await + .cloned() + } +} + +async fn fetch_mapping_from_url( + client: &ClientWithMiddleware, + url: &Url, +) -> miette::Result { + let response = client + .get(url.clone()) + .send() + .await + .into_diagnostic() + .context(format!( + "failed to download pypi mapping from {} location", + url.as_str() + ))?; + + if !response.status().is_success() { + return Err(miette::miette!( + "Could not request mapping located at {:?}", + url.as_str() + )); + } + + let mapping_by_name = response.json().await.into_diagnostic().context(format!( + "failed to parse pypi name mapping located at {}. Please make sure that it's a valid json", + url + ))?; + + Ok(mapping_by_name) +} + +fn fetch_mapping_from_path(path: &Path) -> miette::Result { + let file = fs_err::File::open(path) + .into_diagnostic() + .context(format!("failed to open file {}", path.display()))?; + let reader = std::io::BufReader::new(file); + let mapping_by_name = serde_json::from_reader(reader) + .into_diagnostic() + .context(format!( + "failed to parse pypi name mapping located at {}. Please make sure that it's a valid json", + path.display() + ))?; + + Ok(mapping_by_name) +} + +/// THis is a client that uses a custom in memory mapping to derive purls. +#[derive(Default)] +pub(crate) struct CustomMappingClient { + mapping: MappingByChannel, +} + +impl CustomMappingClient { + /// Returns the mapping associated with a channel. + fn get_channel_mapping(&self, channel: &str) -> Option<&CompressedMapping> { + self.mapping.get(channel.trim_end_matches('/')) + } + + /// Returns true if this mapping applies to the given record. + pub fn is_mapping_for_record(&self, record: &RepoDataRecord) -> bool { + record + .channel + .as_ref() + .is_some_and(|channel| self.get_channel_mapping(channel).is_some()) + } +} + +impl From for CustomMappingClient { + fn from(value: MappingByChannel) -> Self { + Self { mapping: value } + } +} + +impl DerivePurls for CustomMappingClient { + async fn derive_purls( + &self, + record: &RepoDataRecord, + _cache_metrics: &CacheMetrics, + ) -> Result>, MappingError> { + let Some(channel) = record.channel.as_ref() else { + return Ok(None); + }; + + // See if the mapping contains the channel + let Some(custom_mapping) = self.get_channel_mapping(channel) else { + return Ok(None); + }; + + // Find the mapping for this particular record + match custom_mapping.get(record.package_record.name.as_normalized()) { + // The record is in the mapping, and it has a pypi name + Some(Some(mapped_name)) => { + let purl = PackageUrl::builder(String::from("pypi"), mapped_name.to_string()) + .with_qualifier("source", PurlSource::ProjectDefinedMapping.as_str()) + .expect("valid qualifier"); + let built_purl = purl.build().expect("valid pypi package url"); + Ok(Some(vec![built_purl])) + } + Some(None) => { + // The record is in the mapping, but it has no pypi name + Ok(Some(vec![])) + } + None => { + // The record is not in the mapping + Ok(None) + } + } + } +} diff --git a/crates/pypi_mapping/src/custom_pypi_mapping.rs b/crates/pypi_mapping/src/custom_pypi_mapping.rs deleted file mode 100644 index bed1531c32..0000000000 --- a/crates/pypi_mapping/src/custom_pypi_mapping.rs +++ /dev/null @@ -1,192 +0,0 @@ -use std::{ - collections::{BTreeSet, HashMap}, - path::Path, - sync::Arc, -}; - -use miette::{Context, IntoDiagnostic}; -use rattler_conda_types::{PackageUrl, RepoDataRecord}; -use reqwest_middleware::ClientWithMiddleware; -use url::Url; - -use super::{ - build_pypi_purl_from_package_record, is_conda_forge_record, prefix_pypi_name_mapping, - CustomMapping, PurlSource, Reporter, -}; - -pub type CompressedMapping = HashMap>; - -pub async fn fetch_mapping_from_url( - client: &ClientWithMiddleware, - url: &Url, -) -> miette::Result { - let response = client - .get(url.clone()) - .send() - .await - .into_diagnostic() - .context(format!( - "failed to download pypi mapping from {} location", - url.as_str() - ))?; - - if !response.status().is_success() { - return Err(miette::miette!( - "Could not request mapping located at {:?}", - url.as_str() - )); - } - - let mapping_by_name = response.json().await.into_diagnostic().context(format!( - "failed to parse pypi name mapping located at {}. Please make sure that it's a valid json", - url - ))?; - - Ok(mapping_by_name) -} - -pub fn fetch_mapping_from_path(path: &Path) -> miette::Result { - let file = fs_err::File::open(path) - .into_diagnostic() - .context(format!("failed to open file {}", path.display()))?; - let reader = std::io::BufReader::new(file); - let mapping_by_name = serde_json::from_reader(reader) - .into_diagnostic() - .context(format!( - "failed to parse pypi name mapping located at {}. Please make sure that it's a valid json", - path.display() - ))?; - - Ok(mapping_by_name) -} - -/// Amend the records with pypi purls if they are not present yet. -pub async fn amend_pypi_purls( - client: &ClientWithMiddleware, - mapping_url: &CustomMapping, - conda_packages: impl IntoIterator, - reporter: Option>, -) -> miette::Result<()> { - let mut conda_packages = conda_packages.into_iter().collect::>(); - - for package in conda_packages.iter_mut() { - package.channel = package - .channel - .as_ref() - .map(|c| c.trim_end_matches('/').to_string()); - } - let packages_for_prefix_mapping: Vec<_> = conda_packages - .iter() - .filter_map(|record| record.channel.as_ref().map(|channel| (channel, record))) - .filter(|(channel, _)| !mapping_url.mapping.contains_key(*channel)) - .map(|(_, p)| (**p).clone()) - .collect(); - - let custom_mapping = mapping_url.fetch_custom_mapping(client).await?; - - // When all requested channels are present in the custom_mapping, we don't have - // to request from the prefix_mapping. This will avoid fetching unwanted - // URLs, e.g. behind corporate firewalls - if packages_for_prefix_mapping.is_empty() { - for record in conda_packages { - amend_pypi_purls_for_record(record, &custom_mapping)?; - } - } else { - let prefix_mapping = prefix_pypi_name_mapping::conda_pypi_name_mapping( - client, - &packages_for_prefix_mapping, - reporter, - ) - .await?; - let compressed_mapping = - prefix_pypi_name_mapping::conda_pypi_name_compressed_mapping(client).await?; - - for record in conda_packages { - let Some(channel) = record.channel.as_ref() else { - continue; - }; - if !mapping_url.mapping.contains_key(channel) { - prefix_pypi_name_mapping::amend_pypi_purls_for_record( - record, - &prefix_mapping, - &compressed_mapping, - )?; - } else { - amend_pypi_purls_for_record(record, &custom_mapping)?; - } - } - } - - Ok(()) -} - -/// Updates the specified repodata record to include an optional PyPI package -/// name if it is missing. -/// -/// This function guesses the PyPI package name from the conda package name if -/// the record refers to a conda-forge package. -fn amend_pypi_purls_for_record( - record: &mut RepoDataRecord, - custom_mapping: &HashMap, -) -> miette::Result<()> { - // If the package already has a pypi name we can stop here. - if record - .package_record - .purls - .as_ref() - .is_some_and(|vec| vec.iter().any(|p| p.package_type() == "pypi")) - { - return Ok(()); - } - - let mut not_a_pypi = false; - let mut purls = Vec::new(); - - // we verify if we have package channel and name in user provided mapping - if let Some(mapped_channel) = record - .channel - .as_ref() - .and_then(|channel| custom_mapping.get(channel)) - { - if let Some(mapped_name) = mapped_channel.get(record.package_record.name.as_normalized()) { - // we have a pypi name for it so we record a purl - if let Some(name) = mapped_name { - let purl = PackageUrl::builder(String::from("pypi"), name.to_string()) - .with_qualifier("source", PurlSource::ProjectDefinedMapping.as_str()) - .expect("valid qualifier"); - - purls.push(purl.build().expect("valid pypi package url")); - } else { - not_a_pypi = true; - } - } - } - - // if we don't have it and it's channel is conda-forge - // we assume that it's the pypi package - if !not_a_pypi && purls.is_empty() && is_conda_forge_record(record) { - // Convert the conda package names to pypi package names. If the conversion - // fails we just assume that its not a valid python package. - if let Some(purl) = build_pypi_purl_from_package_record(&record.package_record) { - purls.push(purl); - } - } - - let package_purls = record - .package_record - .purls - .get_or_insert_with(BTreeSet::new); - package_purls.extend(purls); - - Ok(()) -} - -pub fn _amend_only_custom_pypi_purls( - conda_packages: &mut [RepoDataRecord], - custom_mapping: &HashMap, -) -> miette::Result<()> { - for record in conda_packages.iter_mut() { - amend_pypi_purls_for_record(record, custom_mapping)?; - } - Ok(()) -} diff --git a/crates/pypi_mapping/src/lib.rs b/crates/pypi_mapping/src/lib.rs index afa73c41e3..bb873712b6 100644 --- a/crates/pypi_mapping/src/lib.rs +++ b/crates/pypi_mapping/src/lib.rs @@ -2,96 +2,46 @@ use std::{ collections::{BTreeSet, HashMap}, path::PathBuf, str::FromStr, - sync::Arc, + sync::{Arc, Mutex}, + time::{Duration, Instant}, }; -use async_once_cell::OnceCell as AsyncCell; -use custom_pypi_mapping::fetch_mapping_from_path; +use futures::{stream::FuturesUnordered, StreamExt}; use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions}; +use itertools::Itertools; +use miette::IntoDiagnostic; use pixi_config::get_cache_dir; -use rattler_conda_types::{PackageRecord, PackageUrl, RepoDataRecord}; +use rattler_conda_types::{PackageUrl, RepoDataRecord}; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; +use thiserror::Error; +use tokio::sync::Semaphore; +use tracing::Instrument; use url::Url; -use crate::custom_pypi_mapping::fetch_mapping_from_url; +mod custom_mapping; +pub mod prefix; +mod reporter; -pub mod custom_pypi_mapping; -pub mod prefix_pypi_name_mapping; +pub use custom_mapping::CustomMapping; +pub use reporter::Reporter; -pub trait Reporter: Send + Sync { - fn download_started(&self, package: &RepoDataRecord, total: usize); - fn download_finished(&self, package: &RepoDataRecord, total: usize); - fn download_failed(&self, package: &RepoDataRecord, total: usize); -} +use crate::custom_mapping::CustomMappingClient; + +/// A compressed mapping is a mapping of a package name to a potential pypi +/// name. +pub type CompressedMapping = HashMap>; pub type ChannelName = String; pub type MappingMap = HashMap; -pub type MappingByChannel = HashMap>>; +pub type MappingByChannel = HashMap; #[derive(Debug, Clone, PartialEq, Eq)] pub enum MappingLocation { Path(PathBuf), Url(Url), -} - -#[derive(Debug)] -/// Struct with a mapping of channel names to their respective mapping locations -/// location could be a remote url or local file -pub struct CustomMapping { - pub mapping: MappingMap, - mapping_value: AsyncCell, -} - -impl CustomMapping { - /// Create a new `CustomMapping` with the specified mapping. - pub fn new(mapping: MappingMap) -> Self { - Self { - mapping, - mapping_value: Default::default(), - } - } - - /// Fetch the custom mapping from the server or load from the local - pub async fn fetch_custom_mapping( - &self, - client: &ClientWithMiddleware, - ) -> miette::Result { - self.mapping_value - .get_or_try_init(async { - let mut mapping_url_to_name: MappingByChannel = Default::default(); - - for (name, url) in self.mapping.iter() { - // Fetch the mapping from the server or from the local - - match url { - MappingLocation::Url(url) => { - let mapping_by_name = match url.scheme() { - "file" => { - let file_path = url.to_file_path().map_err(|_| { - miette::miette!("{} is not a valid file url", url) - })?; - fetch_mapping_from_path(&file_path)? - } - _ => fetch_mapping_from_url(client, url).await?, - }; - - mapping_url_to_name.insert(name.to_string(), mapping_by_name); - } - MappingLocation::Path(path) => { - let mapping_by_name = fetch_mapping_from_path(path)?; - - mapping_url_to_name.insert(name.to_string(), mapping_by_name); - } - } - } - - Ok(mapping_url_to_name) - }) - .await - .cloned() - } + Memory(CompressedMapping), } /// This enum represents the source of mapping @@ -135,79 +85,341 @@ impl PurlSource { } } -pub async fn amend_pypi_purls( +/// Returns `true` if the specified record refers to a conda-forge package. +pub fn is_conda_forge_record(record: &RepoDataRecord) -> bool { + record + .channel + .as_ref() + .and_then(|channel| Url::from_str(channel).ok()) + .is_some_and(|u| is_conda_forge_url(&u)) +} + +/// Returns `true` if the specified url refers to a conda-forge channel. +pub fn is_conda_forge_url(url: &Url) -> bool { + url.path().starts_with("/conda-forge") +} + +/// The mapping client implements the logic to derive purls for conda packages. +/// Internally it uses a combination of sources and also allows overwriting the +/// sources for particular channels. +/// +/// For more information see: +/// - [`prefix::CompressedMappingClient`] +/// - [`prefix::HashMappingClient`] +/// - [`CondaForgeVerbatim`] +#[derive(Clone)] +pub struct MappingClient { client: ClientWithMiddleware, - mapping_source: &MappingSource, - conda_packages: impl IntoIterator, - reporter: Option>, -) -> miette::Result<()> { - // Construct a client with a retry policy and local caching - let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); - let retry_strategy = RetryTransientMiddleware::new_with_policy(retry_policy); - let cache_strategy = Cache(HttpCache { - mode: CacheMode::Default, - manager: CACacheManager { - path: get_cache_dir() - .expect("missing cache directory") - .join(pixi_consts::consts::CONDA_PYPI_MAPPING_CACHE_DIR), - }, - options: HttpCacheOptions::default(), - }); - - let client = ClientBuilder::from_client(client) - .with(cache_strategy) - .with(retry_strategy) - .build(); - - match mapping_source { - MappingSource::Custom(mapping) => { - custom_pypi_mapping::amend_pypi_purls(&client, mapping, conda_packages, reporter) - .await?; + compressed_mapping: prefix::CompressedMappingClient, + hash_mapping: prefix::HashMappingClient, +} + +pub struct MappingClientBuilder { + client: ClientWithMiddleware, + compressed_mapping: prefix::CompressedMappingClientBuilder, + hash_mapping: prefix::HashMappingClientBuilder, +} + +impl MappingClientBuilder { + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn with_concurrency_limit(self, limit: Arc) -> Self { + Self { + compressed_mapping: self + .compressed_mapping + .with_concurrency_limit(limit.clone()), + hash_mapping: self.hash_mapping.with_concurrency_limit(limit), + ..self } - MappingSource::Prefix => { - prefix_pypi_name_mapping::amend_pypi_purls(&client, conda_packages, reporter).await?; + } + + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn set_concurrency_limit(&mut self, limit: Arc) -> &mut Self { + self.compressed_mapping.set_concurrency_limit(limit.clone()); + self.hash_mapping.set_concurrency_limit(limit); + self + } + + /// Finish the construction of the client and return it. + pub fn finish(self) -> MappingClient { + MappingClient { + client: self.client, + compressed_mapping: self.compressed_mapping.finish(), + hash_mapping: self.hash_mapping.finish(), } - MappingSource::Disabled => { - for record in conda_packages { - if let Some(purl) = prefix_pypi_name_mapping::assume_conda_is_pypi(None, record) { - record - .package_record - .purls - .get_or_insert_with(BTreeSet::new) - .insert(purl); + } +} + +#[derive(Debug, Error)] +pub enum MappingError { + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + Reqwest(#[from] reqwest_middleware::Error), +} + +impl MappingClient { + /// Construct a new `MappingClientBuilder` with the provided `Client`. + pub fn builder(client: ClientWithMiddleware) -> MappingClientBuilder { + // Construct a client with a retry policy and local caching + let retry_policy = ExponentialBackoff::builder().build_with_max_retries(3); + let retry_strategy = RetryTransientMiddleware::new_with_policy(retry_policy); + let cache_strategy = Cache(HttpCache { + mode: CacheMode::Default, + manager: CACacheManager { + path: get_cache_dir() + .expect("missing cache directory") + .join(pixi_consts::consts::CONDA_PYPI_MAPPING_CACHE_DIR), + }, + options: HttpCacheOptions::default(), + }); + + let client = ClientBuilder::from_client(client) + .with(cache_strategy) + .with(retry_strategy) + .build(); + + MappingClientBuilder { + client: client.clone(), + compressed_mapping: prefix::CompressedMappingClient::builder(client.clone()), + hash_mapping: prefix::HashMappingClient::builder(client), + } + } + + /// Given a set of `RepoDataRecord`s, amend the purls for each record. + pub async fn amend_purls( + &self, + mapping_source: &MappingSource, + conda_packages: impl IntoIterator, + reporter: Option>, + ) -> miette::Result<()> { + let start = Instant::now(); + + // Collect the records into a vec so we can iterate multiple times. + let mut records = conda_packages.into_iter().collect_vec(); + + // Normalize the channel names by removing the trailing slash + for package in records.iter_mut() { + package.channel = package + .channel + .as_ref() + .map(|c| c.trim_end_matches('/').to_string()); + } + + // Discard all records for which we already have pypi purls. + records.retain(|record| !has_pypi_purl(record)); + + let metrics = CacheMetrics::default(); + + // Fetch custom mapped channels if any. + let custom_mappings = if let MappingSource::Custom(mapping_url) = mapping_source { + Some(CustomMappingClient::from( + mapping_url.fetch_custom_mapping(&self.client).await?, + )) + } else { + None + }; + + let mut amend_futures = FuturesUnordered::new(); + let total_records = records.len(); + for record in records.into_iter() { + let reporter = reporter.clone(); + let custom_mappings = &custom_mappings; + let cache_metrics = &metrics; + let file_name = record.file_name.clone(); + let derive_purls_future = async move { + if let Some(reporter) = reporter.as_deref() { + reporter.download_started(record, total_records); + } + + let derived_purls = if matches!(mapping_source, MappingSource::Disabled) { + Ok(None) + } else if let Some(custom_mappings) = custom_mappings + .as_ref() + .filter(|mapping| mapping.is_mapping_for_record(record)) + { + custom_mappings.derive_purls(record, cache_metrics).await + } else { + self.derive_purls_from_clients(record, cache_metrics).await + }; + + match derived_purls { + Ok(derived_purls) => { + if let Some(reporter) = reporter.as_deref() { + reporter.download_finished(record, total_records); + } + Ok((record, derived_purls)) + } + Err(err) => { + if let Some(reporter) = reporter.as_deref() { + reporter.download_failed(record, total_records); + } + Err(err) + } } } + .instrument(tracing::info_span!("derive_purl", record = file_name)); + + // Add all futures to the futures queue to ensure all can run concurrently. + amend_futures.push(derive_purls_future); } + + let mut amended_records = 0; + let mut total_records = 0; + while let Some(next) = amend_futures.next().await { + let (record, mut derived_purls) = next.into_diagnostic()?; + + // As a last resort use the verbatim conda-forge purls. + if derived_purls.is_none() { + derived_purls = CondaForgeVerbatim + .derive_purls(record, &metrics) + .await + .into_diagnostic()?; + } + + if let Some(derived_purls) = derived_purls { + amend_purls(record, derived_purls); + amended_records += 1; + } + + total_records += 1; + } + + drop(amend_futures); + + let duration = start.elapsed(); + let data = metrics + .data + .into_inner() + .expect("locking shouldnt fail in this case"); + tracing::info!( + "Amended {} out of {} records with purls in {:?}. {} cache hits and {} cache misses ({}%).", + amended_records, + total_records, + Duration::from_millis(duration.as_millis() as u64), + data.cache_hits, data.cache_misses, + if data.cache_hits == 0 && data.cache_misses == 0 { + 100.0 + } else { + ((data.cache_hits as f64)/((data.cache_misses + data.cache_hits) as f64) * 10000.0).round() / 100.0 + }, + ); + + Ok(()) } - Ok(()) + async fn derive_purls_from_clients( + &self, + record: &RepoDataRecord, + cache_metrics: &CacheMetrics, + ) -> Result>, MappingError> { + // Try to get the purls from the hash mapping. + let mut purls = self + .hash_mapping + .derive_purls(record, cache_metrics) + .await?; + + // Otherwise try from the compressed mapping + if purls.is_none() { + purls = self + .compressed_mapping + .derive_purls(record, cache_metrics) + .await?; + } + + Ok(purls) + } } -/// Returns `true` if the specified record refers to a conda-forge package. -pub fn is_conda_forge_record(record: &RepoDataRecord) -> bool { +/// Returns true if the record has a pypi purl. +fn has_pypi_purl(record: &RepoDataRecord) -> bool { record - .channel + .package_record + .purls .as_ref() - .and_then(|channel| Url::from_str(channel).ok()) - .is_some_and(|u| is_conda_forge_url(&u)) + .is_some_and(|vec| vec.iter().any(|p| p.package_type() == "pypi")) } -/// Returns `true` if the specified url refers to a conda-forge channel. -pub fn is_conda_forge_url(url: &Url) -> bool { - url.path().starts_with("/conda-forge") +/// Adds the specified purls to the `purls` field of the record. +fn amend_purls(record: &mut RepoDataRecord, purls: impl IntoIterator) { + let record_purls = record + .package_record + .purls + .get_or_insert_with(BTreeSet::new); + for purl in purls { + record_purls.insert(purl); + } } -/// Build a purl for a `PackageRecord` -/// it will return a purl in this format -/// `pkg:pypi/aiofiles` -pub fn build_pypi_purl_from_package_record(package_record: &PackageRecord) -> Option { - let name = pep508_rs::PackageName::from_str(package_record.name.as_source()).ok(); - let version = pep440_rs::Version::from_str(&package_record.version.as_str()).ok(); - if let (Some(name), Some(_)) = (name, version) { +/// A trait that is implemented for clients that can derive a purl from a +/// particular record. +trait DerivePurls { + /// Derives purls from the given record. + /// + /// Returns `None` if no purls could be derived. Note that this is different + /// from `Some(vec[])` which would indicate that purls could be derived but + /// there were simply none. + async fn derive_purls( + &self, + record: &RepoDataRecord, + _cache_metrics: &CacheMetrics, + ) -> Result>, MappingError>; +} + +/// A struct that provides derived package urls for conda-forge records where +/// the name of the package is just assumed to be the pypi name. +/// +/// This is a fallback for when the mapping is not available. +pub struct CondaForgeVerbatim; + +impl DerivePurls for CondaForgeVerbatim { + async fn derive_purls( + &self, + record: &RepoDataRecord, + _cache_metrics: &CacheMetrics, + ) -> Result>, MappingError> { + if !is_conda_forge_record(record) { + return Ok(None); + } + + // Try to convert the name and version into pep440/pep508 compliant versions. + let (Some(name), Some(_version)) = ( + pep508_rs::PackageName::from_str(record.package_record.name.as_source()).ok(), + pep440_rs::Version::from_str(&record.package_record.version.as_str()).ok(), + ) else { + // If we cannot convert the name or version, we cannot build a purl. + return Ok(Some(vec![])); + }; + + // Build the purl let purl = PackageUrl::builder(String::from("pypi"), name.to_string()); let built_purl = purl.build().expect("valid pypi package url"); - return Some(built_purl); + Ok(Some(vec![built_purl])) } +} + +#[derive(Default)] +pub struct CacheMetrics { + data: Mutex, +} + +impl CacheMetrics { + pub fn record_request_response(&self, response: &reqwest::Response) { + let cache_header = response.headers().get("x-cache"); + if cache_header.and_then(|h| h.to_str().ok()) == Some("HIT") { + let mut data = self.data.lock().unwrap(); + data.cache_hits += 1; + } else { + let mut data = self.data.lock().unwrap(); + data.cache_misses += 1; + tracing::debug!("Cache miss on '{}' ({})", response.url(), response.status()); + } + } +} - None +#[derive(Default)] +struct CacheMetricsData { + cache_hits: usize, + cache_misses: usize, } diff --git a/crates/pypi_mapping/src/prefix/compressed_mapping_client.rs b/crates/pypi_mapping/src/prefix/compressed_mapping_client.rs new file mode 100644 index 0000000000..c10c460ae7 --- /dev/null +++ b/crates/pypi_mapping/src/prefix/compressed_mapping_client.rs @@ -0,0 +1,150 @@ +use std::sync::Arc; + +use async_once_cell::OnceCell; +use rattler_conda_types::{PackageUrl, RepoDataRecord}; +use reqwest_middleware::ClientWithMiddleware; +use tokio::sync::Semaphore; +use url::Url; + +use crate::{ + is_conda_forge_record, CacheMetrics, CompressedMapping, DerivePurls, MappingError, PurlSource, +}; + +const COMPRESSED_MAPPING: &str = + "https://raw.githubusercontent.com/prefix-dev/parselmouth/main/files/compressed_mapping.json"; + +/// A client for fetching and caching the compressed mapping from the +/// parselmouth github repository. +/// +/// This mapping provides a mapping from the conda-forge package names to their +/// pypi counterparts, or `None` if the package is not a pypi package. +/// +/// The downside of this client is that it only contains information for +/// conda-forge packages. +#[derive(Clone)] +pub struct CompressedMappingClient { + inner: Arc, +} + +pub struct CompressedMappingClientBuilder { + client: ClientWithMiddleware, + limit: Option>, +} + +struct CompressedMappingClientInner { + client: ClientWithMiddleware, + mapping: OnceCell, + limit: Option>, +} + +impl CompressedMappingClientBuilder { + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn with_concurrency_limit(self, limit: Arc) -> Self { + Self { + limit: Some(limit), + ..self + } + } + + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn set_concurrency_limit(&mut self, limit: Arc) -> &mut Self { + self.limit = Some(limit); + self + } + + /// Finish the construction of the client and return it. + pub fn finish(self) -> CompressedMappingClient { + CompressedMappingClient { + inner: Arc::new(CompressedMappingClientInner { + client: self.client, + limit: self.limit, + mapping: OnceCell::new(), + }), + } + } +} + +impl CompressedMappingClient { + /// Constructs a new `HashMappingClient` with the provided + /// `ClientWithMiddleware`. + pub fn builder(client: ClientWithMiddleware) -> CompressedMappingClientBuilder { + CompressedMappingClientBuilder { + client, + limit: None, + } + } + + /// Fetches the compressed mapping and caches it to ensure that any + /// subsequent request does not hit the network. + pub async fn get_mapping( + &self, + cache_metrics: &CacheMetrics, + ) -> Result<&CompressedMapping, reqwest_middleware::Error> { + let inner = &self.inner; + inner + .mapping + .get_or_try_init(async { + let compressed_mapping_url = Url::parse(COMPRESSED_MAPPING) + .expect("COMPRESSED_MAPPING static variable should be valid"); + let _permit = match inner.limit.as_ref() { + Some(limit) => Some( + limit + .clone() + .acquire_owned() + .await + .expect("failed to acquire semaphore permit"), + ), + None => None, + }; + let response = inner + .client + .get(compressed_mapping_url) + .send() + .await? + .error_for_status() + .map_err(reqwest_middleware::Error::from)?; + + cache_metrics.record_request_response(&response); + + response.json().await.map_err(Into::into) + }) + .await + } +} + +impl DerivePurls for CompressedMappingClient { + async fn derive_purls( + &self, + record: &RepoDataRecord, + cache_metrics: &CacheMetrics, + ) -> Result>, MappingError> { + // If the record does not refer to a conda-forge mapping we can skip it + if !is_conda_forge_record(record) { + return Ok(None); + } + + // Get the mapping from the server + let mapping = self.get_mapping(cache_metrics).await?; + + // Determine the mapping for the record + let Some(potential_pypi_name) = mapping.get(record.package_record.name.as_normalized()) + else { + return Ok(None); + }; + + // If the mapping is empty, there are no purls. + let Some(pypi_name) = potential_pypi_name else { + return Ok(Some(vec![])); + }; + + // Construct the purl + let purl = PackageUrl::builder(String::from("pypi"), pypi_name) + .with_qualifier("source", PurlSource::CompressedMapping.as_str()) + .expect("valid qualifier"); + let built_purl = purl.build().expect("valid pypi package url"); + + Ok(Some(vec![built_purl])) + } +} diff --git a/crates/pypi_mapping/src/prefix/hash_mapping_client.rs b/crates/pypi_mapping/src/prefix/hash_mapping_client.rs new file mode 100644 index 0000000000..9d3026cf65 --- /dev/null +++ b/crates/pypi_mapping/src/prefix/hash_mapping_client.rs @@ -0,0 +1,296 @@ +use std::{ + collections::HashMap, + sync::{Arc, Weak}, +}; + +use dashmap::{DashMap, Entry}; +use rattler_conda_types::{PackageUrl, RepoDataRecord}; +use rattler_digest::Sha256Hash; +use reqwest::StatusCode; +use reqwest_middleware::ClientWithMiddleware; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use tokio::sync::{broadcast, Semaphore}; + +use crate::{CacheMetrics, DerivePurls, MappingError, PurlSource}; + +const STORAGE_URL: &str = "https://conda-mapping.prefix.dev"; +const HASH_DIR: &str = "hash-v0"; + +/// Information about the pypi package a specific conda package is mapped to. +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct PackagePypiMapping { + pub pypi_normalized_names: Option>, + pub versions: Option>, + pub conda_name: String, + pub package_name: String, + pub direct_url: Option>, +} + +#[derive(Debug, Error)] +pub enum HashMappingClientError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Reqwest(#[from] reqwest_middleware::Error), +} + +impl From for HashMappingClientError { + fn from(err: reqwest::Error) -> Self { + HashMappingClientError::Reqwest(err.into()) + } +} + +impl From for MappingError { + fn from(value: HashMappingClientError) -> Self { + match value { + HashMappingClientError::Io(err) => MappingError::IoError(err), + HashMappingClientError::Reqwest(err) => MappingError::Reqwest(err), + } + } +} + +/// A client for fetching and caching the pypi name mapping from . +/// +/// This provides a hash based mapping to pypi packages which should yield +/// perfect results. The downside is that maybe not all packages are in the map. +/// Therefor, this client should always be combined with another fallback +/// client. +/// +/// This client can be shared between multiple tasks. Individual requests are +/// coalesced. The client can cheaply be cloned. +#[derive(Clone)] +pub struct HashMappingClient { + inner: Arc, +} + +struct HashMappingClientInner { + client: ClientWithMiddleware, + entries: DashMap>>, + limit: Option>, +} + +/// An entry that is either pending or has been fetched. +#[derive(Clone)] +enum PendingOrFetched { + Pending(Weak>), + Fetched(T), +} + +/// A builder for a `HashMappingClient`. +pub struct HashMappingClientBuilder { + client: ClientWithMiddleware, + limit: Option>, +} + +impl HashMappingClientBuilder { + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn with_concurrency_limit(self, limit: Arc) -> Self { + Self { + limit: Some(limit), + ..self + } + } + + /// Sets the concurrency limit for the client. This is useful to limit the + /// maximum number of concurrent requests. + pub fn set_concurrency_limit(&mut self, limit: Arc) -> &mut Self { + self.limit = Some(limit); + self + } + + /// Finish the construction of the client and return it. + pub fn finish(self) -> HashMappingClient { + HashMappingClient { + inner: Arc::new(HashMappingClientInner { + client: self.client, + entries: DashMap::new(), + limit: self.limit, + }), + } + } +} + +impl HashMappingClient { + /// Constructs a new `HashMappingClient` with the provided + /// `ClientWithMiddleware`. + pub fn builder(client: ClientWithMiddleware) -> HashMappingClientBuilder { + HashMappingClientBuilder { + client, + limit: None, + } + } + + /// Fetches the pypi name mapping and caches it to ensure that any + /// subsequent request does not hit the network. + pub async fn get_mapping( + &self, + sha256: Sha256Hash, + cache_metrics: &CacheMetrics, + ) -> Result, HashMappingClientError> { + self.inner.get_mapping(sha256, cache_metrics).await + } +} + +impl HashMappingClientInner { + /// Fetches the pypi name mapping and caches it to ensure that any + /// subsequent request does not hit the network. + pub async fn get_mapping( + &self, + sha256: Sha256Hash, + cache_metrics: &CacheMetrics, + ) -> Result, HashMappingClientError> { + let sender = match self.entries.entry(sha256) { + Entry::Vacant(entry) => { + // Construct a sender so other tasks can subscribe + let (sender, _) = broadcast::channel(1); + let sender = Arc::new(sender); + + // Modify the current entry to the pending entry, this is an atomic operation + // because who holds the entry holds mutable access. + entry.insert(PendingOrFetched::Pending(Arc::downgrade(&sender))); + + sender + } + Entry::Occupied(mut entry) => { + let subdir = entry.get(); + match subdir { + PendingOrFetched::Pending(sender) => { + let sender = sender.upgrade(); + + if let Some(sender) = sender { + // Create a receiver before we drop the entry. While we hold on to + // the entry we have exclusive access to it, this means the task + // currently fetching the mapping will not be able to store a value + // until we drop the entry. + // By creating the receiver here we ensure that we are subscribed + // before the other tasks sends a value over the channel. + let mut receiver = sender.subscribe(); + + // Explicitly drop the entry, so we don't block any other tasks. + drop(entry); + drop(sender); + + // The sender is still active, so we can wait for the subdir to be + // created. + return match receiver.recv().await { + Ok(subdir) => Ok(subdir), + Err(_) => { + // If this happens the sender was dropped. + Err(std::io::Error::new( + std::io::ErrorKind::Interrupted, + "a coalesced request failed", + ) + .into()) + } + }; + } else { + // Construct a sender so other tasks can subscribe + let (sender, _) = broadcast::channel(1); + let sender = Arc::new(sender); + + // Modify the current entry to the pending entry, this is an atomic + // operation because who holds the entry holds mutable access. + entry.insert(PendingOrFetched::Pending(Arc::downgrade(&sender))); + + sender + } + } + PendingOrFetched::Fetched(records) => return Ok(records.clone()), + } + } + }; + + // At this point we have exclusive write access to this specific entry. All + // other tasks will find a pending entry and will wait for the records + // to become available. + // + // Let's start by fetching the record. If an error occurs we immediately return + // the error. This will drop the sender and all other waiting tasks will + // receive an error. + let mapping = { + let _permit = match self.limit.as_ref() { + Some(limit) => Some( + limit + .clone() + .acquire_owned() + .await + .expect("failed to acquire semaphore permit"), + ), + None => None, + }; + try_fetch_mapping(&self.client, &sha256, cache_metrics).await? + }; + + // Store the fetched files in the entry. + self.entries + .insert(sha256, PendingOrFetched::Fetched(mapping.clone())); + + // Send the records to all waiting tasks. We don't care if there are no + // receivers, so we drop the error. + let _ = sender.send(mapping.clone()); + + Ok(mapping) + } +} + +async fn try_fetch_mapping( + client: &ClientWithMiddleware, + sha256: &Sha256Hash, + cache_metrics: &CacheMetrics, +) -> Result, HashMappingClientError> { + let hash_str = format!("{:x}", sha256); + let url = format!("{STORAGE_URL}/{HASH_DIR}/{}", hash_str); + + // Fetch the mapping from the server + let response = client.get(&url).send().await?; + + cache_metrics.record_request_response(&response); + + // If no mapping was found for the hash, return None. + if response.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + + // Otherwise convert the response to a Package struct + let package = response.json().await?; + + Ok(Some(package)) +} + +impl DerivePurls for HashMappingClient { + async fn derive_purls( + &self, + record: &RepoDataRecord, + cache_metrics: &CacheMetrics, + ) -> Result>, MappingError> { + // Get the hash from the record, if there is no sha we cannot derive purls + let Some(sha256) = record.package_record.sha256 else { + return Ok(None); + }; + + // Fetch the mapping from the server, or return None if it doesn't exist + let Some(mapped_package) = self.get_mapping(sha256, cache_metrics).await? else { + return Ok(None); + }; + + // Get the pypi names from the mapping + let Some(mapped_name) = mapped_package.pypi_normalized_names else { + // If there are no pypi names, there are no purls + return Ok(Some(vec![])); + }; + + Ok(Some( + mapped_name + .into_iter() + .map(|pypi_name| { + let purl = PackageUrl::builder(String::from("pypi"), pypi_name) + .with_qualifier("source", PurlSource::HashMapping.as_str()) + .expect("valid qualifier"); + purl.build().expect("valid pypi package url") + }) + .collect(), + )) + } +} diff --git a/crates/pypi_mapping/src/prefix/mod.rs b/crates/pypi_mapping/src/prefix/mod.rs new file mode 100644 index 0000000000..e0e6e62e50 --- /dev/null +++ b/crates/pypi_mapping/src/prefix/mod.rs @@ -0,0 +1,7 @@ +mod compressed_mapping_client; +mod hash_mapping_client; + +pub use compressed_mapping_client::{CompressedMappingClient, CompressedMappingClientBuilder}; +pub use hash_mapping_client::{ + HashMappingClient, HashMappingClientBuilder, HashMappingClientError, +}; diff --git a/crates/pypi_mapping/src/prefix_pypi_name_mapping.rs b/crates/pypi_mapping/src/prefix_pypi_name_mapping.rs deleted file mode 100644 index 7a31c44140..0000000000 --- a/crates/pypi_mapping/src/prefix_pypi_name_mapping.rs +++ /dev/null @@ -1,266 +0,0 @@ -use std::{ - collections::{BTreeSet, HashMap}, - sync::Arc, -}; - -use futures::{stream::FuturesUnordered, StreamExt}; -use itertools::Itertools; -use miette::{IntoDiagnostic, WrapErr}; -use rattler_conda_types::{PackageUrl, RepoDataRecord}; -use rattler_digest::Sha256Hash; -use reqwest::StatusCode; -use reqwest_middleware::ClientWithMiddleware; -use serde::{Deserialize, Serialize}; -use tokio::sync::Semaphore; -use url::Url; - -use super::{ - build_pypi_purl_from_package_record, custom_pypi_mapping, is_conda_forge_record, PurlSource, - Reporter, -}; - -const STORAGE_URL: &str = "https://conda-mapping.prefix.dev"; -const HASH_DIR: &str = "hash-v0"; -const COMPRESSED_MAPPING: &str = - "https://raw.githubusercontent.com/prefix-dev/parselmouth/main/files/compressed_mapping.json"; - -#[derive(Debug, Deserialize, Serialize, Clone)] -pub struct Package { - pypi_normalized_names: Option>, - versions: Option>, - conda_name: String, - package_name: String, - direct_url: Option>, -} - -async fn try_fetch_single_mapping( - client: &ClientWithMiddleware, - sha256: &Sha256Hash, -) -> miette::Result> { - let hash_str = format!("{:x}", sha256); - - // Fetch the mapping from the server - let response = client - .get(format!("{STORAGE_URL}/{HASH_DIR}/{}", hash_str)) - .send() - .await - .into_diagnostic() - .context("failed to download pypi name mapping")?; - - // If no mapping was found for the hash, return None. - if response.status() == StatusCode::NOT_FOUND { - return Ok(None); - } - - // Otherwise convert the response to a Package struct - let package: Package = response - .json() - .await - .into_diagnostic() - .context("failed to parse pypi name mapping")?; - - Ok(Some(package)) -} - -/// Downloads and caches the conda-forge conda-to-pypi name mapping. -pub async fn conda_pypi_name_mapping<'r>( - client: &ClientWithMiddleware, - conda_packages: impl IntoIterator, - reporter: Option>, -) -> miette::Result> { - let filtered_packages = conda_packages - .into_iter() - // because we later skip adding purls for packages - // that have purls - // here we only filter packages that don't them - // to save some requests - .filter(|package| package.package_record.purls.is_none()) - .filter_map(|package| { - package - .package_record - .sha256 - .as_ref() - .map(|hash| (package, *hash)) - }) - .collect_vec(); - - let total_records = filtered_packages.len(); - let mut pending_futures = FuturesUnordered::new(); - let concurrency_limit = Arc::new(Semaphore::new(100)); - for (record, hash) in filtered_packages { - if let Some(reporter) = &reporter { - reporter.download_started(record, total_records); - } - - let client = client.clone(); - let reporter = reporter.clone(); - let concurrency_limit = concurrency_limit.clone(); - - // Create a future that fetches the mapping for the record's hash concurrently - // with the rest of the requests. - pending_futures.push(async move { - // Acquire a permit to limit the number of concurrent requests - let _permit = concurrency_limit - .acquire_owned() - .await - .expect("semaphore error"); - - // Fetch the mapping by the hash of the record. - let result = try_fetch_single_mapping(&client, &hash).await; - - // Report the result to the reporter - if let Some(reporter) = reporter { - match &result { - Ok(_) => reporter.download_finished(record, total_records), - Err(_) => reporter.download_failed(record, total_records), - } - } - - match result { - Ok(Some(package)) => Ok(Some((hash, package))), - Ok(None) => Ok(None), - Err(e) => Err(e), - } - }); - } - - let mut result_map = HashMap::with_capacity(total_records); - while let Some(result) = pending_futures.next().await { - match result { - Ok(Some((hash, package))) => { - // Add the mapping to the result hashmap - result_map.insert(hash, package); - } - Ok(None) => { - // If no mapping was found, do nothing. - } - Err(e) => { - // If an error occurred, bail out,. - return Err(e); - } - } - } - - Ok(result_map) -} - -/// Downloads and caches prefix.dev conda-pypi mapping. -pub async fn conda_pypi_name_compressed_mapping( - client: &ClientWithMiddleware, -) -> miette::Result>> { - let compressed_mapping_url = - Url::parse(COMPRESSED_MAPPING).expect("COMPRESSED_MAPPING static variable should be valid"); - - custom_pypi_mapping::fetch_mapping_from_url(client, &compressed_mapping_url).await -} - -/// Amend the records with pypi purls if they are not present yet. -pub async fn amend_pypi_purls( - client: &ClientWithMiddleware, - conda_packages: impl IntoIterator, - reporter: Option>, -) -> miette::Result<()> { - let conda_packages = conda_packages.into_iter().collect_vec(); - let conda_mapping = - conda_pypi_name_mapping(client, conda_packages.iter().map(|p| *p as &_), reporter).await?; - let compressed_mapping = conda_pypi_name_compressed_mapping(client).await?; - - for record in conda_packages { - amend_pypi_purls_for_record(record, &conda_mapping, &compressed_mapping)?; - } - - Ok(()) -} - -/// Updates the specified repodata record to include an optional PyPI package -/// name if it is missing. -/// -/// This function resolves package pypi purl using the following approach: -/// 1. Tries to find a mapping by package hash. -/// 2. If the mapping is missing, tries to find a .json mapping by name. -/// 3. If both mappings are missing and it's a conda-forge record, assumes it is -/// a PyPI package. -pub fn amend_pypi_purls_for_record( - record: &mut RepoDataRecord, - mapping_by_hash: &HashMap, - compressed_mapping: &HashMap>, -) -> miette::Result<()> { - // If we already figured out the pypi purls, we can skip this record. - if record.package_record.purls.is_some() { - return Ok(()); - } - - let mut purls = None; - - // if package have a hash - if let Some(sha256) = record.package_record.sha256 { - // we look into our mapping by it's hash - if let Some(mapped_name) = mapping_by_hash.get(&sha256) { - let purls = purls.get_or_insert_with(Vec::new); - - // if we have pypi names in mapping - // we populate purls for it - if let Some(pypi_names) = &mapped_name.pypi_normalized_names { - for pypi_name in pypi_names { - let purl = PackageUrl::builder(String::from("pypi"), pypi_name) - .with_qualifier("source", PurlSource::HashMapping.as_str()) - .expect("valid qualifier"); - let built_purl = purl.build().expect("valid pypi package url"); - // Push the value into the vector - purls.push(built_purl); - } - } - // we don't have a mapping for it's hash yet - // so we are looking into our .json map by name - } - } - - // if we don't have a mapping for it's hash yet - // or this package is missing sha256 - // we are looking into our .json map by name - if let Some(possible_mapped_name) = - compressed_mapping.get(record.package_record.name.as_normalized()) - { - if purls.is_none() && is_conda_forge_record(record) { - let purls = purls.get_or_insert_with(Vec::new); - - // if we have a pypi name for it - // we record the purl - if let Some(mapped_name) = possible_mapped_name { - let purl = PackageUrl::builder(String::from("pypi"), mapped_name) - .with_qualifier("source", PurlSource::CompressedMapping.as_str()) - .expect("valid qualifier"); - let built_purl = purl.build().expect("valid pypi package url"); - purls.push(built_purl); - } - } - } - - // package is not in our mapping yet - // so we assume that it is the same as the one from conda-forge - if let Some(purl) = assume_conda_is_pypi(purls.as_ref(), record) { - purls.get_or_insert_with(Vec::new).push(purl); - } - - // If we have found some purls we overwrite whatever was there before. - if let Some(purls) = purls { - record.package_record.purls = Some(BTreeSet::from_iter(purls)); - } - - Ok(()) -} - -/// Try to assume that the conda-forge package is a PyPI package and return a -/// purl. -pub fn assume_conda_is_pypi( - purls: Option<&Vec>, - record: &RepoDataRecord, -) -> Option { - if purls.is_none() && is_conda_forge_record(record) { - // Convert the conda package names to pypi package names. If the conversion - // fails we just assume that its not a valid python package. - build_pypi_purl_from_package_record(&record.package_record) - } else { - None - } -} diff --git a/crates/pypi_mapping/src/reporter.rs b/crates/pypi_mapping/src/reporter.rs new file mode 100644 index 0000000000..c7a0c61b4f --- /dev/null +++ b/crates/pypi_mapping/src/reporter.rs @@ -0,0 +1,7 @@ +use rattler_conda_types::RepoDataRecord; + +pub trait Reporter: Send + Sync { + fn download_started(&self, package: &RepoDataRecord, total: usize); + fn download_finished(&self, package: &RepoDataRecord, total: usize); + fn download_failed(&self, package: &RepoDataRecord, total: usize); +} diff --git a/pixi.lock b/pixi.lock index 393697d7d6..9b0c02298c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -8,7 +8,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/linux-64/binutils-2.43-h4852527_4.conda - conda: https://prefix.dev/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda - conda: https://prefix.dev/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda @@ -20,9 +20,10 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/cargo-nextest-0.9.92-h6c30b3d_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/cffconvert-2.0.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_7.conda - - conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_8.conda + - conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_8.conda - conda: https://prefix.dev/conda-forge/noarch/cli-ui-0.17.2-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -32,7 +33,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/docopt-0.6.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://prefix.dev/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda - conda: https://prefix.dev/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda @@ -44,6 +45,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda - conda: https://prefix.dev/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_8.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -53,7 +57,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_8.conda - conda: https://prefix.dev/conda-forge/linux-64/libcurl-8.12.1-h332b0f4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libev-4.33-hd590300_2.conda @@ -72,7 +76,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.1-hf672d98_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - conda: https://prefix.dev/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda @@ -85,7 +89,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/mimalloc-3.0.1-h18b520e_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.0-hff13881_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.1-hff13881_0.conda - conda: https://prefix.dev/conda-forge/linux-64/mypy-1.11.2-py312h66e93f0_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda @@ -97,6 +101,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/psutil-7.0.0-py312h66e93f0_0.conda - conda: https://prefix.dev/conda-forge/linux-64/py-rattler-0.9.0-py312hda17c39_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pydantic-2.6.4-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/pydantic-core-2.16.3-py312h4b3b743_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda @@ -122,7 +127,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/schema-0.7.7-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://prefix.dev/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/linux-64/tbb-2022.0.0-hceb3a55_0.conda @@ -136,17 +141,18 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/unidecode-1.3.8-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xz-5.6.4-hbcc6ac9_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xz-gpl-tools-5.6.4-hbcc6ac9_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xz-tools-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_1.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda linux-aarch64: - conda: https://prefix.dev/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/binutils-2.43-hf1166c9_4.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/binutils_impl_linux-aarch64-2.43-h4c662bb_4.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/binutils_linux-aarch64-2.43-hf1166c9_4.conda @@ -158,6 +164,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/cargo-nextest-0.9.92-ha3529ed_0.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/cffconvert-2.0.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/cffi-1.17.1-py312hac81daf_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/cli-ui-0.17.2-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda @@ -168,7 +175,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/docopt-0.6.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/fortran-compiler-1.9.0-h25a59a9_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gcc-13.3.0-h8a56e6e_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gcc_impl_linux-aarch64-13.3.0-h80a1502_2.conda @@ -180,6 +187,9 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/gxx-13.3.0-h8a56e6e_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gxx_impl_linux-aarch64-13.3.0-h7eae8fb_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gxx_linux-aarch64-13.3.0-h2864abd_8.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda @@ -205,7 +215,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/libnghttp2-1.64.0-hc8609a4_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnsl-2.0.1-h31becfc_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libsanitizer-13.3.0-ha58e236_2.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libssh2-1.11.1-ha41c0db_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/noarch/libstdcxx-devel_linux-aarch64-13.3.0-h0c07274_102.conda @@ -226,6 +236,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/psutil-7.0.0-py312hb2c0f52_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/py-rattler-0.9.0-py312he723553_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pydantic-2.6.4-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/pydantic-core-2.16.3-py312h3abe38b_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda @@ -251,7 +262,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/schema-0.7.7-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-aarch64-2.17-h68829e0_18.conda - conda: https://prefix.dev/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/tbump-6.9.0-pyhd8ed1ab_0.tar.bz2 @@ -264,16 +275,17 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/unidecode-1.3.8-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xz-5.6.4-h2dbfc1b_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xz-gpl-tools-5.6.4-h2dbfc1b_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xz-tools-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/yaml-0.2.5-hf897c2e_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/zstandard-0.23.0-py312hb2c0f52_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_1.conda osx-64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.1.0-py312h5861a67_2.conda - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda - conda: https://prefix.dev/conda-forge/osx-64/c-ares-1.34.4-hf13058a_0.conda @@ -284,12 +296,13 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/cctools_osx-64-1010.6-hd19c6af_3.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/cffconvert-2.0.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/cffi-1.17.1-py312hf857d28_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_7.conda - - conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_8.conda + - conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_8.conda - conda: https://prefix.dev/conda-forge/osx-64/clang_impl_osx-64-18.1.8-h6a44ed1_23.conda - conda: https://prefix.dev/conda-forge/osx-64/clang_osx-64-18.1.8-h7e5c614_23.conda - - conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_8.conda - conda: https://prefix.dev/conda-forge/osx-64/clangxx_impl_osx-64-18.1.8-h4b7810f_23.conda - conda: https://prefix.dev/conda-forge/osx-64/clangxx_osx-64-18.1.8-h7e5c614_23.conda - conda: https://prefix.dev/conda-forge/noarch/cli-ui-0.17.2-pyhd8ed1ab_0.tar.bz2 @@ -303,13 +316,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/docopt-0.6.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/fortran-compiler-1.9.0-h02557f8_0.conda - conda: https://prefix.dev/conda-forge/osx-64/gfortran-13.2.0-h2c809b3_1.conda - conda: https://prefix.dev/conda-forge/osx-64/gfortran_impl_osx-64-13.2.0-h2bc304d_3.conda - conda: https://prefix.dev/conda-forge/osx-64/gfortran_osx-64-13.2.0-h18f7dce_1.conda - conda: https://prefix.dev/conda-forge/osx-64/git-2.47.1-pl5321h0e333bc_0.conda - conda: https://prefix.dev/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -319,7 +335,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://prefix.dev/conda-forge/osx-64/ld64-951.9-h4e51db5_3.conda - conda: https://prefix.dev/conda-forge/osx-64/ld64_osx-64-951.9-h33512f0_3.conda - - conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_8.conda - conda: https://prefix.dev/conda-forge/osx-64/libcurl-8.12.1-h5dec5d8_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libcxx-19.1.7-hf95d169_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libcxx-devel-18.1.8-h7c275be_7.conda @@ -336,7 +352,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-devel-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libnghttp2-1.64.0-hc7306c3_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libssh2-1.11.1-h3dc7d44_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libxml2-2.13.6-hebb159f_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda @@ -358,6 +374,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/psutil-7.0.0-py312h01d7ebd_0.conda - conda: https://prefix.dev/conda-forge/osx-64/py-rattler-0.9.0-py312hcef750c_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pydantic-2.6.4-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/pydantic-core-2.16.3-py312h1b0e595_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda @@ -384,7 +401,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/osx-64/sigtool-0.1.3-h88f4db0_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/osx-64/tapi-1300.6.5-h390ca13_0.conda - conda: https://prefix.dev/conda-forge/noarch/tbump-6.9.0-pyhd8ed1ab_0.tar.bz2 @@ -397,17 +414,18 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/unidecode-1.3.8-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/xz-5.6.4-h357f2ed_0.conda - conda: https://prefix.dev/conda-forge/osx-64/xz-gpl-tools-5.6.4-h357f2ed_0.conda - conda: https://prefix.dev/conda-forge/osx-64/xz-tools-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/zlib-1.3.1-hd23fc13_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.23.0-py312h01d7ebd_1.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_1.conda osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.1.0-py312hde4cb15_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda - conda: https://prefix.dev/conda-forge/osx-arm64/c-ares-1.34.4-h5505292_0.conda @@ -418,12 +436,13 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/cctools_osx-arm64-1010.6-h3b4f5d3_3.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/cffconvert-2.0.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/cffi-1.17.1-py312h0fad829_0.conda - conda: https://prefix.dev/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_7.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_8.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clang_impl_osx-arm64-18.1.8-h2ae9ea5_23.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clang_osx-arm64-18.1.8-h07b0088_23.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx_impl_osx-arm64-18.1.8-h555f467_23.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx_osx-arm64-18.1.8-h07b0088_23.conda - conda: https://prefix.dev/conda-forge/noarch/cli-ui-0.17.2-pyhd8ed1ab_0.tar.bz2 @@ -437,13 +456,16 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/docopt-0.6.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/fortran-compiler-1.9.0-h5692697_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gfortran-13.2.0-h1ca8e4b_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gfortran_impl_osx-arm64-13.2.0-h252ada1_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gfortran_osx-arm64-13.2.0-h57527a5_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/git-2.47.1-pl5321hd71a902_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -453,7 +475,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ld64-951.9-h4c6efb1_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ld64_osx-arm64-951.9-hb6b49e2_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcurl-8.12.1-h73640d1_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-devel-18.1.8-h6dc3340_7.conda @@ -471,7 +493,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-devel-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libnghttp2-1.64.0-h6d7220d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libssh2-1.11.1-h9cc3647_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libxml2-2.13.6-h178c5d8_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda @@ -493,6 +515,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/psutil-7.0.0-py312hea69d52_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/py-rattler-0.9.0-py312hf9bd80e_0.conda + - conda: https://prefix.dev/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://prefix.dev/conda-forge/noarch/pydantic-2.6.4-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/pydantic-core-2.16.3-py312h5280bc4_0.conda - conda: https://prefix.dev/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda @@ -519,7 +542,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/sigtool-0.1.3-h44b9a77_0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda - conda: https://prefix.dev/conda-forge/noarch/tbump-6.9.0-pyhd8ed1ab_0.tar.bz2 @@ -532,17 +555,18 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/unidecode-1.3.8-pyh29332c3_1.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/xz-5.6.4-h9a6d368_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/xz-gpl-tools-5.6.4-h9a6d368_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/xz-tools-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zlib-1.3.1-h8359307_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.23.0-py312hea69d52_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_1.conda win-64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.1.0-py312h275cf98_2.conda - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda - conda: https://prefix.dev/conda-forge/win-64/c-compiler-1.9.0-hcfcfb64_0.conda @@ -565,7 +589,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/docopt-0.6.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang-19.1.7-hbeecb71_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang_impl_win-64-19.1.7-h719f0c7_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang_win-64-19.1.7-h719f0c7_0.conda @@ -587,10 +611,10 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libllvm19-19.1.7-h3089188_1.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-devel-5.6.4-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.6-he286e8c_0.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://prefix.dev/conda-forge/win-64/lld-19.1.7-hd91d51b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/lld-20.1.0-hd91d51b_0.conda - conda: https://prefix.dev/conda-forge/win-64/llvm-tools-19.1.7-h2a44499_1.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -628,7 +652,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/schema-0.7.7-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tabulate-0.9.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/noarch/tbump-6.9.0-pyhd8ed1ab_0.tar.bz2 - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda @@ -652,8 +676,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/xz-tools-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h7606c53_1.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.6-h0ea2cb4_0.conda + - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h4389bb4_1.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_1.conda docs: channels: - url: https://prefix.dev/conda-forge/ @@ -666,7 +690,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - conda: https://prefix.dev/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/cairo-1.18.2-h3394656_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/cairosvg-2.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda @@ -683,9 +707,12 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/fontconfig-2.15.0-h7e30c49_1.conda - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://prefix.dev/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda + - conda: https://prefix.dev/conda-forge/linux-64/freetype-2.13.3-h48d6fc4_0.conda - conda: https://prefix.dev/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/linux-64/git-cliff-2.8.0-h5550c08_10.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -709,7 +736,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-devel-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libpng-1.6.47-h943b412_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.1-hf672d98_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda @@ -726,7 +753,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-get-deps-0.2.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda @@ -758,13 +785,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/verspec-0.1.0-pyh29332c3_2.conda - conda: https://prefix.dev/conda-forge/linux-64/watchdog-6.0.0-py312h7900ff3_0.conda - conda: https://prefix.dev/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/linux-64/xorg-libice-1.1.2-hb9d3cd8_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/xorg-libsm-1.2.5-he73a12e_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.11-h4f16b4b_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxext-1.3.6-hb9d3cd8_0.conda @@ -774,6 +801,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/xz-tools-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_1.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda linux-aarch64: - conda: https://prefix.dev/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2 @@ -782,7 +810,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/brotli-python-1.1.0-py312h6f74592_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/bzip2-1.0.8-h68df207_7.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ca-certificates-2025.1.31-hcefe29a_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/cairo-1.18.2-h83712da_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/cairo-1.18.4-h83712da_0.conda - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/cairosvg-2.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda @@ -799,9 +827,12 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/fontconfig-2.15.0-h8dda3cd_1.conda - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://prefix.dev/conda-forge/linux-aarch64/freetype-2.12.1-hf0a5ef3_2.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/freetype-2.13.3-he93130f_0.conda - conda: https://prefix.dev/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/git-cliff-2.8.0-hb07fc06_10.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/icu-75.1-hf9b3779_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -825,7 +856,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-devel-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnsl-2.0.1-h31becfc_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libpng-1.6.47-hec79eb8_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libssh2-1.11.1-ha41c0db_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-ng-14.2.0-hf1166c9_2.conda @@ -842,7 +873,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-get-deps-0.2.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ncurses-6.5-ha32ae93_3.conda @@ -874,13 +905,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-h194ca79_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/verspec-0.1.0-pyh29332c3_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/watchdog-6.0.0-py312h8025657_0.conda - conda: https://prefix.dev/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libice-1.1.2-h86ecc28_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libsm-1.2.5-h0808dbd_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libx11-1.8.11-hca56bd8_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libsm-1.2.6-h0808dbd_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libx11-1.8.12-hca56bd8_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libxau-1.0.12-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libxdmcp-1.1.5-h57736b2_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libxext-1.3.6-h57736b2_0.conda @@ -890,6 +921,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/xz-tools-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/yaml-0.2.5-hf897c2e_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/zstandard-0.23.0-py312hb2c0f52_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_1.conda osx-64: - conda: https://prefix.dev/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda @@ -897,7 +929,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/brotli-python-1.1.0-py312h5861a67_2.conda - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda - conda: https://prefix.dev/conda-forge/osx-64/ca-certificates-2025.1.31-h8857fd0_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/cairo-1.18.2-h950ec3b_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/cairosvg-2.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda @@ -914,9 +946,12 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/fontconfig-2.15.0-h37eeddb_1.conda - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://prefix.dev/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda + - conda: https://prefix.dev/conda-forge/osx-64/freetype-2.13.3-h40dfd5c_0.conda - conda: https://prefix.dev/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/osx-64/git-cliff-2.8.0-h0c57a20_10.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -937,7 +972,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-devel-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libpng-1.6.47-h3c4a55f_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libssh2-1.11.1-h3dc7d44_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libtiff-4.7.0-hb77a491_3.conda - conda: https://prefix.dev/conda-forge/osx-64/libwebp-base-1.5.0-h6cf52b4_0.conda @@ -950,7 +985,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-get-deps-0.2.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -982,7 +1017,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/verspec-0.1.0-pyh29332c3_2.conda - conda: https://prefix.dev/conda-forge/osx-64/watchdog-6.0.0-py312h01d7ebd_0.conda - conda: https://prefix.dev/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -993,6 +1028,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/xz-tools-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/yaml-0.2.5-h0d85af4_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.23.0-py312h01d7ebd_1.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_1.conda osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda @@ -1000,7 +1036,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/brotli-python-1.1.0-py312hde4cb15_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ca-certificates-2025.1.31-hf0a4a13_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/cairo-1.18.2-h6a3b0d2_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/cairosvg-2.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda @@ -1017,9 +1053,12 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/fontconfig-2.15.0-h1383a14_1.conda - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://prefix.dev/conda-forge/osx-arm64/freetype-2.12.1-hadb7bae_2.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/freetype-2.13.3-h1d14073_0.conda - conda: https://prefix.dev/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/git-cliff-2.8.0-h195ea4e_10.conda + - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda @@ -1040,7 +1079,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-devel-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libpng-1.6.47-h3783ad8_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libssh2-1.11.1-h9cc3647_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libtiff-4.7.0-h551f018_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libwebp-base-1.5.0-h2471fea_0.conda @@ -1053,7 +1092,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-get-deps-0.2.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda @@ -1085,7 +1124,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/verspec-0.1.0-pyh29332c3_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/watchdog-6.0.0-py312hea69d52_0.conda - conda: https://prefix.dev/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda @@ -1096,6 +1135,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/xz-tools-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/yaml-0.2.5-h3422bc3_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.23.0-py312hea69d52_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_1.conda win-64: - conda: https://prefix.dev/conda-forge/win-64/_openmp_mutex-4.5-2_gnu.conda @@ -1104,7 +1144,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/brotli-python-1.1.0-py312h275cf98_2.conda - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda - conda: https://prefix.dev/conda-forge/win-64/ca-certificates-2025.1.31-h56e8100_0.conda - - conda: https://prefix.dev/conda-forge/win-64/cairo-1.18.2-h5782bbf_1.conda + - conda: https://prefix.dev/conda-forge/win-64/cairo-1.18.4-h5782bbf_0.conda - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/cairosvg-2.7.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/certifi-2025.1.31-pyhd8ed1ab_0.conda @@ -1121,7 +1161,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/fontconfig-2.15.0-h765892d_1.conda - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - - conda: https://prefix.dev/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda + - conda: https://prefix.dev/conda-forge/win-64/freetype-2.13.3-h0b5ce68_0.conda - conda: https://prefix.dev/conda-forge/noarch/ghp-import-2.1.0-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/win-64/git-cliff-2.8.0-hde8316a_10.conda - conda: https://prefix.dev/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda @@ -1148,7 +1188,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-devel-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/libpng-1.6.47-had7236b_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libssh2-1.11.1-he619c9f_0.conda - conda: https://prefix.dev/conda-forge/win-64/libtiff-4.7.0-h797046b_3.conda - conda: https://prefix.dev/conda-forge/win-64/libwebp-base-1.5.0-h3b0e114_0.conda @@ -1162,7 +1202,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-1.6.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-get-deps-0.2.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mkdocs-redirects-1.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/openjpeg-2.5.3-h4d64b90_0.conda @@ -1207,8 +1247,8 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/xz-tools-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h7606c53_1.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.6-h0ea2cb4_0.conda + - conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h4389bb4_1.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_1.conda lint: channels: - url: https://prefix.dev/conda-forge/ @@ -1226,15 +1266,15 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - conda: https://prefix.dev/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - conda: https://prefix.dev/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_7.conda - - conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_8.conda + - conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_8.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda - conda: https://prefix.dev/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda - conda: https://prefix.dev/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://prefix.dev/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda - conda: https://prefix.dev/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda @@ -1247,14 +1287,14 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_8.conda - conda: https://prefix.dev/conda-forge/linux-64/icu-75.1-he02047a_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - conda: https://prefix.dev/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/krb5-1.21.3-h659f571_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda - - conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_7.conda + - conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_8.conda - conda: https://prefix.dev/conda-forge/linux-64/libcurl-8.12.1-h332b0f4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libev-4.33-hd590300_2.conda @@ -1273,7 +1313,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.1-hf672d98_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - conda: https://prefix.dev/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda @@ -1287,12 +1327,12 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/mimalloc-3.0.1-h18b520e_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.0-hff13881_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.1-hff13881_0.conda - conda: https://prefix.dev/conda-forge/linux-64/mypy-1.11.2-py312h66e93f0_0.conda - conda: https://prefix.dev/conda-forge/noarch/mypy_extensions-1.0.0-pyha770c72_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://prefix.dev/conda-forge/noarch/nodeenv-1.9.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/nodejs-22.12.0-hf235a45_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/nodejs-22.13.0-hf235a45_0.conda - conda: https://prefix.dev/conda-forge/linux-64/openssl-3.4.1-h7b32b05_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - conda: https://prefix.dev/conda-forge/linux-64/pcre2-10.44-hba22ea6_2.conda @@ -1318,13 +1358,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml-0.18.10-py312h66e93f0_0.conda - conda: https://prefix.dev/conda-forge/linux-64/ruamel.yaml.clib-0.2.8-py312h66e93f0_1.conda - - conda: https://prefix.dev/conda-forge/linux-64/ruff-0.9.9-py312hf79aa60_0.conda + - conda: https://prefix.dev/conda-forge/linux-64/ruff-0.9.10-py312hf79aa60_0.conda - conda: https://prefix.dev/conda-forge/linux-64/rust-1.84.0-h1a8d7c4_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-src-1.84.0-unix_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-std-x86_64-unknown-linux-gnu-1.84.0-h2c6d0dc_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/linux-64/shellcheck-0.10.0-ha770c72_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://prefix.dev/conda-forge/linux-64/taplo-0.9.3-h53e704d_1.conda - conda: https://prefix.dev/conda-forge/linux-64/tbb-2022.0.0-hceb3a55_0.conda @@ -1362,7 +1402,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/fortran-compiler-1.9.0-h25a59a9_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gcc-13.3.0-h8a56e6e_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gcc_impl_linux-aarch64-13.3.0-h80a1502_2.conda @@ -1375,7 +1415,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/gxx_impl_linux-aarch64-13.3.0-h7eae8fb_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/gxx_linux-aarch64-13.3.0-h2864abd_8.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/icu-75.1-hf9b3779_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/kernel-headers_linux-aarch64-4.18.0-h05a177a_18.conda @@ -1399,7 +1439,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/libnghttp2-1.64.0-hc8609a4_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnsl-2.0.1-h31becfc_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libsanitizer-13.3.0-ha58e236_2.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libssh2-1.11.1-ha41c0db_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/noarch/libstdcxx-devel_linux-aarch64-13.3.0-h0c07274_102.conda @@ -1440,13 +1480,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ruamel.yaml-0.18.10-py312hb2c0f52_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ruamel.yaml.clib-0.2.8-py312hb2c0f52_1.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/ruff-0.9.9-py312hee76d2e_0.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/ruff-0.9.10-py312hee76d2e_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/rust-1.84.0-h21fc29f_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-src-1.84.0-unix_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-std-aarch64-unknown-linux-gnu-1.84.0-hbe8e118_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/shellcheck-0.10.0-h8af1aa0_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-aarch64-2.17-h68829e0_18.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/taplo-0.9.3-h112f5b8_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-h194ca79_0.conda @@ -1475,11 +1515,11 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/cctools_osx-64-1010.6-hd19c6af_3.conda - conda: https://prefix.dev/conda-forge/osx-64/cffi-1.17.1-py312hf857d28_0.conda - conda: https://prefix.dev/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_7.conda - - conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_8.conda + - conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_8.conda - conda: https://prefix.dev/conda-forge/osx-64/clang_impl_osx-64-18.1.8-h6a44ed1_23.conda - conda: https://prefix.dev/conda-forge/osx-64/clang_osx-64-18.1.8-h7e5c614_23.conda - - conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_8.conda - conda: https://prefix.dev/conda-forge/osx-64/clangxx_impl_osx-64-18.1.8-h4b7810f_23.conda - conda: https://prefix.dev/conda-forge/osx-64/clangxx_osx-64-18.1.8-h7e5c614_23.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -1490,7 +1530,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/fortran-compiler-1.9.0-h02557f8_0.conda - conda: https://prefix.dev/conda-forge/osx-64/gfortran-13.2.0-h2c809b3_1.conda - conda: https://prefix.dev/conda-forge/osx-64/gfortran_impl_osx-64-13.2.0-h2bc304d_3.conda @@ -1498,14 +1538,14 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/git-2.47.1-pl5321h0e333bc_0.conda - conda: https://prefix.dev/conda-forge/osx-64/gmp-6.3.0-hf036a51_2.conda - conda: https://prefix.dev/conda-forge/osx-64/icu-75.1-h120a0e1_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda - conda: https://prefix.dev/conda-forge/osx-64/krb5-1.21.3-h37d8d59_0.conda - conda: https://prefix.dev/conda-forge/osx-64/ld64-951.9-h4e51db5_3.conda - conda: https://prefix.dev/conda-forge/osx-64/ld64_osx-64-951.9-h33512f0_3.conda - - conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_7.conda + - conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_8.conda - conda: https://prefix.dev/conda-forge/osx-64/libcurl-8.12.1-h5dec5d8_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libcxx-19.1.7-hf95d169_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libcxx-devel-18.1.8-h7c275be_7.conda @@ -1522,7 +1562,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-devel-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libnghttp2-1.64.0-hc7306c3_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libssh2-1.11.1-h3dc7d44_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libuv-1.50.0-h4cb831e_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libxml2-2.13.6-hebb159f_0.conda @@ -1564,14 +1604,14 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml-0.18.10-py312h01d7ebd_0.conda - conda: https://prefix.dev/conda-forge/osx-64/ruamel.yaml.clib-0.2.8-py312h3d0f464_1.conda - - conda: https://prefix.dev/conda-forge/osx-64/ruff-0.9.9-py312ha54e1fc_0.conda + - conda: https://prefix.dev/conda-forge/osx-64/ruff-0.9.10-py312ha54e1fc_0.conda - conda: https://prefix.dev/conda-forge/osx-64/rust-1.84.0-h34a2095_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-src-1.84.0-unix_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-std-x86_64-apple-darwin-1.84.0-h38e4360_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/osx-64/shellcheck-0.10.0-h7dd6a17_0.conda - conda: https://prefix.dev/conda-forge/osx-64/sigtool-0.1.3-h88f4db0_0.tar.bz2 - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/tapi-1300.6.5-h390ca13_0.conda - conda: https://prefix.dev/conda-forge/osx-64/taplo-0.9.3-hf3953a5_1.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda @@ -1600,11 +1640,11 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/cctools_osx-arm64-1010.6-h3b4f5d3_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/cffi-1.17.1-py312h0fad829_0.conda - conda: https://prefix.dev/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_7.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_8.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clang_impl_osx-arm64-18.1.8-h2ae9ea5_23.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clang_osx-arm64-18.1.8-h07b0088_23.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx_impl_osx-arm64-18.1.8-h555f467_23.conda - conda: https://prefix.dev/conda-forge/osx-arm64/clangxx_osx-arm64-18.1.8-h07b0088_23.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda @@ -1615,7 +1655,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/fortran-compiler-1.9.0-h5692697_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gfortran-13.2.0-h1ca8e4b_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gfortran_impl_osx-arm64-13.2.0-h252ada1_3.conda @@ -1623,14 +1663,14 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/git-2.47.1-pl5321hd71a902_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/gmp-6.3.0-h7bae524_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/icu-75.1-hfee45f7_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/isl-0.26-imath32_h347afa1_101.conda - conda: https://prefix.dev/conda-forge/osx-arm64/krb5-1.21.3-h237132a_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ld64-951.9-h4c6efb1_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ld64_osx-arm64-951.9-hb6b49e2_3.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_7.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_8.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcurl-8.12.1-h73640d1_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-devel-18.1.8-h6dc3340_7.conda @@ -1648,7 +1688,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-devel-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libnghttp2-1.64.0-h6d7220d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libssh2-1.11.1-h9cc3647_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libuv-1.50.0-h5505292_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libxml2-2.13.6-h178c5d8_0.conda @@ -1690,14 +1730,14 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml-0.18.10-py312hea69d52_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ruamel.yaml.clib-0.2.8-py312h0bf5046_1.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.9.9-py312h31a5b27_0.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.9.10-py312h31a5b27_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/rust-1.84.0-h4ff7c5d_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-src-1.84.0-unix_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-std-aarch64-apple-darwin-1.84.0-hf6ec828_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/shellcheck-0.10.0-hecfb573_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/sigtool-0.1.3-h44b9a77_0.tar.bz2 - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tapi-1300.6.5-h03f4b80_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/taplo-0.9.3-hdf53557_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda @@ -1733,13 +1773,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/distlib-0.3.9-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang-19.1.7-hbeecb71_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang_impl_win-64-19.1.7-h719f0c7_0.conda - conda: https://prefix.dev/conda-forge/win-64/flang_win-64-19.1.7-h719f0c7_0.conda - conda: https://prefix.dev/conda-forge/win-64/fortran-compiler-1.9.0-h95e3450_0.conda - conda: https://prefix.dev/conda-forge/win-64/git-2.47.1-h57928b3_0.conda - - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.6.4-he0c23c2_0.conda @@ -1751,10 +1791,10 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libllvm19-19.1.7-h3089188_1.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-devel-5.6.4-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libxml2-2.13.6-he286e8c_0.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://prefix.dev/conda-forge/win-64/lld-19.1.7-hd91d51b_0.conda + - conda: https://prefix.dev/conda-forge/win-64/lld-20.1.0-hd91d51b_0.conda - conda: https://prefix.dev/conda-forge/win-64/llvm-tools-19.1.7-h2a44499_1.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -1785,13 +1825,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml-0.18.10-py312h4389bb4_0.conda - conda: https://prefix.dev/conda-forge/win-64/ruamel.yaml.clib-0.2.8-py312h4389bb4_1.conda - - conda: https://prefix.dev/conda-forge/win-64/ruff-0.9.9-py312hc33538c_0.conda + - conda: https://prefix.dev/conda-forge/win-64/ruff-0.9.10-py312hc33538c_0.conda - conda: https://prefix.dev/conda-forge/win-64/rust-1.84.0-hf8d6059_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-src-1.84.0-win_0.conda - conda: https://prefix.dev/conda-forge/noarch/rust-std-x86_64-pc-windows-msvc-1.84.0-h17fc481_0.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/win-64/shellcheck-0.10.0-h57928b3_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/taplo-0.9.3-ha073cba_1.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda @@ -1812,7 +1852,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/xz-tools-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/yaml-0.2.5-h8ffe710_2.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.6-h0ea2cb4_0.conda + - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_1.conda pypi-gen: channels: - url: https://prefix.dev/conda-forge/ @@ -1843,7 +1883,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnghttp2-1.64.0-h161d5f1_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.1-hf672d98_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_2.conda @@ -1863,7 +1903,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda @@ -1892,7 +1932,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnghttp2-1.64.0-hc8609a4_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnsl-2.0.1-h31becfc_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libssh2-1.11.1-ha41c0db_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-ng-14.2.0-hf1166c9_2.conda @@ -1912,7 +1952,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/readline-8.2-h8382b9d_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-h194ca79_0.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_1.conda @@ -1936,7 +1976,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/libintl-0.23.1-h27064b9_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libnghttp2-1.64.0-hc7306c3_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libssh2-1.11.1-h3dc7d44_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda @@ -1952,7 +1992,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/readline-8.2-h7cca4af_2.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-64/zstd-1.5.7-h8210216_1.conda @@ -1976,7 +2016,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/libintl-0.23.1-h493aca8_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libnghttp2-1.64.0-h6d7220d_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libssh2-1.11.1-h9cc3647_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda @@ -1992,7 +2032,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/readline-8.2-h1d1bf99_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_1.conda @@ -2007,7 +2047,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libexpat-2.6.4-he0c23c2_0.conda - conda: https://prefix.dev/conda-forge/win-64/libffi-3.4.6-h537db12_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.1-ha4e3fda_0.conda - conda: https://prefix.dev/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda @@ -2018,7 +2058,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/tzdata-2025a-h78e105d_0.conda - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-hbf610ac_24.conda @@ -2032,13 +2072,13 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://prefix.dev/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - conda: https://prefix.dev/conda-forge/linux-64/ca-certificates-2025.1.31-hbcca054_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/jsonschema-3.2.0-pyhd8ed1ab_3.tar.bz2 @@ -2051,7 +2091,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/liblzma-devel-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda @@ -2080,7 +2120,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda @@ -2096,13 +2136,13 @@ environments: linux-aarch64: - conda: https://prefix.dev/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2 - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/bzip2-1.0.8-h68df207_7.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/ca-certificates-2025.1.31-hcefe29a_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/jsonschema-3.2.0-pyhd8ed1ab_3.tar.bz2 @@ -2115,7 +2155,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-devel-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libnsl-2.0.1-h31becfc_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libuuid-2.38.1-hb4cce97_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libxcrypt-4.4.36-h31becfc_1.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda @@ -2144,7 +2184,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/tk-8.6.13-h194ca79_0.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda @@ -2159,13 +2199,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda osx-64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/osx-64/bzip2-1.0.8-hfdf4475_7.conda - conda: https://prefix.dev/conda-forge/osx-64/ca-certificates-2025.1.31-h8857fd0_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/jsonschema-3.2.0-pyhd8ed1ab_3.tar.bz2 @@ -2173,7 +2213,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.4.6-h281671d_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-devel-5.6.4-hd471939_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -2200,7 +2240,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda @@ -2215,13 +2255,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda osx-arm64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/bzip2-1.0.8-h99b78c6_7.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ca-certificates-2025.1.31-hf0a4a13_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/jsonschema-3.2.0-pyhd8ed1ab_3.tar.bz2 @@ -2229,7 +2269,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-devel-5.6.4-h39f12f2_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -2256,7 +2296,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/tk-8.6.13-h5083fa2_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda @@ -2271,13 +2311,13 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda win-64: - conda: https://prefix.dev/conda-forge/noarch/annotated-types-0.7.0-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda + - conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://prefix.dev/conda-forge/win-64/bzip2-1.0.8-h2466b09_7.conda - conda: https://prefix.dev/conda-forge/win-64/ca-certificates-2025.1.31-h56e8100_0.conda - conda: https://prefix.dev/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://prefix.dev/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/jsonschema-3.2.0-pyhd8ed1ab_3.tar.bz2 @@ -2285,7 +2325,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libffi-3.4.6-h537db12_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-devel-5.6.4-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -2310,7 +2350,7 @@ environments: - conda: https://prefix.dev/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda - conda: https://prefix.dev/conda-forge/noarch/six-1.17.0-pyhd8ed1ab_0.conda - - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda + - conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda - conda: https://prefix.dev/conda-forge/win-64/tk-8.6.13-h5226925_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - conda: https://prefix.dev/conda-forge/noarch/tomli-w-1.2.0-pyhd8ed1ab_0.conda @@ -2331,17 +2371,17 @@ environments: - url: https://prefix.dev/conda-forge/ packages: linux-64: - - conda: https://prefix.dev/conda-forge/linux-64/micromamba-2.0.5-1.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-64/micromamba-2.0.7-0.tar.bz2 linux-aarch64: - - conda: https://prefix.dev/conda-forge/linux-aarch64/micromamba-2.0.5-1.tar.bz2 + - conda: https://prefix.dev/conda-forge/linux-aarch64/micromamba-2.0.7-0.tar.bz2 osx-64: - conda: https://prefix.dev/conda-forge/osx-64/libcxx-19.1.7-hf95d169_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/micromamba-2.0.5-1.tar.bz2 + - conda: https://prefix.dev/conda-forge/osx-64/micromamba-2.0.7-0.tar.bz2 osx-arm64: - conda: https://prefix.dev/conda-forge/osx-arm64/libcxx-19.1.7-ha82da77_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/micromamba-2.0.5-1.tar.bz2 + - conda: https://prefix.dev/conda-forge/osx-arm64/micromamba-2.0.7-0.tar.bz2 win-64: - - conda: https://prefix.dev/conda-forge/win-64/micromamba-2.0.5-1.tar.bz2 + - conda: https://prefix.dev/conda-forge/win-64/micromamba-2.0.7-0.tar.bz2 - conda: https://prefix.dev/conda-forge/win-64/ucrt-10.0.22621.0-h57928b3_1.conda - conda: https://prefix.dev/conda-forge/win-64/vc-14.3-hbf610ac_24.conda - conda: https://prefix.dev/conda-forge/win-64/vc14_runtime-14.42.34438-hfd919c2_24.conda @@ -2362,7 +2402,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-64/libgomp-14.2.0-h767d61c_2.conda - conda: https://prefix.dev/conda-forge/linux-64/liblzma-5.6.4-hb9d3cd8_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libmpdec-4.0.0-h4bc722e_0.conda - - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda + - conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libstdcxx-14.2.0-h8f9b012_2.conda - conda: https://prefix.dev/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://prefix.dev/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda @@ -2386,7 +2426,7 @@ environments: - conda: https://prefix.dev/conda-forge/linux-aarch64/libgomp-14.2.0-he277a41_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/liblzma-5.6.4-h86ecc28_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libmpdec-4.0.0-h68df207_0.conda - - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda + - conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_2.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libuuid-2.38.1-hb4cce97_0.conda - conda: https://prefix.dev/conda-forge/linux-aarch64/libzlib-1.3.1-h86ecc28_2.conda @@ -2405,7 +2445,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-64/libffi-3.4.6-h281671d_0.conda - conda: https://prefix.dev/conda-forge/osx-64/liblzma-5.6.4-hd471939_0.conda - conda: https://prefix.dev/conda-forge/osx-64/libmpdec-4.0.0-hfdf4475_0.conda - - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda + - conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda - conda: https://prefix.dev/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://prefix.dev/conda-forge/osx-64/ncurses-6.5-h0622a9a_3.conda - conda: https://prefix.dev/conda-forge/osx-64/openssl-3.4.1-hc426f3f_0.conda @@ -2422,7 +2462,7 @@ environments: - conda: https://prefix.dev/conda-forge/osx-arm64/libffi-3.4.2-h3422bc3_5.tar.bz2 - conda: https://prefix.dev/conda-forge/osx-arm64/liblzma-5.6.4-h39f12f2_0.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libmpdec-4.0.0-h99b78c6_0.conda - - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda + - conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://prefix.dev/conda-forge/osx-arm64/ncurses-6.5-h5e97a16_3.conda - conda: https://prefix.dev/conda-forge/osx-arm64/openssl-3.4.1-h81ee809_0.conda @@ -2439,7 +2479,7 @@ environments: - conda: https://prefix.dev/conda-forge/win-64/libffi-3.4.6-h537db12_0.conda - conda: https://prefix.dev/conda-forge/win-64/liblzma-5.6.4-h2466b09_0.conda - conda: https://prefix.dev/conda-forge/win-64/libmpdec-4.0.0-h2466b09_0.conda - - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda + - conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda - conda: https://prefix.dev/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - conda: https://prefix.dev/conda-forge/win-64/openssl-3.4.1-ha4e3fda_0.conda - conda: https://prefix.dev/conda-forge/win-64/python-3.13.2-h261c0b1_101_cp313.conda @@ -2560,15 +2600,15 @@ packages: license_family: MIT size: 18074 timestamp: 1733247158254 -- conda: https://prefix.dev/conda-forge/noarch/attrs-25.1.0-pyh71513ae_0.conda - sha256: 1f267886522dfb9ae4e5ebbc3135b5eb13cff27bdbfe8d881a4d893459166ab4 - md5: 2cc3f588512f04f3a0c64b4e9bedc02d +- conda: https://prefix.dev/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda + sha256: 99c53ffbcb5dc58084faf18587b215f9ac8ced36bbfb55fa807c00967e419019 + md5: a10d11958cadc13fdb43df75f8b1903f depends: - python >=3.9 license: MIT license_family: MIT - size: 56370 - timestamp: 1737819298139 + size: 57181 + timestamp: 1741918625732 - conda: https://prefix.dev/conda-forge/noarch/babel-2.17.0-pyhd8ed1ab_0.conda sha256: 1c656a35800b7f57f7371605bc6507c8d3ad60fbaaec65876fce7f73df1fc8ac md5: 0a01c169f0ab0f91b26e77a3301fbfe4 @@ -2888,9 +2928,9 @@ packages: license: ISC size: 158690 timestamp: 1738298232550 -- conda: https://prefix.dev/conda-forge/linux-64/cairo-1.18.2-h3394656_1.conda - sha256: de7d0d094e53decc005cb13e527be2635b8f604978da497d4c0d282c7dc08385 - md5: b34c2833a1f56db610aeb27f206d800d +- conda: https://prefix.dev/conda-forge/linux-64/cairo-1.18.4-h3394656_0.conda + sha256: 3bd6a391ad60e471de76c0e9db34986c4b5058587fbf2efa5a7f54645e28c2c7 + md5: 09262e66b19567aff4f592fb53b28760 depends: - __glibc >=2.17,<3.0.a0 - fontconfig >=2.15.0,<3.0a0 @@ -2900,22 +2940,22 @@ packages: - libexpat >=2.6.4,<3.0a0 - libgcc >=13 - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.44,<1.7.0a0 + - libpng >=1.6.47,<1.7.0a0 - libstdcxx >=13 - libxcb >=1.17.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - pixman >=0.44.2,<1.0a0 - - xorg-libice >=1.1.1,<2.0a0 - - xorg-libsm >=1.2.4,<2.0a0 - - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.5,<2.0a0 + - xorg-libx11 >=1.8.11,<2.0a0 - xorg-libxext >=1.3.6,<2.0a0 - - xorg-libxrender >=0.9.11,<0.10.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 license: LGPL-2.1-only or MPL-1.1 - size: 978868 - timestamp: 1733790976384 -- conda: https://prefix.dev/conda-forge/linux-aarch64/cairo-1.18.2-h83712da_1.conda - sha256: 0353e175859c4989251628e4c8f9fb2dc52546b0c031ffe4541eb087ac586573 - md5: e7b46975d2c9a4666da0e9bb8a087f28 + size: 978114 + timestamp: 1741554591855 +- conda: https://prefix.dev/conda-forge/linux-aarch64/cairo-1.18.4-h83712da_0.conda + sha256: 37cfff940d2d02259afdab75eb2dbac42cf830adadee78d3733d160a1de2cc66 + md5: cd55953a67ec727db5dc32b167201aa6 depends: - fontconfig >=2.15.0,<3.0a0 - fonts-conda-ecosystem @@ -2924,22 +2964,22 @@ packages: - libexpat >=2.6.4,<3.0a0 - libgcc >=13 - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.44,<1.7.0a0 + - libpng >=1.6.47,<1.7.0a0 - libstdcxx >=13 - libxcb >=1.17.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - pixman >=0.44.2,<1.0a0 - - xorg-libice >=1.1.1,<2.0a0 - - xorg-libsm >=1.2.4,<2.0a0 - - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.5,<2.0a0 + - xorg-libx11 >=1.8.11,<2.0a0 - xorg-libxext >=1.3.6,<2.0a0 - - xorg-libxrender >=0.9.11,<0.10.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 license: LGPL-2.1-only or MPL-1.1 - size: 980455 - timestamp: 1733791018944 -- conda: https://prefix.dev/conda-forge/osx-64/cairo-1.18.2-h950ec3b_1.conda - sha256: ad8c41650e5a10d9177e9d92652d2bd5fe9eefa095ebd4805835c3f067c0202b - md5: ae293443dff77ba14eab9e9ee68ec833 + size: 966667 + timestamp: 1741554768968 +- conda: https://prefix.dev/conda-forge/osx-64/cairo-1.18.4-h950ec3b_0.conda + sha256: d4297c3a9bcff9add3c5a46c6e793b88567354828bcfdb6fc9f6b1ab34aa4913 + md5: 32403b4ef529a2018e4d8c4f2a719f16 depends: - __osx >=10.13 - fontconfig >=2.15.0,<3.0a0 @@ -2949,15 +2989,15 @@ packages: - libcxx >=18 - libexpat >=2.6.4,<3.0a0 - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.44,<1.7.0a0 + - libpng >=1.6.47,<1.7.0a0 - libzlib >=1.3.1,<2.0a0 - pixman >=0.44.2,<1.0a0 license: LGPL-2.1-only or MPL-1.1 - size: 891731 - timestamp: 1733791233860 -- conda: https://prefix.dev/conda-forge/osx-arm64/cairo-1.18.2-h6a3b0d2_1.conda - sha256: 9a28344e806b89c87fda0cdabd2fb961e5d2ff97107dba25bac9f5dc57220cc3 - md5: 8e3666c3f6e2c3e57aa261ab103a3600 + size: 893252 + timestamp: 1741554808521 +- conda: https://prefix.dev/conda-forge/osx-arm64/cairo-1.18.4-h6a3b0d2_0.conda + sha256: 00439d69bdd94eaf51656fdf479e0c853278439d22ae151cabf40eb17399d95f + md5: 38f6df8bc8c668417b904369a01ba2e2 depends: - __osx >=11.0 - fontconfig >=2.15.0,<3.0a0 @@ -2967,15 +3007,15 @@ packages: - libcxx >=18 - libexpat >=2.6.4,<3.0a0 - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.44,<1.7.0a0 + - libpng >=1.6.47,<1.7.0a0 - libzlib >=1.3.1,<2.0a0 - pixman >=0.44.2,<1.0a0 license: LGPL-2.1-only or MPL-1.1 - size: 894517 - timestamp: 1733791145035 -- conda: https://prefix.dev/conda-forge/win-64/cairo-1.18.2-h5782bbf_1.conda - sha256: 86fb783e19f7c46ad781d853b650f4cef1c3f2b1b07dd112afe1fc278bc73020 - md5: 63ff2bf400dde4fad0bed56debee5c16 + size: 896173 + timestamp: 1741554795915 +- conda: https://prefix.dev/conda-forge/win-64/cairo-1.18.4-h5782bbf_0.conda + sha256: b9f577bddb033dba4533e851853924bfe7b7c1623d0697df382eef177308a917 + md5: 20e32ced54300292aff690a69c5e7b97 depends: - fontconfig >=2.15.0,<3.0a0 - fonts-conda-ecosystem @@ -2983,15 +3023,15 @@ packages: - icu >=75.1,<76.0a0 - libexpat >=2.6.4,<3.0a0 - libglib >=2.82.2,<3.0a0 - - libpng >=1.6.44,<1.7.0a0 + - libpng >=1.6.47,<1.7.0a0 - libzlib >=1.3.1,<2.0a0 - pixman >=0.44.2,<1.0a0 - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: LGPL-2.1-only or MPL-1.1 - size: 1515969 - timestamp: 1733791355894 + size: 1524254 + timestamp: 1741555212198 - conda: https://prefix.dev/conda-forge/noarch/cairocffi-1.7.1-pyhd8ed1ab_1.conda sha256: 1f9642b94454fbcb605f5c711c512cea2147246a4e23aa9baddbda5324089c78 md5: 54b7517cb759be8279efe23708528fe5 @@ -3244,36 +3284,36 @@ packages: license_family: MIT size: 47438 timestamp: 1735929811779 -- conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_7.conda - sha256: 53f6f1434324bbf5fadc6b38e4f5a0a8843d47b5ccec1a02e220f7f54e68d5ad - md5: d8b25a2436ff7fbe42ad683b2713932f +- conda: https://prefix.dev/conda-forge/linux-64/clang-18.1.8-default_h9e3a008_8.conda + sha256: 0c25ae52604e56af87664507bb84fb3d44059041cfe41f40d1607fbf2bdacc93 + md5: 966fde44cc7fdf6af9890defd8d1cfc0 depends: - binutils_impl_linux-64 - - clang-18 18.1.8 default_hb5137d0_7 + - clang-18 18.1.8 default_hb5137d0_8 - libgcc-devel_linux-64 - sysroot_linux-64 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 71043 - timestamp: 1738271899246 -- conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_7.conda - sha256: def6d2facf51e34dca06f3e9063c52fadb096783d7d8d601f309380f14ce5810 - md5: 623987a715f5fb4cbee8f059d91d0397 + size: 75669 + timestamp: 1742267113770 +- conda: https://prefix.dev/conda-forge/osx-64/clang-18.1.8-default_h576c50e_8.conda + sha256: ff3ec1361dadb7d47ae0e1276e861e48f1a3e5a23c11eb832915cbada3da0861 + md5: 0a7a5caf8e1f0b52b96104bbd2ee677f depends: - - clang-18 18.1.8 default_h3571c67_7 + - clang-18 18.1.8 default_h3571c67_8 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 71151 - timestamp: 1738270236118 -- conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_7.conda - sha256: 72fccf47176327aeece0da9570565be0902c23e89c1a34858f13481aeee386d1 - md5: 0d19d68f06474d97b1b945b3eae132ac + size: 76209 + timestamp: 1742267099930 +- conda: https://prefix.dev/conda-forge/osx-arm64/clang-18.1.8-default_h474c9e2_8.conda + sha256: 42965afb7a7a2af44b164d079b256127a9d9580e756cce08f8a00836d1e82952 + md5: 2c01e8675aa80bf6a25494b76005ffdc depends: - - clang-18 18.1.8 default_hf90f093_7 + - clang-18 18.1.8 default_hf90f093_8 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 71114 - timestamp: 1738267030948 + size: 76217 + timestamp: 1742266196177 - conda: https://prefix.dev/conda-forge/win-64/clang-19.1.7-default_hec7ea82_1.conda sha256: 0695ccc884fbd3f14141deac0392d8b8f25a74da699c7a3268b1cca4435c0915 md5: 1cd9d2137de8388eb8bf323652df18c1 @@ -3288,43 +3328,43 @@ packages: license_family: Apache size: 102371217 timestamp: 1737786691610 -- conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_7.conda - sha256: d703875711c68e745ee221d5af736f9137d9a582475b743c967edefa899dcb11 - md5: 008ded7331a8b5f5f9a5daa8e4fe0d84 +- conda: https://prefix.dev/conda-forge/linux-64/clang-18-18.1.8-default_hb5137d0_8.conda + sha256: 8f32c94d206e5bf5db81a70c12a2aa32c7670bcca267509d550dc87bcc8f4e30 + md5: c30dd0146d3824260b51c5e9ffbca97e depends: - __glibc >=2.17,<3.0.a0 - - libclang-cpp18.1 18.1.8 default_hb5137d0_7 + - libclang-cpp18.1 18.1.8 default_hb5137d0_8 - libgcc >=13 - libllvm18 >=18.1.8,<18.2.0a0 - libstdcxx >=13 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 822892 - timestamp: 1738271833111 -- conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_7.conda - sha256: 3a74de8af6896d276847332c1a350bab3c1791a13090588463edf4fabf9d424d - md5: 098293f10df1166408bac04351b917c5 + size: 827219 + timestamp: 1742267048743 +- conda: https://prefix.dev/conda-forge/osx-64/clang-18-18.1.8-default_h3571c67_8.conda + sha256: 25f572a069d9ff1d6306d6d29a2b42d0395017cc36e9df581c98d6ad2c5876aa + md5: c40e72e808995df189d70d9a438d77ac depends: - __osx >=10.13 - - libclang-cpp18.1 18.1.8 default_h3571c67_7 + - libclang-cpp18.1 18.1.8 default_h3571c67_8 - libcxx >=18.1.8 - libllvm18 >=18.1.8,<18.2.0a0 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 807874 - timestamp: 1738270127631 -- conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_7.conda - sha256: 4d3e1222408141db88eff3c1ed68f659b32bda5a3b1cb0cd2c5ea9c2f8d5643c - md5: d91b8b92eb2153057c1dccbcc3f8c6c3 + size: 815278 + timestamp: 1742266953803 +- conda: https://prefix.dev/conda-forge/osx-arm64/clang-18-18.1.8-default_hf90f093_8.conda + sha256: 984da230a6197273060fcdb4c97e7d2430f8393547a478ac4e8b32d33a64c89c + md5: 8d92b636afa379ae7845575d87ae1ad0 depends: - __osx >=11.0 - - libclang-cpp18.1 18.1.8 default_hf90f093_7 + - libclang-cpp18.1 18.1.8 default_hf90f093_8 - libcxx >=18.1.8 - libllvm18 >=18.1.8,<18.2.0a0 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 807189 - timestamp: 1738266841744 + size: 811547 + timestamp: 1742266095150 - conda: https://prefix.dev/conda-forge/win-64/clang-19-19.1.7-default_hec7ea82_1.conda sha256: 9b98d0183f4242eb0eb73597c95742bc0dbd18a1b7eb5c6c7cfe7810f682e7db md5: e8659125a0143ac495cfd54ff39363aa @@ -3382,26 +3422,26 @@ packages: license_family: BSD size: 21100 timestamp: 1731985043856 -- conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_7.conda - sha256: a1b0a353fbc465034ceafa6dbf3cf6e4ce9be07088bb38e44e30c9ab1265057e - md5: f2ec690c4ac8d9e6ffbf3be019d68170 +- conda: https://prefix.dev/conda-forge/osx-64/clangxx-18.1.8-default_heb2e8d1_8.conda + sha256: a99947172ab2a3bf244ea1c024e7e3a8e1aabb8921cc5e648004f246f5df87c7 + md5: 06a53a18fa886ec96f519b9022eeb449 depends: - - clang 18.1.8 default_h576c50e_7 + - clang 18.1.8 default_h576c50e_8 - libcxx-devel 18.1.8.* license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 71187 - timestamp: 1738270260674 -- conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_7.conda - sha256: cc29cf6dd2930a57dbb8eb2fed8547114dc4b223b40c76a9ab0330aaf4685fb1 - md5: 8c1d4c1dbdcd479c1c4a4c9ebebf4d1f + size: 76312 + timestamp: 1742267130243 +- conda: https://prefix.dev/conda-forge/osx-arm64/clangxx-18.1.8-default_h1ffe849_8.conda + sha256: c002e7ff1511a0278c693fb05d8c6f729fda4e76b65faef91bb6721b93b24d4b + md5: 6f88136be9a2b5e5e6e7bb69c35d8180 depends: - - clang 18.1.8 default_h474c9e2_7 + - clang 18.1.8 default_h474c9e2_8 - libcxx-devel 18.1.8.* license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 71194 - timestamp: 1738267045229 + size: 76359 + timestamp: 1742266208123 - conda: https://prefix.dev/conda-forge/osx-64/clangxx_impl_osx-64-18.1.8-h4b7810f_23.conda sha256: f8b2b92c1389ddaadf3eb8f11d63403fcbb5b532299d9f86f54cc1f256fad1c0 md5: 8f15135d550beba3e9a0af94661bed16 @@ -3739,14 +3779,14 @@ packages: license_family: MIT size: 38835 timestamp: 1733231086305 -- conda: https://prefix.dev/conda-forge/noarch/filelock-3.17.0-pyhd8ed1ab_0.conda - sha256: 006d7e5a0c17a6973596dd86bfc80d74ce541144d2aee2d22d46fd41df560a63 - md5: 7f402b4a1007ee355bc50ce4d24d4a57 +- conda: https://prefix.dev/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + sha256: de7b6d4c4f865609ae88db6fa03c8b7544c2452a1aa5451eb7700aad16824570 + md5: 4547b39256e296bb758166893e909a7c depends: - python >=3.9 license: Unlicense - size: 17544 - timestamp: 1737517924333 + size: 17887 + timestamp: 1741969612334 - conda: https://prefix.dev/conda-forge/win-64/flang-19.1.7-hbeecb71_0.conda sha256: 36cff091f0dc82c022225e51ebd1d2eba6269144c0c19580d3b313e430a70740 md5: a00b1ff46537989d170dda28dd99975f @@ -3959,56 +3999,59 @@ packages: license_family: BSD size: 6546 timestamp: 1736437099100 -- conda: https://prefix.dev/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda - sha256: b2e3c449ec9d907dd4656cb0dc93e140f447175b125a3824b31368b06c666bb6 - md5: 9ae35c3d96db2c94ce0cef86efdfa2cb +- conda: https://prefix.dev/conda-forge/linux-64/freetype-2.13.3-h48d6fc4_0.conda + sha256: 7385577509a9c4730130f54bb6841b9b416249d5f4e9f74bf313e6378e313c57 + md5: 9ecfd6f2ca17077dd9c2d24770bb9ccd depends: - - libgcc-ng >=12 - - libpng >=1.6.39,<1.7.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libpng >=1.6.47,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 license: GPL-2.0-only OR FTL - size: 634972 - timestamp: 1694615932610 -- conda: https://prefix.dev/conda-forge/linux-aarch64/freetype-2.12.1-hf0a5ef3_2.conda - sha256: 7af93030f4407f076dce181062360efac2cd54dce863b5d7765287a6f5382537 - md5: a5ab74c5bd158c3d5532b66d8d83d907 + size: 639682 + timestamp: 1741863789964 +- conda: https://prefix.dev/conda-forge/linux-aarch64/freetype-2.13.3-he93130f_0.conda + sha256: 242eb2a7888d3d624535ec3c488c9db7d4cdfabb6e9ae78beea6abc45d6a4eae + md5: 3743da39462f21956d6429a4a554ff4f depends: - - libgcc-ng >=12 - - libpng >=1.6.39,<1.7.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - libgcc >=13 + - libpng >=1.6.47,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 license: GPL-2.0-only OR FTL - size: 642092 - timestamp: 1694617858496 -- conda: https://prefix.dev/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda - sha256: b292cf5a25f094eeb4b66e37d99a97894aafd04a5683980852a8cbddccdc8e4e - md5: 25152fce119320c980e5470e64834b50 + size: 648847 + timestamp: 1741863827451 +- conda: https://prefix.dev/conda-forge/osx-64/freetype-2.13.3-h40dfd5c_0.conda + sha256: 66cc36a313accf28f4ab9b40ad11e4a8ff757c11314cd499435d9b8df1fa0150 + md5: e391f0c2d07df272cf7c6df235e97bb9 depends: - - libpng >=1.6.39,<1.7.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - __osx >=10.13 + - libpng >=1.6.47,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 license: GPL-2.0-only OR FTL - size: 599300 - timestamp: 1694616137838 -- conda: https://prefix.dev/conda-forge/osx-arm64/freetype-2.12.1-hadb7bae_2.conda - sha256: 791673127e037a2dc0eebe122dc4f904cb3f6e635bb888f42cbe1a76b48748d9 - md5: e6085e516a3e304ce41a8ee08b9b89ad + size: 602964 + timestamp: 1741863884014 +- conda: https://prefix.dev/conda-forge/osx-arm64/freetype-2.13.3-h1d14073_0.conda + sha256: 2c273de32431c431a118a8cd33afb6efc616ddbbab9e5ba0fe31e3b4d1ff57a3 + md5: 630445a505ea6e59f55714853d8c9ed0 depends: - - libpng >=1.6.39,<1.7.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - __osx >=11.0 + - libpng >=1.6.47,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 license: GPL-2.0-only OR FTL - size: 596430 - timestamp: 1694616332835 -- conda: https://prefix.dev/conda-forge/win-64/freetype-2.12.1-hdaf720e_2.conda - sha256: 2c53ee8879e05e149a9e525481d36adfd660a6abda26fd731376fa64ff03e728 - md5: 3761b23693f768dc75a8fd0a73ca053f + size: 590002 + timestamp: 1741863913870 +- conda: https://prefix.dev/conda-forge/win-64/freetype-2.13.3-h0b5ce68_0.conda + sha256: 67e3af0fbe6c25f5ab1af9a3d3000464c5e88a8a0b4b06602f4a5243a8a1fd42 + md5: 9c461ed7b07fb360d2c8cfe726c7d521 depends: - - libpng >=1.6.39,<1.7.0a0 - - libzlib >=1.2.13,<2.0.0a0 + - libpng >=1.6.47,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: GPL-2.0-only OR FTL - size: 510306 - timestamp: 1694616398888 + size: 510718 + timestamp: 1741864688363 - conda: https://prefix.dev/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda sha256: 300f077029e7626d69cc250a69acd6018c1fced3f5bf76adf37854f3370d2c45 md5: d92e51bf4b6bdbfe45e5884fb0755afe @@ -4571,16 +4614,16 @@ packages: license_family: MIT size: 14544252 timestamp: 1720853966338 -- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.8-pyhd8ed1ab_0.conda - sha256: 26347a71ff3bf9d3d775b6764a85782d4b9238a8e3a5c16a548325724dccbdea - md5: 153a6ad50ad9db7bb4e042ee52a56f87 +- conda: https://prefix.dev/conda-forge/noarch/identify-2.6.9-pyhd8ed1ab_0.conda + sha256: b74a2ffa7be9278d7b8770b6870c360747149c683865e63476b0e1db23038429 + md5: 542f45bf054c6b9cf8d00a3b1976f945 depends: - python >=3.9 - ukkonen license: MIT license_family: MIT - size: 78619 - timestamp: 1740257841338 + size: 78600 + timestamp: 1741502780749 - conda: https://prefix.dev/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 md5: 39a4f67be3286c86d696df570b1201b7 @@ -4953,9 +4996,9 @@ packages: license_family: Apache size: 194365 timestamp: 1657977692274 -- conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_7.conda - sha256: 28982cf5038c5f141f4f6374ca3cb22d85e50bcee8ea76b3df8fe0de125672f2 - md5: 8dacc063fe4ec190fd265d9b2638a90f +- conda: https://prefix.dev/conda-forge/linux-64/libclang-cpp18.1-18.1.8-default_hb5137d0_8.conda + sha256: 7ceb754d14a3d8e0903fc6b8cf4e83a5243c9d33228d3e76cce75e3ae9471792 + md5: 68b5368f22987402f6a9df8253318388 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -4963,30 +5006,30 @@ packages: - libstdcxx >=13 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 19270692 - timestamp: 1738271687132 -- conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_7.conda - sha256: 81917105ee2f49e85f0abd684696ad9d9a81aef9d825e563e0013fd9a1ccbd23 - md5: d22bdc2b1ecf45631c5aad91f660623a + size: 19275343 + timestamp: 1742266940688 +- conda: https://prefix.dev/conda-forge/osx-64/libclang-cpp18.1-18.1.8-default_h3571c67_8.conda + sha256: 9e9af164c1ddfd114a786aceacf64df0041c3528a97cc96c06f3bf1040485e29 + md5: 1444a2cd1f78fccea7dacb658f8aeb39 depends: - __osx >=10.13 - libcxx >=18.1.8 - libllvm18 >=18.1.8,<18.2.0a0 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 13901227 - timestamp: 1738269502537 -- conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_7.conda - sha256: f3abadc7fa0a98814c07644b8117ac25d03a1cb78be8ea40a32bcd2dede5b3c9 - md5: 3a2e0aa2daf3b1a009b87fd7fa5d4a41 + size: 13905920 + timestamp: 1742266347128 +- conda: https://prefix.dev/conda-forge/osx-arm64/libclang-cpp18.1-18.1.8-default_hf90f093_8.conda + sha256: b736c4c3a32d4aa16b4af7b2094b4f3786ea34723cccb9918579206706000f90 + md5: a5f883cd77dcc0f62a0eca8445d9e147 depends: - __osx >=11.0 - libcxx >=18.1.8 - libllvm18 >=18.1.8,<18.2.0a0 license: Apache-2.0 WITH LLVM-exception license_family: Apache - size: 13325600 - timestamp: 1738266301426 + size: 13330731 + timestamp: 1742265504673 - conda: https://prefix.dev/conda-forge/linux-64/libcurl-8.12.1-h332b0f4_0.conda sha256: 2ebc3039af29269e4cdb858fca36265e5e400c1125a4bcd84ae73a596e0e76ca md5: 45e9dc4e7b25e2841deb392be085500e @@ -6120,53 +6163,53 @@ packages: license_family: GPL size: 4156159 timestamp: 1740241051716 -- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_1.conda - sha256: 7a09eef804ef7cf4d88215c2297eabb72af8ad0bd5b012060111c289f14bbe7d - md5: 73cea06049cc4174578b432320a003b8 +- conda: https://prefix.dev/conda-forge/linux-64/libsqlite-3.49.1-hee588c1_2.conda + sha256: a086289bf75c33adc1daed3f1422024504ffb5c3c8b3285c49f025c29708ed16 + md5: 962d6ac93c30b1dfc54c9cccafd1003e depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 915956 - timestamp: 1739953155793 -- conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_1.conda - sha256: 920fb3b7d3b873babf79a3e392cc82d43b8bd02a573ccaff34219efb5cf7b51e - md5: 150d64241fa27d9d35a7f421ca968a6c + size: 918664 + timestamp: 1742083674731 +- conda: https://prefix.dev/conda-forge/linux-aarch64/libsqlite-3.49.1-h5eb1b54_2.conda + sha256: c0eb05c6db32b52cc80e06a2badfa11fbaa66b49f1e7cff77aa691b74a294dcc + md5: 7c45959e187fd3313f9f1734464baecc depends: - libgcc >=13 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 915118 - timestamp: 1739953101699 -- conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_1.conda - sha256: 859e5f1a39e320b3575b98b7a80ab7c62b337465b12b181c8bbe305fecc9430b - md5: 7958168c20fbbc5014e1fbda868ed700 + size: 916419 + timestamp: 1742083699438 +- conda: https://prefix.dev/conda-forge/osx-64/libsqlite-3.49.1-hdb6dae5_2.conda + sha256: 82695c9b16a702de615c8303387384c6ec5cf8b98e16458e5b1935b950e4ec38 + md5: 1819e770584a7e83a81541d8253cbabe depends: - __osx >=10.13 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 977598 - timestamp: 1739953439197 -- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_1.conda - sha256: 266639fb10ca92287961574b0b4d6031fa40dd9d723d64a0fcb08513a24dab03 - md5: c83357a21092bd952933c36c5cb4f4d6 + size: 977701 + timestamp: 1742083869897 +- conda: https://prefix.dev/conda-forge/osx-arm64/libsqlite-3.49.1-h3f77e49_2.conda + sha256: 907a95f73623c343fc14785cbfefcb7a6b4f2bcf9294fcb295c121611c3a590d + md5: 3b1e330d775170ac46dff9a94c253bd0 depends: - __osx >=11.0 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 898767 - timestamp: 1739953312379 -- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_1.conda - sha256: 08669790e4de89201079e93e8a8d8c51a3cd57a19dd559bb0d5bc6c9a7970b99 - md5: 88931435901c1f13d4e3a472c24965aa + size: 900188 + timestamp: 1742083865246 +- conda: https://prefix.dev/conda-forge/win-64/libsqlite-3.49.1-h67fdade_2.conda + sha256: c092d42d00fd85cf609cc58574ba2b03c141af5762283f36f5dd445ef7c0f4fe + md5: b58b66d4ad1aaf1c2543cbbd6afb1a59 depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: Unlicense - size: 1081190 - timestamp: 1739953491995 + size: 1081292 + timestamp: 1742083956001 - conda: https://prefix.dev/conda-forge/linux-64/libssh2-1.11.1-hf672d98_0.conda sha256: 0407ac9fda2bb67e11e357066eff144c845801d00b5f664efbc48813af1e7bb9 md5: be2de152d8073ef1c01b7728475f2fe7 @@ -6674,22 +6717,22 @@ packages: license_family: Other size: 55476 timestamp: 1727963768015 -- conda: https://prefix.dev/conda-forge/win-64/lld-19.1.7-hd91d51b_0.conda - sha256: 8b276064075c0f9e26ced696006507e19ce8beb166025a2ee75177ffa954eb94 - md5: d79e71d58a04f4936725c5531855535d +- conda: https://prefix.dev/conda-forge/win-64/lld-20.1.0-hd91d51b_0.conda + sha256: 6fb07cff6aea8f68998f8e1f7881893f940d74e965026f004299da8160303f76 + md5: 3834ce6dfd992ebc8495a370900839d4 depends: - - libxml2 >=2.13.5,<3.0a0 + - libxml2 >=2.13.6,<3.0a0 - libzlib >=1.3.1,<2.0a0 - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 - - zstd >=1.5.6,<1.6.0a0 + - zstd >=1.5.7,<1.6.0a0 constrains: - - llvm ==19.1.7 + - llvm ==20.1.0 license: Apache-2.0 WITH LLVM-exception license_family: APACHE - size: 122998237 - timestamp: 1736927286267 + size: 132045546 + timestamp: 1741667548656 - conda: https://prefix.dev/conda-forge/osx-64/llvm-openmp-19.1.7-ha54dae1_0.conda sha256: b5b06821b0d4143f66ba652ffe6f535696dc3a4096175d9be8b19b1a7350c86d md5: 65d08c50518999e69f421838c1d5b91f @@ -6924,53 +6967,53 @@ packages: license_family: MIT size: 11676 timestamp: 1734157119152 -- conda: https://prefix.dev/conda-forge/linux-64/micromamba-2.0.5-1.tar.bz2 - sha256: ff1a10b46e9ee673bb672ac464d4787b4e9c3569b75816a56f76dddc0b1f07fc - md5: c7e1935236a28b33495b2754d35975d5 +- conda: https://prefix.dev/conda-forge/linux-64/micromamba-2.0.7-0.tar.bz2 + sha256: e181494d8932a0dc6ab0fe38dab6dacce9b814f72f74986f7037f94402116940 + md5: bb8f922e685d24f93006c431fa84b68f depends: - __glibc >=2.17,<3.0.a0 license: BSD-3-Clause AND MIT AND OpenSSL license_family: BSD - size: 6026023 - timestamp: 1739283910438 -- conda: https://prefix.dev/conda-forge/linux-aarch64/micromamba-2.0.5-1.tar.bz2 - sha256: ec8bdee05dfe7c410079b355864f0f52edf919bd6e2c7b16cebea04605fa3fff - md5: aff3ef770e509223e5195484aa7d1e88 + size: 6049158 + timestamp: 1741703425823 +- conda: https://prefix.dev/conda-forge/linux-aarch64/micromamba-2.0.7-0.tar.bz2 + sha256: 543079535922bf84e447f67f23c3ea6f54b2d84dd8e58ace99b47cfe6aa06ec6 + md5: 9f9599a01a3ee3657821ca535e82a641 license: BSD-3-Clause AND MIT AND OpenSSL license_family: BSD - size: 7270893 - timestamp: 1739284400281 -- conda: https://prefix.dev/conda-forge/osx-64/micromamba-2.0.5-1.tar.bz2 - sha256: d8ae81a89dbddf88dcf7e31c68a6f9633b95898ff22adc549b9bca034344512a - md5: c51bd53e0375068c0b5bcb0301913d72 + size: 7411567 + timestamp: 1741703532452 +- conda: https://prefix.dev/conda-forge/osx-64/micromamba-2.0.7-0.tar.bz2 + sha256: dbdc5a132dd461dbcc5051d6a61b24119daa8da29c4ac0dd03ac56c0dd9f8267 + md5: d04544a5c5efa2d5e4f764e4130257fe depends: - __osx >=10.13 - libcxx >=18 license: BSD-3-Clause AND MIT AND OpenSSL license_family: BSD - size: 6101512 - timestamp: 1739284386668 -- conda: https://prefix.dev/conda-forge/osx-arm64/micromamba-2.0.5-1.tar.bz2 - sha256: fe6c1dce20ab39f81a75d201b77b64a795ef8bd08a6dede6cfb2fb4444e940b1 - md5: 5bf031792d0def930aadf06012fad590 + size: 6112724 + timestamp: 1741703533917 +- conda: https://prefix.dev/conda-forge/osx-arm64/micromamba-2.0.7-0.tar.bz2 + sha256: ff820151dfe9d7cff31ce40942b4ef946a747ed1274427d6211a0099bed8913e + md5: 1690393cbebd3a3f6fcaa24961ba3c4c depends: - __osx >=11.0 - libcxx >=18 license: BSD-3-Clause AND MIT AND OpenSSL license_family: BSD - size: 6081669 - timestamp: 1739284285609 -- conda: https://prefix.dev/conda-forge/win-64/micromamba-2.0.5-1.tar.bz2 - sha256: 73ca8990ac0bbd2249365158d34c5231688f3a6cc0acddad87cd3dd49523a1fa - md5: 4b1e4e3c86adb9fb0fd41580789ee57b + size: 6109160 + timestamp: 1741703545031 +- conda: https://prefix.dev/conda-forge/win-64/micromamba-2.0.7-0.tar.bz2 + sha256: d09bc27f1842a94ef6f26c35ff28992c51d428c759cc7e76cd16663eaec1d057 + md5: f76e65c214eebe86bd014955dae4499a depends: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 license: BSD-3-Clause AND MIT AND OpenSSL license_family: BSD - size: 3955820 - timestamp: 1739285826822 + size: 3967509 + timestamp: 1741705718962 - conda: https://prefix.dev/conda-forge/noarch/mike-2.0.0-pyhd8ed1ab_0.conda sha256: b7246e31059f3d5680e5e649508421e4e1d64a7a1a400dec36afcbdbef3690e2 md5: e1f6f7682915f4d0a32b147ac8228515 @@ -7036,9 +7079,9 @@ packages: license_family: MIT size: 14757 timestamp: 1734353035244 -- conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.7-pyhd8ed1ab_0.conda - sha256: d3a07f5db1ac84435624a3bc775429b9ef5221adc37a7fa88a2c11286063b71b - md5: 4655855f3ee501f5d72cd7fc116a704b +- conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-9.6.9-pyhd8ed1ab_0.conda + sha256: 1cfc8d5a5097228b1fe07e9d3807e48109597c9d9250e20ed4ee6c82de2615d1 + md5: 69499025c6a2d2e706eb0ff782fcde3a depends: - babel >=2.10,<3.dev0 - backrefs >=5.7.post1,<6.dev0 @@ -7054,8 +7097,8 @@ packages: - requests >=2.26,<3.dev0 license: MIT license_family: MIT - size: 4914264 - timestamp: 1741015984147 + size: 4929294 + timestamp: 1742220372293 - conda: https://prefix.dev/conda-forge/noarch/mkdocs-material-extensions-1.3.1-pyhd8ed1ab_1.conda sha256: f62955d40926770ab65cc54f7db5fde6c073a3ba36a0787a7a5767017da50aa3 md5: de8af4000a4872e16fb784c649679c8e @@ -7077,9 +7120,9 @@ packages: license_family: MIT size: 11774 timestamp: 1735382041439 -- conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.0-hff13881_0.conda - sha256: b3c4c75c9f913f7ddfb82ed1db163d7e63c6ffd10a9225ee3e11bb5e71427309 - md5: a0dce224e649991d83d323521395faa7 +- conda: https://prefix.dev/conda-forge/linux-64/mold-2.37.1-hff13881_0.conda + sha256: fe743a5bbff439107e923e6cd261356fdd614f23d3e2a5be97a4401c6c984830 + md5: 5439054b60d442af2365d0da68179217 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -7090,8 +7133,9 @@ packages: - tbb >=2021.13.0 - zstd >=1.5.7,<1.6.0a0 license: MIT - size: 2744383 - timestamp: 1741378411824 + license_family: MIT + size: 2768236 + timestamp: 1741621601088 - conda: https://prefix.dev/conda-forge/osx-64/mpc-1.3.1-h9d8efa1_1.conda sha256: dcf91571da6c2f0db96d43a1b639047def05a0e1b6436d42c9129ab14af47b10 md5: 0520855aaae268ea413d6bc913f1384c @@ -7261,22 +7305,22 @@ packages: license_family: BSD size: 34574 timestamp: 1734112236147 -- conda: https://prefix.dev/conda-forge/linux-64/nodejs-22.12.0-hf235a45_0.conda - sha256: 1a519b80bc3d5afddeccb593711df2e60ac48ecf3e903f7bdc279f64f7210fc4 - md5: 30458a23bf5568d2bc0e1fed6a4e2b12 +- conda: https://prefix.dev/conda-forge/linux-64/nodejs-22.13.0-hf235a45_0.conda + sha256: 925ea8839d6f26d0eb4204675b98a862803a9a9657fd36a4a22c4c29a479a911 + md5: 1f9efd96347aa008bd2c735d7d88fc75 depends: - __glibc >=2.28,<3.0.a0 - icu >=75.1,<76.0a0 - libgcc >=13 - libstdcxx >=13 - - libuv >=1.49.2,<2.0a0 + - libuv >=1.50.0,<2.0a0 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.4.1,<4.0a0 - zlib license: MIT license_family: MIT - size: 21796933 - timestamp: 1734113054756 + size: 21691794 + timestamp: 1741809786920 - conda: https://prefix.dev/conda-forge/linux-aarch64/nodejs-22.13.0-h8374285_0.conda sha256: 7878e84a0162041c7c45814832f1635ea0b4454481af619a20a3be291744c032 md5: 3a81ef0e4e847685774c35bbf42598e2 @@ -9080,9 +9124,9 @@ packages: license_family: MIT size: 108926 timestamp: 1728725024979 -- conda: https://prefix.dev/conda-forge/linux-64/ruff-0.9.9-py312hf79aa60_0.conda - sha256: b14661ab12b6b707b9253c28347fd4774e85099d0c89aeab5b78f349853b8202 - md5: a65261e4f2a922a94d7da5e9b1cfcc17 +- conda: https://prefix.dev/conda-forge/linux-64/ruff-0.9.10-py312hf79aa60_0.conda + sha256: 44fa5e9269af6c316ec811c9692240f14034e1154e75bdddf29d064a099168d8 + md5: e05e5bfa635c06cfc60fba96425c25f6 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -9093,11 +9137,11 @@ packages: - __glibc >=2.17 license: MIT license_family: MIT - size: 8858262 - timestamp: 1741052458951 -- conda: https://prefix.dev/conda-forge/linux-aarch64/ruff-0.9.9-py312hee76d2e_0.conda - sha256: 12cf021451d5354069a06230a365c107d048f9f531a78d653d907bece3726368 - md5: cbe136c54cc554840f03cbcfad0ff420 + size: 8858366 + timestamp: 1741446179212 +- conda: https://prefix.dev/conda-forge/linux-aarch64/ruff-0.9.10-py312hee76d2e_0.conda + sha256: 6d4b9f062265292b9a51cb1e49b1cc7f677daafa14d19be2dd9b07836368833c + md5: 79da1223add0c0a97863b4791059cf80 depends: - libgcc >=13 - libstdcxx >=13 @@ -9108,11 +9152,11 @@ packages: - __glibc >=2.17 license: MIT license_family: MIT - size: 8514441 - timestamp: 1741052408839 -- conda: https://prefix.dev/conda-forge/osx-64/ruff-0.9.9-py312ha54e1fc_0.conda - sha256: 1667d0e44eff86f75b5befd8b07cc12bd4867b88e3954695c683fe4c4c68b3f2 - md5: 2b62cd0fe255d43eae76ac2ba17210e0 + size: 8509637 + timestamp: 1741446279901 +- conda: https://prefix.dev/conda-forge/osx-64/ruff-0.9.10-py312ha54e1fc_0.conda + sha256: ae00f94fc90785d8c7152dee235183479715e91cde0640a2f49fc63b741d8350 + md5: 9228d1cc99fdfb103d07bb2ffb1af3e5 depends: - __osx >=10.13 - libcxx >=18 @@ -9122,11 +9166,11 @@ packages: - __osx >=10.13 license: MIT license_family: MIT - size: 8149160 - timestamp: 1741052721389 -- conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.9.9-py312h31a5b27_0.conda - sha256: 24991d9351ea73e5098b777bfd0dfe5ae9b36ab9bc5d7c830443bd70c8861754 - md5: 8e9f6c75f1d741c1c747b403a2a4be65 + size: 8157248 + timestamp: 1741446556906 +- conda: https://prefix.dev/conda-forge/osx-arm64/ruff-0.9.10-py312h31a5b27_0.conda + sha256: 1139ad9adfd73bb3ba496eb739bbc42a4e556c13dd70b19c28206418ff64669f + md5: 5c58b988200ff179074db37e1f7eb445 depends: - __osx >=11.0 - libcxx >=18 @@ -9137,11 +9181,11 @@ packages: - __osx >=11.0 license: MIT license_family: MIT - size: 7768619 - timestamp: 1741052939426 -- conda: https://prefix.dev/conda-forge/win-64/ruff-0.9.9-py312hc33538c_0.conda - sha256: 397af51b1b30e8531b1922931f614bc1af0b2dd9c438a2b64c0bda710d21a438 - md5: cd6a9efdef75ab72843e211866f3591f + size: 7789869 + timestamp: 1741446433184 +- conda: https://prefix.dev/conda-forge/win-64/ruff-0.9.10-py312hc33538c_0.conda + sha256: 5b550ef5bfbd991e147e0fafc77bf58888b5ff188063919b53bf4113186c50e6 + md5: 0d963d79f03bcdd6b674e200e74a78a6 depends: - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 @@ -9150,8 +9194,8 @@ packages: - vc14_runtime >=14.29.30139 license: MIT license_family: MIT - size: 7932763 - timestamp: 1741053655784 + size: 7930800 + timestamp: 1741447029410 - conda: https://prefix.dev/conda-forge/linux-64/rust-1.84.0-h1a8d7c4_0.conda sha256: a71f8e4fcfdb2ef40e8c1631c4cbe3bbad45b11e9317e6ea125783717d43920b md5: a0d0badd8f8d61bb70aeeee701b7db07 @@ -9368,16 +9412,16 @@ packages: license_family: MIT size: 16385 timestamp: 1733381032766 -- conda: https://prefix.dev/conda-forge/noarch/syrupy-4.8.2-pyhd8ed1ab_0.conda - sha256: 32376bf4d4ddaa9b3039a3e12049e549f02e42daee2576f3f3636a5eed6b8fa9 - md5: 5bf3b12b89f4490d43fa4a6d0a91ae21 +- conda: https://prefix.dev/conda-forge/noarch/syrupy-4.9.0-pyhd8ed1ab_0.conda + sha256: fe4e6b5a74f8c16641b0739a14e765187f9309da6ede6e3aa5251c3013d6be81 + md5: e9a1c5eb629d8cb34ca1af7b26c59bcb depends: - pytest >=7.0.0,<9.0.0 - python >=3.9,<4.0 license: Apache-2.0 license_family: APACHE - size: 45971 - timestamp: 1740148719384 + size: 47212 + timestamp: 1741544637850 - conda: https://prefix.dev/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda sha256: 69ab5804bdd2e8e493d5709eebff382a72fab3e9af6adf93a237ccf8f7dbd624 md5: 460eba7851277ec1fd80a1a24080787a @@ -9600,15 +9644,15 @@ packages: license_family: MIT size: 37372 timestamp: 1733230836889 -- conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.3.18-pyhd8ed1ab_0.conda - sha256: 8cd43b561122bfeb7e99df2dc3ec5633d5888e54fa07c059d993a5971b3f3a94 - md5: 810ef4243f6d79c0b8053f21fbee2101 +- conda: https://prefix.dev/conda-forge/noarch/trove-classifiers-2025.3.13.13-pyhd8ed1ab_0.conda + sha256: f4332aecfd16d8b4346ac2814fa8f451976303b96643020578c8a8b20d5f9397 + md5: 381d84cfaa6492e7a358d876edd66dfd depends: - python >=3.9 license: Apache-2.0 license_family: Apache - size: 18705 - timestamp: 1741073502142 + size: 18767 + timestamp: 1741887081003 - conda: https://prefix.dev/conda-forge/noarch/types-pyyaml-6.0.12.20241230-pyhd8ed1ab_0.conda sha256: 523022421f5b4a6695ab65f0cf038ea27a5705d83d06abeb9bd910a02fdbf0c6 md5: 99c1ed80714baf80417c937664d41cb1 @@ -9785,17 +9829,6 @@ packages: license_family: GPL size: 189827 timestamp: 1733714839478 -- conda: https://prefix.dev/conda-forge/noarch/urllib3-2.2.2-pyhd8ed1ab_0.conda - sha256: 8cd972048f297b8e0601158ce352f5ca9510dda9f2706a46560220aa58b9f038 - md5: 92cdb6fe54b78739ad70637e4f0deb07 - depends: - - brotli-python >=1.0.9 - - pysocks >=1.5.6,<2.0,!=1.5.7 - - python >=3.8 - license: MIT - license_family: MIT - size: 95016 - timestamp: 1718653149133 - conda: https://prefix.dev/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda sha256: 114919ffa80c328127dab9c8e7a38f9d563c617691fb81fccb11c1e86763727e md5: 32674f8dbfb7b26410ed580dd3c10a29 @@ -9977,9 +10010,9 @@ packages: license_family: MIT size: 60433 timestamp: 1734229908988 -- conda: https://prefix.dev/conda-forge/linux-64/xorg-libsm-1.2.5-he73a12e_0.conda - sha256: 760f43df6c2ce8cbbbcb8f2f3b7fc0f306716c011e28d1d340f3dfa8ccf29185 - md5: 4c3e9fab69804ec6077697922d70c6e2 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libsm-1.2.6-he73a12e_0.conda + sha256: 277841c43a39f738927145930ff963c5ce4c4dacf66637a3d95d802a64173250 + md5: 1c74ff8c35dcadf952a16f752ca5aa49 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -9987,40 +10020,40 @@ packages: - xorg-libice >=1.1.2,<2.0a0 license: MIT license_family: MIT - size: 27198 - timestamp: 1734229639785 -- conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libsm-1.2.5-h0808dbd_0.conda - sha256: 2749a32a00ccd8feaab6039d7848ed875880c13d3b2601afd1788600ce5f9075 - md5: 3983c253f53f67a9d8710fc96646950f + size: 27590 + timestamp: 1741896361728 +- conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libsm-1.2.6-h0808dbd_0.conda + sha256: b86a819cd16f90c01d9d81892155126d01555a20dabd5f3091da59d6309afd0a + md5: 2d1409c50882819cb1af2de82e2b7208 depends: - libgcc >=13 - libuuid >=2.38.1,<3.0a0 - - xorg-libice >=1.1.1,<2.0a0 + - xorg-libice >=1.1.2,<2.0a0 license: MIT license_family: MIT - size: 28061 - timestamp: 1734232077988 -- conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.11-h4f16b4b_0.conda - sha256: a0e7fca9e341dc2455b20cd320fc1655e011f7f5f28367ecf8617cccd4bb2821 - md5: b6eb6d0cb323179af168df8fe16fb0a1 + size: 28701 + timestamp: 1741897678254 +- conda: https://prefix.dev/conda-forge/linux-64/xorg-libx11-1.8.12-h4f16b4b_0.conda + sha256: 51909270b1a6c5474ed3978628b341b4d4472cd22610e5f22b506855a5e20f67 + md5: db038ce880f100acc74dba10302b5630 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libxcb >=1.17.0,<2.0a0 license: MIT license_family: MIT - size: 835157 - timestamp: 1738613163812 -- conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libx11-1.8.11-hca56bd8_0.conda - sha256: 351ce88b3c2df30520721d45e3d32127a0f7f406be234c25a224ed46f082d2e5 - md5: b4f818a0a4e60cffe755381166c82888 + size: 835896 + timestamp: 1741901112627 +- conda: https://prefix.dev/conda-forge/linux-aarch64/xorg-libx11-1.8.12-hca56bd8_0.conda + sha256: 452977d8ad96f04ec668ba74f46e70a53e00f99c0e0307956aeca75894c8131d + md5: 3df132f0048b9639bc091ef22937c111 depends: - libgcc >=13 - libxcb >=1.17.0,<2.0a0 license: MIT license_family: MIT - size: 864226 - timestamp: 1738613196126 + size: 864850 + timestamp: 1741901264068 - conda: https://prefix.dev/conda-forge/linux-64/xorg-libxau-1.0.12-hb9d3cd8_0.conda sha256: ed10c9283974d311855ae08a16dfd7e56241fac632aec3b92e3cfe73cff31038 md5: f6ebe2cb3f82ba6c057dde5d9debe4f7 @@ -10398,9 +10431,60 @@ packages: license_family: Other size: 77606 timestamp: 1727963209370 -- conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h7606c53_1.conda - sha256: 3e0c718aa18dcac7f080844dbe0aea41a9cea75083019ce02e8a784926239826 - md5: a92cc3435b2fd6f51463f5a4db5c50b1 +- conda: https://prefix.dev/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_1.conda + sha256: b4fd6bd1cb87a183a8bbe85b4e87a1e7c51473309d0d82cd88d38fb021bcf41e + md5: d28b82fcc8d1b462b595af4b15a6cdcf + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=1.11 + - libgcc >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + size: 731658 + timestamp: 1741853415477 +- conda: https://prefix.dev/conda-forge/linux-aarch64/zstandard-0.23.0-py312hb2c0f52_1.conda + sha256: 0c0b17aa65255fe39a7f7d2c1feea3c1787a9c79329674b5981b6f13250bf230 + md5: 21505638229948770e18bd31635285ea + depends: + - cffi >=1.11 + - libgcc >=13 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + size: 696505 + timestamp: 1741853476146 +- conda: https://prefix.dev/conda-forge/osx-64/zstandard-0.23.0-py312h01d7ebd_1.conda + sha256: 5d2635e81ff5d61c87383c62824988154acefeae63f408d03dbefcb80cba5f02 + md5: 493516415601e57f73bda23e91dda742 + depends: + - __osx >=10.13 + - cffi >=1.11 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + size: 688202 + timestamp: 1741853531183 +- conda: https://prefix.dev/conda-forge/osx-arm64/zstandard-0.23.0-py312hea69d52_1.conda + sha256: db7ed45ce0ed42de5b799c094f15c064e5e7e88bbee128f8d15a0565367f3c41 + md5: b0af1b749dbf9621fbea742c2de68ff8 + depends: + - __osx >=11.0 + - cffi >=1.11 + - python >=3.12,<3.13.0a0 + - python >=3.12,<3.13.0a0 *_cpython + - python_abi 3.12.* *_cp312 + license: BSD-3-Clause + license_family: BSD + size: 531069 + timestamp: 1741853718145 +- conda: https://prefix.dev/conda-forge/win-64/zstandard-0.23.0-py312h4389bb4_1.conda + sha256: 17f2abbda821be146b549498fab3d0eb9cafb210e163b983524db91524b8dcb5 + md5: 5028543ffb67666ca4fc3ebd620c97b8 depends: - cffi >=1.11 - python >=3.12,<3.13.0a0 @@ -10408,12 +10492,10 @@ packages: - ucrt >=10.0.20348.0 - vc >=14.2,<15 - vc14_runtime >=14.29.30139 - - zstd >=1.5.6,<1.5.7.0a0 - - zstd >=1.5.6,<1.6.0a0 license: BSD-3-Clause license_family: BSD - size: 320624 - timestamp: 1725305934189 + size: 444958 + timestamp: 1741853730076 - conda: https://prefix.dev/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_1.conda sha256: 532d3623961e34c53aba98db2ad0a33b7a52ff90d6960e505fb2d2efc06bb7da md5: 02e4e2fa41a6528afba2e54cbc4280ff @@ -10457,18 +10539,6 @@ packages: license_family: BSD size: 399981 timestamp: 1740255382232 -- conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.6-h0ea2cb4_0.conda - sha256: 768e30dc513568491818fb068ee867c57c514b553915536da09e5d10b4ebf3c3 - md5: 9a17230f95733c04dc40a2b1e5491d74 - depends: - - libzlib >=1.2.13,<2.0.0a0 - - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 - license: BSD-3-Clause - license_family: BSD - size: 349143 - timestamp: 1714723445995 - conda: https://prefix.dev/conda-forge/win-64/zstd-1.5.7-hbeecb71_1.conda sha256: a59b096b95f20910158c927797e9144ed9c7970f1b4aca58e6d6c8db9f653006 md5: bf190adcc22f146d8ec66da215c9d78b diff --git a/pixi_docs/Cargo.lock b/pixi_docs/Cargo.lock index 8369d3c142..77a0622664 100644 --- a/pixi_docs/Cargo.lock +++ b/pixi_docs/Cargo.lock @@ -4336,7 +4336,6 @@ dependencies = [ "rattler_conda_types", "rattler_networking", "rattler_repodata_gateway", - "reqwest-middleware", "serde", "serde_ignored", "serde_json", @@ -4771,6 +4770,7 @@ name = "pypi_mapping" version = "0.1.0" dependencies = [ "async-once-cell", + "dashmap", "fs-err", "futures", "http-cache-reqwest", @@ -4787,7 +4787,9 @@ dependencies = [ "reqwest-retry", "serde", "serde_json", + "thiserror 2.0.12", "tokio", + "tracing", "url", ] @@ -4983,9 +4985,9 @@ dependencies = [ [[package]] name = "rattler" -version = "0.33.1" +version = "0.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454e5091f529a7c24f0b568b7c00b3c978ffb9c4020db8bb1e10dd6e73c9aaec" +checksum = "1f2f6a77d473028c29adf77cbc29d651d6566ca551bad3434e2314d992c6c1f9" dependencies = [ "anyhow", "clap", @@ -5150,9 +5152,9 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82434c783daf87bdcfee820db93fe12119cf469ca5abc174c8c45963d48aff2" +checksum = "cd9d126c14a5f5a50027c9f05992e7caf2122acb7ce528b9009ee92d475dd329" dependencies = [ "chrono", "configparser", @@ -5252,9 +5254,9 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.22.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6073881466bbd0e726061451e20341df1c4331220d5b489b09549388856d5da" +checksum = "ba15cb481b9d065194160fddff2fd21d15cac723622a1acddc8fae6b65ce0c7c" dependencies = [ "anyhow", "async-compression", diff --git a/src/cli/exec.rs b/src/cli/exec.rs index 73e90bad3b..7954a1d47e 100644 --- a/src/cli/exec.rs +++ b/src/cli/exec.rs @@ -141,7 +141,7 @@ pub async fn create_exec_prefix( .context("failed to write lock status to prefix guard")?; // Construct a gateway to get repodata. - let gateway = config.gateway(client.clone()); + let gateway = config.gateway().with_client(client.clone()).finish(); // Determine the specs to use for the environment let specs = if args.specs.is_empty() { diff --git a/src/cli/search.rs b/src/cli/search.rs index 6f9bb35f8d..ee5d47d92a 100644 --- a/src/cli/search.rs +++ b/src/cli/search.rs @@ -148,7 +148,7 @@ pub async fn execute_impl( let config = Config::load_global(); // Fetch the all names from the repodata using gateway - let gateway = config.gateway(client.clone()); + let gateway = config.gateway().with_client(client).finish(); let all_names = await_in_progress("loading all package names", |_| async { gateway diff --git a/src/global/project/mod.rs b/src/global/project/mod.rs index a8cf5a5ee1..cb8811ecff 100644 --- a/src/global/project/mod.rs +++ b/src/global/project/mod.rs @@ -1,24 +1,11 @@ -use self::trampoline::{Configuration, ConfigurationParseError, Trampoline}; -use super::{ - common::{get_install_changes, shortcut_sync_status, EnvironmentUpdate}, - install::find_binary_by_name, - trampoline::{self, GlobalExecutable}, - BinDir, EnvRoot, StateChange, StateChanges, -}; -use crate::{ - global::{ - common::{ - channel_url_to_prioritized_channel, expose_scripts_sync_status, find_package_records, - }, - find_executables, find_executables_for_many_records, - install::{create_executable_trampolines, script_exec_mapping}, - project::environment::environment_specs_in_sync, - EnvDir, - }, - prefix::{Executable, Prefix}, - repodata::Repodata, - rlimit::try_increase_rlimit_to_sensible, +use std::{ + ffi::OsStr, + fmt::{Debug, Formatter}, + path::{Path, PathBuf}, + str::FromStr, + sync::{Arc, LazyLock}, }; + use ahash::HashSet; pub(crate) use environment::EnvironmentName; use fancy_display::FancyDisplay; @@ -51,16 +38,32 @@ use rattler_repodata_gateway::Gateway; use rattler_solve::{resolvo::Solver, SolverImpl, SolverTask}; use rattler_virtual_packages::{VirtualPackage, VirtualPackageOverrides}; use reqwest_middleware::ClientWithMiddleware; -use std::sync::LazyLock; -use std::{ - ffi::OsStr, - fmt::{Debug, Formatter}, - path::{Path, PathBuf}, - str::FromStr, -}; +use tokio::sync::Semaphore; use toml_edit::DocumentMut; use uv_configuration::RAYON_INITIALIZE; +use self::trampoline::{Configuration, ConfigurationParseError, Trampoline}; +use super::{ + common::{get_install_changes, shortcut_sync_status, EnvironmentUpdate}, + install::find_binary_by_name, + trampoline::{self, GlobalExecutable}, + BinDir, EnvRoot, StateChange, StateChanges, +}; +use crate::{ + global::{ + common::{ + channel_url_to_prioritized_channel, expose_scripts_sync_status, find_package_records, + }, + find_executables, find_executables_for_many_records, + install::{create_executable_trampolines, script_exec_mapping}, + project::environment::environment_specs_in_sync, + EnvDir, + }, + prefix::{Executable, Prefix}, + repodata::Repodata, + rlimit::try_increase_rlimit_to_sensible, +}; + mod environment; mod manifest; mod parsed_manifest; @@ -91,6 +94,8 @@ pub struct Project { /// This is wrapped in a `OnceCell` to allow for lazy initialization. // TODO: once https://github.com/rust-lang/rust/issues/109737 is stabilized, switch to OnceLock repodata_gateway: OnceCell, + /// The concurrent request semaphore + concurrent_downloads_semaphore: OnceCell>, } impl Debug for Project { @@ -268,6 +273,7 @@ impl Project { bin_dir, client, repodata_gateway, + concurrent_downloads_semaphore: OnceCell::new(), } } @@ -615,7 +621,8 @@ impl Project { let env_dir = EnvDir::from_env_root(self.env_root.clone(), env_name).await?; let mut state_changes = StateChanges::new_with_env(env_name.clone()); - // Remove all shortcuts, using the information still available in the environment + // Remove all shortcuts, using the information still available in the + // environment state_changes |= self.remove_shortcuts(env_name).await?; // Remove the environment from the manifest, if it exists, otherwise ignore @@ -685,7 +692,8 @@ impl Project { Ok(all_executables) } - /// Get installed executables of direct dependencies of a specific environment. + /// Get installed executables of direct dependencies of a specific + /// environment. pub async fn executables_of_direct_dependencies( &self, env_name: &EnvironmentName, @@ -1058,7 +1066,8 @@ impl Project { if !env_set.contains(&env_name) { // Test if the environment directory is a conda environment if let Ok(true) = env_path.join(consts::CONDA_META_DIR).try_exists() { - // Remove all shortcuts, using the information still available in the environment + // Remove all shortcuts, using the information still available in the + // environment state_changes |= self.remove_shortcuts(&env_name).await?; // Remove the conda environment @@ -1183,6 +1192,17 @@ impl Project { } Ok(state_changes) } + + /// Returns a semaphore than can be used to limit the number of concurrent + /// according to the user configuration. + fn concurrent_downloads_semaphore(&self) -> Arc { + self.concurrent_downloads_semaphore + .get_or_init(|| { + let max_concurrent_downloads = self.config().max_concurrent_downloads(); + Arc::new(Semaphore::new(max_concurrent_downloads)) + }) + .clone() + } } impl Repodata for Project { @@ -1190,7 +1210,13 @@ impl Repodata for Project { fn repodata_gateway(&self) -> miette::Result<&Gateway> { self.repodata_gateway.get_or_try_init(|| { let client = self.authenticated_client()?.clone(); - Ok(self.config().gateway(client)) + let concurrent_downloads = self.concurrent_downloads_semaphore(); + Ok(self + .config() + .gateway() + .with_client(client) + .with_max_concurrent_requests(concurrent_downloads) + .finish()) }) } } diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index 29dfe93cfe..4cdbe28afb 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -9,10 +9,6 @@ use std::{ time::{Duration, Instant}, }; -use crate::{ - environment::{CondaPrefixUpdated, CondaPrefixUpdaterBuilder}, - workspace::{get_activated_environment_variables, EnvironmentVars}, -}; use barrier_cell::BarrierCell; use fancy_display::FancyDisplay; use futures::{stream::FuturesUnordered, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; @@ -29,7 +25,7 @@ use pixi_uv_conversions::{ to_extra_name, to_marker_environment, to_normalize, to_uv_extra_name, to_uv_normalize, ConversionError, }; -use pypi_mapping::{self}; +use pypi_mapping::{self, MappingClient}; use pypi_modifiers::pypi_marker_env::determine_marker_environment; use rattler::package_cache::PackageCache; use rattler_conda_types::{Arch, MatchSpec, PackageName, ParseStrictness, Platform}; @@ -37,7 +33,6 @@ use rattler_lock::{ LockFile, ParseCondaLockError, PypiIndexes, PypiPackageData, PypiPackageEnvironmentData, }; use rattler_repodata_gateway::{Gateway, RepoData}; -use reqwest_middleware::ClientWithMiddleware; use thiserror::Error; use tokio::sync::Semaphore; use tracing::Instrument; @@ -51,9 +46,9 @@ use crate::{ activation::CurrentEnvVarBehavior, build::{BuildContext, GlobHashCache, SourceCheckoutReporter}, environment::{ - self, read_environment_file, write_environment_file, EnvironmentFile, LockFileUsage, - LockedEnvironmentHash, PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, - PythonStatus, + self, read_environment_file, write_environment_file, CondaPrefixUpdated, + CondaPrefixUpdaterBuilder, EnvironmentFile, LockFileUsage, LockedEnvironmentHash, + PerEnvironmentAndPlatform, PerGroup, PerGroupAndPlatform, PythonStatus, }, lock_file::{ self, @@ -65,8 +60,9 @@ use crate::{ prefix::Prefix, repodata::Repodata, workspace::{ + get_activated_environment_variables, grouped_environment::{GroupedEnvironment, GroupedEnvironmentName}, - Environment, HasWorkspaceRef, + Environment, EnvironmentVars, HasWorkspaceRef, }, Workspace, }; @@ -74,15 +70,16 @@ use crate::{ impl Workspace { /// Ensures that the lock-file is up-to-date with the project. /// - /// This function will return a `LockFileDerivedData` struct that contains the - /// lock-file and any potential derived data that was computed as part of this - /// function. The derived data might be usable by other functions to avoid - /// recomputing the same data. + /// This function will return a `LockFileDerivedData` struct that contains + /// the lock-file and any potential derived data that was computed as + /// part of this function. The derived data might be usable by other + /// functions to avoid recomputing the same data. /// - /// This function starts by checking if the lock-file is up-to-date. If it is - /// not up-to-date it will construct a task graph of all the work that needs to - /// be done to update the lock-file. The tasks are awaited in a specific order - /// to make sure that we can start instantiating prefixes as soon as possible. + /// This function starts by checking if the lock-file is up-to-date. If it + /// is not up-to-date it will construct a task graph of all the work + /// that needs to be done to update the lock-file. The tasks are awaited + /// in a specific order to make sure that we can start instantiating + /// prefixes as soon as possible. pub async fn update_lock_file( &self, options: UpdateLockFileOptions, @@ -157,8 +154,8 @@ impl Workspace { Ok(lock_file_derived_data) } - /// Loads the lockfile for the workspace or returns `Lockfile::default` if none - /// could be found. + /// Loads the lockfile for the workspace or returns `Lockfile::default` if + /// none could be found. pub async fn load_lock_file(&self) -> miette::Result { let lock_file_path = self.lock_file_path(); if lock_file_path.is_file() { @@ -651,6 +648,9 @@ pub struct UpdateContext<'p> { /// The package cache to use when instantiating prefixes. package_cache: PackageCache, + /// The mapping client to use when fetching pypi mappings. + mapping_client: MappingClient, + /// A semaphore to limit the number of concurrent solves. conda_solve_semaphore: Arc, @@ -836,6 +836,9 @@ pub struct UpdateContextBuilder<'p> { /// The package cache to use during the update process. package_cache: Option, + /// The mapping client to use for fetching pypi mappings. + mapping_client: Option, + /// The maximum number of concurrent solves that are allowed to run. If this /// value is `None` a heuristic is used based on the number of cores /// available from the system. @@ -1079,12 +1082,18 @@ impl<'p> UpdateContextBuilder<'p> { // tool context let tool_context = ToolContext::builder() .with_gateway(gateway) - .with_client(client) + .with_client(client.clone()) .build(); let build_context = BuildContext::from_workspace(project)?.with_tool_context(Arc::new(tool_context)); + let mapping_client = self.mapping_client.unwrap_or_else(|| { + MappingClient::builder(client) + .with_concurrency_limit(project.concurrent_downloads_semaphore()) + .finish() + }); + Ok(UpdateContext { project, @@ -1100,6 +1109,7 @@ impl<'p> UpdateContextBuilder<'p> { grouped_solved_repodata_records: HashMap::new(), grouped_solved_pypi_records: HashMap::new(), + mapping_client, package_cache, conda_solve_semaphore: Arc::new(Semaphore::new(self.max_concurrent_solves)), pypi_solve_semaphore: Arc::new(Semaphore::new(determine_pypi_solve_permits(project))), @@ -1124,6 +1134,7 @@ impl<'p> UpdateContext<'p> { max_concurrent_solves: project.config().max_concurrent_solves(), io_concurrency_limit: None, glob_hash_cache: None, + mapping_client: None, } } @@ -1197,9 +1208,9 @@ impl<'p> UpdateContext<'p> { source.clone(), locked_group_records, project.repodata_gateway()?.clone(), + self.mapping_client.clone(), platform, self.conda_solve_semaphore.clone(), - project.authenticated_client()?.clone(), channel_priority, self.build_context.clone(), ) @@ -1634,9 +1645,9 @@ async fn spawn_solve_conda_environment_task( group: GroupedEnvironment<'_>, existing_repodata_records: Arc, repodata_gateway: Gateway, + mapping_client: MappingClient, platform: Platform, concurrency_semaphore: Arc, - client: ClientWithMiddleware, channel_priority: ChannelPriority, build_context: BuildContext, ) -> miette::Result { @@ -1847,13 +1858,13 @@ async fn spawn_solve_conda_environment_task( // we need them. if has_pypi_dependencies { pb.set_message("mapping conda to pypi packages"); - pypi_mapping::amend_pypi_purls( - client, - &pypi_name_mapping_location, - records.iter_mut().filter_map(PixiRecord::as_binary_mut), - Some(pb.purl_amend_reporter()), - ) - .await?; + mapping_client + .amend_purls( + &pypi_name_mapping_location, + records.iter_mut().filter_map(PixiRecord::as_binary_mut), + Some(pb.purl_amend_reporter()), + ) + .await?; } // Turn the records into a map by name @@ -2100,20 +2111,8 @@ async fn spawn_solve_pypi_task<'p>( let environment_name = grouped_environment.name().clone(); - let pypi_name_mapping_location = grouped_environment.workspace().pypi_name_mapping_source()?; - - let mut pixi_solve_records = repodata_records.records.clone(); - let locked_pypi_records = locked_pypi_packages.records.clone(); - - pypi_mapping::amend_pypi_purls( - environment.workspace().authenticated_client()?.clone(), - pypi_name_mapping_location, - pixi_solve_records - .iter_mut() - .filter_map(PixiRecord::as_binary_mut), - None, - ) - .await?; + let pixi_solve_records = &repodata_records.records; + let locked_pypi_records = &locked_pypi_packages.records; let pypi_options = environment.pypi_options(); let (pypi_packages, duration, prefix_task_result) = async move { @@ -2139,8 +2138,8 @@ async fn spawn_solve_pypi_task<'p>( &pypi_options, requirements, system_requirements, - &pixi_solve_records, - &locked_pypi_records, + pixi_solve_records, + locked_pypi_records, platform, &pb.pb, &project_root, diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index 2644b45bd4..c28d6267a5 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -24,7 +24,7 @@ pub use environment::Environment; pub use has_project_ref::HasWorkspaceRef; use indexmap::Equivalent; use itertools::Itertools; -use miette::IntoDiagnostic; +use miette::{Context, IntoDiagnostic}; use once_cell::sync::OnceCell; use pep508_rs::Requirement; use pixi_config::Config; @@ -43,7 +43,8 @@ use rattler_networking::s3_middleware; use rattler_repodata_gateway::Gateway; use reqwest_middleware::ClientWithMiddleware; pub use solve_group::SolveGroup; -use url::{ParseError, Url}; +use tokio::sync::Semaphore; +use url::Url; pub use workspace_mut::WorkspaceMut; use xxhash_rust::xxh3::xxh3_64; @@ -153,8 +154,12 @@ pub struct Workspace { /// The global configuration as loaded from the config file(s) config: Config, + /// The S3 configuration s3_config: HashMap, + + /// The concurrent request semaphore + concurrent_downloads_semaphore: OnceCell>, } impl Debug for Workspace { @@ -182,7 +187,8 @@ impl Workspace { // Canonicalize the root path let root = &manifest.workspace.provenance.path; let root = dunce::canonicalize(root).unwrap_or(root.to_path_buf()); - // Take the parent after canonicalizing to ensure this works even when the manifest + // Take the parent after canonicalizing to ensure this works even when the + // manifest let root = root .parent() .expect("manifest path should always have a parent") @@ -215,6 +221,7 @@ impl Workspace { config, s3_config, repodata_gateway: Default::default(), + concurrent_downloads_semaphore: OnceCell::default(), } } @@ -436,6 +443,17 @@ impl Workspace { Ok(&self.client_and_authenticated_client()?.1) } + /// Returns a semaphore than can be used to limit the number of concurrent + /// according to the user configuration. + pub fn concurrent_downloads_semaphore(&self) -> Arc { + self.concurrent_downloads_semaphore + .get_or_init(|| { + let max_concurrent_downloads = self.config().max_concurrent_downloads(); + Arc::new(Semaphore::new(max_concurrent_downloads)) + }) + .clone() + } + fn client_and_authenticated_client( &self, ) -> miette::Result<&(reqwest::Client, ClientWithMiddleware)> { @@ -533,18 +551,32 @@ impl Workspace { let mapping = channel_to_location_map .iter() .map(|(channel, mapping_location)| { - let url_or_path = match Url::parse(mapping_location) { - Ok(url) => MappingLocation::Url(url), - Err(err) => { - if let ParseError::RelativeUrlWithoutBase = err { - MappingLocation::Path(PathBuf::from(mapping_location)) - } else { - miette::bail!("Could not convert {mapping_location} to neither URL or Path") + let url_or_path = if mapping_location.starts_with("https://") + || mapping_location.starts_with("http://") + || mapping_location.starts_with("file://") + { + match Url::parse(mapping_location) { + Ok(url) => MappingLocation::Url(url), + Err(err) => { + return Err(err).into_diagnostic().context(format!( + "Could not convert {mapping_location} to URL" + )) } } + } else { + let path = PathBuf::from(mapping_location); + let abs_path = if path.is_relative() { + channel_config.root_dir.join(path) + } else { + path + }; + MappingLocation::Path(abs_path) }; - Ok((channel.canonical_name().trim_end_matches('/').into(), url_or_path)) + Ok(( + channel.canonical_name().trim_end_matches('/').into(), + url_or_path, + )) }) .collect::>>()?; @@ -1020,7 +1052,12 @@ mod tests { .trim_end_matches('/') ) .unwrap(), - &MappingLocation::Path(PathBuf::from("mapping.json")) + &MappingLocation::Path( + workspace + .channel_config() + .root_dir + .join(PathBuf::from("mapping.json")) + ) ); } diff --git a/src/workspace/repodata.rs b/src/workspace/repodata.rs index cc0b5b86b6..c280d8573b 100644 --- a/src/workspace/repodata.rs +++ b/src/workspace/repodata.rs @@ -1,11 +1,19 @@ -use crate::repodata::Repodata; -use crate::workspace::Workspace; use rattler_repodata_gateway::Gateway; +use crate::{repodata::Repodata, workspace::Workspace}; + impl Repodata for Workspace { /// Returns the [`Gateway`] used by this project. fn repodata_gateway(&self) -> miette::Result<&Gateway> { - self.repodata_gateway - .get_or_try_init(|| Ok(self.config().gateway(self.authenticated_client()?.clone()))) + self.repodata_gateway.get_or_try_init(|| { + let client = self.authenticated_client()?.clone(); + let concurrent_downloads = self.concurrent_downloads_semaphore(); + Ok(self + .config() + .gateway() + .with_client(client) + .with_max_concurrent_requests(concurrent_downloads) + .finish()) + }) } } diff --git a/tests/integration_rust/solve_group_tests.rs b/tests/integration_rust/solve_group_tests.rs index cb22666143..48a0046779 100644 --- a/tests/integration_rust/solve_group_tests.rs +++ b/tests/integration_rust/solve_group_tests.rs @@ -1,9 +1,11 @@ use std::{ collections::{BTreeSet, HashMap}, + path::Path, str::FromStr, + sync::Arc, }; -use pypi_mapping::{self, PurlSource}; +use pypi_mapping::{self, CustomMapping, MappingLocation, MappingSource, PurlSource}; use rattler_conda_types::{PackageName, Platform, RepoDataRecord}; use rattler_lock::DEFAULT_ENVIRONMENT_NAME; use reqwest_middleware::ClientBuilder; @@ -179,22 +181,11 @@ async fn test_purl_are_missing_for_non_conda_forge() { channel: Some("dummy-channel".to_owned()), }; - let packages = vec![repo_data_record.clone()]; - - let conda_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_mapping(client, &packages, None) - .await - .unwrap(); - // We are using custom mapping - let compressed_mapping = - HashMap::from([("foo-bar-car".to_owned(), Some("my-test-name".to_owned()))]); - - pypi_mapping::prefix_pypi_name_mapping::amend_pypi_purls_for_record( - &mut repo_data_record, - &conda_mapping, - &compressed_mapping, - ) - .unwrap(); + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls(&MappingSource::Prefix, vec![&mut repo_data_record], None) + .await + .unwrap(); // Because foo-bar-car is not from conda-forge channel // We verify that purls are missing for non-conda-forge packages @@ -223,22 +214,23 @@ async fn test_purl_are_generated_using_custom_mapping() { channel: Some("https://conda.anaconda.org/conda-forge/".to_owned()), }; - let packages = vec![repo_data_record.clone()]; - - let conda_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_mapping(client, &packages, None) - .await - .unwrap(); // We are using custom mapping let compressed_mapping = HashMap::from([("foo-bar-car".to_owned(), Some("my-test-name".to_owned()))]); - - pypi_mapping::prefix_pypi_name_mapping::amend_pypi_purls_for_record( - &mut repo_data_record, - &conda_mapping, - &compressed_mapping, - ) - .unwrap(); + let source = HashMap::from([( + "https://conda.anaconda.org/conda-forge".to_owned(), + MappingLocation::Memory(compressed_mapping), + )]); + + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + &MappingSource::Custom(Arc::new(CustomMapping::new(source))), + vec![&mut repo_data_record], + None, + ) + .await + .unwrap(); let first_purl = repo_data_record .package_record @@ -268,24 +260,13 @@ async fn test_compressed_mapping_catch_not_pandoc_not_a_python_package() { channel: Some("https://conda.anaconda.org/conda-forge/".to_owned()), }; - let packages = vec![repo_data_record.clone()]; - - let conda_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_mapping(client, &packages, None) - .await - .unwrap(); + let packages = vec![&mut repo_data_record]; - let compressed_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_compressed_mapping(client) - .await - .unwrap(); - - pypi_mapping::prefix_pypi_name_mapping::amend_pypi_purls_for_record( - &mut repo_data_record, - &conda_mapping, - &compressed_mapping, - ) - .unwrap(); + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls(&MappingSource::Prefix, packages, None) + .await + .unwrap(); // pandoc is not a python package // so purls for it should be empty @@ -327,31 +308,24 @@ async fn test_dont_record_not_present_package_as_purl() { channel: Some("https://conda.anaconda.org/conda-forge/".to_owned()), }; - let packages = vec![repo_data_record.clone(), boltons_repo_data_record.clone()]; - - let conda_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_mapping(client, &packages, None) - .await - .unwrap(); - - let compressed_mapping = - pypi_mapping::prefix_pypi_name_mapping::conda_pypi_name_compressed_mapping(client) - .await - .unwrap(); - - pypi_mapping::prefix_pypi_name_mapping::amend_pypi_purls_for_record( - &mut repo_data_record, - &conda_mapping, - &compressed_mapping, - ) - .unwrap(); + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + vec![&mut repo_data_record, &mut boltons_repo_data_record], + None, + ) + .await + .unwrap(); - pypi_mapping::prefix_pypi_name_mapping::amend_pypi_purls_for_record( - &mut boltons_repo_data_record, - &conda_mapping, - &compressed_mapping, - ) - .unwrap(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + vec![&mut repo_data_record, &mut boltons_repo_data_record], + None, + ) + .await + .unwrap(); let first_purl = repo_data_record .package_record @@ -380,17 +354,37 @@ async fn test_dont_record_not_present_package_as_purl() { ); } +fn absolute_custom_mapping_path() -> String { + dunce::simplified( + &Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/mapping_files/custom_mapping.json"), + ) + .display() + .to_string() + .replace("\\", "/") +} + +fn absolute_compressed_mapping_path() -> String { + dunce::simplified( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/data/mapping_files/compressed_mapping.json"), + ) + .display() + .to_string() + .replace("\\", "/") +} + #[tokio::test] async fn test_we_record_not_present_package_as_purl_for_custom_mapping() { - let pixi = PixiControl::from_manifest( + let pixi = PixiControl::from_manifest(&format!( r#" [project] name = "test-channel-change" channels = ["conda-forge"] platforms = ["linux-64"] - conda-pypi-map = { 'conda-forge' = "tests/data/mapping_files/compressed_mapping.json" } + conda-pypi-map = {{ 'conda-forge' = "{}" }} "#, - ) + absolute_compressed_mapping_path() + )) .unwrap(); let project = pixi.workspace().unwrap(); @@ -424,13 +418,13 @@ async fn test_we_record_not_present_package_as_purl_for_custom_mapping() { let mut packages = vec![repo_data_record, boltons_repo_data_record]; - let mapping_map = project - .pypi_name_mapping_source() - .unwrap() - .custom() - .unwrap(); - - pypi_mapping::custom_pypi_mapping::amend_pypi_purls(client, &mapping_map, &mut packages, None) + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) .await .unwrap(); @@ -443,6 +437,8 @@ async fn test_we_record_not_present_package_as_purl_for_custom_mapping() { .and_then(BTreeSet::first) .unwrap(); + println!("{boltons_first_purl}"); + // for boltons we have a mapping record // so we test that we also record source=project-defined-mapping qualifier assert_eq!(boltons_first_purl.name(), "boltons"); @@ -469,14 +465,16 @@ async fn test_we_record_not_present_package_as_purl_for_custom_mapping() { #[tokio::test] async fn test_custom_mapping_channel_with_suffix() { - let pixi = PixiControl::from_manifest(r#" + let pixi = PixiControl::from_manifest(&format!( + r#" [project] name = "test-channel-change" channels = ["conda-forge"] platforms = ["linux-64"] - conda-pypi-map = { "https://conda.anaconda.org/conda-forge/" = "tests/data/mapping_files/custom_mapping.json" } + conda-pypi-map = {{ "https://conda.anaconda.org/conda-forge/" = "{}" }} "#, - ) + absolute_custom_mapping_path() + )) .unwrap(); let project = pixi.workspace().unwrap(); @@ -494,11 +492,13 @@ async fn test_custom_mapping_channel_with_suffix() { let mut packages = vec![repo_data_record]; - let mapping_source = project.pypi_name_mapping_source().unwrap(); - - let mapping_map = mapping_source.custom().unwrap(); - - pypi_mapping::custom_pypi_mapping::amend_pypi_purls(client, &mapping_map, &mut packages, None) + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) .await .unwrap(); @@ -520,14 +520,16 @@ async fn test_custom_mapping_channel_with_suffix() { #[tokio::test] async fn test_repo_data_record_channel_with_suffix() { - let pixi = PixiControl::from_manifest(r#" + let pixi = PixiControl::from_manifest(&format!( + r#" [project] name = "test-channel-change" channels = ["conda-forge"] platforms = ["linux-64"] - conda-pypi-map = { "https://conda.anaconda.org/conda-forge" = "tests/data/mapping_files/custom_mapping.json" } + conda-pypi-map = {{ "https://conda.anaconda.org/conda-forge" = "{}" }} "#, - ) + absolute_custom_mapping_path(), + )) .unwrap(); let project = pixi.workspace().unwrap(); @@ -545,11 +547,13 @@ async fn test_repo_data_record_channel_with_suffix() { let mut packages = vec![repo_data_record]; - let mapping_source = project.pypi_name_mapping_source().unwrap(); - - let mapping_map = mapping_source.custom().unwrap(); - - pypi_mapping::custom_pypi_mapping::amend_pypi_purls(client, &mapping_map, &mut packages, None) + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) .await .unwrap(); @@ -570,15 +574,16 @@ async fn test_repo_data_record_channel_with_suffix() { #[tokio::test] async fn test_path_channel() { - let pixi = PixiControl::from_manifest( + let pixi = PixiControl::from_manifest(&format!( r#" [project] name = "test-channel-change" channels = ["file:///home/user/staged-recipes/build_artifacts"] platforms = ["linux-64"] - conda-pypi-map = {"file:///home/user/staged-recipes/build_artifacts" = "tests/data/mapping_files/custom_mapping.json"} + conda-pypi-map = {{"file:///home/user/staged-recipes/build_artifacts" = "{}" }} "#, - ) + absolute_custom_mapping_path() + )) .unwrap(); let project = pixi.workspace().unwrap(); @@ -596,11 +601,13 @@ async fn test_path_channel() { let mut packages = vec![repo_data_record]; - let mapping_source = project.pypi_name_mapping_source().unwrap(); - - let mapping_map = mapping_source.custom().unwrap(); - - pypi_mapping::custom_pypi_mapping::amend_pypi_purls(client, &mapping_map, &mut packages, None) + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) .await .unwrap(); @@ -670,10 +677,13 @@ async fn test_file_url_as_mapping_location() { let mut packages = vec![repo_data_record]; - let mapping_source = project.pypi_name_mapping_source().unwrap(); - - let mapping_map = mapping_source.custom().unwrap(); - pypi_mapping::custom_pypi_mapping::amend_pypi_purls(client, &mapping_map, &mut packages, None) + let mapping_client = pypi_mapping::MappingClient::builder(client.clone()).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) .await .unwrap(); @@ -727,14 +737,15 @@ async fn test_disabled_mapping() { let mut packages = vec![boltons_repo_data_record]; - pypi_mapping::amend_pypi_purls( - blocked_client, - project.pypi_name_mapping_source().unwrap(), - &mut packages, - None, - ) - .await - .unwrap(); + let mapping_client = pypi_mapping::MappingClient::builder(blocked_client).finish(); + mapping_client + .amend_purls( + project.pypi_name_mapping_source().unwrap(), + &mut packages, + None, + ) + .await + .unwrap(); let boltons_package = packages.pop().unwrap();