Skip to content

Commit 0c7edc9

Browse files
Refactor result_set.py: remove is_first_page flag, deduplicate row conversion logic
- Remove is_first_page parameter from _parse_result_rows; move _is_first_row_column_labels detection to callers (_pre_fetch, _fetch_all_rows) - Change _process_rows to accept parsed rows and offset instead of raw response - Add optional converter parameter to _get_rows and reuse it in _fetch_all_rows to eliminate duplicated row conversion loop - Remove unnecessary cast() calls from _process_rows and _is_first_row_column_labels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cd45053 commit 0c7edc9

File tree

1 file changed

+39
-38
lines changed

1 file changed

+39
-38
lines changed

pyathena/result_set.py

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,16 @@ def _fetch(self) -> None:
364364
if not self._next_token:
365365
raise ProgrammingError("NextToken is none or empty.")
366366
response = self.__fetch(self._next_token)
367-
self._process_rows(response)
367+
rows, self._next_token = self._parse_result_rows(response)
368+
self._process_rows(rows)
368369

369370
def _pre_fetch(self) -> None:
370371
response = self.__fetch()
371372
self._process_metadata(response)
372373
self._process_update_count(response)
373-
self._process_rows(response)
374+
rows, self._next_token = self._parse_result_rows(response)
375+
offset = 1 if rows and self._is_first_row_column_labels(rows) else 0
376+
self._process_rows(rows, offset)
374377

375378
def fetchone(
376379
self,
@@ -439,35 +442,37 @@ def _process_update_count(self, response: Dict[str, Any]) -> None:
439442
self._rowcount = update_count
440443

441444
def _get_rows(
442-
self, offset: int, metadata: Tuple[Any, ...], rows: List[Dict[str, Any]]
445+
self,
446+
offset: int,
447+
metadata: Tuple[Any, ...],
448+
rows: List[Dict[str, Any]],
449+
converter: Optional[Converter] = None,
443450
) -> List[Union[Tuple[Optional[Any], ...], Dict[Any, Optional[Any]]]]:
451+
conv = converter or self._converter
444452
return [
445453
tuple(
446454
[
447-
self._converter.convert(meta.get("Type"), row.get("VarCharValue"))
455+
conv.convert(meta.get("Type"), row.get("VarCharValue"))
448456
for meta, row in zip(metadata, rows[i].get("Data", []), strict=False)
449457
]
450458
)
451459
for i in range(offset, len(rows))
452460
]
453461

454462
def _parse_result_rows(
455-
self, response: Dict[str, Any], is_first_page: bool
456-
) -> Tuple[List[Dict[str, Any]], int, Optional[str]]:
457-
"""Parse a GetQueryResults response into raw rows, offset, and next token.
463+
self, response: Dict[str, Any]
464+
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
465+
"""Parse a GetQueryResults response into raw rows and next token.
458466
459-
Handles response validation, column-header detection, and pagination
460-
token extraction. This is the shared parsing logic used by both
461-
``_process_rows`` (normal path) and ``_fetch_all_rows`` (API fallback).
467+
Handles response validation and pagination token extraction.
468+
This is the shared parsing logic used by both ``_pre_fetch``
469+
(normal path) and ``_fetch_all_rows`` (API fallback).
462470
463471
Args:
464472
response: Raw response dict from ``GetQueryResults`` API.
465-
is_first_page: Whether this is the first page of results. Used to
466-
detect the column-label header row.
467473
468474
Returns:
469-
Tuple of (rows, offset, next_token) where *offset* is 1 when
470-
the first row is a column-label header, 0 otherwise.
475+
Tuple of (rows, next_token).
471476
"""
472477
result_set = response.get("ResultSet")
473478
if not result_set:
@@ -476,22 +481,16 @@ def _parse_result_rows(
476481
if rows is None:
477482
raise DataError("KeyError `Rows`")
478483
next_token = response.get("NextToken")
479-
if not rows:
480-
return rows, 0, next_token
481-
offset = 1 if is_first_page and self._is_first_row_column_labels(rows) else 0
482-
return rows, offset, next_token
483-
484-
def _process_rows(self, response: Dict[str, Any]) -> None:
485-
rows, offset, self._next_token = self._parse_result_rows(response, not self._next_token)
486-
if rows:
487-
metadata = cast(Tuple[Any, ...], self._metadata)
488-
processed_rows = self._get_rows(offset, metadata, rows)
484+
return rows, next_token
485+
486+
def _process_rows(self, rows: List[Dict[str, Any]], offset: int = 0) -> None:
487+
if rows and self._metadata:
488+
processed_rows = self._get_rows(offset, self._metadata, rows)
489489
self._rows.extend(processed_rows)
490490

491491
def _is_first_row_column_labels(self, rows: List[Dict[str, Any]]) -> bool:
492492
first_row_data = rows[0].get("Data", [])
493-
metadata = cast(Tuple[Any, Any], self._metadata)
494-
for meta, data in zip(metadata, first_row_data, strict=False):
493+
for meta, data in zip(self._metadata or (), first_row_data, strict=False):
495494
if meta.get("Name") != data.get("VarCharValue"):
496495
return False
497496
return True
@@ -524,23 +523,20 @@ def _fetch_all_rows(
524523

525524
converter = DefaultTypeConverter()
526525
all_rows: List[Tuple[Optional[Any], ...]] = []
527-
metadata = cast(Tuple[Any, ...], self._metadata)
528526
next_token: Optional[str] = None
529-
is_first_page = True
530527

531528
while True:
532529
response = self.__get_query_results(self.DEFAULT_FETCH_SIZE, next_token)
533-
rows, offset, next_token = self._parse_result_rows(response, is_first_page)
530+
rows, next_token = self._parse_result_rows(response)
534531

535-
for i in range(offset, len(rows)):
536-
all_rows.append(
537-
tuple(
538-
converter.convert(meta.get("Type"), row.get("VarCharValue"))
539-
for meta, row in zip(metadata, rows[i].get("Data", []), strict=False)
540-
)
532+
offset = 1 if rows and self._is_first_row_column_labels(rows) else 0
533+
all_rows.extend(
534+
cast(
535+
List[Tuple[Optional[Any], ...]],
536+
self._get_rows(offset, self._metadata, rows, converter),
541537
)
538+
)
542539

543-
is_first_page = False
544540
if not next_token:
545541
break
546542

@@ -628,14 +624,19 @@ class AthenaDictResultSet(AthenaResultSet):
628624
dict_type: Type[Any] = dict
629625

630626
def _get_rows(
631-
self, offset: int, metadata: Tuple[Any, ...], rows: List[Dict[str, Any]]
627+
self,
628+
offset: int,
629+
metadata: Tuple[Any, ...],
630+
rows: List[Dict[str, Any]],
631+
converter: Optional[Converter] = None,
632632
) -> List[Union[Tuple[Optional[Any], ...], Dict[Any, Optional[Any]]]]:
633+
conv = converter or self._converter
633634
return [
634635
self.dict_type(
635636
[
636637
(
637638
meta.get("Name"),
638-
self._converter.convert(meta.get("Type"), row.get("VarCharValue")),
639+
conv.convert(meta.get("Type"), row.get("VarCharValue")),
639640
)
640641
for meta, row in zip(metadata, rows[i].get("Data", []), strict=False)
641642
]

0 commit comments

Comments
 (0)