@@ -528,14 +528,20 @@ def store_multi_timeseries_df(
528528 DELETE_INSERT.
529529 override_protection: bool, optional, default is False
530530 A flag to ignore the protected data quality flag when storing data.
531- multithread: bool, default is false
531+ multithread: bool, default is true
532532 Specifies whether to store chunked time series values using multiple threads.
533533 max_workers: Int, Optional, default is None
534534 It is a number of Threads aka size of pool in concurrent.futures.ThreadPoolExecutor.
535535
536536 Returns
537537 -------
538538 None
539+
540+ Raises
541+ ------
542+ RuntimeError
543+ If any series fails to store. The message identifies failed series;
544+ other series may already have been stored successfully.
539545 """
540546
541547 def store_ts_ids (
@@ -544,24 +550,21 @@ def store_ts_ids(
544550 office_id : str ,
545551 version_date : Optional [datetime ] = None ,
546552 ) -> None :
547- try :
548- units = data ["units" ].iloc [0 ]
549- data_json = timeseries_df_to_json (
550- data = data ,
551- ts_id = ts_id ,
552- units = units ,
553- office_id = office_id ,
554- version_date = version_date ,
555- )
556- store_timeseries (
557- data = data_json ,
558- create_as_ltrs = create_as_ltrs ,
559- store_rule = store_rule ,
560- override_protection = override_protection ,
561- multithread = multithread ,
562- )
563- except Exception as e :
564- print (f"Error processing { ts_id } : { e } " )
553+ units = data ["units" ].iloc [0 ]
554+ data_json = timeseries_df_to_json (
555+ data = data ,
556+ ts_id = ts_id ,
557+ units = units ,
558+ office_id = office_id ,
559+ version_date = version_date ,
560+ )
561+ store_timeseries (
562+ data = data_json ,
563+ create_as_ltrs = create_as_ltrs ,
564+ store_rule = store_rule ,
565+ override_protection = override_protection ,
566+ multithread = multithread ,
567+ )
565568 return None
566569
567570 required_columns = ["date-time" , "value" , "ts_id" , "units" ]
@@ -577,7 +580,9 @@ def store_ts_ids(
577580 ts_data_all ["ts_id" ].astype (str ) + ":" + ts_data_all ["version_date" ].astype (str )
578581 ).unique ()
579582
583+ errors : List [str ] = []
580584 with concurrent .futures .ThreadPoolExecutor (max_workers = max_workers ) as executor :
585+ futures = {}
581586 for unique_tsid in unique_tsids :
582587 ts_id , version_date = unique_tsid .split (":" , 1 )
583588 if version_date != "NaT" :
@@ -592,9 +597,21 @@ def store_ts_ids(
592597 (ts_data_all ["ts_id" ] == ts_id ) & ts_data_all ["version_date" ].isna ()
593598 ]
594599 if not data .empty :
595- executor .submit (
600+ future = executor .submit (
596601 store_ts_ids , ts_data , ts_id , office_id , version_date_dt
597602 )
603+ futures [future ] = unique_tsid
604+
605+ for future in concurrent .futures .as_completed (futures ):
606+ try :
607+ future .result ()
608+ except Exception as e :
609+ errors .append (f"{ futures [future ]} : { e } " )
610+
611+ if errors :
612+ raise RuntimeError (
613+ f"{ len (errors )} time series failed to store:\n " + "\n " .join (errors )
614+ )
598615
599616
600617def chunk_timeseries_data (
@@ -686,7 +703,14 @@ def store_timeseries(
686703 if len (chunks ) == 1 or not multithread :
687704 return api .post (endpoint , data , params )
688705
689- actual_workers = min (max_workers , len (chunks ))
706+ if max_workers <= 0 :
707+ raise ValueError ("max_workers must be greater than 0" )
708+
709+ # A new series must exist before multiple transactions can write its data.
710+ # Complete one normal write first, then retain parallelism for the rest.
711+ _call_with_retry (api .post , endpoint , chunks [0 ], params )
712+ remaining_chunks = chunks [1 :]
713+ actual_workers = min (max_workers , len (remaining_chunks ))
690714 logging .debug (
691715 f"Storing { len (chunks )} chunks of timeseries data with { actual_workers } threads"
692716 )
@@ -698,7 +722,7 @@ def store_timeseries(
698722 with concurrent .futures .ThreadPoolExecutor (max_workers = actual_workers ) as executor :
699723 future_to_chunk = {
700724 executor .submit (_call_with_retry , api .post , endpoint , chunk , params ): chunk
701- for chunk in chunks
725+ for chunk in remaining_chunks
702726 }
703727
704728 for future in concurrent .futures .as_completed (future_to_chunk ):
0 commit comments