1"""
2Read SAS sas7bdat or xport files.
3"""
4
5from __future__ import annotations
6
7from abc import (
8 ABC,
9 abstractmethod,
10)
11from collections.abc import Iterator
12from typing import (
13 TYPE_CHECKING,
14 Self,
15 overload,
16)
17
18from pandas.util._decorators import set_module
19
20from pandas.io.common import stringify_path
21
22if TYPE_CHECKING:
23 from collections.abc import Hashable
24 from types import TracebackType
25
26 from pandas._typing import (
27 CompressionOptions,
28 FilePath,
29 ReadBuffer,
30 )
31
32 from pandas import DataFrame
33
34
35@set_module("pandas.api.typing")
36class SASReader(Iterator["DataFrame"], ABC):
37 """
38 Abstract class for XportReader and SAS7BDATReader.
39 """
40
41 @abstractmethod
42 def read(self, nrows: int | None = None) -> DataFrame: ...
43
44 @abstractmethod
45 def close(self) -> None: ...
46
47 def __enter__(self) -> Self:
48 return self
49
50 def __exit__(
51 self,
52 exc_type: type[BaseException] | None,
53 exc_value: BaseException | None,
54 traceback: TracebackType | None,
55 ) -> None:
56 self.close()
57
58
59@overload
60def read_sas(
61 filepath_or_buffer: FilePath | ReadBuffer[bytes],
62 *,
63 format: str | None = ...,
64 index: Hashable | None = ...,
65 encoding: str | None = ...,
66 chunksize: int = ...,
67 iterator: bool = ...,
68 compression: CompressionOptions = ...,
69) -> SASReader: ...
70
71
72@overload
73def read_sas(
74 filepath_or_buffer: FilePath | ReadBuffer[bytes],
75 *,
76 format: str | None = ...,
77 index: Hashable | None = ...,
78 encoding: str | None = ...,
79 chunksize: None = ...,
80 iterator: bool = ...,
81 compression: CompressionOptions = ...,
82) -> DataFrame | SASReader: ...
83
84
85@set_module("pandas")
86def read_sas(
87 filepath_or_buffer: FilePath | ReadBuffer[bytes],
88 *,
89 format: str | None = None,
90 index: Hashable | None = None,
91 encoding: str | None = None,
92 chunksize: int | None = None,
93 iterator: bool = False,
94 compression: CompressionOptions = "infer",
95) -> DataFrame | SASReader:
96 """
97 Read SAS files stored as either XPORT or SAS7BDAT format files.
98
99 Parameters
100 ----------
101 filepath_or_buffer : str, path object, or file-like object
102 String, path object (implementing ``os.PathLike[str]``), or file-like
103 object implementing a binary ``read()`` function. The string could be
104 a URL. Valid URL schemes include http, ftp, s3, and file. For file
105 URLs, a host is expected. A local file could be:
106 ``file://localhost/path/to/table.sas7bdat``.
107 format : str {'xport', 'sas7bdat'} or None
108 If None, file format is inferred from file extension. If 'xport' or
109 'sas7bdat', uses the corresponding format.
110 index : identifier of index column, defaults to None
111 Identifier of column that should be used as index of the DataFrame.
112 encoding : str, default is None
113 Encoding for text data. If None, text data are stored as raw bytes.
114 chunksize : int
115 Read file `chunksize` lines at a time, returns iterator.
116 iterator : bool, defaults to False
117 If True, returns an iterator for reading the file incrementally.
118 compression : str or dict, default 'infer'
119 For on-the-fly decompression of on-disk data. If 'infer' and
120 'filepath_or_buffer' is path-like, then detect compression from the
121 following extensions: '.gz', '.bz2', '.zip', '.xz', '.zst', '.tar',
122 '.tar.gz', '.tar.xz' or '.tar.bz2' (otherwise no compression).
123 Set to ``None`` for no decompression.
124 Can also be a dict with key ``'method'`` set to one of {``'zip'``,
125 ``'gzip'``, ``'bz2'``, ``'zstd'``, ``'xz'``, ``'tar'``} and other
126 key-value pairs are forwarded to ``zipfile.ZipFile``,
127 ``gzip.GzipFile``, ``bz2.BZ2File``, ``zstandard.ZstdCompressor``,
128 ``lzma.LZMAFile`` or ``tarfile.TarFile``, respectively.
129 As an example, the following could be passed for faster compression
130 and to create a reproducible gzip archive:
131 ``compression={'method': 'gzip', 'compresslevel': 1, 'mtime': 1}``.
132
133 Returns
134 -------
135 DataFrame, SAS7BDATReader, or XportReader
136 DataFrame if iterator=False and chunksize=None, else SAS7BDATReader
137 or XportReader, file format is inferred from file extension.
138
139 See Also
140 --------
141 read_csv : Read a comma-separated values (csv) file into a DataFrame.
142 read_excel : Read an Excel file into a pandas DataFrame.
143 read_spss : Read an SPSS file into a pandas DataFrame.
144 read_orc : Load an ORC object into a pandas DataFrame.
145 read_feather : Load a feather-format object into a pandas DataFrame.
146
147 Examples
148 --------
149 >>> df = pd.read_sas("sas_data.sas7bdat") # doctest: +SKIP
150 """
151 if format is None:
152 buffer_error_msg = (
153 "If this is a buffer object rather "
154 "than a string name, you must specify a format string"
155 )
156 filepath_or_buffer = stringify_path(filepath_or_buffer)
157 if not isinstance(filepath_or_buffer, str):
158 raise ValueError(buffer_error_msg)
159 fname = filepath_or_buffer.lower()
160 if ".xpt" in fname:
161 format = "xport"
162 elif ".sas7bdat" in fname:
163 format = "sas7bdat"
164 else:
165 raise ValueError(
166 f"unable to infer format of SAS file from filename: {fname!r}"
167 )
168
169 reader: SASReader
170 if format.lower() == "xport":
171 from pandas.io.sas.sas_xport import XportReader
172
173 reader = XportReader(
174 filepath_or_buffer,
175 index=index,
176 encoding=encoding,
177 chunksize=chunksize,
178 compression=compression,
179 )
180 elif format.lower() == "sas7bdat":
181 from pandas.io.sas.sas7bdat import SAS7BDATReader
182
183 reader = SAS7BDATReader(
184 filepath_or_buffer,
185 index=index,
186 encoding=encoding,
187 chunksize=chunksize,
188 compression=compression,
189 )
190 else:
191 raise ValueError("unknown SAS format")
192
193 if iterator or chunksize:
194 return reader
195
196 with reader:
197 return reader.read()