569569}
570570
571571
572+ def is_nvidia_package (pkg_name : str ) -> bool :
573+ """Check if a package is from NVIDIA and should use pypi.nvidia.com"""
574+ return pkg_name .startswith ("nvidia-" )
575+
576+
577+ def get_package_source_url (pkg_name : str ) -> str :
578+ """Get the source URL for a package based on its type"""
579+ if is_nvidia_package (pkg_name ):
580+ return f"https://pypi.nvidia.com/{ pkg_name } /"
581+ else :
582+ return f"https://pypi.org/simple/{ pkg_name } /"
583+
584+
572585def download (url : str ) -> bytes :
573586 from urllib .request import urlopen
574587
575588 with urlopen (url ) as conn :
576589 return conn .read ()
577590
578591
579- def is_stable (package_version : str ) -> bool :
580- return bool (re .match (r"^([0-9]+\.)+[0-9]+$" , package_version ))
592+ def replace_relative_links_with_absolute (html : str , base_url : str ) -> str :
593+ """
594+ Replace all relative links in HTML with absolute links.
595+
596+ Args:
597+ html: HTML content as string
598+ base_url: Base URL to prepend to relative links
599+
600+ Returns:
601+ Modified HTML with absolute links
602+ """
603+ # Ensure base_url ends with /
604+ if not base_url .endswith ("/" ):
605+ base_url += "/"
606+
607+ # Pattern to match href attributes with relative URLs (not starting with http:// or https://)
608+ def replace_href (match ):
609+ full_match = match .group (0 )
610+ url = match .group (1 )
611+
612+ # If URL is already absolute, don't modify it
613+ if (
614+ url .startswith ("http://" )
615+ or url .startswith ("https://" )
616+ or url .startswith ("//" )
617+ ):
618+ return full_match
619+
620+ # Remove leading ./ or /
621+ url = url .lstrip ("./" )
622+ url = url .lstrip ("/" )
623+
624+ # Replace with absolute URL
625+ return f'href="{ base_url } { url } "'
626+
627+ # Replace href="..." patterns
628+ html = re .sub (r'href="([^"]+)"' , replace_href , html )
629+
630+ return html
581631
582632
583- def parse_simple_idx (url : str ) -> Dict [str , str ]:
584- html = download (url ).decode ("ascii" )
585- return {
633+ def parse_simple_idx (url : str ) -> tuple [Dict [str , str ], str ]:
634+ """
635+ Parse a simple package index and return package dict and raw HTML.
636+
637+ Returns:
638+ Tuple of (package_dict, raw_html)
639+ """
640+ html = download (url ).decode ("utf-8" , errors = "ignore" )
641+ packages = {
586642 name : url
587643 for (url , name ) in re .findall ('<a href="([^"]+)"[^>]*>([^>]+)</a>' , html )
588644 }
645+ return packages , html
589646
590647
591- def get_whl_versions (idx : Dict [str , str ]) -> List [str ]:
592- return [
593- k .split ("-" )[1 ]
594- for k in idx .keys ()
595- if k .endswith (".whl" ) and is_stable (k .split ("-" )[1 ])
596- ]
648+ def upload_index_html (
649+ pkg_name : str ,
650+ prefix : str ,
651+ html : str ,
652+ base_url : str ,
653+ * ,
654+ dry_run : bool = False ,
655+ ) -> None :
656+ """Upload modified index.html to S3 with absolute links"""
657+ # Replace relative links with absolute links
658+ modified_html = replace_relative_links_with_absolute (html , base_url )
597659
660+ index_key = f"{ prefix } /{ pkg_name } /index.html"
598661
599- def get_wheels_of_version (idx : Dict [str , str ], version : str ) -> Dict [str , str ]:
600- return {
601- k : v
602- for (k , v ) in idx .items ()
603- if k .endswith (".whl" ) and k .split ("-" )[1 ] == version
604- }
662+ if dry_run :
663+ print (f"Dry Run - not uploading index.html to s3://pytorch/{ index_key } " )
664+ return
665+
666+ print (f"Uploading index.html to s3://pytorch/{ index_key } " )
667+ BUCKET .Object (key = index_key ).put (
668+ ACL = "public-read" , ContentType = "text/html" , Body = modified_html .encode ("utf-8" )
669+ )
605670
606671
607- def upload_missing_whls (
608- pkg_name : str = "numpy" ,
609- prefix : str = "whl/test" ,
672+ def upload_package_using_simple_index (
673+ pkg_name : str ,
674+ prefix : str ,
610675 * ,
611676 dry_run : bool = False ,
612- only_pypi : bool = False ,
613- target_version : str = "latest" ,
614677) -> None :
615- pypi_idx = parse_simple_idx (f"https://pypi.org/simple/{ pkg_name } " )
616- pypi_versions = get_whl_versions (pypi_idx )
617-
618- # Determine which version to use
619- if target_version == "latest" or not target_version :
620- selected_version = pypi_versions [- 1 ] if pypi_versions else None
621- elif target_version in pypi_versions :
622- selected_version = target_version
623- else :
624- print (
625- f"Warning: Version { target_version } not found for { pkg_name } , using latest"
626- )
627- selected_version = pypi_versions [- 1 ] if pypi_versions else None
678+ """
679+ Upload package index.html from PyPI Simple Index.
680+ Simply copies the index.html with absolute links - no wheel uploads or version filtering.
681+ Works for both NVIDIA and non-NVIDIA packages.
682+ """
683+ source_url = get_package_source_url (pkg_name )
684+ is_nvidia = is_nvidia_package (pkg_name )
628685
629- if not selected_version :
630- print (f"No stable versions found for { pkg_name } " )
686+ print (
687+ f"Processing { pkg_name } using { 'NVIDIA' if is_nvidia else 'PyPI' } Simple Index: { source_url } "
688+ )
689+
690+ # Parse the index and get raw HTML
691+ try :
692+ _ , raw_html = parse_simple_idx (source_url )
693+ except Exception as e :
694+ print (f"Error fetching package { pkg_name } : { e } " )
631695 return
632696
633- pypi_latest_packages = get_wheels_of_version (pypi_idx , selected_version )
634-
635- download_latest_packages : Dict [str , str ] = {}
636- if not only_pypi :
637- download_idx = parse_simple_idx (
638- f"https://download.pytorch.org/{ prefix } /{ pkg_name } "
639- )
640- download_latest_packages = get_wheels_of_version (download_idx , selected_version )
641-
642- has_updates = False
643- for pkg in pypi_latest_packages :
644- if pkg in download_latest_packages :
645- continue
646- # Skip pp packages
647- if "-pp3" in pkg :
648- continue
649- # Skip win32 packages
650- if "-win32" in pkg :
651- continue
652- # Skip muslinux packages
653- if "-musllinux" in pkg :
654- continue
655- print (f"Downloading { pkg } " )
656- if dry_run :
657- has_updates = True
658- print (f"Dry Run - not Uploading { pkg } to s3://pytorch/{ prefix } /" )
659- continue
660- data = download (pypi_idx [pkg ])
661- print (f"Uploading { pkg } to s3://pytorch/{ prefix } /" )
662- BUCKET .Object (key = f"{ prefix } /{ pkg } " ).put (
663- ACL = "public-read" , ContentType = "binary/octet-stream" , Body = data
664- )
665- has_updates = True
666- if not has_updates :
667- print (f"{ pkg_name } is already at version { selected_version } for { prefix } " )
697+ # Upload modified index.html with absolute links
698+ upload_index_html (pkg_name , prefix , raw_html , source_url , dry_run = dry_run )
699+
700+ print (f"Successfully processed index.html for { pkg_name } " )
668701
669702
670703def main () -> None :
671704 from argparse import ArgumentParser
672705
673- parser = ArgumentParser ("Upload dependent packages to s3://pytorch" )
706+ parser = ArgumentParser ("Upload dependent package indexes to s3://pytorch" )
674707 # Get unique paths from the packages list
675708 project_paths = list (
676709 {
@@ -682,7 +715,6 @@ def main() -> None:
682715 project_paths += ["all" ]
683716 parser .add_argument ("--package" , choices = project_paths , default = "torch" )
684717 parser .add_argument ("--dry-run" , action = "store_true" )
685- parser .add_argument ("--only-pypi" , action = "store_true" )
686718 parser .add_argument ("--include-stable" , action = "store_true" )
687719 args = parser .parse_args ()
688720
@@ -707,12 +739,8 @@ def main() -> None:
707739 else :
708740 full_path = f"{ prefix } "
709741
710- upload_missing_whls (
711- pkg_name ,
712- full_path ,
713- dry_run = args .dry_run ,
714- only_pypi = args .only_pypi ,
715- target_version = pkg_config ["version" ],
742+ upload_package_using_simple_index (
743+ pkg_name , full_path , dry_run = args .dry_run
716744 )
717745
718746
0 commit comments