1use std::borrow::Cow;
36use std::path;
37use std::path::Path;
38use std::str::FromStr;
39
40use url::Url;
41
42use uv_distribution_filename::{
43 DistExtension, SourceDistExtension, SourceDistFilename, WheelFilename,
44};
45use uv_fs::normalize_absolute_path;
46use uv_git_types::GitUrl;
47use uv_normalize::PackageName;
48use uv_pep440::Version;
49use uv_pep508::{Pep508Url, VerbatimUrl};
50use uv_pypi_types::{
51 ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, VerbatimParsedUrl,
52};
53use uv_redacted::DisplaySafeUrl;
54
55pub use crate::annotation::*;
56pub use crate::any::*;
57pub use crate::build_info::*;
58pub use crate::build_requires::*;
59pub use crate::buildable::*;
60pub use crate::cached::*;
61pub use crate::config_settings::*;
62pub use crate::dependency_metadata::*;
63pub use crate::diagnostic::*;
64pub use crate::dist_error::*;
65pub use crate::error::*;
66pub use crate::exclude_newer::*;
67pub use crate::file::*;
68pub use crate::hash::*;
69pub use crate::id::*;
70pub use crate::index::*;
71pub use crate::index_name::*;
72pub use crate::index_url::*;
73pub use crate::installed::*;
74pub use crate::known_platform::*;
75pub use crate::origin::*;
76pub use crate::pip_index::*;
77pub use crate::prioritized_distribution::*;
78pub use crate::requested::*;
79pub use crate::requirement::*;
80pub use crate::requires_python::*;
81pub use crate::resolution::*;
82pub use crate::resolved::*;
83pub use crate::specified_requirement::*;
84pub use crate::status_code_strategy::*;
85pub use crate::traits::*;
86
87mod annotation;
88mod any;
89mod build_info;
90mod build_requires;
91mod buildable;
92mod cached;
93mod config_settings;
94mod dependency_metadata;
95mod diagnostic;
96mod dist_error;
97mod error;
98mod exclude_newer;
99mod file;
100mod hash;
101mod id;
102mod index;
103mod index_name;
104mod index_url;
105mod installed;
106mod known_platform;
107mod origin;
108mod pip_index;
109mod prioritized_distribution;
110mod requested;
111mod requirement;
112mod requires_python;
113mod resolution;
114mod resolved;
115mod specified_requirement;
116mod status_code_strategy;
117mod traits;
118
119#[derive(Debug, Clone)]
120pub enum VersionOrUrlRef<'a, T: Pep508Url = VerbatimUrl> {
121 Version(&'a Version),
123 Url(&'a T),
125}
126
127impl<'a, T: Pep508Url> VersionOrUrlRef<'a, T> {
128 pub fn url(&self) -> Option<&'a T> {
130 match self {
131 Self::Version(_) => None,
132 Self::Url(url) => Some(url),
133 }
134 }
135}
136
137impl Verbatim for VersionOrUrlRef<'_> {
138 fn verbatim(&self) -> Cow<'_, str> {
139 match self {
140 Self::Version(version) => Cow::Owned(format!("=={version}")),
141 Self::Url(url) => Cow::Owned(format!(" @ {}", url.verbatim())),
142 }
143 }
144}
145
146impl std::fmt::Display for VersionOrUrlRef<'_> {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 match self {
149 Self::Version(version) => write!(f, "=={version}"),
150 Self::Url(url) => write!(f, " @ {url}"),
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
156pub enum InstalledVersion<'a> {
157 Version(&'a Version),
159 Url(&'a DisplaySafeUrl, &'a Version),
162}
163
164impl<'a> InstalledVersion<'a> {
165 pub fn url(&self) -> Option<&'a DisplaySafeUrl> {
167 match self {
168 Self::Version(_) => None,
169 Self::Url(url, _) => Some(url),
170 }
171 }
172
173 pub fn version(&self) -> &'a Version {
175 match self {
176 Self::Version(version) => version,
177 Self::Url(_, version) => version,
178 }
179 }
180}
181
182impl std::fmt::Display for InstalledVersion<'_> {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 match self {
185 Self::Version(version) => write!(f, "=={version}"),
186 Self::Url(url, version) => write!(f, "=={version} (from {url})"),
187 }
188 }
189}
190
191#[derive(Debug, Clone, Hash, PartialEq, Eq)]
195pub enum Dist {
196 Built(BuiltDist),
197 Source(SourceDist),
198}
199
200#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
202pub enum DistRef<'a> {
203 Built(&'a BuiltDist),
204 Source(&'a SourceDist),
205}
206
207#[derive(Debug, Clone, Hash, PartialEq, Eq)]
209pub enum BuiltDist {
210 Registry(RegistryBuiltDist),
211 DirectUrl(DirectUrlBuiltDist),
212 Path(PathBuiltDist),
213}
214
215#[derive(Debug, Clone, Hash, PartialEq, Eq)]
217pub enum SourceDist {
218 Registry(RegistrySourceDist),
219 DirectUrl(DirectUrlSourceDist),
220 Git(GitSourceDist),
221 Path(PathSourceDist),
222 Directory(DirectorySourceDist),
223}
224
225#[derive(Debug, Clone, Hash, PartialEq, Eq)]
227pub struct RegistryBuiltWheel {
228 pub filename: WheelFilename,
229 pub file: Box<File>,
230 pub index: IndexUrl,
231}
232
233#[derive(Debug, Clone, Hash, PartialEq, Eq)]
235pub struct RegistryBuiltDist {
236 pub wheels: Vec<RegistryBuiltWheel>,
239 pub best_wheel_index: usize,
244 pub sdist: Option<RegistrySourceDist>,
252 }
262
263#[derive(Debug, Clone, Hash, PartialEq, Eq)]
265pub struct DirectUrlBuiltDist {
266 pub filename: WheelFilename,
269 pub location: Box<DisplaySafeUrl>,
271 pub url: VerbatimUrl,
273}
274
275#[derive(Debug, Clone, Hash, PartialEq, Eq)]
277pub struct PathBuiltDist {
278 pub filename: WheelFilename,
279 pub install_path: Box<Path>,
281 pub url: VerbatimUrl,
283}
284
285#[derive(Debug, Clone, Hash, PartialEq, Eq)]
287pub struct RegistrySourceDist {
288 pub name: PackageName,
289 pub version: Version,
290 pub file: Box<File>,
291 pub ext: SourceDistExtension,
293 pub index: IndexUrl,
294 pub wheels: Vec<RegistryBuiltWheel>,
302}
303
304#[derive(Debug, Clone, Hash, PartialEq, Eq)]
306pub struct DirectUrlSourceDist {
307 pub name: PackageName,
310 pub location: Box<DisplaySafeUrl>,
312 pub subdirectory: Option<Box<Path>>,
314 pub ext: SourceDistExtension,
316 pub url: VerbatimUrl,
318}
319
320#[derive(Debug, Clone, Hash, PartialEq, Eq)]
322pub struct GitSourceDist {
323 pub name: PackageName,
324 pub git: Box<GitUrl>,
326 pub subdirectory: Option<Box<Path>>,
328 pub url: VerbatimUrl,
330}
331
332#[derive(Debug, Clone, Hash, PartialEq, Eq)]
334pub struct PathSourceDist {
335 pub name: PackageName,
336 pub version: Option<Version>,
337 pub install_path: Box<Path>,
339 pub ext: SourceDistExtension,
341 pub url: VerbatimUrl,
343}
344
345#[derive(Debug, Clone, Hash, PartialEq, Eq)]
347pub struct DirectorySourceDist {
348 pub name: PackageName,
349 pub install_path: Box<Path>,
351 pub editable: Option<bool>,
353 pub r#virtual: Option<bool>,
355 pub url: VerbatimUrl,
357}
358
359impl Dist {
360 pub fn from_http_url(
363 name: PackageName,
364 url: VerbatimUrl,
365 location: DisplaySafeUrl,
366 subdirectory: Option<Box<Path>>,
367 ext: DistExtension,
368 ) -> Result<Self, Error> {
369 match ext {
370 DistExtension::Wheel => {
371 let filename = WheelFilename::from_str(&url.filename()?)?;
373 if filename.name != name {
374 return Err(Error::PackageNameMismatch(
375 name,
376 filename.name,
377 url.verbatim().to_string(),
378 ));
379 }
380
381 Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
382 filename,
383 location: Box::new(location),
384 url,
385 })))
386 }
387 DistExtension::Source(ext) => {
388 Ok(Self::Source(SourceDist::DirectUrl(DirectUrlSourceDist {
389 name,
390 location: Box::new(location),
391 subdirectory,
392 ext,
393 url,
394 })))
395 }
396 }
397 }
398
399 pub fn from_file_url(
401 name: PackageName,
402 url: VerbatimUrl,
403 install_path: &Path,
404 ext: DistExtension,
405 ) -> Result<Self, Error> {
406 let install_path = path::absolute(install_path)?;
408
409 let install_path = normalize_absolute_path(&install_path)?;
411
412 if !install_path.exists() {
414 return Err(Error::NotFound(url.to_url()));
415 }
416
417 match ext {
419 DistExtension::Wheel => {
420 let filename = WheelFilename::from_str(&url.filename()?)?;
422 if filename.name != name {
423 return Err(Error::PackageNameMismatch(
424 name,
425 filename.name,
426 url.verbatim().to_string(),
427 ));
428 }
429 Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
430 filename,
431 install_path: install_path.into_boxed_path(),
432 url,
433 })))
434 }
435 DistExtension::Source(ext) => {
436 let version = url
438 .filename()
439 .ok()
440 .and_then(|filename| {
441 SourceDistFilename::parse(filename.as_ref(), ext, &name).ok()
442 })
443 .map(|filename| filename.version);
444
445 Ok(Self::Source(SourceDist::Path(PathSourceDist {
446 name,
447 version,
448 install_path: install_path.into_boxed_path(),
449 ext,
450 url,
451 })))
452 }
453 }
454 }
455
456 pub fn from_directory_url(
458 name: PackageName,
459 url: VerbatimUrl,
460 install_path: &Path,
461 editable: Option<bool>,
462 r#virtual: Option<bool>,
463 ) -> Result<Self, Error> {
464 let install_path = path::absolute(install_path)?;
466
467 let install_path = normalize_absolute_path(&install_path)?;
469
470 if !install_path.exists() {
472 return Err(Error::NotFound(url.to_url()));
473 }
474
475 Ok(Self::Source(SourceDist::Directory(DirectorySourceDist {
477 name,
478 install_path: install_path.into_boxed_path(),
479 editable,
480 r#virtual,
481 url,
482 })))
483 }
484
485 pub fn from_git_url(
487 name: PackageName,
488 url: VerbatimUrl,
489 git: GitUrl,
490 subdirectory: Option<Box<Path>>,
491 ) -> Result<Self, Error> {
492 Ok(Self::Source(SourceDist::Git(GitSourceDist {
493 name,
494 git: Box::new(git),
495 subdirectory,
496 url,
497 })))
498 }
499
500 pub fn from_url(name: PackageName, url: VerbatimParsedUrl) -> Result<Self, Error> {
502 match url.parsed_url {
503 ParsedUrl::Archive(archive) => Self::from_http_url(
504 name,
505 url.verbatim,
506 archive.url,
507 archive.subdirectory,
508 archive.ext,
509 ),
510 ParsedUrl::Path(file) => {
511 Self::from_file_url(name, url.verbatim, &file.install_path, file.ext)
512 }
513 ParsedUrl::Directory(directory) => Self::from_directory_url(
514 name,
515 url.verbatim,
516 &directory.install_path,
517 directory.editable,
518 directory.r#virtual,
519 ),
520 ParsedUrl::Git(git) => {
521 Self::from_git_url(name, url.verbatim, git.url, git.subdirectory)
522 }
523 }
524 }
525
526 pub fn is_editable(&self) -> bool {
528 match self {
529 Self::Source(dist) => dist.is_editable(),
530 Self::Built(_) => false,
531 }
532 }
533
534 pub fn is_local(&self) -> bool {
536 match self {
537 Self::Source(dist) => dist.is_local(),
538 Self::Built(dist) => dist.is_local(),
539 }
540 }
541
542 pub fn index(&self) -> Option<&IndexUrl> {
544 match self {
545 Self::Built(dist) => dist.index(),
546 Self::Source(dist) => dist.index(),
547 }
548 }
549
550 pub fn file(&self) -> Option<&File> {
552 match self {
553 Self::Built(built) => built.file(),
554 Self::Source(source) => source.file(),
555 }
556 }
557
558 pub fn source_tree(&self) -> Option<&Path> {
560 match self {
561 Self::Built { .. } => None,
562 Self::Source(source) => source.source_tree(),
563 }
564 }
565
566 pub fn version(&self) -> Option<&Version> {
568 match self {
569 Self::Built(wheel) => Some(wheel.version()),
570 Self::Source(source_dist) => source_dist.version(),
571 }
572 }
573
574 pub fn as_ref(&self) -> DistRef<'_> {
576 match self {
577 Self::Built(dist) => DistRef::Built(dist),
578 Self::Source(dist) => DistRef::Source(dist),
579 }
580 }
581}
582
583impl<'a> From<&'a Dist> for DistRef<'a> {
584 fn from(dist: &'a Dist) -> Self {
585 match dist {
586 Dist::Built(built) => DistRef::Built(built),
587 Dist::Source(source) => DistRef::Source(source),
588 }
589 }
590}
591
592impl<'a> From<&'a SourceDist> for DistRef<'a> {
593 fn from(dist: &'a SourceDist) -> Self {
594 DistRef::Source(dist)
595 }
596}
597
598impl<'a> From<&'a BuiltDist> for DistRef<'a> {
599 fn from(dist: &'a BuiltDist) -> Self {
600 DistRef::Built(dist)
601 }
602}
603
604impl BuiltDist {
605 pub fn is_local(&self) -> bool {
607 matches!(self, Self::Path(_))
608 }
609
610 pub fn index(&self) -> Option<&IndexUrl> {
612 match self {
613 Self::Registry(registry) => Some(®istry.best_wheel().index),
614 Self::DirectUrl(_) => None,
615 Self::Path(_) => None,
616 }
617 }
618
619 pub fn file(&self) -> Option<&File> {
621 match self {
622 Self::Registry(registry) => Some(®istry.best_wheel().file),
623 Self::DirectUrl(_) | Self::Path(_) => None,
624 }
625 }
626
627 pub fn version(&self) -> &Version {
628 match self {
629 Self::Registry(wheels) => &wheels.best_wheel().filename.version,
630 Self::DirectUrl(wheel) => &wheel.filename.version,
631 Self::Path(wheel) => &wheel.filename.version,
632 }
633 }
634}
635
636impl SourceDist {
637 pub fn extension(&self) -> Option<SourceDistExtension> {
639 match self {
640 Self::Registry(source_dist) => Some(source_dist.ext),
641 Self::DirectUrl(source_dist) => Some(source_dist.ext),
642 Self::Path(source_dist) => Some(source_dist.ext),
643 Self::Git(_) | Self::Directory(_) => None,
644 }
645 }
646
647 pub fn index(&self) -> Option<&IndexUrl> {
649 match self {
650 Self::Registry(registry) => Some(®istry.index),
651 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
652 }
653 }
654
655 pub fn file(&self) -> Option<&File> {
657 match self {
658 Self::Registry(registry) => Some(®istry.file),
659 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
660 }
661 }
662
663 pub fn version(&self) -> Option<&Version> {
665 match self {
666 Self::Registry(source_dist) => Some(&source_dist.version),
667 Self::DirectUrl(_) | Self::Git(_) | Self::Path(_) | Self::Directory(_) => None,
668 }
669 }
670
671 pub fn is_editable(&self) -> bool {
673 match self {
674 Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
675 _ => false,
676 }
677 }
678
679 pub fn is_virtual(&self) -> bool {
681 match self {
682 Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
683 _ => false,
684 }
685 }
686
687 pub fn is_local(&self) -> bool {
689 matches!(self, Self::Directory(_) | Self::Path(_))
690 }
691
692 pub fn as_path(&self) -> Option<&Path> {
694 match self {
695 Self::Path(dist) => Some(&dist.install_path),
696 Self::Directory(dist) => Some(&dist.install_path),
697 _ => None,
698 }
699 }
700
701 pub fn source_tree(&self) -> Option<&Path> {
703 match self {
704 Self::Directory(dist) => Some(&dist.install_path),
705 _ => None,
706 }
707 }
708}
709
710impl RegistryBuiltDist {
711 pub fn best_wheel(&self) -> &RegistryBuiltWheel {
713 &self.wheels[self.best_wheel_index]
714 }
715}
716
717impl DirectUrlBuiltDist {
718 pub fn parsed_url(&self) -> ParsedUrl {
720 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
721 (*self.location).clone(),
722 None,
723 DistExtension::Wheel,
724 ))
725 }
726}
727
728impl PathBuiltDist {
729 pub fn parsed_url(&self) -> ParsedUrl {
731 ParsedUrl::Path(ParsedPathUrl::from_source(
732 self.install_path.clone(),
733 DistExtension::Wheel,
734 self.url.to_url(),
735 ))
736 }
737}
738
739impl PathSourceDist {
740 pub fn parsed_url(&self) -> ParsedUrl {
742 ParsedUrl::Path(ParsedPathUrl::from_source(
743 self.install_path.clone(),
744 DistExtension::Source(self.ext),
745 self.url.to_url(),
746 ))
747 }
748}
749
750impl DirectUrlSourceDist {
751 pub fn parsed_url(&self) -> ParsedUrl {
753 ParsedUrl::Archive(ParsedArchiveUrl::from_source(
754 (*self.location).clone(),
755 self.subdirectory.clone(),
756 DistExtension::Source(self.ext),
757 ))
758 }
759}
760
761impl GitSourceDist {
762 pub fn parsed_url(&self) -> ParsedUrl {
764 ParsedUrl::Git(ParsedGitUrl::from_source(
765 (*self.git).clone(),
766 self.subdirectory.clone(),
767 ))
768 }
769}
770
771impl DirectorySourceDist {
772 pub fn parsed_url(&self) -> ParsedUrl {
774 ParsedUrl::Directory(ParsedDirectoryUrl::from_source(
775 self.install_path.clone(),
776 self.editable,
777 self.r#virtual,
778 self.url.to_url(),
779 ))
780 }
781}
782
783impl Name for RegistryBuiltWheel {
784 fn name(&self) -> &PackageName {
785 &self.filename.name
786 }
787}
788
789impl Name for RegistryBuiltDist {
790 fn name(&self) -> &PackageName {
791 self.best_wheel().name()
792 }
793}
794
795impl Name for DirectUrlBuiltDist {
796 fn name(&self) -> &PackageName {
797 &self.filename.name
798 }
799}
800
801impl Name for PathBuiltDist {
802 fn name(&self) -> &PackageName {
803 &self.filename.name
804 }
805}
806
807impl Name for RegistrySourceDist {
808 fn name(&self) -> &PackageName {
809 &self.name
810 }
811}
812
813impl Name for DirectUrlSourceDist {
814 fn name(&self) -> &PackageName {
815 &self.name
816 }
817}
818
819impl Name for GitSourceDist {
820 fn name(&self) -> &PackageName {
821 &self.name
822 }
823}
824
825impl Name for PathSourceDist {
826 fn name(&self) -> &PackageName {
827 &self.name
828 }
829}
830
831impl Name for DirectorySourceDist {
832 fn name(&self) -> &PackageName {
833 &self.name
834 }
835}
836
837impl Name for SourceDist {
838 fn name(&self) -> &PackageName {
839 match self {
840 Self::Registry(dist) => dist.name(),
841 Self::DirectUrl(dist) => dist.name(),
842 Self::Git(dist) => dist.name(),
843 Self::Path(dist) => dist.name(),
844 Self::Directory(dist) => dist.name(),
845 }
846 }
847}
848
849impl Name for BuiltDist {
850 fn name(&self) -> &PackageName {
851 match self {
852 Self::Registry(dist) => dist.name(),
853 Self::DirectUrl(dist) => dist.name(),
854 Self::Path(dist) => dist.name(),
855 }
856 }
857}
858
859impl Name for Dist {
860 fn name(&self) -> &PackageName {
861 match self {
862 Self::Built(dist) => dist.name(),
863 Self::Source(dist) => dist.name(),
864 }
865 }
866}
867
868impl Name for CompatibleDist<'_> {
869 fn name(&self) -> &PackageName {
870 match self {
871 Self::InstalledDist(dist) => dist.name(),
872 Self::SourceDist {
873 sdist,
874 prioritized: _,
875 } => sdist.name(),
876 Self::CompatibleWheel {
877 wheel,
878 priority: _,
879 prioritized: _,
880 } => wheel.name(),
881 Self::IncompatibleWheel {
882 sdist,
883 wheel: _,
884 prioritized: _,
885 } => sdist.name(),
886 }
887 }
888}
889
890impl DistributionMetadata for RegistryBuiltWheel {
891 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
892 VersionOrUrlRef::Version(&self.filename.version)
893 }
894}
895
896impl DistributionMetadata for RegistryBuiltDist {
897 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
898 self.best_wheel().version_or_url()
899 }
900}
901
902impl DistributionMetadata for DirectUrlBuiltDist {
903 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
904 VersionOrUrlRef::Url(&self.url)
905 }
906
907 fn version_id(&self) -> VersionId {
908 VersionId::from_archive(self.location.as_ref(), None)
909 }
910}
911
912impl DistributionMetadata for PathBuiltDist {
913 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
914 VersionOrUrlRef::Url(&self.url)
915 }
916
917 fn version_id(&self) -> VersionId {
918 VersionId::from_path(self.install_path.as_ref())
919 }
920}
921
922impl DistributionMetadata for RegistrySourceDist {
923 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
924 VersionOrUrlRef::Version(&self.version)
925 }
926}
927
928impl DistributionMetadata for DirectUrlSourceDist {
929 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
930 VersionOrUrlRef::Url(&self.url)
931 }
932
933 fn version_id(&self) -> VersionId {
934 VersionId::from_archive(self.location.as_ref(), self.subdirectory.as_deref())
935 }
936}
937
938impl DistributionMetadata for GitSourceDist {
939 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
940 VersionOrUrlRef::Url(&self.url)
941 }
942
943 fn version_id(&self) -> VersionId {
944 VersionId::from_git(self.git.as_ref(), self.subdirectory.as_deref())
945 }
946}
947
948impl DistributionMetadata for PathSourceDist {
949 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
950 VersionOrUrlRef::Url(&self.url)
951 }
952
953 fn version_id(&self) -> VersionId {
954 VersionId::from_path(self.install_path.as_ref())
955 }
956}
957
958impl DistributionMetadata for DirectorySourceDist {
959 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
960 VersionOrUrlRef::Url(&self.url)
961 }
962
963 fn version_id(&self) -> VersionId {
964 VersionId::from_directory(self.install_path.as_ref())
965 }
966}
967
968impl DistributionMetadata for SourceDist {
969 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
970 match self {
971 Self::Registry(dist) => dist.version_or_url(),
972 Self::DirectUrl(dist) => dist.version_or_url(),
973 Self::Git(dist) => dist.version_or_url(),
974 Self::Path(dist) => dist.version_or_url(),
975 Self::Directory(dist) => dist.version_or_url(),
976 }
977 }
978
979 fn version_id(&self) -> VersionId {
980 match self {
981 Self::Registry(dist) => dist.version_id(),
982 Self::DirectUrl(dist) => dist.version_id(),
983 Self::Git(dist) => dist.version_id(),
984 Self::Path(dist) => dist.version_id(),
985 Self::Directory(dist) => dist.version_id(),
986 }
987 }
988}
989
990impl DistributionMetadata for BuiltDist {
991 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
992 match self {
993 Self::Registry(dist) => dist.version_or_url(),
994 Self::DirectUrl(dist) => dist.version_or_url(),
995 Self::Path(dist) => dist.version_or_url(),
996 }
997 }
998
999 fn version_id(&self) -> VersionId {
1000 match self {
1001 Self::Registry(dist) => dist.version_id(),
1002 Self::DirectUrl(dist) => dist.version_id(),
1003 Self::Path(dist) => dist.version_id(),
1004 }
1005 }
1006}
1007
1008impl DistributionMetadata for Dist {
1009 fn version_or_url(&self) -> VersionOrUrlRef<'_> {
1010 match self {
1011 Self::Built(dist) => dist.version_or_url(),
1012 Self::Source(dist) => dist.version_or_url(),
1013 }
1014 }
1015
1016 fn version_id(&self) -> VersionId {
1017 match self {
1018 Self::Built(dist) => dist.version_id(),
1019 Self::Source(dist) => dist.version_id(),
1020 }
1021 }
1022}
1023
1024impl RemoteSource for File {
1025 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1026 Ok(Cow::Borrowed(&self.filename))
1027 }
1028
1029 fn size(&self) -> Option<u64> {
1030 self.size
1031 }
1032}
1033
1034impl RemoteSource for Url {
1035 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1036 let mut path_segments = self
1038 .path_segments()
1039 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1040
1041 let last = path_segments
1043 .next_back()
1044 .expect("path segments is non-empty");
1045
1046 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1048
1049 Ok(filename)
1050 }
1051
1052 fn size(&self) -> Option<u64> {
1053 None
1054 }
1055}
1056
1057impl RemoteSource for UrlString {
1058 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1059 let last = self
1061 .base_str()
1062 .split('/')
1063 .next_back()
1064 .ok_or_else(|| Error::MissingPathSegments(self.to_string()))?;
1065
1066 let filename = percent_encoding::percent_decode_str(last).decode_utf8()?;
1068
1069 Ok(filename)
1070 }
1071
1072 fn size(&self) -> Option<u64> {
1073 None
1074 }
1075}
1076
1077impl RemoteSource for RegistryBuiltWheel {
1078 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1079 self.file.filename()
1080 }
1081
1082 fn size(&self) -> Option<u64> {
1083 self.file.size()
1084 }
1085}
1086
1087impl RemoteSource for RegistryBuiltDist {
1088 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1089 self.best_wheel().filename()
1090 }
1091
1092 fn size(&self) -> Option<u64> {
1093 self.best_wheel().size()
1094 }
1095}
1096
1097impl RemoteSource for RegistrySourceDist {
1098 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1099 self.file.filename()
1100 }
1101
1102 fn size(&self) -> Option<u64> {
1103 self.file.size()
1104 }
1105}
1106
1107impl RemoteSource for DirectUrlBuiltDist {
1108 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1109 self.url.filename()
1110 }
1111
1112 fn size(&self) -> Option<u64> {
1113 self.url.size()
1114 }
1115}
1116
1117impl RemoteSource for DirectUrlSourceDist {
1118 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1119 self.url.filename()
1120 }
1121
1122 fn size(&self) -> Option<u64> {
1123 self.url.size()
1124 }
1125}
1126
1127impl RemoteSource for GitSourceDist {
1128 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1129 match self.url.filename()? {
1131 Cow::Borrowed(filename) => {
1132 if let Some((_, filename)) = filename.rsplit_once('@') {
1133 Ok(Cow::Borrowed(filename))
1134 } else {
1135 Ok(Cow::Borrowed(filename))
1136 }
1137 }
1138 Cow::Owned(filename) => {
1139 if let Some((_, filename)) = filename.rsplit_once('@') {
1140 Ok(Cow::Owned(filename.to_owned()))
1141 } else {
1142 Ok(Cow::Owned(filename))
1143 }
1144 }
1145 }
1146 }
1147
1148 fn size(&self) -> Option<u64> {
1149 self.url.size()
1150 }
1151}
1152
1153impl RemoteSource for PathBuiltDist {
1154 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1155 self.url.filename()
1156 }
1157
1158 fn size(&self) -> Option<u64> {
1159 self.url.size()
1160 }
1161}
1162
1163impl RemoteSource for PathSourceDist {
1164 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1165 self.url.filename()
1166 }
1167
1168 fn size(&self) -> Option<u64> {
1169 self.url.size()
1170 }
1171}
1172
1173impl RemoteSource for DirectorySourceDist {
1174 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1175 self.url.filename()
1176 }
1177
1178 fn size(&self) -> Option<u64> {
1179 self.url.size()
1180 }
1181}
1182
1183impl RemoteSource for SourceDist {
1184 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1185 match self {
1186 Self::Registry(dist) => dist.filename(),
1187 Self::DirectUrl(dist) => dist.filename(),
1188 Self::Git(dist) => dist.filename(),
1189 Self::Path(dist) => dist.filename(),
1190 Self::Directory(dist) => dist.filename(),
1191 }
1192 }
1193
1194 fn size(&self) -> Option<u64> {
1195 match self {
1196 Self::Registry(dist) => dist.size(),
1197 Self::DirectUrl(dist) => dist.size(),
1198 Self::Git(dist) => dist.size(),
1199 Self::Path(dist) => dist.size(),
1200 Self::Directory(dist) => dist.size(),
1201 }
1202 }
1203}
1204
1205impl RemoteSource for BuiltDist {
1206 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1207 match self {
1208 Self::Registry(dist) => dist.filename(),
1209 Self::DirectUrl(dist) => dist.filename(),
1210 Self::Path(dist) => dist.filename(),
1211 }
1212 }
1213
1214 fn size(&self) -> Option<u64> {
1215 match self {
1216 Self::Registry(dist) => dist.size(),
1217 Self::DirectUrl(dist) => dist.size(),
1218 Self::Path(dist) => dist.size(),
1219 }
1220 }
1221}
1222
1223impl RemoteSource for Dist {
1224 fn filename(&self) -> Result<Cow<'_, str>, Error> {
1225 match self {
1226 Self::Built(dist) => dist.filename(),
1227 Self::Source(dist) => dist.filename(),
1228 }
1229 }
1230
1231 fn size(&self) -> Option<u64> {
1232 match self {
1233 Self::Built(dist) => dist.size(),
1234 Self::Source(dist) => dist.size(),
1235 }
1236 }
1237}
1238
1239impl Identifier for DisplaySafeUrl {
1240 fn distribution_id(&self) -> DistributionId {
1241 DistributionId::Url(uv_cache_key::CanonicalUrl::new(self))
1242 }
1243
1244 fn resource_id(&self) -> ResourceId {
1245 ResourceId::Url(uv_cache_key::RepositoryUrl::new(self))
1246 }
1247}
1248
1249impl Identifier for File {
1250 fn distribution_id(&self) -> DistributionId {
1251 self.hashes
1252 .first()
1253 .cloned()
1254 .map(DistributionId::Digest)
1255 .unwrap_or_else(|| self.url.distribution_id())
1256 }
1257
1258 fn resource_id(&self) -> ResourceId {
1259 self.hashes
1260 .first()
1261 .cloned()
1262 .map(ResourceId::Digest)
1263 .unwrap_or_else(|| self.url.resource_id())
1264 }
1265}
1266
1267impl Identifier for Path {
1268 fn distribution_id(&self) -> DistributionId {
1269 DistributionId::PathBuf(self.to_path_buf())
1270 }
1271
1272 fn resource_id(&self) -> ResourceId {
1273 ResourceId::PathBuf(self.to_path_buf())
1274 }
1275}
1276
1277impl Identifier for FileLocation {
1278 fn distribution_id(&self) -> DistributionId {
1279 match self {
1280 Self::RelativeUrl(base, url) => {
1281 DistributionId::RelativeUrl(base.to_string(), url.to_string())
1282 }
1283 Self::AbsoluteUrl(url) => DistributionId::AbsoluteUrl(url.to_string()),
1284 }
1285 }
1286
1287 fn resource_id(&self) -> ResourceId {
1288 match self {
1289 Self::RelativeUrl(base, url) => {
1290 ResourceId::RelativeUrl(base.to_string(), url.to_string())
1291 }
1292 Self::AbsoluteUrl(url) => ResourceId::AbsoluteUrl(url.to_string()),
1293 }
1294 }
1295}
1296
1297impl Identifier for RegistryBuiltWheel {
1298 fn distribution_id(&self) -> DistributionId {
1299 self.file.distribution_id()
1300 }
1301
1302 fn resource_id(&self) -> ResourceId {
1303 self.file.resource_id()
1304 }
1305}
1306
1307impl Identifier for RegistryBuiltDist {
1308 fn distribution_id(&self) -> DistributionId {
1309 self.best_wheel().distribution_id()
1310 }
1311
1312 fn resource_id(&self) -> ResourceId {
1313 self.best_wheel().resource_id()
1314 }
1315}
1316
1317impl Identifier for RegistrySourceDist {
1318 fn distribution_id(&self) -> DistributionId {
1319 self.file.distribution_id()
1320 }
1321
1322 fn resource_id(&self) -> ResourceId {
1323 self.file.resource_id()
1324 }
1325}
1326
1327impl Identifier for DirectUrlBuiltDist {
1328 fn distribution_id(&self) -> DistributionId {
1329 self.url.distribution_id()
1330 }
1331
1332 fn resource_id(&self) -> ResourceId {
1333 self.url.resource_id()
1334 }
1335}
1336
1337impl Identifier for DirectUrlSourceDist {
1338 fn distribution_id(&self) -> DistributionId {
1339 self.url.distribution_id()
1340 }
1341
1342 fn resource_id(&self) -> ResourceId {
1343 self.url.resource_id()
1344 }
1345}
1346
1347impl Identifier for PathBuiltDist {
1348 fn distribution_id(&self) -> DistributionId {
1349 self.url.distribution_id()
1350 }
1351
1352 fn resource_id(&self) -> ResourceId {
1353 self.url.resource_id()
1354 }
1355}
1356
1357impl Identifier for PathSourceDist {
1358 fn distribution_id(&self) -> DistributionId {
1359 self.url.distribution_id()
1360 }
1361
1362 fn resource_id(&self) -> ResourceId {
1363 self.url.resource_id()
1364 }
1365}
1366
1367impl Identifier for DirectorySourceDist {
1368 fn distribution_id(&self) -> DistributionId {
1369 self.url.distribution_id()
1370 }
1371
1372 fn resource_id(&self) -> ResourceId {
1373 self.url.resource_id()
1374 }
1375}
1376
1377impl Identifier for GitSourceDist {
1378 fn distribution_id(&self) -> DistributionId {
1379 self.url.distribution_id()
1380 }
1381
1382 fn resource_id(&self) -> ResourceId {
1383 self.url.resource_id()
1384 }
1385}
1386
1387impl Identifier for SourceDist {
1388 fn distribution_id(&self) -> DistributionId {
1389 match self {
1390 Self::Registry(dist) => dist.distribution_id(),
1391 Self::DirectUrl(dist) => dist.distribution_id(),
1392 Self::Git(dist) => dist.distribution_id(),
1393 Self::Path(dist) => dist.distribution_id(),
1394 Self::Directory(dist) => dist.distribution_id(),
1395 }
1396 }
1397
1398 fn resource_id(&self) -> ResourceId {
1399 match self {
1400 Self::Registry(dist) => dist.resource_id(),
1401 Self::DirectUrl(dist) => dist.resource_id(),
1402 Self::Git(dist) => dist.resource_id(),
1403 Self::Path(dist) => dist.resource_id(),
1404 Self::Directory(dist) => dist.resource_id(),
1405 }
1406 }
1407}
1408
1409impl Identifier for BuiltDist {
1410 fn distribution_id(&self) -> DistributionId {
1411 match self {
1412 Self::Registry(dist) => dist.distribution_id(),
1413 Self::DirectUrl(dist) => dist.distribution_id(),
1414 Self::Path(dist) => dist.distribution_id(),
1415 }
1416 }
1417
1418 fn resource_id(&self) -> ResourceId {
1419 match self {
1420 Self::Registry(dist) => dist.resource_id(),
1421 Self::DirectUrl(dist) => dist.resource_id(),
1422 Self::Path(dist) => dist.resource_id(),
1423 }
1424 }
1425}
1426
1427impl Identifier for InstalledDist {
1428 fn distribution_id(&self) -> DistributionId {
1429 self.install_path().distribution_id()
1430 }
1431
1432 fn resource_id(&self) -> ResourceId {
1433 self.install_path().resource_id()
1434 }
1435}
1436
1437impl Identifier for Dist {
1438 fn distribution_id(&self) -> DistributionId {
1439 match self {
1440 Self::Built(dist) => dist.distribution_id(),
1441 Self::Source(dist) => dist.distribution_id(),
1442 }
1443 }
1444
1445 fn resource_id(&self) -> ResourceId {
1446 match self {
1447 Self::Built(dist) => dist.resource_id(),
1448 Self::Source(dist) => dist.resource_id(),
1449 }
1450 }
1451}
1452
1453impl Identifier for DirectSourceUrl<'_> {
1454 fn distribution_id(&self) -> DistributionId {
1455 self.url.distribution_id()
1456 }
1457
1458 fn resource_id(&self) -> ResourceId {
1459 self.url.resource_id()
1460 }
1461}
1462
1463impl Identifier for GitSourceUrl<'_> {
1464 fn distribution_id(&self) -> DistributionId {
1465 self.url.distribution_id()
1466 }
1467
1468 fn resource_id(&self) -> ResourceId {
1469 self.url.resource_id()
1470 }
1471}
1472
1473impl Identifier for PathSourceUrl<'_> {
1474 fn distribution_id(&self) -> DistributionId {
1475 self.url.distribution_id()
1476 }
1477
1478 fn resource_id(&self) -> ResourceId {
1479 self.url.resource_id()
1480 }
1481}
1482
1483impl Identifier for DirectorySourceUrl<'_> {
1484 fn distribution_id(&self) -> DistributionId {
1485 self.url.distribution_id()
1486 }
1487
1488 fn resource_id(&self) -> ResourceId {
1489 self.url.resource_id()
1490 }
1491}
1492
1493impl Identifier for SourceUrl<'_> {
1494 fn distribution_id(&self) -> DistributionId {
1495 match self {
1496 Self::Direct(url) => url.distribution_id(),
1497 Self::Git(url) => url.distribution_id(),
1498 Self::Path(url) => url.distribution_id(),
1499 Self::Directory(url) => url.distribution_id(),
1500 }
1501 }
1502
1503 fn resource_id(&self) -> ResourceId {
1504 match self {
1505 Self::Direct(url) => url.resource_id(),
1506 Self::Git(url) => url.resource_id(),
1507 Self::Path(url) => url.resource_id(),
1508 Self::Directory(url) => url.resource_id(),
1509 }
1510 }
1511}
1512
1513impl Identifier for BuildableSource<'_> {
1514 fn distribution_id(&self) -> DistributionId {
1515 match self {
1516 Self::Dist(source) => source.distribution_id(),
1517 Self::Url(source) => source.distribution_id(),
1518 }
1519 }
1520
1521 fn resource_id(&self) -> ResourceId {
1522 match self {
1523 Self::Dist(source) => source.resource_id(),
1524 Self::Url(source) => source.resource_id(),
1525 }
1526 }
1527}
1528
1529#[cfg(test)]
1530mod test {
1531 use crate::{BuiltDist, Dist, RemoteSource, SourceDist, UrlString};
1532 use uv_redacted::DisplaySafeUrl;
1533
1534 #[test]
1536 fn dist_size() {
1537 assert!(size_of::<Dist>() <= 200, "{}", size_of::<Dist>());
1538 assert!(size_of::<BuiltDist>() <= 200, "{}", size_of::<BuiltDist>());
1539 assert!(
1540 size_of::<SourceDist>() <= 176,
1541 "{}",
1542 size_of::<SourceDist>()
1543 );
1544 }
1545
1546 #[test]
1547 fn remote_source() {
1548 for url in [
1549 "https://example.com/foo-0.1.0.tar.gz",
1550 "https://example.com/foo-0.1.0.tar.gz#fragment",
1551 "https://example.com/foo-0.1.0.tar.gz?query",
1552 "https://example.com/foo-0.1.0.tar.gz?query#fragment",
1553 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment",
1554 "https://example.com/foo-0.1.0.tar.gz?query=1/2#fragment/3",
1555 ] {
1556 let url = DisplaySafeUrl::parse(url).unwrap();
1557 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1558 let url = UrlString::from(url.clone());
1559 assert_eq!(url.filename().unwrap(), "foo-0.1.0.tar.gz", "{url}");
1560 }
1561 }
1562}