11"""PyPI and direct package downloading."""
22
3- import sys
4- import os
5- import re
6- import io
7- import shutil
8- import socket
93import base64
10- import hashlib
11- import itertools
124import configparser
5+ import hashlib
136import html
147import http .client
8+ import io
9+ import itertools
10+ import os
11+ import re
12+ import shutil
13+ import socket
14+ import subprocess
15+ import sys
16+ import urllib .error
1517import urllib .parse
1618import urllib .request
17- import urllib .error
18- from functools import wraps
19-
20- import setuptools
21- from pkg_resources import (
22- CHECKOUT_DIST ,
23- Distribution ,
24- BINARY_DIST ,
25- normalize_path ,
26- SOURCE_DIST ,
27- Environment ,
28- find_distributions ,
29- safe_name ,
30- safe_version ,
31- to_filename ,
32- Requirement ,
33- DEVELOP_DIST ,
34- EGG_DIST ,
35- parse_version ,
36- )
3719from distutils import log
3820from distutils .errors import DistutilsError
3921from fnmatch import translate
40- from setuptools .wheel import Wheel
41- from setuptools .extern .more_itertools import unique_everseen
22+ from functools import wraps
4223
24+ import setuptools
25+ from pkg_resources import (BINARY_DIST , CHECKOUT_DIST , DEVELOP_DIST , EGG_DIST ,
26+ SOURCE_DIST , Distribution , Environment , Requirement ,
27+ find_distributions , normalize_path , parse_version , safe_name ,
28+ safe_version , to_filename )
29+ from setuptools .extern .more_itertools import unique_everseen
30+ from setuptools .wheel import Wheel
4331
4432EGG_FRAGMENT = re .compile (r'^egg=([-A-Za-z0-9_.+!]+)$' )
4533HREF = re .compile (r"""href\s*=\s*['"]?([^'"> ]+)""" , re .I )
@@ -195,7 +183,7 @@ def interpret_distro_name(
195183 '-' .join (parts [p :]),
196184 py_version = py_version ,
197185 precedence = precedence ,
198- platform = platform
186+ platform = platform ,
199187 )
200188
201189
@@ -305,7 +293,7 @@ def __init__(
305293 ca_bundle = None ,
306294 verify_ssl = True ,
307295 * args ,
308- ** kw
296+ ** kw ,
309297 ):
310298 super ().__init__ (* args , ** kw )
311299 self .index_url = index_url + "/" [: not index_url .endswith ('/' )]
@@ -586,7 +574,7 @@ def download(self, spec, tmpdir):
586574 scheme = URL_SCHEME (spec )
587575 if scheme :
588576 # It's a url, download it to tmpdir
589- found = self ._download_url (scheme . group ( 1 ), spec , tmpdir )
577+ found = self ._download_url (spec , tmpdir )
590578 base , fragment = egg_info_for_url (spec )
591579 if base .endswith ('.py' ):
592580 found = self .gen_setup (found , fragment , tmpdir )
@@ -813,7 +801,7 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12)
813801 else :
814802 raise DistutilsError ("Download error for %s: %s" % (url , v )) from v
815803
816- def _download_url (self , scheme , url , tmpdir ):
804+ def _download_url (self , url , tmpdir ):
817805 # Determine download filename
818806 #
819807 name , fragment = egg_info_for_url (url )
@@ -828,19 +816,59 @@ def _download_url(self, scheme, url, tmpdir):
828816
829817 filename = os .path .join (tmpdir , name )
830818
831- # Download the file
832- #
833- if scheme == 'svn' or scheme .startswith ('svn+' ):
834- return self ._download_svn (url , filename )
835- elif scheme == 'git' or scheme .startswith ('git+' ):
836- return self ._download_git (url , filename )
837- elif scheme .startswith ('hg+' ):
838- return self ._download_hg (url , filename )
839- elif scheme == 'file' :
840- return urllib .request .url2pathname (urllib .parse .urlparse (url )[2 ])
841- else :
842- self .url_ok (url , True ) # raises error if not allowed
843- return self ._attempt_download (url , filename )
819+ return self ._download_vcs (url , filename ) or self ._download_other (url , filename )
820+
821+ @staticmethod
822+ def _resolve_vcs (url ):
823+ """
824+ >>> rvcs = PackageIndex._resolve_vcs
825+ >>> rvcs('git+http://foo/bar')
826+ 'git'
827+ >>> rvcs('hg+https://foo/bar')
828+ 'hg'
829+ >>> rvcs('git:myhost')
830+ 'git'
831+ >>> rvcs('hg:myhost')
832+ >>> rvcs('http://foo/bar')
833+ """
834+ scheme = urllib .parse .urlsplit (url ).scheme
835+ pre , sep , post = scheme .partition ('+' )
836+ # svn and git have their own protocol; hg does not
837+ allowed = set (['svn' , 'git' ] + ['hg' ] * bool (sep ))
838+ return next (iter ({pre } & allowed ), None )
839+
840+ def _download_vcs (self , url , spec_filename ):
841+ vcs = self ._resolve_vcs (url )
842+ if not vcs :
843+ return
844+ if vcs == 'svn' :
845+ raise DistutilsError (
846+ f"Invalid config, SVN download is not supported: { url } "
847+ )
848+
849+ filename , _ , _ = spec_filename .partition ('#' )
850+ url , rev = self ._vcs_split_rev_from_url (url )
851+
852+ self .info (f"Doing { vcs } clone from { url } to { filename } " )
853+ subprocess .check_call ([vcs , 'clone' , '--quiet' , url , filename ])
854+
855+ co_commands = dict (
856+ git = [vcs , '-C' , filename , 'checkout' , '--quiet' , rev ],
857+ hg = [vcs , '--cwd' , filename , 'up' , '-C' , '-r' , rev , '-q' ],
858+ )
859+ if rev is not None :
860+ self .info (f"Checking out { rev } " )
861+ subprocess .check_call (co_commands [vcs ])
862+
863+ return filename
864+
865+ def _download_other (self , url , filename ):
866+ scheme = urllib .parse .urlsplit (url ).scheme
867+ if scheme == 'file' : # pragma: no cover
868+ return urllib .request .url2pathname (urllib .parse .urlparse (url ).path )
869+ # raise error if not allowed
870+ self .url_ok (url , True )
871+ return self ._attempt_download (url , filename )
844872
845873 def scan_url (self , url ):
846874 self .process_url (url , True )
@@ -856,64 +884,37 @@ def _invalid_download_html(self, url, headers, filename):
856884 os .unlink (filename )
857885 raise DistutilsError (f"Unexpected HTML page found at { url } " )
858886
859- def _download_svn (self , url , _filename ):
860- raise DistutilsError (f"Invalid config, SVN download is not supported: { url } " )
861-
862887 @staticmethod
863- def _vcs_split_rev_from_url (url , pop_prefix = False ):
864- scheme , netloc , path , query , frag = urllib .parse .urlsplit (url )
888+ def _vcs_split_rev_from_url (url ):
889+ """
890+ Given a possible VCS URL, return a clean URL and resolved revision if any.
891+
892+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url
893+ >>> vsrfu('git+https://github.com/pypa/[email protected] #egg-info=setuptools') 894+ ('https://github.com/pypa/setuptools', 'v69.0.0')
895+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
896+ ('https://github.com/pypa/setuptools', None)
897+ >>> vsrfu('http://foo/bar')
898+ ('http://foo/bar', None)
899+ """
900+ parts = urllib .parse .urlsplit (url )
865901
866- scheme = scheme .split ('+' , 1 )[- 1 ]
902+ clean_scheme = parts . scheme .split ('+' , 1 )[- 1 ]
867903
868904 # Some fragment identification fails
869- path = path .split ('#' , 1 )[ 0 ]
905+ no_fragment_path , _ , _ = parts . path .partition ('#' )
870906
871- rev = None
872- if '@' in path :
873- path , rev = path .rsplit ('@' , 1 )
907+ pre , sep , post = no_fragment_path .rpartition ('@' )
908+ clean_path , rev = (pre , post ) if sep else (post , None )
874909
875- # Also, discard fragment
876- url = urllib .parse .urlunsplit ((scheme , netloc , path , query , '' ))
910+ resolved = parts ._replace (
911+ scheme = clean_scheme ,
912+ path = clean_path ,
913+ # discard the fragment
914+ fragment = '' ,
915+ ).geturl ()
877916
878- return url , rev
879-
880- def _download_git (self , url , filename ):
881- filename = filename .split ('#' , 1 )[0 ]
882- url , rev = self ._vcs_split_rev_from_url (url , pop_prefix = True )
883-
884- self .info ("Doing git clone from %s to %s" , url , filename )
885- os .system ("git clone --quiet %s %s" % (url , filename ))
886-
887- if rev is not None :
888- self .info ("Checking out %s" , rev )
889- os .system (
890- "git -C %s checkout --quiet %s"
891- % (
892- filename ,
893- rev ,
894- )
895- )
896-
897- return filename
898-
899- def _download_hg (self , url , filename ):
900- filename = filename .split ('#' , 1 )[0 ]
901- url , rev = self ._vcs_split_rev_from_url (url , pop_prefix = True )
902-
903- self .info ("Doing hg clone from %s to %s" , url , filename )
904- os .system ("hg clone --quiet %s %s" % (url , filename ))
905-
906- if rev is not None :
907- self .info ("Updating to %s" , rev )
908- os .system (
909- "hg --cwd %s up -C -r %s -q"
910- % (
911- filename ,
912- rev ,
913- )
914- )
915-
916- return filename
917+ return resolved , rev
917918
918919 def debug (self , msg , * args ):
919920 log .debug (msg , * args )
0 commit comments