Skip to content

Instantly share code, notes, and snippets.

Last active Sep 30, 2020
What would you like to do?

new PyPI: a rant from a package maintainer

(Package names and versions have been changed to protect the innocent. This is an edited version of real events, reproduced to the best of my memory. And it goes without mention that I was constantly googling and trying different things.)

(This rant was created for the distutils-sig mailing list. Click here to read the responses from the community and PyPI developeres.)


  • Everything is "legacy".
  • Uploads fail for unfathomable reasons.
  • Metadata fails to update and manual changes are not possible.
  • Documentation is all over the place and outdated.
  • TestPyPI is extremely confusing/confused.
  • What should have taken 30 seconds spiraled into consuming hours of my life and most of my sanity.

Task: upload a new version of a package.

Tools at my disposal:

  • Account and package registered.
  • New version ready to go in my machine.
  • Script with steps required, boiling down to:
    • Bump version at
    • python sdist --format=zip bdist --format=zip bdist_wheel --universal bdist_wininst upload
    • Or python register to just update the description.
  • Pre-created configuration file ~/.pypirc.

username = BoppreH
password = hunter2
  • Battle tested scripts that had performed releases like this dozens of times.
  • The fact that the package is a universal pure Python package with zero dependencies (it's one of the selling points).
  • Updated pip, updated setuptools, Python 3.6 64 bits, Windows 10 64 bits, sane environment.

Act I: "Just take the damn files"

Everything is going well, until python [...] upload chokes during upload of one of the files. The transfer hangs, a timeout occurs. Oh dear. No problem, I'll just python upload...

error: No dist file created in earlier command

Short memory much? So I run the full command again...

Uploading distributions to
HTTPError: 400 Client Error: File already exists. for url:

It's the exact same file. You could add a check for it and ignore, but no, it had to be an error. Then I have to play the game of "let's figure out which part of the command created which of 6 files and upload one by one". This was a pain, but manageable.

Checking the release I see the description is just the name of the package. It seems that long_description in was missed entirely. I check my tools, and they seem to be sending the correct data. Regardless, I'll just edit the description manually in but then my browser greets me with: ...

Gone (This API has been deprecated and removed from legacy PyPI in favor of using the APIs available in the new implementation of PyPI (located at For more information about migrating your use of this API to, please see For more information about the sunsetting of this API, please see

Act II: "Please just tell me which one you want me to use"

It seems that changing the description is not possible via the website. No problem, I'll just run python register, which should update the documentation. By the way, why was this feature included in the register command? I feel weird using it. Or should I say "used to feel weird", because...

$ python register
Registering package to
Server response (410): Project pre-registration is no longer required or supported, so continue directly to uploading files.

Hmm. Maybe the answer to my conundrum is in this "new" mentioned before. I visit the page and I'm greeted with a red banner with a warning sign: "This is a pre-production deployment of Warehouse. Changes made here affect the production instance of PyPI (". I login in this new website, visit my package page and... there's no way to edit the description there either.

At this point I have to stop and look at the bigger picture of this migration:

  • My has been communicating with by default. Note the legacy part.
  • The website has pages that were "deprecated and removed from legacy PyPI in favor of using the APIs available in the new implementation of PyPI". But the "new APIs" seem to be missing at last one important feature, and I must have missed the deprecation period that preceded the complete removal of the old APIs. A friendly email would have been nice if you expect me to migrate to a new solution.
  • Looking around, it seems that I should be using a new tool called twine to upload my files after has created them. This should fix the short memory problem and ensure TLS.
  • The kicker? Twine also uploads to by default. Editing my .pypirc to force the URL to be gives me 405 Client Error: Method Not Allowed.

The new tool seems to be using legacy APIs, the old website is responding with "Gone" to reasonable requests, the new website is "pre-production" and missing the feature that was removed from the old one, and I see no way to use non-legacy APIs. I feel like someone is telling me to fuck-off.

After all this I still didn't solve my problem, and have to bump my software version to update the description in PyPI. So I bump my version, publish a new release that just says "Fixed README, sorry", confuse my users, and...

$ twine upload dist/*
HTTPError: 400 Client Error: Invalid file extension. for url:

What? But I just uploaded a wizard a moment ago! Why is this error happening now? Why are there exactly 0 results on Google for this error? It seems that my users will have to live without an installation wizard and a weird release.

Act III: Test wasteland

I notice I am confused. I'm clearly under some profound misunderstandings here. I don't want to piss my users off any more, so I look for a way to test all this in a safe environment. There is TestPyPI, built exactly for this situation: Finally something makes sense.

Haha, no.

Step 3 in that tutorial is to register your package:

$ python register -r
running register
running egg_info
writing package.egg-info\PKG-INFO
writing dependency_links to package.egg-info\dependency_links.txt
writing top-level names to package.egg-info\top_level.txt
reading manifest file 'package.egg-info\SOURCES.txt'
writing manifest file 'package.egg-info\SOURCES.txt'
running check
Registering package to
Server response (410): Gone (This API has been deprecated and removed from legacy PyPI in favor of using the APIs available in the new implementation of PyPI (located at For more information about migrating your use of this API to, please see For more information about the sunsetting of this API, please see

To make matters worse, the tutorial also says: "Note that the test server uses an older implementation of the upload interface and hence does not support auto-registration the way the live service does when using twine's default configuration."

I stumble about in the dark for a while, then find this notice in the website for TestPyPI:

Package Authors

Submit packages with "python upload". The index hosts package docs. You may also use the web form. You must register. Testing? Use testpypi.

I'm not sure why TestPyPI is telling me to check out TestPyPI, so let's ignore that. See that "web form" link? Here's the entire contents of that "form":

To submit information to this index, you have two options:

  1. Use twine!
  2. Use the "register" command.

This must be a prank. The tutorial just told me that TestPyPI does not support "auto-registration [...] using twine's default configuration", so if you are telling me to use Twine, you better give more details than an exclamation mark. And "python register" has been killed, we've been through this before.

Maybe the tutorial is outdated, and TestPyPI supports auto-registration now? Sure, let's go with that.

$ python sdist
$ twine upload dist\package-1.3.4.tar.gz -r testpypi
Uploading distributions to
Uploading package-1.3.4.tar.gz
HTTPError: 410 Client Error: Gone (This API has been deprecated and removed from legacy PyPI in favor of using the APIs available in the new implementation of PyPI (located at For more information about migrating your use of this API to, please see For more information about the sunsetting of this API, please see for url:

God dammnit, so I cannot even upload files to TestPyPI? Let's check out that mailing list announcement at the end of the error message. Wait, this looks interesting:

[...] For those of you who are using TestPyPI, that will also be affected, and the required URL for the new upload endpoint for TestPyPI is [...] For TestPyPI the change to disable uploads to legacy will be made in the next couple of days, likely this weekend.

So the tutorial, the only page that seems to have any details on how to use TestPyPI, is telling users to send data to the wrong URL. Also, what do they mean by "disable uploads to legacy"? Is it, the URL from the tutorial, which is "legacy" in the sense of being the old one, or, which is explicitly legacy in the name? Oh well, the tutorial one is not working, so let's try the latter. I edit my .pypirc, rerun twine and...

$ twine upload dist\package-1.3.4.tar.gz -r testpypi
Uploading distributions to
Uploading package-1.3.4.tar.gz

Finally, something worked. I go visit my newly created package at and... the description is still missing. I download my own .tar.gz from there, check PKG-INFO, and the description is there. python -rms to check my setup and no problems are reported. The new website doesn't show me the raw package metadata, so I go over to the legacy website at, more specifically (not a fan o the URL, by the way).

Metadata-Version: 1.1
Name: package
Version: 1.3.4
Author: BoppreH
Author-email: boppreh at gmail com
Summary: My short summary
License: MIT
Description: package

Some data is correct, but the description that I uploaded has been replaced with just the package name, and also the keywords and trove classifiers are missing entirely. I can't python register to upload metadata, and I can't python sdist upload because I already uploaded a source distribution, so I try python bdist upload:

# python bdist upload -r testpypi
Submitting C:\Users\BoppreH\Desktop\source\package\dist\ to
Upload failed (400): Unknown type of file.
error: Upload failed (400): Unknown type of file.

You gotta be kidding me. It shouldn't make a difference, but let's try with twine:

$ twine upload dist\
Uploading distributions to
HTTPError: 400 Client Error: Only one sdist may be uploaded per release. for url:

But... but... It's not an sdist, it's a bdist! Maybe one implies the other? Let's see what the docs say:

$ python --help | grep bdist

Nothing. The website also doesn't mention why twine would confuse the two.

Act IV: I give up.

I can't even specify the description of a package, not even during registration or upload. Either this whole PyPI thing is an absolute clusterfuck, or I'm under some severe misunderstandings that no documentation, error message or mailing list announcement was able to fix.

I cannot even report the issues. First, because I'm so burned that I don't care to create individual reports for all the issues. Second, because there's no clear location to report them. For example, if my package description is missing, do I report to:

  • setuptools because it might have created a file with wrong syntax?
  • twine because it might have uploaded incorrect metadata?
  • warehouse because it's their API that I'm hitting?
  • pypi-legacy because the API is emulating it, or maybe it's just a display issue on the website?
  • or to the generic packaging-problems repository, which almost looks like the correct place, until you realize that no page has linked to it, it's empty except for a bunch of random issues and a wiki with a roadmap for PyPA developers last updated 5 months ago?

I am trying to donate my precious free time to the Python community by offering free software, receiving absolutely no compensation whatsoever, and every step of the experience has been shitty. Python packages have always been full of quirks, but this time it seems like all the quirks broke at once, and the result is incomprehensible.

I know other people are using these tools, so it's not like everything is broken for everyone. But my bad experiences happened, and they must have an explanation. What do I expect from this rant? Either someone to tell me "yes, PyPI made mistakes, we're going to fix it", or "hey, it's your fault because you did X wrong".

I don't want to throw my work away just because dealing with PyPI is excruciatingly painful.

Copy link

jayfk commented Aug 4, 2017

Your .pypirc contains a password.

Copy link

offbyone commented Aug 4, 2017

Does it? I only see ******* here.

Copy link

mithrandi commented Aug 4, 2017

The /legacy endpoints are poorly named; I think the name was chosen because they are still providing the "legacy API" and there are plans for a better API at some point, but such an API does not actually exist yet: the "legacy API" is the only one available at this point in time. https://{test,} is the old endpoint, https://{test.,} is the new endpoint.

Also there's a ton of stuff in setuptools (ie. …) that is bad / broken / has been replaced but there's no obvious way to figure out what's what. I don't actually know what type of distribution bdist even builds; I think the errors you got are because PyPI/Twine don't know either. Probably you wanted bdist_wheel? (You'll need the wheel package installed for this command to exist)

PyPI also doesn't allow uploading .exe installers anymore; pip doesn't know how to automatically install them; the only things you can upload now are sdists (.tar.gz etc.), wheels (.whl), and eggs (.egg, but you don't want to use these).

Copy link

sureshvv commented Aug 6, 2017

What is the latest news? Is it really as bad as this? I have several packages on pypi that I have not updated waiting for the dust to settle.

Copy link

boppreh commented Aug 10, 2017

Sorry for not responding here, I was not expecting comments on a "secret" gist and Github doesn't send notifications.

Thanks for the warning, but "hunter2" is a common joke password because of .

Thank you for your response, it does help explaining what happened in some of the issues above.


I have several packages on pypi that I have not updated waiting for the dust to settle.

Updating packages should be fine. Just note that you won't be able to change the metadata of a published version, and avoid Windows style newlines in the description.

Is it really as bad as this?

All of those issues did happen, and as far as I understand none of it was caused by mistakes of own (unless you count not following the distutils mailing list and keeping up with PEPs, which would have prevented one or two issues).

What is the latest news?

You can follow the responses from the developers here: .

Copy link

rsyring commented Aug 11, 2017

@boppreh Just FYI, the TestPyPI docs have been updated:

Copy link

lod commented Sep 21, 2017

I just got hit by this too, with a twist:

  twine upload --repository testpypi dist/*
  Uploading distributions to
  HTTPError: 410 Client Error: Gone (...  please see ...)

That documentation includes, among other things, the line "Then simply delete the line starting with repository and you will use your upload tool’s default URL."

Which caused twine to upload my test package to the live repository system.

This was caused by following the Setting up TestPyPI in pypirc section of the documentation at

Copy link

Addicted-to-coding commented Sep 30, 2020

I'm getting the error message:
InvalidConfiguration: Missing 'testpypi' section from the configuration file
or not a complete URL in --repository-url.
Maybe you have an out-dated '~/.pypirc' format?
more info:
even after I modify my ~/.pypirc to include the working URL:
Does anyone know how to fix this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment