Skip to content

Commit c32fc58

Browse files
Merge pull request #735 from pyathena-dev/fix/734-execute-legacy-kwargs
Restore legacy keyword arguments on the private _execute() methods
2 parents 4caf557 + 322ce8a commit c32fc58

4 files changed

Lines changed: 156 additions & 2 deletions

File tree

pyathena/aio/common.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,28 @@ async def _execute( # type: ignore[override]
2828
self,
2929
operation: str,
3030
parameters: dict[str, Any] | list[str] | None = None,
31+
work_group: str | None = None,
32+
s3_staging_dir: str | None = None,
33+
cache_size: int | None = None,
34+
cache_expiration_time: int | None = None,
35+
result_reuse_enable: bool | None = None,
36+
result_reuse_minutes: int | None = None,
37+
paramstyle: str | None = None,
3138
options: ExecuteOptions | None = None,
3239
) -> str:
33-
options = ExecuteOptions.resolve(options)
40+
# The individual keyword arguments are retained for backward compatibility
41+
# with external callers that predate ExecuteOptions, mirroring
42+
# BaseCursor._execute().
43+
options = ExecuteOptions.resolve(
44+
options,
45+
work_group=work_group,
46+
s3_staging_dir=s3_staging_dir,
47+
cache_size=cache_size,
48+
cache_expiration_time=cache_expiration_time,
49+
result_reuse_enable=result_reuse_enable,
50+
result_reuse_minutes=result_reuse_minutes,
51+
paramstyle=paramstyle,
52+
)
3453
query, execution_parameters = self._prepare_query(operation, parameters, options.paramstyle)
3554

3655
request = self._build_start_query_execution_request(

pyathena/common.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,9 +714,28 @@ def _execute(
714714
self,
715715
operation: str,
716716
parameters: dict[str, Any] | list[str] | None = None,
717+
work_group: str | None = None,
718+
s3_staging_dir: str | None = None,
719+
cache_size: int | None = None,
720+
cache_expiration_time: int | None = None,
721+
result_reuse_enable: bool | None = None,
722+
result_reuse_minutes: int | None = None,
723+
paramstyle: str | None = None,
717724
options: ExecuteOptions | None = None,
718725
) -> str:
719-
options = ExecuteOptions.resolve(options)
726+
# The individual keyword arguments are retained for backward compatibility
727+
# with external callers that predate ExecuteOptions (e.g. dbt-athena <= 1.10.x
728+
# calls _execute() with work_group/s3_staging_dir/cache_* keywords).
729+
options = ExecuteOptions.resolve(
730+
options,
731+
work_group=work_group,
732+
s3_staging_dir=s3_staging_dir,
733+
cache_size=cache_size,
734+
cache_expiration_time=cache_expiration_time,
735+
result_reuse_enable=result_reuse_enable,
736+
result_reuse_minutes=result_reuse_minutes,
737+
paramstyle=paramstyle,
738+
)
720739
query, execution_parameters = self._prepare_query(operation, parameters, options.paramstyle)
721740

722741
request = self._build_start_query_execution_request(

tests/pyathena/aio/test_cursor.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import re
22
from datetime import datetime
3+
from unittest.mock import AsyncMock, MagicMock, patch
34

45
import pytest
56

67
from pyathena import ExecuteOptions
8+
from pyathena.aio.cursor import AioCursor
79
from pyathena.error import DatabaseError, ProgrammingError
810
from pyathena.model import AthenaQueryExecution
911
from pyathena.result_set import AthenaResultSet
12+
from pyathena.util import RetryConfig
1013
from tests import ENV
1114
from tests.pyathena.aio.conftest import _aio_connect
1215

@@ -79,6 +82,54 @@ async def test_execute_with_options(self, aio_cursor):
7982
assert callback_results == [aio_cursor.query_id]
8083
assert await aio_cursor.fetchone() == (1,)
8184

85+
async def test_execute_internal_legacy_kwargs_passthrough(self):
86+
"""The pre-3.35 _execute() keywords are forwarded to the request (no AWS).
87+
88+
Mirrors the synchronous cursor test (regression test for #734).
89+
"""
90+
cursor = AioCursor.__new__(AioCursor) # bypass __init__ to avoid AWS calls
91+
cursor._connection = MagicMock()
92+
cursor._connection.client.start_query_execution.return_value = {
93+
"QueryExecutionId": "test_query_id"
94+
}
95+
cursor._retry_config = RetryConfig()
96+
97+
with (
98+
patch.object(
99+
AioCursor, "_build_start_query_execution_request", return_value={}
100+
) as request_mock,
101+
patch.object(
102+
AioCursor, "_find_previous_query_id", new_callable=AsyncMock, return_value=None
103+
) as cache_mock,
104+
):
105+
query_id = await cursor._execute(
106+
"SELECT 1",
107+
parameters=None,
108+
work_group="test_work_group",
109+
s3_staging_dir="s3://test-bucket/path/",
110+
cache_size=10,
111+
cache_expiration_time=100,
112+
result_reuse_enable=True,
113+
result_reuse_minutes=5,
114+
paramstyle="qmark",
115+
)
116+
117+
assert query_id == "test_query_id"
118+
request_mock.assert_called_once_with(
119+
query="SELECT 1",
120+
work_group="test_work_group",
121+
s3_staging_dir="s3://test-bucket/path/",
122+
result_reuse_enable=True,
123+
result_reuse_minutes=5,
124+
execution_parameters=None,
125+
)
126+
cache_mock.assert_awaited_once_with(
127+
"SELECT 1",
128+
"test_work_group",
129+
cache_size=10,
130+
cache_expiration_time=100,
131+
)
132+
82133
async def test_no_result_set_raises(self, aio_cursor):
83134
with pytest.raises(ProgrammingError):
84135
await aio_cursor.fetchone()

tests/pyathena/test_cursor.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pyathena.cursor import Cursor
2121
from pyathena.error import DatabaseError, NotSupportedError, ProgrammingError
2222
from pyathena.model import AthenaQueryExecution
23+
from pyathena.util import RetryConfig
2324
from tests import ENV
2425
from tests.pyathena.conftest import connect
2526

@@ -879,6 +880,70 @@ def test_execute_kwargs_override_options(self, cursor):
879880
assert from_kwargs == [cursor.query_id]
880881
assert cursor.fetchone() == (1,)
881882

883+
def test_execute_internal_legacy_kwargs(self, cursor):
884+
"""The private _execute() accepts the pre-3.35 individual keyword arguments.
885+
886+
Regression test for #734: external callers such as dbt-athena <= 1.10.x
887+
invoke _execute() directly with individual keywords instead of options.
888+
"""
889+
query_id = cursor._execute(
890+
"SELECT 1",
891+
parameters=None,
892+
work_group=ENV.default_work_group,
893+
s3_staging_dir=None,
894+
cache_size=0,
895+
cache_expiration_time=0,
896+
)
897+
query_execution = cursor._poll(query_id)
898+
assert query_execution.state == AthenaQueryExecution.STATE_SUCCEEDED
899+
900+
def test_execute_internal_legacy_kwargs_passthrough(self):
901+
"""The pre-3.35 _execute() keywords are forwarded to the request (no AWS).
902+
903+
Regression test for #734: on 3.35.0 this call raised
904+
``TypeError: _execute() got an unexpected keyword argument 'work_group'``.
905+
"""
906+
cursor = Cursor.__new__(Cursor) # bypass __init__ to avoid AWS calls
907+
cursor._connection = MagicMock()
908+
cursor._connection.client.start_query_execution.return_value = {
909+
"QueryExecutionId": "test_query_id"
910+
}
911+
cursor._retry_config = RetryConfig()
912+
913+
with (
914+
patch.object(
915+
Cursor, "_build_start_query_execution_request", return_value={}
916+
) as request_mock,
917+
patch.object(Cursor, "_find_previous_query_id", return_value=None) as cache_mock,
918+
):
919+
query_id = cursor._execute(
920+
"SELECT 1",
921+
parameters=None,
922+
work_group="test_work_group",
923+
s3_staging_dir="s3://test-bucket/path/",
924+
cache_size=10,
925+
cache_expiration_time=100,
926+
result_reuse_enable=True,
927+
result_reuse_minutes=5,
928+
paramstyle="qmark",
929+
)
930+
931+
assert query_id == "test_query_id"
932+
request_mock.assert_called_once_with(
933+
query="SELECT 1",
934+
work_group="test_work_group",
935+
s3_staging_dir="s3://test-bucket/path/",
936+
result_reuse_enable=True,
937+
result_reuse_minutes=5,
938+
execution_parameters=None,
939+
)
940+
cache_mock.assert_called_once_with(
941+
"SELECT 1",
942+
"test_work_group",
943+
cache_size=10,
944+
cache_expiration_time=100,
945+
)
946+
882947
def test_connection_level_callback(self):
883948
"""Test connection-level default callback."""
884949
callback_results = []

0 commit comments

Comments
 (0)