2929import re
3030import subprocess
3131import sys
32- from collections .abc import Generator
32+ from collections .abc import Generator , Iterable
3333from functools import cache
3434from os import path
3535from typing import Any , NoReturn
@@ -308,19 +308,54 @@ def post_object(base_url: str, objtype: str, datadict: dict[str, Any]) -> int:
308308 headers = headers ,
309309 )
310310 if resp .status_code != 201 :
311+ messages = [f"Creating { objtype } failed: { resp .status_code } " ]
311312 try :
312313 info = json .loads (resp .text )
313- print (info .get ("error_message" , "No error message." ))
314- print (info .get ("traceback" , "" ))
315- except : # noqa: E722
314+ except json .JSONDecodeError :
316315 pass
317- print (f"Creating { objtype } failed: { resp .status_code } " )
318- return - 1
316+ else :
317+ if isinstance (info , dict ):
318+ error_message = info .get ("error_message" )
319+ traceback = info .get ("traceback" )
320+ if error_message :
321+ messages .append (str (error_message ))
322+ if traceback :
323+ messages .append (str (traceback ))
324+ raise RuntimeError ("\n " .join (messages ))
319325 newloc = resp .headers ["Location" ]
320326 pk = int (newloc .strip ("/" ).split ("/" )[- 1 ])
321327 return pk
322328
323329
330+ def delete_object (base_url : str , objtype : str , pk : int ) -> None :
331+ """Delete an existing API object."""
332+ resp = requests .delete (base_url + f"downloads/{ objtype } /{ pk } /" , headers = headers )
333+ if resp .status_code != 204 :
334+ raise RuntimeError (f"Deleting { objtype } { pk } failed: { resp .status_code } " )
335+
336+
337+ def create_release_files (base_url : str , file_dicts : Iterable [dict [str , Any ]]) -> int :
338+ """Create ReleaseFile objects and clean up this run's rows on failure."""
339+ created_pks : list [int ] = []
340+ try :
341+ for file_dict in file_dicts :
342+ file_pk = post_object (base_url , "release_file" , file_dict )
343+ created_pks .append (file_pk )
344+ print ("Created as id =" , file_pk )
345+ except Exception as create_error :
346+ cleanup_errors = []
347+ for file_pk in reversed (created_pks ):
348+ try :
349+ delete_object (base_url , "release_file" , file_pk )
350+ except Exception as cleanup_error :
351+ cleanup_errors .append (f"{ file_pk } : { cleanup_error } " )
352+ if cleanup_errors :
353+ message = "Failed to clean up partially created release files:\n "
354+ raise RuntimeError (message + "\n " .join (cleanup_errors )) from create_error
355+ raise
356+ return len (created_pks )
357+
358+
324359def sign_release_files_with_sigstore (
325360 ftp_root : str , release : str , release_files : list [tuple [str , str , str , bool , str ]]
326361) -> None :
@@ -453,7 +488,6 @@ def main() -> None:
453488
454489 release_files = list (list_files (args .ftp_root , rel ))
455490 sign_release_files_with_sigstore (args .ftp_root , rel , release_files )
456- n = 0
457491 file_dicts = {}
458492 for rfile , file_desc , os_slug , add_download , add_desc in release_files :
459493 if not os_slug :
@@ -473,11 +507,7 @@ def main() -> None:
473507 )
474508 if resp .status_code != 204 :
475509 raise RuntimeError (f"deleting previous releases failed: { resp .status_code } " )
476- for file_dict in file_dicts .values ():
477- file_pk = post_object (args .base_url , "release_file" , file_dict )
478- if file_pk >= 0 :
479- print ("Created as id =" , file_pk )
480- n += 1
510+ n = create_release_files (args .base_url , file_dicts .values ())
481511 print (f"Done - { n } files added" )
482512
483513
0 commit comments