@@ -33,6 +33,7 @@ use std::path::{Path, PathBuf};
3333use anyhow:: { Context , Result } ;
3434use rustc_hash:: FxHashSet ;
3535use tracing:: instrument;
36+ use url:: Url ;
3637
3738use uv_cache_key:: CanonicalUrl ;
3839use uv_client:: BaseClientBuilder ;
@@ -45,8 +46,9 @@ use uv_distribution_types::{
4546use uv_fs:: { CWD , Simplified } ;
4647use uv_normalize:: { ExtraName , PackageName , PipGroupName } ;
4748use uv_pypi_types:: PyProjectToml ;
49+ use uv_redacted:: DisplaySafeUrl ;
4850use uv_requirements_txt:: { RequirementsTxt , RequirementsTxtRequirement , SourceCache } ;
49- use uv_scripts:: { Pep723Error , Pep723Metadata , Pep723Script } ;
51+ use uv_scripts:: Pep723Metadata ;
5052use uv_warnings:: warn_user;
5153
5254use crate :: { RequirementsSource , SourceTree } ;
@@ -269,8 +271,8 @@ impl RequirementsSpecification {
269271 Self :: from_requirements_txt ( requirements_txt)
270272 }
271273 RequirementsSource :: PyprojectToml ( path) => {
272- let contents = match fs_err:: tokio:: read_to_string ( & path) . await {
273- Ok ( contents ) => contents ,
274+ let content = match fs_err:: tokio:: read_to_string ( & path) . await {
275+ Ok ( content ) => content ,
274276 Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
275277 return Err ( anyhow:: anyhow!( "File not found: `{}`" , path. user_display( ) ) ) ;
276278 }
@@ -282,7 +284,7 @@ impl RequirementsSpecification {
282284 ) ) ;
283285 }
284286 } ;
285- let pyproject_toml = toml:: from_str :: < PyProjectToml > ( & contents )
287+ let pyproject_toml = toml:: from_str :: < PyProjectToml > ( & content )
286288 . with_context ( || format ! ( "Failed to parse: `{}`" , path. user_display( ) ) ) ?;
287289
288290 Self {
@@ -291,24 +293,26 @@ impl RequirementsSpecification {
291293 }
292294 }
293295 RequirementsSource :: Pep723Script ( path) => {
294- let script = match Pep723Script :: read ( & path) . await {
296+ let content = if let Some ( content) = cache. get ( path. as_path ( ) ) {
297+ content. clone ( )
298+ } else {
299+ let content = read_file ( path, client_builder) . await ?;
300+ cache. insert ( path. clone ( ) , content. clone ( ) ) ;
301+ content
302+ } ;
303+
304+ let metadata = match Pep723Metadata :: parse ( content. as_bytes ( ) ) {
295305 Ok ( Some ( script) ) => script,
296306 Ok ( None ) => {
297307 return Err ( anyhow:: anyhow!(
298308 "`{}` does not contain inline script metadata" ,
299309 path. user_display( ) ,
300310 ) ) ;
301311 }
302- Err ( Pep723Error :: Io ( err) ) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
303- return Err ( anyhow:: anyhow!(
304- "Failed to read `{}` (not found)" ,
305- path. user_display( ) ,
306- ) ) ;
307- }
308312 Err ( err) => return Err ( err. into ( ) ) ,
309313 } ;
310314
311- Self :: from_pep723_metadata ( & script . metadata )
315+ Self :: from_pep723_metadata ( & metadata)
312316 }
313317 RequirementsSource :: SetupPy ( path) => {
314318 if !path. is_file ( ) {
@@ -347,11 +351,10 @@ impl RequirementsSpecification {
347351 ) ) ;
348352 }
349353 RequirementsSource :: Extensionless ( path) => {
350- // Read the file content.
351354 let content = if let Some ( content) = cache. get ( path. as_path ( ) ) {
352355 content. clone ( )
353356 } else {
354- let content = uv_fs :: read_to_string_transcode ( & path) . await ?;
357+ let content = read_file ( path, client_builder ) . await ?;
355358 cache. insert ( path. clone ( ) , content. clone ( ) ) ;
356359 content
357360 } ;
@@ -741,3 +744,32 @@ pub struct GroupsSpecification {
741744 /// The enabled groups.
742745 pub groups : Vec < PipGroupName > ,
743746}
747+
748+ /// Read the contents of a path, fetching over HTTP(S) if necessary.
749+ async fn read_file ( path : & Path , client_builder : & BaseClientBuilder < ' _ > ) -> Result < String > {
750+ // If the path is a URL, fetch it over HTTP(S).
751+ if path. starts_with ( "http://" ) || path. starts_with ( "https://" ) {
752+ // Only continue if we are absolutely certain no local file exists.
753+ //
754+ // We don't do this check on Windows since the file path would
755+ // be invalid anyway, and thus couldn't refer to a local file.
756+ if !cfg ! ( unix) || matches ! ( path. try_exists( ) , Ok ( false ) ) {
757+ let url = DisplaySafeUrl :: parse ( & path. to_string_lossy ( ) ) ?;
758+
759+ let client = client_builder. build ( ) ;
760+ let response = client
761+ . for_host ( & url)
762+ . get ( Url :: from ( url. clone ( ) ) )
763+ . send ( )
764+ . await ?;
765+
766+ response. error_for_status_ref ( ) ?;
767+
768+ return Ok ( response. text ( ) . await ?) ;
769+ }
770+ }
771+
772+ // Read the file content.
773+ let content = uv_fs:: read_to_string_transcode ( path) . await ?;
774+ Ok ( content)
775+ }
0 commit comments