@@ -427,6 +427,7 @@ def _resolve_query_without_cache_regular(
427427 boto3_session : Optional [boto3 .Session ],
428428 execution_params : Optional [List [str ]] = None ,
429429 dtype_backend : Literal ["numpy_nullable" , "pyarrow" ] = "numpy_nullable" ,
430+ client_request_token : Optional [str ] = None ,
430431) -> Union [pd .DataFrame , Iterator [pd .DataFrame ]]:
431432 wg_config : _WorkGroupConfig = _get_workgroup_config (session = boto3_session , workgroup = workgroup )
432433 s3_output = _get_s3_output (s3_output = s3_output , wg_config = wg_config , boto3_session = boto3_session )
@@ -442,6 +443,7 @@ def _resolve_query_without_cache_regular(
442443 encryption = encryption ,
443444 kms_key = kms_key ,
444445 execution_params = execution_params ,
446+ client_request_token = client_request_token ,
445447 boto3_session = boto3_session ,
446448 )
447449 _logger .debug ("Query id: %s" , query_id )
@@ -490,6 +492,7 @@ def _resolve_query_without_cache(
490492 pyarrow_additional_kwargs : Optional [Dict [str , Any ]] = None ,
491493 execution_params : Optional [List [str ]] = None ,
492494 dtype_backend : Literal ["numpy_nullable" , "pyarrow" ] = "numpy_nullable" ,
495+ client_request_token : Optional [str ] = None ,
493496) -> Union [pd .DataFrame , Iterator [pd .DataFrame ]]:
494497 """
495498 Execute a query in Athena and returns results as DataFrame, back to `read_sql_query`.
@@ -570,6 +573,7 @@ def _resolve_query_without_cache(
570573 boto3_session = boto3_session ,
571574 execution_params = execution_params ,
572575 dtype_backend = dtype_backend ,
576+ client_request_token = client_request_token ,
573577 )
574578
575579
@@ -776,6 +780,7 @@ def read_sql_query( # pylint: disable=too-many-arguments,too-many-locals
776780 keep_files : bool = True ,
777781 use_threads : Union [bool , int ] = True ,
778782 boto3_session : Optional [boto3 .Session ] = None ,
783+ client_request_token : Optional [str ] = None ,
779784 athena_cache_settings : Optional [typing .AthenaCacheSettings ] = None ,
780785 data_source : Optional [str ] = None ,
781786 athena_query_wait_polling_delay : float = _QUERY_WAIT_POLLING_DELAY ,
@@ -938,6 +943,13 @@ def read_sql_query( # pylint: disable=too-many-arguments,too-many-locals
938943 If integer is provided, specified number is used.
939944 boto3_session : boto3.Session(), optional
940945 Boto3 Session. The default boto3 session will be used if boto3_session receive None.
946+ client_request_token : str, optional
947+ A unique case-sensitive string used to ensure the request to create the query is idempotent (executes only once).
948+ If another StartQueryExecution request is received, the same response is returned and another query is not created.
949+ If a parameter has changed, for example, the QueryString , an error is returned.
950+ If you pass the same client_request_token value with different parameters the query fails with error
951+ message "Idempotent parameters do not match". Use this only with ctas_approach=False and unload_approach=False
952+ and disabled cache.
941953 athena_cache_settings: typing.AthenaCacheSettings, optional
942954 Parameters of the Athena cache settings such as max_cache_seconds, max_cache_query_inspections,
943955 max_remote_cache_entries, and max_local_cache_entries.
@@ -1022,46 +1034,44 @@ def read_sql_query( # pylint: disable=too-many-arguments,too-many-locals
10221034 raise exceptions .InvalidArgumentCombination ("Only one of ctas_approach=True or unload_approach=True is allowed" )
10231035 if unload_parameters and unload_parameters .get ("file_format" ) not in (None , "PARQUET" ):
10241036 raise exceptions .InvalidArgumentCombination ("Only PARQUET file format is supported if unload_approach=True" )
1037+ if client_request_token and athena_cache_settings :
1038+ raise exceptions .InvalidArgumentCombination (
1039+ "Only one of `client_request_token` or `athena_cache_settings` is allowed."
1040+ )
1041+ if client_request_token and (ctas_approach or unload_approach ):
1042+ raise exceptions .InvalidArgumentCombination (
1043+ "Using `client_request_token` is only allowed when `ctas_approach=False` and `unload_approach=False`."
1044+ )
10251045 chunksize = sys .maxsize if ctas_approach is False and chunksize is True else chunksize
10261046
10271047 # Substitute query parameters if applicable
10281048 sql , execution_params = _apply_formatter (sql , params , paramstyle )
10291049
1030- athena_cache_settings = athena_cache_settings if athena_cache_settings else {}
1031- max_cache_seconds = athena_cache_settings .get ("max_cache_seconds" , 0 )
1032- max_cache_query_inspections = athena_cache_settings .get ("max_cache_query_inspections" , 50 )
1033- max_remote_cache_entries = athena_cache_settings .get ("max_remote_cache_entries" , 50 )
1034- max_local_cache_entries = athena_cache_settings .get ("max_local_cache_entries" , 100 )
1035-
1036- max_remote_cache_entries = min (max_remote_cache_entries , max_local_cache_entries )
1037-
1038- _cache_manager .max_cache_size = max_local_cache_entries
1039- cache_info : _CacheInfo = _check_for_cached_results (
1040- sql = sql ,
1041- boto3_session = boto3_session ,
1042- workgroup = workgroup ,
1043- max_cache_seconds = max_cache_seconds ,
1044- max_cache_query_inspections = max_cache_query_inspections ,
1045- max_remote_cache_entries = max_remote_cache_entries ,
1046- )
1047- _logger .debug ("Cache info:\n %s" , cache_info )
1048- if cache_info .has_valid_cache is True :
1049- _logger .debug ("Valid cache found. Retrieving..." )
1050- try :
1051- return _resolve_query_with_cache (
1052- cache_info = cache_info ,
1053- categories = categories ,
1054- chunksize = chunksize ,
1055- use_threads = use_threads ,
1056- session = boto3_session ,
1057- athena_query_wait_polling_delay = athena_query_wait_polling_delay ,
1058- s3_additional_kwargs = s3_additional_kwargs ,
1059- pyarrow_additional_kwargs = pyarrow_additional_kwargs ,
1060- dtype_backend = dtype_backend ,
1061- )
1062- except Exception as e : # pylint: disable=broad-except
1063- _logger .error (e ) # if there is anything wrong with the cache, just fallback to the usual path
1064- _logger .debug ("Corrupted cache. Continuing to execute query..." )
1050+ if not client_request_token :
1051+ cache_info : _CacheInfo = _check_for_cached_results (
1052+ sql = sql ,
1053+ boto3_session = boto3_session ,
1054+ workgroup = workgroup ,
1055+ athena_cache_settings = athena_cache_settings ,
1056+ )
1057+ _logger .debug ("Cache info:\n %s" , cache_info )
1058+ if cache_info .has_valid_cache is True :
1059+ _logger .debug ("Valid cache found. Retrieving..." )
1060+ try :
1061+ return _resolve_query_with_cache (
1062+ cache_info = cache_info ,
1063+ categories = categories ,
1064+ chunksize = chunksize ,
1065+ use_threads = use_threads ,
1066+ session = boto3_session ,
1067+ athena_query_wait_polling_delay = athena_query_wait_polling_delay ,
1068+ s3_additional_kwargs = s3_additional_kwargs ,
1069+ pyarrow_additional_kwargs = pyarrow_additional_kwargs ,
1070+ dtype_backend = dtype_backend ,
1071+ )
1072+ except Exception as e : # pylint: disable=broad-except
1073+ _logger .error (e ) # if there is anything wrong with the cache, just fallback to the usual path
1074+ _logger .debug ("Corrupted cache. Continuing to execute query..." )
10651075
10661076 ctas_parameters = ctas_parameters if ctas_parameters else {}
10671077 ctas_database = ctas_parameters .get ("database" )
@@ -1094,6 +1104,7 @@ def read_sql_query( # pylint: disable=too-many-arguments,too-many-locals
10941104 pyarrow_additional_kwargs = pyarrow_additional_kwargs ,
10951105 execution_params = execution_params ,
10961106 dtype_backend = dtype_backend ,
1107+ client_request_token = client_request_token ,
10971108 )
10981109
10991110
@@ -1117,6 +1128,7 @@ def read_sql_table(
11171128 keep_files : bool = True ,
11181129 use_threads : Union [bool , int ] = True ,
11191130 boto3_session : Optional [boto3 .Session ] = None ,
1131+ client_request_token : Optional [str ] = None ,
11201132 athena_cache_settings : Optional [typing .AthenaCacheSettings ] = None ,
11211133 data_source : Optional [str ] = None ,
11221134 dtype_backend : Literal ["numpy_nullable" , "pyarrow" ] = "numpy_nullable" ,
@@ -1274,6 +1286,13 @@ def read_sql_table(
12741286 If integer is provided, specified number is used.
12751287 boto3_session : boto3.Session(), optional
12761288 Boto3 Session. The default boto3 session will be used if boto3_session receive None.
1289+ client_request_token : str, optional
1290+ A unique case-sensitive string used to ensure the request to create the query is idempotent (executes only once).
1291+ If another StartQueryExecution request is received, the same response is returned and another query is not created.
1292+ If a parameter has changed, for example, the QueryString , an error is returned.
1293+ If you pass the same client_request_token value with different parameters the query fails with error
1294+ message "Idempotent parameters do not match". Use this only with ctas_approach=False and unload_approach=False
1295+ and disabled cache.
12771296 athena_cache_settings: typing.AthenaCacheSettings, optional
12781297 Parameters of the Athena cache settings such as max_cache_seconds, max_cache_query_inspections,
12791298 max_remote_cache_entries, and max_local_cache_entries.
@@ -1327,6 +1346,7 @@ def read_sql_table(
13271346 keep_files = keep_files ,
13281347 use_threads = use_threads ,
13291348 boto3_session = boto3_session ,
1349+ client_request_token = client_request_token ,
13301350 athena_cache_settings = athena_cache_settings ,
13311351 data_source = data_source ,
13321352 s3_additional_kwargs = s3_additional_kwargs ,
0 commit comments