1
1
use std:: collections:: BTreeMap ;
2
2
use std:: fmt:: Debug ;
3
- use std:: io;
4
3
use std:: path:: PathBuf ;
5
4
use std:: str:: FromStr ;
6
5
@@ -16,7 +15,9 @@ use tracing::{info_span, instrument, trace, warn, Instrument};
16
15
use url:: Url ;
17
16
18
17
use distribution_filename:: { DistFilename , SourceDistFilename , WheelFilename } ;
19
- use distribution_types:: { BuiltDist , File , FileLocation , IndexUrl , IndexUrls , Name } ;
18
+ use distribution_types:: {
19
+ BuiltDist , File , FileLocation , IndexCapabilities , IndexUrl , IndexUrls , Name , RegistryBuiltDist ,
20
+ } ;
20
21
use install_wheel_rs:: metadata:: { find_archive_dist_info, is_metadata_entry} ;
21
22
use pep440_rs:: Version ;
22
23
use pep508_rs:: MarkerEnvironment ;
@@ -147,7 +148,7 @@ impl<'a> RegistryClientBuilder<'a> {
147
148
}
148
149
149
150
impl < ' a > TryFrom < BaseClientBuilder < ' a > > for RegistryClientBuilder < ' a > {
150
- type Error = io:: Error ;
151
+ type Error = std :: io:: Error ;
151
152
152
153
fn try_from ( value : BaseClientBuilder < ' a > ) -> Result < Self , Self :: Error > {
153
154
Ok ( Self {
@@ -197,6 +198,22 @@ impl RegistryClient {
197
198
self . timeout
198
199
}
199
200
201
+ /// Whether a [`File`] supports metadata prefetching.
202
+ pub fn should_prefetch (
203
+ & self ,
204
+ wheel : & RegistryBuiltDist ,
205
+ capabilities : & IndexCapabilities ,
206
+ ) -> bool {
207
+ let wheel = wheel. best_wheel ( ) ;
208
+ if wheel. file . dist_info_metadata {
209
+ return true ;
210
+ }
211
+ if capabilities. supports_range_requests ( & wheel. index ) {
212
+ return true ;
213
+ }
214
+ false
215
+ }
216
+
200
217
/// Fetch a package from the `PyPI` simple API.
201
218
///
202
219
/// "simple" here refers to [PEP 503 – Simple Repository API](https://peps.python.org/pep-0503/)
@@ -395,14 +412,19 @@ impl RegistryClient {
395
412
OwnedArchive :: from_unarchived ( & metadata)
396
413
}
397
414
415
+ // STOPSHIP: Pass in capabilities.
398
416
/// Fetch the metadata for a remote wheel file.
399
417
///
400
418
/// For a remote wheel, we try the following ways to fetch the metadata:
401
419
/// 1. From a [PEP 658](https://peps.python.org/pep-0658/) data-dist-info-metadata url
402
420
/// 2. From a remote wheel by partial zip reading
403
421
/// 3. From a (temp) download of a remote wheel (this is a fallback, the webserver should support range requests)
404
422
#[ instrument( skip_all, fields( % built_dist) ) ]
405
- pub async fn wheel_metadata ( & self , built_dist : & BuiltDist ) -> Result < Metadata23 , Error > {
423
+ pub async fn wheel_metadata (
424
+ & self ,
425
+ built_dist : & BuiltDist ,
426
+ capabilities : & IndexCapabilities ,
427
+ ) -> Result < Metadata23 , Error > {
406
428
let metadata = match & built_dist {
407
429
BuiltDist :: Registry ( wheels) => {
408
430
#[ derive( Debug , Clone ) ]
@@ -451,7 +473,7 @@ impl RegistryClient {
451
473
. await ?
452
474
}
453
475
WheelLocation :: Url ( url) => {
454
- self . wheel_metadata_registry ( & wheel. index , & wheel. file , & url)
476
+ self . wheel_metadata_registry ( & wheel. index , & wheel. file , & url, capabilities )
455
477
. await ?
456
478
}
457
479
}
@@ -460,7 +482,9 @@ impl RegistryClient {
460
482
self . wheel_metadata_no_pep658 (
461
483
& wheel. filename ,
462
484
& wheel. url ,
485
+ None ,
463
486
WheelCache :: Url ( & wheel. url ) ,
487
+ capabilities,
464
488
)
465
489
. await ?
466
490
}
@@ -489,6 +513,7 @@ impl RegistryClient {
489
513
index : & IndexUrl ,
490
514
file : & File ,
491
515
url : & Url ,
516
+ capabilities : & IndexCapabilities ,
492
517
) -> Result < Metadata23 , Error > {
493
518
// If the metadata file is available at its own url (PEP 658), download it from there.
494
519
let filename = WheelFilename :: from_str ( & file. filename ) . map_err ( ErrorKind :: WheelFilename ) ?;
@@ -536,8 +561,14 @@ impl RegistryClient {
536
561
// If we lack PEP 658 support, try using HTTP range requests to read only the
537
562
// `.dist-info/METADATA` file from the zip, and if that also fails, download the whole wheel
538
563
// into the cache and read from there
539
- self . wheel_metadata_no_pep658 ( & filename, url, WheelCache :: Index ( index) )
540
- . await
564
+ self . wheel_metadata_no_pep658 (
565
+ & filename,
566
+ url,
567
+ Some ( index) ,
568
+ WheelCache :: Index ( index) ,
569
+ capabilities,
570
+ )
571
+ . await
541
572
}
542
573
}
543
574
@@ -546,7 +577,9 @@ impl RegistryClient {
546
577
& self ,
547
578
filename : & ' data WheelFilename ,
548
579
url : & ' data Url ,
580
+ index : Option < & ' data IndexUrl > ,
549
581
cache_shard : WheelCache < ' data > ,
582
+ capabilities : & ' data IndexCapabilities ,
550
583
) -> Result < Metadata23 , Error > {
551
584
let cache_entry = self . cache . entry (
552
585
CacheBucket :: Wheels ,
@@ -562,72 +595,80 @@ impl RegistryClient {
562
595
Connectivity :: Offline => CacheControl :: AllowStale ,
563
596
} ;
564
597
565
- let req = self
566
- . uncached_client ( url)
567
- . head ( url. clone ( ) )
568
- . header (
569
- "accept-encoding" ,
570
- http:: HeaderValue :: from_static ( "identity" ) ,
571
- )
572
- . build ( )
573
- . map_err ( ErrorKind :: from) ?;
598
+ // Attempt to fetch via a range request.
599
+ if index. map_or ( true , |index| capabilities. supports_range_requests ( index) ) {
600
+ let req = self
601
+ . uncached_client ( url)
602
+ . head ( url. clone ( ) )
603
+ . header (
604
+ "accept-encoding" ,
605
+ http:: HeaderValue :: from_static ( "identity" ) ,
606
+ )
607
+ . build ( )
608
+ . map_err ( ErrorKind :: from) ?;
574
609
575
- // Copy authorization headers from the HEAD request to subsequent requests
576
- let mut headers = HeaderMap :: default ( ) ;
577
- if let Some ( authorization) = req. headers ( ) . get ( "authorization" ) {
578
- headers. append ( "authorization" , authorization. clone ( ) ) ;
579
- }
610
+ // Copy authorization headers from the HEAD request to subsequent requests
611
+ let mut headers = HeaderMap :: default ( ) ;
612
+ if let Some ( authorization) = req. headers ( ) . get ( "authorization" ) {
613
+ headers. append ( "authorization" , authorization. clone ( ) ) ;
614
+ }
580
615
581
- // This response callback is special, we actually make a number of subsequent requests to
582
- // fetch the file from the remote zip.
583
- let read_metadata_range_request = |response : Response | {
584
- async {
585
- let mut reader = AsyncHttpRangeReader :: from_head_response (
586
- self . uncached_client ( url) . clone ( ) ,
587
- response,
588
- url. clone ( ) ,
589
- headers,
616
+ // This response callback is special, we actually make a number of subsequent requests to
617
+ // fetch the file from the remote zip.
618
+ let read_metadata_range_request = |response : Response | {
619
+ async {
620
+ let mut reader = AsyncHttpRangeReader :: from_head_response (
621
+ self . uncached_client ( url) . clone ( ) ,
622
+ response,
623
+ url. clone ( ) ,
624
+ headers,
625
+ )
626
+ . await
627
+ . map_err ( ErrorKind :: AsyncHttpRangeReader ) ?;
628
+ trace ! ( "Getting metadata for {filename} by range request" ) ;
629
+ let text = wheel_metadata_from_remote_zip ( filename, & mut reader) . await ?;
630
+ let metadata = Metadata23 :: parse_metadata ( text. as_bytes ( ) ) . map_err ( |err| {
631
+ Error :: from ( ErrorKind :: MetadataParseError (
632
+ filename. clone ( ) ,
633
+ url. to_string ( ) ,
634
+ Box :: new ( err) ,
635
+ ) )
636
+ } ) ?;
637
+ Ok :: < Metadata23 , CachedClientError < Error > > ( metadata)
638
+ }
639
+ . boxed_local ( )
640
+ . instrument ( info_span ! ( "read_metadata_range_request" , wheel = %filename) )
641
+ } ;
642
+
643
+ let result = self
644
+ . cached_client ( )
645
+ . get_serde (
646
+ req,
647
+ & cache_entry,
648
+ cache_control,
649
+ read_metadata_range_request,
590
650
)
591
651
. await
592
- . map_err ( ErrorKind :: AsyncHttpRangeReader ) ?;
593
- trace ! ( "Getting metadata for {filename} by range request" ) ;
594
- let text = wheel_metadata_from_remote_zip ( filename, & mut reader) . await ?;
595
- let metadata = Metadata23 :: parse_metadata ( text. as_bytes ( ) ) . map_err ( |err| {
596
- Error :: from ( ErrorKind :: MetadataParseError (
597
- filename. clone ( ) ,
598
- url. to_string ( ) ,
599
- Box :: new ( err) ,
600
- ) )
601
- } ) ?;
602
- Ok :: < Metadata23 , CachedClientError < Error > > ( metadata)
603
- }
604
- . boxed_local ( )
605
- . instrument ( info_span ! ( "read_metadata_range_request" , wheel = %filename) )
606
- } ;
652
+ . map_err ( crate :: Error :: from) ;
607
653
608
- let result = self
609
- . cached_client ( )
610
- . get_serde (
611
- req,
612
- & cache_entry,
613
- cache_control,
614
- read_metadata_range_request,
615
- )
616
- . await
617
- . map_err ( crate :: Error :: from) ;
618
-
619
- match result {
620
- Ok ( metadata) => return Ok ( metadata) ,
621
- Err ( err) => {
622
- if err. is_http_range_requests_unsupported ( ) {
623
- // The range request version failed. Fall back to streaming the file to search
624
- // for the METADATA file.
625
- warn ! ( "Range requests not supported for {filename}; streaming wheel" ) ;
626
- } else {
627
- return Err ( err) ;
654
+ match result {
655
+ Ok ( metadata) => return Ok ( metadata) ,
656
+ Err ( err) => {
657
+ if err. is_http_range_requests_unsupported ( ) {
658
+ // The range request version failed. Fall back to streaming the file to search
659
+ // for the METADATA file.
660
+ warn ! ( "Range requests not supported for {filename}; streaming wheel" ) ;
661
+
662
+ // Mark the index as not supporting range requests.
663
+ if let Some ( index) = index {
664
+ capabilities. set_supports_range_requests ( index. clone ( ) , false ) ;
665
+ }
666
+ } else {
667
+ return Err ( err) ;
668
+ }
628
669
}
629
- }
630
- } ;
670
+ } ;
671
+ }
631
672
632
673
// Create a request to stream the file.
633
674
let req = self
0 commit comments