twine: leaving out a trailing slash in the pypi URL causes twine to fail with a 410

Given a [test] repository described as https://test.pypi.org/legacy in a .pypirc file, twine upload --repository test dist/* fails with HTTPError: 410 Client Error: This API has moved to https://test.pypi.org/legacy/. See TODO for more information. for url: https://test.pypi.org/pypi.

<runciter> though i'd expect a 301 or 302
<runciter> not a very big deal because the correct URL is in the error message
<runciter> (maybe not a deal at all)
...
<sumanah> coming back to the "missing trailing slash" thing. To make sure I have this right: you had a [test] section in your .pypirc that had the right URL but missing a trailing slash, correct?
<runciter> sumanah: yep!

Repro case with .pypirc: https://gist.github.com/markrwilliams/01a235daeee2d6a0068a22d2faa7b2cb

Could we in this case fix the trailing slash to make life easier for users?

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 2
  • Comments: 20 (17 by maintainers)

Most upvoted comments

@brainwane Seems reasonable, and per my comment in #460:

I would probably do that in utils.normalize_repository_url by appending a trailing slash if the URL is in _HOSTNAMES.

That said, I’m slightly wary of implicitly altering the configured repositories, and continuing down the path of special-casing. In light of #499 & #509, I’m wondering if a “better” approach would be to sanity check the repository URL prior to upload, and abort with a helpful error message.

Something like:

if "pypi.org" in url and url not in [DEFAULT_REPOSITORY, TEST_REPOSITORY]:
    print(
        f"The configured repository {url} is not a known PyPI repository.\n"
        f"Did you mean {DEFAULT_REPOSITORY} or {TEST_REPOSITORY}?\n"
        f"See {PYPIRC_DOCS} for details."
    )
    return

This would put up a temporary roadblock, but would encourage folks to have a “correct” configuration. If that sounds like a good idea, I’m inclined to open that as a new enhancement issue, and close this one.

Maybe the @pypa/twine-maintainers have opinions?

After taking a closer look, I’m going to close this with no further changes. Unlike the redirect case, a 404 on a URL without a trailing slash doesn’t necessarily imply that adding a trailing slash is the proper resolution; for example, it could mean a typo in the URL.

@meowmeowmeowcat I haven’t closed it because there was some follow up that I wanted to do:

https://github.com/pypa/twine/issues/310#issuecomment-405951929 shows that devpi returns “Not Found” instead of redirecting. I’m inclined to follow up with a commit or PR to handle that case in utils.check_status_code

@meowmeowmeowcat @sigmavirus24 I appreciate the suggestions to help the user, but this also feels like a complex bit of behavior to implement, test, and review. For example, thus far, Twine does not write to the configuration file. It also seems like this issue doesn’t come up very often.

With all that in mind, I would rather start with the lighter-weight “help” message proposed in https://github.com/pypa/twine/issues/310#issuecomment-540253403, maybe extending it to mention the configuration file, or suggesting --verbose. Hopefully, that will encourage folks to fix their configuration.

Then, we could ask users whether they want to update the configured repository in the twine directly:

I would think this is helpful but I would also give people the ability to specify the config file to save it to. Some people might want to diff the two files to ensure it’s only changed the URL. I would also remove If you are uploading distribution(s) to PyPI or TestPyPI, because ostensibly they’ve already answered “Yes” (or something along those lines) so all we need to say is We're appending a / to the URL ({url}) in your {configfilepath}. Do you want us to save this configuration for you for future use? [y/N]: and then say Where should we save this? [{configfilepath}]: I’m explicitly saying {configfilepath} because a user could be using --config and we should update that and not ~/.pypirc.