Skip to content

Commit 9335bfb

Browse files
authored
Merge pull request #434 from davidrft/custom-pypirc-file
Add custom pypirc support
2 parents 01e64e9 + ecac8fb commit 9335bfb

File tree

5 files changed

+123
-30
lines changed

5 files changed

+123
-30
lines changed

doc/cmdline.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ or another repository.
7171
Name of a repository to upload packages to. Should match a section in
7272
``~/.pypirc``. The default is ``pypi``.
7373

74+
.. option:: --pypirc <pypirc>
75+
76+
The .pypirc config file to be used. The default is ``~/.pypirc``.
77+
7478
.. seealso:: :doc:`upload`
7579

7680
.. _install_cmd:

doc/upload.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ you can configure Flit in two main ways:
1414
Using .pypirc
1515
-------------
1616

17-
You can create or edit a config file in your home directory, ``~/.pypirc``.
17+
You can create or edit a config file in your home directory, ``~/.pypirc`` that
18+
will be used by default or you can specify a custom location.
1819
This is also used by other Python tools such as `twine
1920
<https://pypi.python.org/pypi/twine>`_.
2021

flit/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ def main(argv=None):
116116
)
117117
)
118118

119+
parser_publish.add_argument('--pypirc',
120+
help="The .pypirc config file to be used. DEFAULT = \"~/.pypirc\""
121+
)
122+
119123
parser_publish.add_argument('--repository',
120-
help="Name of the repository to upload to (must be in ~/.pypirc)"
124+
help="Name of the repository to upload to (must be in the specified .pypirc file)"
121125
)
122126

123127
# flit install --------------------------------------------
@@ -184,7 +188,7 @@ def gen_setup_py():
184188
log.warning("Passing --repository before the 'upload' subcommand is deprecated: pass it after")
185189
repository = args.repository or args.deprecated_repository
186190
from .upload import main
187-
main(args.ini_file, repository, formats=set(args.format or []),
191+
main(args.ini_file, repository, args.pypirc, formats=set(args.format or []),
188192
gen_setup_py=gen_setup_py())
189193

190194
elif args.subcmd == 'install':

flit/upload.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
log = logging.getLogger(__name__)
1919

2020
PYPI = "https://upload.pypi.org/legacy/"
21+
PYPIRC_DEFAULT = "~/.pypirc"
2122

2223
SWITCH_TO_HTTPS = (
2324
"http://pypi.python.org/",
@@ -59,13 +60,13 @@ def get_repositories(file="~/.pypirc"):
5960
return repos
6061

6162

62-
def get_repository(name=None, cfg_file="~/.pypirc"):
63+
def get_repository(pypirc_path="~/.pypirc", name=None):
6364
"""Get the url, username and password for one repository.
64-
65+
6566
Returns a dict with keys 'url', 'username', 'password'.
6667
6768
There is a hierarchy of possible sources of information:
68-
69+
6970
Index URL:
7071
1. Command line arg --repository (looked up in .pypirc)
7172
2. $FLIT_INDEX_URL
@@ -85,7 +86,8 @@ def get_repository(name=None, cfg_file="~/.pypirc"):
8586
3. keyring
8687
4. Terminal prompt (store to keyring if available)
8788
"""
88-
repos_cfg = get_repositories(cfg_file)
89+
log.debug("Loading repositories config from %r", pypirc_path)
90+
repos_cfg = get_repositories(pypirc_path)
8991

9092
if name is not None:
9193
repo = repos_cfg[name]
@@ -114,7 +116,7 @@ def get_repository(name=None, cfg_file="~/.pypirc"):
114116
while not repo['username']:
115117
repo['username'] = input("Username: ")
116118
if repo['url'] == PYPI:
117-
write_pypirc(repo)
119+
write_pypirc(repo, pypirc_path)
118120
elif not repo['username']:
119121
raise Exception("Could not find username for upload.")
120122

@@ -237,10 +239,10 @@ def upload_file(file:Path, metadata:Metadata, repo):
237239
resp.raise_for_status()
238240

239241

240-
def do_upload(file:Path, metadata:Metadata, repo_name=None):
242+
def do_upload(file:Path, metadata:Metadata, pypirc_path="~/.pypirc", repo_name=None):
241243
"""Upload a file to an index server.
242244
"""
243-
repo = get_repository(repo_name)
245+
repo = get_repository(pypirc_path, repo_name)
244246
upload_file(file, metadata, repo)
245247

246248
if repo['is_warehouse']:
@@ -252,12 +254,17 @@ def do_upload(file:Path, metadata:Metadata, repo_name=None):
252254
log.info("Package is at %s/%s", repo['url'], metadata.name)
253255

254256

255-
def main(ini_path, repo_name, formats=None, gen_setup_py=True):
257+
def main(ini_path, repo_name, pypirc_path=None, formats=None, gen_setup_py=True):
256258
"""Build and upload wheel and sdist."""
259+
if pypirc_path is None:
260+
pypirc_path = PYPIRC_DEFAULT
261+
elif not os.path.isfile(pypirc_path):
262+
raise FileNotFoundError("The specified pypirc config file does not exist.")
263+
257264
from . import build
258265
built = build.main(ini_path, formats=formats, gen_setup_py=gen_setup_py)
259266

260267
if built.wheel is not None:
261-
do_upload(built.wheel.file, built.wheel.builder.metadata, repo_name)
268+
do_upload(built.wheel.file, built.wheel.builder.metadata, pypirc_path, repo_name)
262269
if built.sdist is not None:
263-
do_upload(built.sdist.file, built.sdist.builder.metadata, repo_name)
270+
do_upload(built.sdist.file, built.sdist.builder.metadata, pypirc_path, repo_name)

tests/test_upload.py

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from contextlib import contextmanager
2+
from tempfile import NamedTemporaryFile
3+
import os
24
import io
35
import pathlib
46
import sys
57

8+
import pytest
69
import responses
710
from testpath import modified_env
811
from unittest.mock import patch
912

1013
from flit import upload
14+
from flit.build import ALL_FORMATS
1115

1216
samples_dir = pathlib.Path(__file__).parent / 'samples'
1317

@@ -17,16 +21,6 @@
1721
'is_warehouse': True,
1822
}
1923

20-
@responses.activate
21-
def test_upload(copy_sample):
22-
responses.add(responses.POST, upload.PYPI, status=200)
23-
td = copy_sample('module1_toml')
24-
25-
with patch('flit.upload.get_repository', return_value=repo_settings):
26-
upload.main(td / 'pyproject.toml', repo_name='pypi')
27-
28-
assert len(responses.calls) == 2
29-
3024
pypirc1 = """
3125
[distutils]
3226
index-servers =
@@ -38,19 +32,43 @@ def test_upload(copy_sample):
3832
"""
3933
# That's not a real password. Well, hopefully not.
4034

35+
@contextmanager
36+
def temp_pypirc(content):
37+
try:
38+
temp_file = NamedTemporaryFile("w+", delete=False)
39+
temp_file.write(content)
40+
temp_file.close()
41+
yield temp_file.name
42+
finally:
43+
os.unlink(temp_file.name)
44+
45+
46+
@responses.activate
47+
def test_upload(copy_sample):
48+
responses.add(responses.POST, upload.PYPI, status=200)
49+
td = copy_sample('module1_toml')
50+
51+
with temp_pypirc(pypirc1) as pypirc, \
52+
patch('flit.upload.get_repository', return_value=repo_settings):
53+
upload.main(td / 'pyproject.toml', repo_name='pypi', pypirc_path=pypirc)
54+
55+
assert len(responses.calls) == 2
56+
4157
def test_get_repository():
42-
repo = upload.get_repository(cfg_file=io.StringIO(pypirc1))
43-
assert repo['url'] == upload.PYPI
44-
assert repo['username'] == 'fred'
45-
assert repo['password'] == 's3cret'
58+
with temp_pypirc(pypirc1) as pypirc:
59+
repo = upload.get_repository(pypirc_path=pypirc)
60+
assert repo['url'] == upload.PYPI
61+
assert repo['username'] == 'fred'
62+
assert repo['password'] == 's3cret'
4663

4764
def test_get_repository_env():
48-
with modified_env({
65+
with temp_pypirc(pypirc1) as pypirc, \
66+
modified_env({
4967
'FLIT_INDEX_URL': 'https://pypi.example.com',
5068
'FLIT_USERNAME': 'alice',
5169
'FLIT_PASSWORD': 'p4ssword', # Also not a real password
5270
}):
53-
repo = upload.get_repository(cfg_file=io.StringIO(pypirc1))
71+
repo = upload.get_repository(pypirc_path=pypirc)
5472
# Because we haven't specified a repo name, environment variables should
5573
# have higher priority than the config file.
5674
assert repo['url'] == 'https://pypi.example.com'
@@ -87,7 +105,66 @@ def get_password(service_name, username):
87105
def test_get_repository_keyring():
88106
with modified_env({'FLIT_PASSWORD': None}), \
89107
_fake_keyring('tops3cret'):
90-
repo = upload.get_repository(cfg_file=io.StringIO(pypirc2))
108+
repo = upload.get_repository(pypirc_path=io.StringIO(pypirc2))
91109

92110
assert repo['username'] == 'fred'
93111
assert repo['password'] == 'tops3cret'
112+
113+
114+
pypirc3_repo = "https://invalid-repo.inv"
115+
pypirc3_user = "test"
116+
pypirc3_pass = "not_a_real_password"
117+
pypirc3 = f"""
118+
[distutils] =
119+
index-servers =
120+
test123
121+
122+
[test123]
123+
repository: {pypirc3_repo}
124+
username: {pypirc3_user}
125+
password: {pypirc3_pass}
126+
"""
127+
128+
129+
def test_upload_pypirc_file(copy_sample):
130+
with temp_pypirc(pypirc3) as pypirc, \
131+
patch("flit.upload.upload_file") as upload_file:
132+
td = copy_sample("module1_toml")
133+
formats = list(ALL_FORMATS)[:1]
134+
upload.main(
135+
td / "pyproject.toml",
136+
formats=set(formats),
137+
repo_name="test123",
138+
pypirc_path=pypirc,
139+
)
140+
_, _, repo = upload_file.call_args[0]
141+
142+
assert repo["url"] == pypirc3_repo
143+
assert repo["username"] == pypirc3_user
144+
assert repo["password"] == pypirc3_pass
145+
146+
147+
def test_upload_invalid_pypirc_file(copy_sample):
148+
with patch("flit.upload.upload_file"):
149+
td = copy_sample("module1_toml")
150+
formats = list(ALL_FORMATS)[:1]
151+
with pytest.raises(FileNotFoundError):
152+
upload.main(
153+
td / "pyproject.toml",
154+
formats=set(formats),
155+
repo_name="test123",
156+
pypirc_path="./file.invalid",
157+
)
158+
159+
def test_upload_default_pypirc_file(copy_sample):
160+
with patch("flit.upload.do_upload") as do_upload:
161+
td = copy_sample("module1_toml")
162+
formats = list(ALL_FORMATS)[:1]
163+
upload.main(
164+
td / "pyproject.toml",
165+
formats=set(formats),
166+
repo_name="test123",
167+
)
168+
169+
file = do_upload.call_args[0][2]
170+
assert file == "~/.pypirc"

0 commit comments

Comments
 (0)