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
@@ -307,19 +307,54 @@ def post_object(base_url: str, objtype: str, datadict: dict[str, Any]) -> int:
307307 """Create a new API object."""
308308 resp = session .post (f"{ base_url } downloads/{ objtype } /" , data = json .dumps (datadict ))
309309 if resp .status_code != 201 :
310+ messages = [f"Creating { objtype } failed: { resp .status_code } " ]
310311 try :
311312 info = json .loads (resp .text )
312- print (info .get ("error_message" , "No error message." ))
313- print (info .get ("traceback" , "" ))
314- except : # noqa: E722
313+ except json .JSONDecodeError :
315314 pass
316- print (f"Creating { objtype } failed: { resp .status_code } " )
317- return - 1
315+ else :
316+ if isinstance (info , dict ):
317+ error_message = info .get ("error_message" )
318+ traceback = info .get ("traceback" )
319+ if error_message :
320+ messages .append (str (error_message ))
321+ if traceback :
322+ messages .append (str (traceback ))
323+ raise RuntimeError ("\n " .join (messages ))
318324 newloc = resp .headers ["Location" ]
319325 pk = int (newloc .strip ("/" ).split ("/" )[- 1 ])
320326 return pk
321327
322328
329+ def delete_object (base_url : str , objtype : str , pk : int ) -> None :
330+ """Delete an existing API object."""
331+ resp = session .delete (f"{ base_url } downloads/{ objtype } /{ pk } /" )
332+ if resp .status_code != 204 :
333+ raise RuntimeError (f"Deleting { objtype } { pk } failed: { resp .status_code } " )
334+
335+
336+ def create_release_files (base_url : str , file_dicts : Iterable [dict [str , Any ]]) -> int :
337+ """Create ReleaseFile objects and clean up this run's rows on failure."""
338+ created_pks : list [int ] = []
339+ try :
340+ for file_dict in file_dicts :
341+ file_pk = post_object (base_url , "release_file" , file_dict )
342+ created_pks .append (file_pk )
343+ print ("Created as id =" , file_pk )
344+ except Exception as create_error :
345+ cleanup_errors = []
346+ for file_pk in reversed (created_pks ):
347+ try :
348+ delete_object (base_url , "release_file" , file_pk )
349+ except Exception as cleanup_error :
350+ cleanup_errors .append (f"{ file_pk } : { cleanup_error } " )
351+ if cleanup_errors :
352+ message = "Failed to clean up partially created release files:\n "
353+ raise RuntimeError (message + "\n " .join (cleanup_errors )) from create_error
354+ raise
355+ return len (created_pks )
356+
357+
323358def sign_release_files_with_sigstore (
324359 ftp_root : str , release : str , release_files : list [tuple [str , str , str , bool , str ]]
325360) -> None :
@@ -452,7 +487,6 @@ def main() -> None:
452487
453488 release_files = list (list_files (args .ftp_root , rel ))
454489 sign_release_files_with_sigstore (args .ftp_root , rel , release_files )
455- n = 0
456490 file_dicts = {}
457491 for rfile , file_desc , os_slug , add_download , add_desc in release_files :
458492 if not os_slug :
@@ -470,11 +504,7 @@ def main() -> None:
470504 resp = session .delete (f"{ args .base_url } downloads/release_file/?release={ rel_pk } " )
471505 if resp .status_code != 204 :
472506 raise RuntimeError (f"deleting previous releases failed: { resp .status_code } " )
473- for file_dict in file_dicts .values ():
474- file_pk = post_object (args .base_url , "release_file" , file_dict )
475- if file_pk >= 0 :
476- print ("Created as id =" , file_pk )
477- n += 1
507+ n = create_release_files (args .base_url , file_dicts .values ())
478508 print (f"Done - { n } files added" )
479509
480510
0 commit comments