22from __future__ import annotations
33
44import csv
5- from typing import Any , Iterator , List , Optional , Tuple
5+ from collections .abc import Iterator
6+ from typing import Any , List , Optional , Tuple
67
78
8- class DefaultCSVReader :
9+ class DefaultCSVReader ( Iterator [ List [ str ]]) :
910 """CSV reader using Python's standard csv module.
1011
1112 This reader wraps Python's standard csv.reader and treats empty fields
@@ -33,9 +34,10 @@ def __init__(self, file_obj: Any, delimiter: str = ",") -> None:
3334 file_obj: File-like object to read from.
3435 delimiter: Field delimiter character.
3536 """
37+ self ._file : Optional [Any ] = file_obj
3638 self ._reader = csv .reader (file_obj , delimiter = delimiter )
3739
38- def __iter__ (self ) -> Iterator [ List [ str ]] :
40+ def __iter__ (self ) -> "DefaultCSVReader" :
3941 """Iterate over rows in the CSV file."""
4042 return self
4143
@@ -46,17 +48,33 @@ def __next__(self) -> List[str]:
4648 List of field values as strings.
4749
4850 Raises:
49- StopIteration: When end of file is reached.
51+ StopIteration: When end of file is reached or reader is closed .
5052 """
53+ if self ._file is None :
54+ raise StopIteration
5155 row = next (self ._reader )
5256 # Python's csv.reader returns [] for empty lines; normalize to ['']
5357 # to represent a single empty field (consistent with single-value handling)
5458 if not row :
5559 return ["" ]
5660 return row
5761
62+ def close (self ) -> None :
63+ """Close the underlying file object."""
64+ if self ._file is not None :
65+ self ._file .close ()
66+ self ._file = None
67+
68+ def __enter__ (self ) -> "DefaultCSVReader" :
69+ """Enter context manager."""
70+ return self
71+
72+ def __exit__ (self , exc_type : Any , exc_val : Any , exc_tb : Any ) -> None :
73+ """Exit context manager and close resources."""
74+ self .close ()
75+
5876
59- class AthenaCSVReader :
77+ class AthenaCSVReader ( Iterator [ List [ Optional [ str ]]]) :
6078 """CSV reader that distinguishes between NULL and empty string.
6179
6280 This is the default reader for S3FSCursor.
@@ -87,10 +105,10 @@ def __init__(self, file_obj: Any, delimiter: str = ",") -> None:
87105 file_obj: File-like object to read from.
88106 delimiter: Field delimiter character.
89107 """
90- self ._file = file_obj
108+ self ._file : Optional [ Any ] = file_obj
91109 self ._delimiter = delimiter
92110
93- def __iter__ (self ) -> Iterator [ List [ Optional [ str ]]] :
111+ def __iter__ (self ) -> "AthenaCSVReader" :
94112 """Iterate over rows in the CSV file."""
95113 return self
96114
@@ -101,8 +119,10 @@ def __next__(self) -> List[Optional[str]]:
101119 List of field values, with None for NULL and '' for empty string.
102120
103121 Raises:
104- StopIteration: When end of file is reached.
122+ StopIteration: When end of file is reached or reader is closed .
105123 """
124+ if self ._file is None :
125+ raise StopIteration
106126 line = self ._file .readline ()
107127 if not line :
108128 raise StopIteration
@@ -234,3 +254,17 @@ def _parse_unquoted_field(self, line: str, pos: int) -> Tuple[str, int]:
234254 pos += 1
235255
236256 return value , pos
257+
258+ def close (self ) -> None :
259+ """Close the underlying file object."""
260+ if self ._file is not None :
261+ self ._file .close ()
262+ self ._file = None
263+
264+ def __enter__ (self ) -> "AthenaCSVReader" :
265+ """Enter context manager."""
266+ return self
267+
268+ def __exit__ (self , exc_type : Any , exc_val : Any , exc_tb : Any ) -> None :
269+ """Exit context manager and close resources."""
270+ self .close ()
0 commit comments