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