1 """distutils.pypirc 2 3 Provides the PyPIRCCommand class, the base class for the command classes 4 that uses .pypirc in the distutils.command package. 5 """ 6 import os 7 from configparser import RawConfigParser 8 9 from distutils.cmd import Command 10 11 DEFAULT_PYPIRC = """\ 12 [distutils] 13 index-servers = 14 pypi 15 16 [pypi] 17 username:%s 18 password:%s 19 """ 20 21 class PyPIRCCommand(Command): 22 """Base command that knows how to handle the .pypirc file 23 """ 24 DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/' 25 DEFAULT_REALM = 'pypi' 26 repository = None 27 realm = None 28 29 user_options = [ 30 ('repository=', 'r', 31 "url of repository [default: %s]" % \ 32 DEFAULT_REPOSITORY), 33 ('show-response', None, 34 'display full response text from server')] 35 36 boolean_options = ['show-response'] 37 38 def _get_rc_file(self): 39 """Returns rc file path.""" 40 return os.path.join(os.path.expanduser('~'), '.pypirc') 41 42 def _store_pypirc(self, username, password): 43 """Creates a default .pypirc file.""" 44 rc = self._get_rc_file() 45 with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: 46 f.write(DEFAULT_PYPIRC % (username, password)) 47 48 def _read_pypirc(self): 49 """Reads the .pypirc file.""" 50 rc = self._get_rc_file() 51 if os.path.exists(rc): 52 self.announce('Using PyPI login from %s' % rc) 53 repository = self.repository or self.DEFAULT_REPOSITORY 54 realm = self.realm or self.DEFAULT_REALM 55 56 config = RawConfigParser() 57 config.read(rc) 58 sections = config.sections() 59 if 'distutils' in sections: 60 # let's get the list of servers 61 index_servers = config.get('distutils', 'index-servers') 62 _servers = [server.strip() for server in 63 index_servers.split('\n') 64 if server.strip() != ''] 65 if _servers == []: 66 # nothing set, let's try to get the default pypi 67 if 'pypi' in sections: 68 _servers = ['pypi'] 69 else: 70 # the file is not properly defined, returning 71 # an empty dict 72 return {} 73 for server in _servers: 74 current = {'server': server} 75 current['username'] = config.get(server, 'username') 76 77 # optional params 78 for key, default in (('repository', 79 self.DEFAULT_REPOSITORY), 80 ('realm', self.DEFAULT_REALM), 81 ('password', None)): 82 if config.has_option(server, key): 83 current[key] = config.get(server, key) 84 else: 85 current[key] = default 86 87 # work around people having "repository" for the "pypi" 88 # section of their config set to the HTTP (rather than 89 # HTTPS) URL 90 if (server == 'pypi' and 91 repository in (self.DEFAULT_REPOSITORY, 'pypi')): 92 current['repository'] = self.DEFAULT_REPOSITORY 93 return current 94 95 if (current['server'] == repository or 96 current['repository'] == repository): 97 return current 98 elif 'server-login' in sections: 99 # old format 100 server = 'server-login' 101 if config.has_option(server, 'repository'): 102 repository = config.get(server, 'repository') 103 else: 104 repository = self.DEFAULT_REPOSITORY 105 return {'username': config.get(server, 'username'), 106 'password': config.get(server, 'password'), 107 'repository': repository, 108 'server': server, 109 'realm': self.DEFAULT_REALM} 110 111 return {} 112 113 def _read_pypi_response(self, response): 114 """Read and decode a PyPI HTTP response.""" 115 import cgi 116 content_type = response.getheader('content-type', 'text/plain') 117 encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii') 118 return response.read().decode(encoding) 119 120 def initialize_options(self): 121 """Initialize options.""" 122 self.repository = None 123 self.realm = None 124 self.show_response = 0 125 126 def finalize_options(self): 127 """Finalizes options.""" 128 if self.repository is None: 129 self.repository = self.DEFAULT_REPOSITORY 130 if self.realm is None: 131 self.realm = self.DEFAULT_REALM 132