|
3 | 3 |
|
4 | 4 | import asyncio |
5 | 5 | import logging |
6 | | -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast |
| 6 | +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast |
7 | 7 |
|
8 | 8 | from pyathena.aio.common import WithAsyncFetch |
9 | 9 | from pyathena.arrow.converter import ( |
|
25 | 25 | class AioArrowCursor(WithAsyncFetch): |
26 | 26 | """Native asyncio cursor that returns results as Apache Arrow Tables. |
27 | 27 |
|
28 | | - Uses ``asyncio.to_thread()`` to create the result set off the event loop. |
29 | | - Since ``AthenaArrowResultSet`` loads all data in ``__init__`` (via S3), |
30 | | - fetch methods are synchronous (in-memory only) and do not need to be async. |
| 28 | + Uses ``asyncio.to_thread()`` for both result set creation and fetch |
| 29 | + operations, keeping the event loop free. |
31 | 30 |
|
32 | 31 | Example: |
33 | 32 | >>> async with await pyathena.aconnect(...) as conn: |
@@ -153,6 +152,72 @@ async def execute( # type: ignore[override] |
153 | 152 | raise OperationalError(query_execution.state_change_reason) |
154 | 153 | return self |
155 | 154 |
|
| 155 | + async def fetchone( # type: ignore[override] |
| 156 | + self, |
| 157 | + ) -> Optional[Union[Tuple[Optional[Any], ...], Dict[Any, Optional[Any]]]]: |
| 158 | + """Fetch the next row of the result set. |
| 159 | +
|
| 160 | + Wraps the synchronous fetch in ``asyncio.to_thread`` to avoid |
| 161 | + blocking the event loop. |
| 162 | +
|
| 163 | + Returns: |
| 164 | + A tuple representing the next row, or None if no more rows. |
| 165 | +
|
| 166 | + Raises: |
| 167 | + ProgrammingError: If no result set is available. |
| 168 | + """ |
| 169 | + if not self.has_result_set: |
| 170 | + raise ProgrammingError("No result set.") |
| 171 | + result_set = cast(AthenaArrowResultSet, self.result_set) |
| 172 | + return await asyncio.to_thread(result_set.fetchone) |
| 173 | + |
| 174 | + async def fetchmany( # type: ignore[override] |
| 175 | + self, size: Optional[int] = None |
| 176 | + ) -> List[Union[Tuple[Optional[Any], ...], Dict[Any, Optional[Any]]]]: |
| 177 | + """Fetch multiple rows from the result set. |
| 178 | +
|
| 179 | + Wraps the synchronous fetch in ``asyncio.to_thread`` to avoid |
| 180 | + blocking the event loop. |
| 181 | +
|
| 182 | + Args: |
| 183 | + size: Maximum number of rows to fetch. Defaults to arraysize. |
| 184 | +
|
| 185 | + Returns: |
| 186 | + List of tuples representing the fetched rows. |
| 187 | +
|
| 188 | + Raises: |
| 189 | + ProgrammingError: If no result set is available. |
| 190 | + """ |
| 191 | + if not self.has_result_set: |
| 192 | + raise ProgrammingError("No result set.") |
| 193 | + result_set = cast(AthenaArrowResultSet, self.result_set) |
| 194 | + return await asyncio.to_thread(result_set.fetchmany, size) |
| 195 | + |
| 196 | + async def fetchall( # type: ignore[override] |
| 197 | + self, |
| 198 | + ) -> List[Union[Tuple[Optional[Any], ...], Dict[Any, Optional[Any]]]]: |
| 199 | + """Fetch all remaining rows from the result set. |
| 200 | +
|
| 201 | + Wraps the synchronous fetch in ``asyncio.to_thread`` to avoid |
| 202 | + blocking the event loop. |
| 203 | +
|
| 204 | + Returns: |
| 205 | + List of tuples representing all remaining rows. |
| 206 | +
|
| 207 | + Raises: |
| 208 | + ProgrammingError: If no result set is available. |
| 209 | + """ |
| 210 | + if not self.has_result_set: |
| 211 | + raise ProgrammingError("No result set.") |
| 212 | + result_set = cast(AthenaArrowResultSet, self.result_set) |
| 213 | + return await asyncio.to_thread(result_set.fetchall) |
| 214 | + |
| 215 | + async def __anext__(self): |
| 216 | + row = await self.fetchone() |
| 217 | + if row is None: |
| 218 | + raise StopAsyncIteration |
| 219 | + return row |
| 220 | + |
156 | 221 | def as_arrow(self) -> "Table": |
157 | 222 | """Return query results as an Apache Arrow Table. |
158 | 223 |
|
|
0 commit comments