23
23
TYPE_CHECKING ,
24
24
Any ,
25
25
AnyStr ,
26
+ Callable ,
26
27
Final ,
27
28
Hashable ,
28
29
Sequence ,
@@ -1152,6 +1153,7 @@ def __init__(
1152
1153
self ._encoding = ""
1153
1154
self ._chunksize = chunksize
1154
1155
self ._using_iterator = False
1156
+ self ._entered = False
1155
1157
if self ._chunksize is None :
1156
1158
self ._chunksize = 1
1157
1159
elif not isinstance (chunksize , int ) or chunksize <= 0 :
@@ -1181,21 +1183,36 @@ def _open_file(self) -> None:
1181
1183
"""
1182
1184
Open the file (with compression options, etc.), and read header information.
1183
1185
"""
1184
- with get_handle (
1186
+ if not self ._entered :
1187
+ warnings .warn (
1188
+ "StataReader is being used without using a context manager. "
1189
+ "Using StataReader as a context manager is the only supported method." ,
1190
+ ResourceWarning ,
1191
+ stacklevel = find_stack_level (),
1192
+ )
1193
+ handles = get_handle (
1185
1194
self ._original_path_or_buf ,
1186
1195
"rb" ,
1187
1196
storage_options = self ._storage_options ,
1188
1197
is_text = False ,
1189
1198
compression = self ._compression ,
1190
- ) as handles :
1191
- # Copy to BytesIO, and ensure no encoding
1192
- self ._path_or_buf = BytesIO (handles .handle .read ())
1199
+ )
1200
+ if hasattr (handles .handle , "seekable" ) and handles .handle .seekable ():
1201
+ # If the handle is directly seekable, use it without an extra copy.
1202
+ self ._path_or_buf = handles .handle
1203
+ self ._close_file = handles .close
1204
+ else :
1205
+ # Copy to memory, and ensure no encoding.
1206
+ with handles :
1207
+ self ._path_or_buf = BytesIO (handles .handle .read ())
1208
+ self ._close_file = self ._path_or_buf .close
1193
1209
1194
1210
self ._read_header ()
1195
1211
self ._setup_dtype ()
1196
1212
1197
1213
def __enter__ (self ) -> StataReader :
1198
1214
"""enter context manager"""
1215
+ self ._entered = True
1199
1216
return self
1200
1217
1201
1218
def __exit__ (
@@ -1204,12 +1221,26 @@ def __exit__(
1204
1221
exc_value : BaseException | None ,
1205
1222
traceback : TracebackType | None ,
1206
1223
) -> None :
1207
- """exit context manager"""
1208
- self .close ()
1224
+ if self . _close_file :
1225
+ self ._close_file ()
1209
1226
1210
1227
def close (self ) -> None :
1211
- """close the handle if its open"""
1212
- self ._path_or_buf .close ()
1228
+ """Close the handle if its open.
1229
+
1230
+ .. deprecated: 2.0.0
1231
+
1232
+ The close method is not part of the public API.
1233
+ The only supported way to use StataReader is to use it as a context manager.
1234
+ """
1235
+ warnings .warn (
1236
+ "The StataReader.close() method is not part of the public API and "
1237
+ "will be removed in a future version without notice. "
1238
+ "Using StataReader as a context manager is the only supported method." ,
1239
+ FutureWarning ,
1240
+ stacklevel = 2 ,
1241
+ )
1242
+ if self ._close_file :
1243
+ self ._close_file ()
1213
1244
1214
1245
def _set_encoding (self ) -> None :
1215
1246
"""
0 commit comments