blob: 2171abd6969f6823454d704c5eea3e278bbe8005 [file] [log] [blame]
Olivier Deprezf4ef2d02021-04-20 13:36:24 +02001"""distutils.pypirc
2
3Provides the PyPIRCCommand class, the base class for the command classes
4that uses .pypirc in the distutils.command package.
5"""
6import os
7from configparser import RawConfigParser
8
9from distutils.cmd import Command
10
11DEFAULT_PYPIRC = """\
12[distutils]
13index-servers =
14 pypi
15
16[pypi]
17username:%s
18password:%s
19"""
20
21class 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
55 config = RawConfigParser()
56 config.read(rc)
57 sections = config.sections()
58 if 'distutils' in sections:
59 # let's get the list of servers
60 index_servers = config.get('distutils', 'index-servers')
61 _servers = [server.strip() for server in
62 index_servers.split('\n')
63 if server.strip() != '']
64 if _servers == []:
65 # nothing set, let's try to get the default pypi
66 if 'pypi' in sections:
67 _servers = ['pypi']
68 else:
69 # the file is not properly defined, returning
70 # an empty dict
71 return {}
72 for server in _servers:
73 current = {'server': server}
74 current['username'] = config.get(server, 'username')
75
76 # optional params
77 for key, default in (('repository',
78 self.DEFAULT_REPOSITORY),
79 ('realm', self.DEFAULT_REALM),
80 ('password', None)):
81 if config.has_option(server, key):
82 current[key] = config.get(server, key)
83 else:
84 current[key] = default
85
86 # work around people having "repository" for the "pypi"
87 # section of their config set to the HTTP (rather than
88 # HTTPS) URL
89 if (server == 'pypi' and
90 repository in (self.DEFAULT_REPOSITORY, 'pypi')):
91 current['repository'] = self.DEFAULT_REPOSITORY
92 return current
93
94 if (current['server'] == repository or
95 current['repository'] == repository):
96 return current
97 elif 'server-login' in sections:
98 # old format
99 server = 'server-login'
100 if config.has_option(server, 'repository'):
101 repository = config.get(server, 'repository')
102 else:
103 repository = self.DEFAULT_REPOSITORY
104 return {'username': config.get(server, 'username'),
105 'password': config.get(server, 'password'),
106 'repository': repository,
107 'server': server,
108 'realm': self.DEFAULT_REALM}
109
110 return {}
111
112 def _read_pypi_response(self, response):
113 """Read and decode a PyPI HTTP response."""
114 import cgi
115 content_type = response.getheader('content-type', 'text/plain')
116 encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii')
117 return response.read().decode(encoding)
118
119 def initialize_options(self):
120 """Initialize options."""
121 self.repository = None
122 self.realm = None
123 self.show_response = 0
124
125 def finalize_options(self):
126 """Finalizes options."""
127 if self.repository is None:
128 self.repository = self.DEFAULT_REPOSITORY
129 if self.realm is None:
130 self.realm = self.DEFAULT_REALM