Skip to content

Commit

Permalink
Merge pull request #434 from davidrft/custom-pypirc-file
Browse files Browse the repository at this point in the history
Add custom pypirc support
  • Loading branch information
takluyver committed Sep 29, 2021
2 parents 01e64e9 + ecac8fb commit 9335bfb
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 30 deletions.
4 changes: 4 additions & 0 deletions doc/cmdline.rst
Expand Up @@ -71,6 +71,10 @@ or another repository.
Name of a repository to upload packages to. Should match a section in
``~/.pypirc``. The default is ``pypi``.

.. option:: --pypirc <pypirc>

The .pypirc config file to be used. The default is ``~/.pypirc``.

.. seealso:: :doc:`upload`

.. _install_cmd:
Expand Down
3 changes: 2 additions & 1 deletion doc/upload.rst
Expand Up @@ -14,7 +14,8 @@ you can configure Flit in two main ways:
Using .pypirc
-------------

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

Expand Down
8 changes: 6 additions & 2 deletions flit/__init__.py
Expand Up @@ -116,8 +116,12 @@ def main(argv=None):
)
)

parser_publish.add_argument('--pypirc',
help="The .pypirc config file to be used. DEFAULT = \"~/.pypirc\""
)

parser_publish.add_argument('--repository',
help="Name of the repository to upload to (must be in ~/.pypirc)"
help="Name of the repository to upload to (must be in the specified .pypirc file)"
)

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

elif args.subcmd == 'install':
Expand Down
27 changes: 17 additions & 10 deletions flit/upload.py
Expand Up @@ -18,6 +18,7 @@
log = logging.getLogger(__name__)

PYPI = "https://upload.pypi.org/legacy/"
PYPIRC_DEFAULT = "~/.pypirc"

SWITCH_TO_HTTPS = (
"http://pypi.python.org/",
Expand Down Expand Up @@ -59,13 +60,13 @@ def get_repositories(file="~/.pypirc"):
return repos


def get_repository(name=None, cfg_file="~/.pypirc"):
def get_repository(pypirc_path="~/.pypirc", name=None):
"""Get the url, username and password for one repository.
Returns a dict with keys 'url', 'username', 'password'.
There is a hierarchy of possible sources of information:
Index URL:
1. Command line arg --repository (looked up in .pypirc)
2. $FLIT_INDEX_URL
Expand All @@ -85,7 +86,8 @@ def get_repository(name=None, cfg_file="~/.pypirc"):
3. keyring
4. Terminal prompt (store to keyring if available)
"""
repos_cfg = get_repositories(cfg_file)
log.debug("Loading repositories config from %r", pypirc_path)
repos_cfg = get_repositories(pypirc_path)

if name is not None:
repo = repos_cfg[name]
Expand Down Expand Up @@ -114,7 +116,7 @@ def get_repository(name=None, cfg_file="~/.pypirc"):
while not repo['username']:
repo['username'] = input("Username: ")
if repo['url'] == PYPI:
write_pypirc(repo)
write_pypirc(repo, pypirc_path)
elif not repo['username']:
raise Exception("Could not find username for upload.")

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


def do_upload(file:Path, metadata:Metadata, repo_name=None):
def do_upload(file:Path, metadata:Metadata, pypirc_path="~/.pypirc", repo_name=None):
"""Upload a file to an index server.
"""
repo = get_repository(repo_name)
repo = get_repository(pypirc_path, repo_name)
upload_file(file, metadata, repo)

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


def main(ini_path, repo_name, formats=None, gen_setup_py=True):
def main(ini_path, repo_name, pypirc_path=None, formats=None, gen_setup_py=True):
"""Build and upload wheel and sdist."""
if pypirc_path is None:
pypirc_path = PYPIRC_DEFAULT
elif not os.path.isfile(pypirc_path):
raise FileNotFoundError("The specified pypirc config file does not exist.")

from . import build
built = build.main(ini_path, formats=formats, gen_setup_py=gen_setup_py)

if built.wheel is not None:
do_upload(built.wheel.file, built.wheel.builder.metadata, repo_name)
do_upload(built.wheel.file, built.wheel.builder.metadata, pypirc_path, repo_name)
if built.sdist is not None:
do_upload(built.sdist.file, built.sdist.builder.metadata, repo_name)
do_upload(built.sdist.file, built.sdist.builder.metadata, pypirc_path, repo_name)
111 changes: 94 additions & 17 deletions tests/test_upload.py
@@ -1,13 +1,17 @@
from contextlib import contextmanager
from tempfile import NamedTemporaryFile
import os
import io
import pathlib
import sys

import pytest
import responses
from testpath import modified_env
from unittest.mock import patch

from flit import upload
from flit.build import ALL_FORMATS

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

Expand All @@ -17,16 +21,6 @@
'is_warehouse': True,
}

@responses.activate
def test_upload(copy_sample):
responses.add(responses.POST, upload.PYPI, status=200)
td = copy_sample('module1_toml')

with patch('flit.upload.get_repository', return_value=repo_settings):
upload.main(td / 'pyproject.toml', repo_name='pypi')

assert len(responses.calls) == 2

pypirc1 = """
[distutils]
index-servers =
Expand All @@ -38,19 +32,43 @@ def test_upload(copy_sample):
"""
# That's not a real password. Well, hopefully not.

@contextmanager
def temp_pypirc(content):
try:
temp_file = NamedTemporaryFile("w+", delete=False)
temp_file.write(content)
temp_file.close()
yield temp_file.name
finally:
os.unlink(temp_file.name)


@responses.activate
def test_upload(copy_sample):
responses.add(responses.POST, upload.PYPI, status=200)
td = copy_sample('module1_toml')

with temp_pypirc(pypirc1) as pypirc, \
patch('flit.upload.get_repository', return_value=repo_settings):
upload.main(td / 'pyproject.toml', repo_name='pypi', pypirc_path=pypirc)

assert len(responses.calls) == 2

def test_get_repository():
repo = upload.get_repository(cfg_file=io.StringIO(pypirc1))
assert repo['url'] == upload.PYPI
assert repo['username'] == 'fred'
assert repo['password'] == 's3cret'
with temp_pypirc(pypirc1) as pypirc:
repo = upload.get_repository(pypirc_path=pypirc)
assert repo['url'] == upload.PYPI
assert repo['username'] == 'fred'
assert repo['password'] == 's3cret'

def test_get_repository_env():
with modified_env({
with temp_pypirc(pypirc1) as pypirc, \
modified_env({
'FLIT_INDEX_URL': 'https://pypi.example.com',
'FLIT_USERNAME': 'alice',
'FLIT_PASSWORD': 'p4ssword', # Also not a real password
}):
repo = upload.get_repository(cfg_file=io.StringIO(pypirc1))
repo = upload.get_repository(pypirc_path=pypirc)
# Because we haven't specified a repo name, environment variables should
# have higher priority than the config file.
assert repo['url'] == 'https://pypi.example.com'
Expand Down Expand Up @@ -87,7 +105,66 @@ def get_password(service_name, username):
def test_get_repository_keyring():
with modified_env({'FLIT_PASSWORD': None}), \
_fake_keyring('tops3cret'):
repo = upload.get_repository(cfg_file=io.StringIO(pypirc2))
repo = upload.get_repository(pypirc_path=io.StringIO(pypirc2))

assert repo['username'] == 'fred'
assert repo['password'] == 'tops3cret'


pypirc3_repo = "https://invalid-repo.inv"
pypirc3_user = "test"
pypirc3_pass = "not_a_real_password"
pypirc3 = f"""
[distutils] =
index-servers =
test123
[test123]
repository: {pypirc3_repo}
username: {pypirc3_user}
password: {pypirc3_pass}
"""


def test_upload_pypirc_file(copy_sample):
with temp_pypirc(pypirc3) as pypirc, \
patch("flit.upload.upload_file") as upload_file:
td = copy_sample("module1_toml")
formats = list(ALL_FORMATS)[:1]
upload.main(
td / "pyproject.toml",
formats=set(formats),
repo_name="test123",
pypirc_path=pypirc,
)
_, _, repo = upload_file.call_args[0]

assert repo["url"] == pypirc3_repo
assert repo["username"] == pypirc3_user
assert repo["password"] == pypirc3_pass


def test_upload_invalid_pypirc_file(copy_sample):
with patch("flit.upload.upload_file"):
td = copy_sample("module1_toml")
formats = list(ALL_FORMATS)[:1]
with pytest.raises(FileNotFoundError):
upload.main(
td / "pyproject.toml",
formats=set(formats),
repo_name="test123",
pypirc_path="./file.invalid",
)

def test_upload_default_pypirc_file(copy_sample):
with patch("flit.upload.do_upload") as do_upload:
td = copy_sample("module1_toml")
formats = list(ALL_FORMATS)[:1]
upload.main(
td / "pyproject.toml",
formats=set(formats),
repo_name="test123",
)

file = do_upload.call_args[0][2]
assert file == "~/.pypirc"

0 comments on commit 9335bfb

Please sign in to comment.