@@ -55,6 +55,7 @@ def __init__(
5555 comment : Optional [str ] = None ,
5656 config_file : str = utils .DEFAULT_CONFIG_FILE ,
5757 skip_existing : bool = False ,
58+ ignored_http_statuses : Optional [list [int ]] = None ,
5859 cacert : Optional [str ] = None ,
5960 client_cert : Optional [str ] = None ,
6061 repository_name : str = "pypi" ,
@@ -88,8 +89,10 @@ def __init__(
8889 The path to the configuration file to use.
8990 :param skip_existing:
9091 Specify whether twine should continue uploading files if one
91- of them already exists. This primarily supports PyPI. Other
92- package indexes may not be supported.
92+ of them already exists. Only for use with PyPI.
93+ :param ignored_http_statuses:
94+ Specify a set of HTTP status codes to ignore, continuing to upload
95+ other files.
9396 :param cacert:
9497 The path to the bundle of certificates used to verify the TLS
9598 connection to the package index.
@@ -113,6 +116,7 @@ def __init__(
113116 self .verbose = verbose
114117 self .disable_progress_bar = disable_progress_bar
115118 self .skip_existing = skip_existing
119+ self .ignored_http_statuses = set (ignored_http_statuses or [])
116120 self ._handle_repository_options (
117121 repository_name = repository_name ,
118122 repository_url = repository_url ,
@@ -245,8 +249,18 @@ def register_argparse_arguments(parser: argparse.ArgumentParser) -> None:
245249 default = False ,
246250 action = "store_true" ,
247251 help = "Continue uploading files if one already exists. (Only valid "
248- "when uploading to PyPI. Other implementations may not "
249- "support this.)" ,
252+ "when uploading to PyPI. Not supported with other "
253+ "implementations.)" ,
254+ )
255+ parser .add_argument (
256+ "--ignore-http-status" ,
257+ type = int ,
258+ action = "append" ,
259+ dest = "ignored_http_statuses" ,
260+ metavar = "status_code" ,
261+ help = "Ignore the specified HTTP status code and continue uploading"
262+ " files. May be specified multiple times."
263+ " (Not supported when uploading to PyPI.)" ,
250264 )
251265 parser .add_argument (
252266 "--cert" ,
@@ -318,19 +332,30 @@ def verify_feature_capability(self) -> None:
318332
319333 This presently checks:
320334 - ``--skip-existing`` was only provided for PyPI and TestPyPI
335+ - ``--ignore-http-status`` was not provided for PyPI or TestPyPI
321336
322337 :raises twine.exceptions.UnsupportedConfiguration:
323338 The configured features are not available with the configured
324339 repository.
325340 """
326341 repository_url = cast (str , self .repository_config ["repository" ])
327342
328- if self .skip_existing and not repository_url .startswith (
329- (repository .WAREHOUSE , repository .TEST_WAREHOUSE )
330- ):
331- raise exceptions .UnsupportedConfiguration .Builder ().with_feature (
332- "--skip-existing"
333- ).with_repository_url (repository_url ).finalize ()
343+ exc_builder = exceptions .UnsupportedConfiguration .Builder ()
344+ exc_builder .with_repository_url (repository_url )
345+
346+ pypi_urls = (repository .WAREHOUSE , repository .TEST_WAREHOUSE )
347+
348+ if repository_url .startswith (pypi_urls ):
349+ # is PyPI
350+ if self .ignored_http_statuses :
351+ exc_builder .with_feature ("--ignore-http-status" )
352+ else :
353+ # is not PyPI
354+ if self .skip_existing :
355+ exc_builder .with_feature ("--skip-existing" )
356+
357+ if exc_builder .features :
358+ raise exc_builder .finalize ()
334359
335360 def check_repository_url (self ) -> None :
336361 """Verify we are not using legacy PyPI.
0 commit comments