diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index a5cb716317689..c35f01470763b 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -461,6 +461,7 @@ Other API changes October 2022. (:issue:`46312`) - :func:`read_json` now raises ``FileNotFoundError`` (previously ``ValueError``) when input is a string ending in ``.json``, ``.json.gz``, ``.json.bz2``, etc. but no such file exists. (:issue:`29102`) - Operations with :class:`Timestamp` or :class:`Timedelta` that would previously raise ``OverflowError`` instead raise ``OutOfBoundsDatetime`` or ``OutOfBoundsTimedelta`` where appropriate (:issue:`47268`) +- When :func:`read_sas` previously returned ``None``, it now returns an empty :class:`DataFrame` (:issue:`47410`) - .. --------------------------------------------------------------------------- diff --git a/pandas/io/sas/sas7bdat.py b/pandas/io/sas/sas7bdat.py index a992c1af5ddaf..5298178b4efcd 100644 --- a/pandas/io/sas/sas7bdat.py +++ b/pandas/io/sas/sas7bdat.py @@ -163,12 +163,12 @@ def __init__( self, path_or_buf: FilePath | ReadBuffer[bytes], index=None, - convert_dates=True, - blank_missing=True, - chunksize=None, - encoding=None, - convert_text=True, - convert_header_text=True, + convert_dates: bool = True, + blank_missing: bool = True, + chunksize: int | None = None, + encoding: str | None = None, + convert_text: bool = True, + convert_header_text: bool = True, compression: CompressionOptions = "infer", ) -> None: @@ -361,9 +361,9 @@ def _get_properties(self) -> None: self.encoding or self.default_encoding ) - def __next__(self): + def __next__(self) -> DataFrame: da = self.read(nrows=self.chunksize or 1) - if da is None: + if da.empty: self.close() raise StopIteration return da @@ -732,7 +732,7 @@ def _process_format_subheader(self, offset: int, length: int) -> None: self.column_formats.append(column_format) self.columns.append(col) - def read(self, nrows: int | None = None) -> DataFrame | None: + def read(self, nrows: int | None = None) -> DataFrame: if (nrows is None) and (self.chunksize is not None): nrows = self.chunksize @@ -744,7 +744,7 @@ def read(self, nrows: int | None = None) -> DataFrame | None: raise EmptyDataError("No columns to parse from file") if nrows > 0 and self._current_row_in_file_index >= self.row_count: - return None + return DataFrame() m = self.row_count - self._current_row_in_file_index if nrows > m: diff --git a/pandas/io/sas/sas_xport.py b/pandas/io/sas/sas_xport.py index db09983cacfbc..500e88eb0ef76 100644 --- a/pandas/io/sas/sas_xport.py +++ b/pandas/io/sas/sas_xport.py @@ -280,7 +280,7 @@ def __init__( self.close() raise - def close(self): + def close(self) -> None: self.handles.close() def _get_row(self): @@ -463,7 +463,7 @@ def _missing_double(self, vec): return miss @Appender(_read_method_doc) - def read(self, nrows=None): + def read(self, nrows: int | None = None) -> pd.DataFrame: if nrows is None: nrows = self.nobs diff --git a/pandas/io/sas/sasreader.py b/pandas/io/sas/sasreader.py index ff50df886e627..052e674d1a488 100644 --- a/pandas/io/sas/sasreader.py +++ b/pandas/io/sas/sasreader.py @@ -38,17 +38,17 @@ class ReaderBase(metaclass=ABCMeta): """ @abstractmethod - def read(self, nrows=None): + def read(self, nrows: int | None = None) -> DataFrame: pass @abstractmethod - def close(self): + def close(self) -> None: pass - def __enter__(self): + def __enter__(self) -> ReaderBase: return self - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type, exc_value, traceback) -> None: self.close() diff --git a/pyright_reportGeneralTypeIssues.json b/pyright_reportGeneralTypeIssues.json index f6f9bed1af065..83d76e752a057 100644 --- a/pyright_reportGeneralTypeIssues.json +++ b/pyright_reportGeneralTypeIssues.json @@ -105,8 +105,7 @@ "pandas/io/parsers/base_parser.py", "pandas/io/parsers/c_parser_wrapper.py", "pandas/io/pytables.py", - "pandas/io/sas/sas7bdat.py", - "pandas/io/sas/sasreader.py", + "pandas/io/sas/sas_xport.py", "pandas/io/sql.py", "pandas/io/stata.py", "pandas/io/xml.py",