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