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 import warnings
9
10 from distutils.cmd import Command
11
12 DEFAULT_PYPIRC = """\
13 [distutils]
14 index-servers =
15 pypi
16
17 [pypi]
18 username:%s
19 password:%s
20 """
21
22 class ESC[4;38;5;81mPyPIRCCommand(ESC[4;38;5;149mCommand):
23 """Base command that knows how to handle the .pypirc file
24 """
25 DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
26 DEFAULT_REALM = 'pypi'
27 repository = None
28 realm = None
29
30 user_options = [
31 ('repository=', 'r',
32 "url of repository [default: %s]" % \
33 DEFAULT_REPOSITORY),
34 ('show-response', None,
35 'display full response text from server')]
36
37 boolean_options = ['show-response']
38
39 def _get_rc_file(self):
40 """Returns rc file path."""
41 return os.path.join(os.path.expanduser('~'), '.pypirc')
42
43 def _store_pypirc(self, username, password):
44 """Creates a default .pypirc file."""
45 rc = self._get_rc_file()
46 with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f:
47 f.write(DEFAULT_PYPIRC % (username, password))
48
49 def _read_pypirc(self):
50 """Reads the .pypirc file."""
51 rc = self._get_rc_file()
52 if os.path.exists(rc):
53 self.announce('Using PyPI login from %s' % rc)
54 repository = self.repository or self.DEFAULT_REPOSITORY
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 with warnings.catch_warnings():
116 warnings.simplefilter("ignore", DeprecationWarning)
117 import cgi
118 content_type = response.getheader('content-type', 'text/plain')
119 encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii')
120 return response.read().decode(encoding)
121
122 def initialize_options(self):
123 """Initialize options."""
124 self.repository = None
125 self.realm = None
126 self.show_response = 0
127
128 def finalize_options(self):
129 """Finalizes options."""
130 if self.repository is None:
131 self.repository = self.DEFAULT_REPOSITORY
132 if self.realm is None:
133 self.realm = self.DEFAULT_REALM