Skip to content

Commit 7bbf128

Browse files
Extend retry_api_call with stop_on instead of a private variant
retry_api_call and async_retry_api_call take a keyword-only stop_on predicate; the private _retry_api_call and _async_retry_api_call go. Existing callers that pass **request built from a single-key literal now annotate it as dict[str, Any], since mypy would otherwise match its values against stop_on; the values are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent be54210 commit 7bbf128

7 files changed

Lines changed: 47 additions & 64 deletions

File tree

‎pyathena/aio/common.py‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from botocore.exceptions import BotoCoreError, ClientError
1111

12-
from pyathena.aio.util import _async_retry_api_call, async_retry_api_call
12+
from pyathena.aio.util import async_retry_api_call
1313
from pyathena.common import BaseCursor, CursorIterator
1414
from pyathena.error import DatabaseError, OperationalError, ProgrammingError
1515
from pyathena.glue import GlueMetadataClient
@@ -88,7 +88,7 @@ async def _execute( # type: ignore[override]
8888
return query_id
8989

9090
async def _get_query_execution(self, query_id: str) -> AthenaQueryExecution: # type: ignore[override]
91-
request = {"QueryExecutionId": query_id}
91+
request: dict[str, Any] = {"QueryExecutionId": query_id}
9292
try:
9393
response = await async_retry_api_call(
9494
self._connection.client.get_query_execution,
@@ -128,7 +128,7 @@ async def _poll(self, query_id: str) -> AthenaQueryExecution: # type: ignore[ov
128128
return query_execution
129129

130130
async def _cancel(self, query_id: str) -> None: # type: ignore[override]
131-
request = {"QueryExecutionId": query_id}
131+
request: dict[str, Any] = {"QueryExecutionId": query_id}
132132
try:
133133
await async_retry_api_call(
134134
self._connection.client.stop_query_execution,
@@ -283,11 +283,11 @@ async def _list_databases( # type: ignore[override]
283283
max_results=max_results,
284284
)
285285
try:
286-
response = await _async_retry_api_call(
286+
response = await async_retry_api_call(
287287
self.connection._client.list_databases,
288-
self._retry_config,
289-
_logger,
290-
stop_on,
288+
config=self._retry_config,
289+
logger=_logger,
290+
stop_on=stop_on,
291291
**request,
292292
)
293293
except Exception as e:
@@ -342,11 +342,11 @@ async def _get_table_metadata( # type: ignore[override]
342342
schema_name=schema_name,
343343
)
344344
try:
345-
response = await _async_retry_api_call(
345+
response = await async_retry_api_call(
346346
self._connection.client.get_table_metadata,
347-
self._retry_config,
348-
_logger,
349-
stop_on,
347+
config=self._retry_config,
348+
logger=_logger,
349+
stop_on=stop_on,
350350
**request,
351351
)
352352
except Exception as e:
@@ -397,11 +397,11 @@ async def _list_table_metadata( # type: ignore[override]
397397
max_results=max_results,
398398
)
399399
try:
400-
response = await _async_retry_api_call(
400+
response = await async_retry_api_call(
401401
self.connection._client.list_table_metadata,
402-
self._retry_config,
403-
_logger,
404-
stop_on,
402+
config=self._retry_config,
403+
logger=_logger,
404+
stop_on=stop_on,
405405
**request,
406406
)
407407
except Exception as e:

‎pyathena/aio/util.py‎

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
from collections.abc import Callable
1313
from typing import Any
1414

15-
from pyathena.util import RetryConfig, _retry_api_call, retry_api_call
15+
from pyathena.util import RetryConfig, retry_api_call
1616

1717

1818
async def async_retry_api_call(
1919
func: Callable[..., Any],
2020
config: RetryConfig,
2121
logger: logging.Logger | None = None,
2222
*args: Any,
23+
stop_on: Callable[[BaseException], bool] | None = None,
2324
**kwargs: Any,
2425
) -> Any:
2526
"""Execute a function with retry logic in a thread to avoid blocking the event loop.
@@ -32,21 +33,12 @@ async def async_retry_api_call(
3233
config: RetryConfig instance specifying retry behavior.
3334
logger: Optional logger for retry attempt logging.
3435
*args: Positional arguments to pass to ``retry_api_call``.
36+
stop_on: Passed to ``retry_api_call``.
3537
**kwargs: Keyword arguments to pass to the function.
3638
3739
Returns:
3840
The result of the successful function call.
3941
"""
40-
return await asyncio.to_thread(retry_api_call, func, config, logger, *args, **kwargs)
41-
42-
43-
async def _async_retry_api_call(
44-
func: Callable[..., Any],
45-
config: RetryConfig,
46-
logger: logging.Logger | None,
47-
stop_on: Callable[[BaseException], bool] | None,
48-
*args: Any,
49-
**kwargs: Any,
50-
) -> Any:
51-
"""``async_retry_api_call`` that raises an exception ``stop_on`` accepts at once."""
52-
return await asyncio.to_thread(_retry_api_call, func, config, logger, stop_on, *args, **kwargs)
42+
return await asyncio.to_thread(
43+
retry_api_call, func, config, logger, *args, stop_on=stop_on, **kwargs
44+
)

‎pyathena/common.py‎

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
RetryConfig,
3030
_get_error_code,
3131
_is_throttling_error,
32-
_retry_api_call,
3332
retry_api_call,
3433
)
3534

@@ -424,11 +423,11 @@ def _list_databases(
424423
max_results=max_results,
425424
)
426425
try:
427-
response = _retry_api_call(
426+
response = retry_api_call(
428427
self.connection._client.list_databases,
429-
self._retry_config,
430-
_logger,
431-
stop_on,
428+
config=self._retry_config,
429+
logger=_logger,
430+
stop_on=stop_on,
432431
**request,
433432
)
434433
except Exception as e:
@@ -498,11 +497,11 @@ def _get_table_metadata(
498497
schema_name=schema_name,
499498
)
500499
try:
501-
response = _retry_api_call(
500+
response = retry_api_call(
502501
self._connection.client.get_table_metadata,
503-
self._retry_config,
504-
_logger,
505-
stop_on,
502+
config=self._retry_config,
503+
logger=_logger,
504+
stop_on=stop_on,
506505
**request,
507506
)
508507
except Exception as e:
@@ -553,11 +552,11 @@ def _list_table_metadata(
553552
max_results=max_results,
554553
)
555554
try:
556-
response = _retry_api_call(
555+
response = retry_api_call(
557556
self.connection._client.list_table_metadata,
558-
self._retry_config,
559-
_logger,
560-
stop_on,
557+
config=self._retry_config,
558+
logger=_logger,
559+
stop_on=stop_on,
561560
**request,
562561
)
563562
except Exception as e:
@@ -610,7 +609,7 @@ def athena_request(
610609
)
611610

612611
def _get_query_execution(self, query_id: str) -> AthenaQueryExecution:
613-
request = {"QueryExecutionId": query_id}
612+
request: dict[str, Any] = {"QueryExecutionId": query_id}
614613
try:
615614
response = retry_api_call(
616615
self._connection.client.get_query_execution,
@@ -625,7 +624,7 @@ def _get_query_execution(self, query_id: str) -> AthenaQueryExecution:
625624
return AthenaQueryExecution(response)
626625

627626
def _get_calculation_execution_status(self, query_id: str) -> AthenaCalculationExecutionStatus:
628-
request = {"CalculationExecutionId": query_id}
627+
request: dict[str, Any] = {"CalculationExecutionId": query_id}
629628
try:
630629
response = retry_api_call(
631630
self._connection.client.get_calculation_execution_status,
@@ -640,7 +639,7 @@ def _get_calculation_execution_status(self, query_id: str) -> AthenaCalculationE
640639
return AthenaCalculationExecutionStatus(response)
641640

642641
def _get_calculation_execution(self, query_id: str) -> AthenaCalculationExecution:
643-
request = {"CalculationExecutionId": query_id}
642+
request: dict[str, Any] = {"CalculationExecutionId": query_id}
644643
try:
645644
response = retry_api_call(
646645
self._connection.client.get_calculation_execution,
@@ -942,7 +941,7 @@ def close(self) -> None:
942941
raise NotImplementedError # pragma: no cover
943942

944943
def _cancel(self, query_id: str) -> None:
945-
request = {"QueryExecutionId": query_id}
944+
request: dict[str, Any] = {"QueryExecutionId": query_id}
946945
try:
947946
retry_api_call(
948947
self._connection.client.stop_query_execution,

‎pyathena/filesystem/s3.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ def __init__(
168168
self.version_aware = version_aware
169169

170170
requester_pays = kwargs.pop("requester_pays", False)
171-
self.request_kwargs = {"RequestPayer": "requester"} if requester_pays else {}
171+
self.request_kwargs: dict[str, Any] = (
172+
{"RequestPayer": "requester"} if requester_pays else {}
173+
)
172174

173175
def _get_client_compatible_with_s3fs(self, **kwargs) -> BaseClient:
174176
"""Build a boto3 S3 client from s3fs-compatible constructor arguments.

‎pyathena/spark/common.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def _wait_for_idle_session(self, session_id: str):
147147
time.sleep(self._poll_interval)
148148

149149
def _exists_session(self, session_id: str) -> bool:
150-
request = {"SessionId": session_id}
150+
request: dict[str, Any] = {"SessionId": session_id}
151151
try:
152152
retry_api_call(
153153
self._connection.client.get_session,
@@ -193,7 +193,7 @@ def _start_session(self) -> str:
193193
return session_id
194194

195195
def _terminate_session(self) -> None:
196-
request = {"SessionId": self._session_id}
196+
request: dict[str, Any] = {"SessionId": self._session_id}
197197
try:
198198
retry_api_call(
199199
self._connection.client.terminate_session,
@@ -231,7 +231,7 @@ def _poll(self, query_id: str) -> AthenaQueryExecution | AthenaCalculationExecut
231231
return query_execution
232232

233233
def _cancel(self, query_id: str) -> None:
234-
request = {"CalculationExecutionId": query_id}
234+
request: dict[str, Any] = {"CalculationExecutionId": query_id}
235235
try:
236236
retry_api_call(
237237
self._connection.client.stop_calculation_execution,

‎pyathena/util.py‎

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def retry_api_call(
215215
config: RetryConfig,
216216
logger: logging.Logger | None = None,
217217
*args,
218+
stop_on: Callable[[BaseException], bool] | None = None,
218219
**kwargs,
219220
) -> Any:
220221
"""Execute a function with automatic retry logic for AWS API calls.
@@ -228,6 +229,8 @@ def retry_api_call(
228229
config: RetryConfig instance specifying retry behavior.
229230
logger: Optional logger for retry attempt logging.
230231
*args: Positional arguments to pass to the function.
232+
stop_on: Optional predicate; an exception it accepts is raised at once
233+
instead of being retried.
231234
**kwargs: Keyword arguments to pass to the function.
232235
233236
Returns:
@@ -251,18 +254,6 @@ def retry_api_call(
251254
This includes recognized Glue error codes wrapped in MetadataException.
252255
Other errors are propagated without retrying.
253256
"""
254-
return _retry_api_call(func, config, logger, None, *args, **kwargs)
255-
256-
257-
def _retry_api_call(
258-
func: Callable[..., Any],
259-
config: RetryConfig,
260-
logger: logging.Logger | None,
261-
stop_on: Callable[[BaseException], bool] | None,
262-
*args,
263-
**kwargs,
264-
) -> Any:
265-
"""``retry_api_call`` that raises an exception ``stop_on`` accepts at once."""
266257
retry = tenacity.Retrying(
267258
retry=retry_if_exception(
268259
lambda ex: is_retryable_error(ex, config) and not (stop_on and stop_on(ex))

‎tests/pyathena/test_util.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from pyathena.util import (
99
RetryConfig,
1010
_is_throttling_error,
11-
_retry_api_call,
1211
_without_retries,
1312
is_retryable_error,
1413
parse_output_location,
@@ -338,7 +337,7 @@ def call():
338337
)
339338

340339
with pytest.raises(ClientError) as caught:
341-
_retry_api_call(call, config, None, _is_throttling_error)
340+
retry_api_call(call, config, stop_on=_is_throttling_error)
342341

343342
assert caught.value is error
344343
assert calls == expected_calls

0 commit comments

Comments
 (0)