Repository: statsmodels/statsmodels
Pull request: ENH: Handle pathlib.Path objects
- [ ] closes #6652 
- [ ] tests added / passed. 


Problem statement:
Handle pathlib paths with `get_file_obj`
#### Is your feature request related to a problem? Please describe
I'm trying to read a statsmodels.tsa.statespace.sarimax.SARIMAXResults pickle file I've saved. I'd like to pass a pathlib.Path object as I find it easier to handle (I work with different machines, Mac and Windows).

#### Describe the solution you'd like
The function `open` already supports pathlib objects. Why not using it?

You could replace `statsmodels.iolib.openfile.get_file_obj` with:

```python
if _is_string_like(fname):
    return _open(fname, mode, encoding)
try:  # try opening it with 'open'
    return open(fname, mode, encoding)
except TypeError:  # else, maybe return context manager (please check if it's always TypeError)
    try:
        # Make sure the object has the write methods
        if 'r' in mode:
            fname.read
        if 'w' in mode or 'a' in mode:
            fname.write
    except AttributeError:
        raise ValueError('fname must be a string or a file-like object')
    return EmptyContextManager(fname)
```

This is just a suggestion though :)

Hints from issue discussion:
A PR would be welcome for this enhancement.

On Tue, Apr 21, 2020, 21:09 Giulio Beseghi <notifications@github.com> wrote:

> Is your feature request related to a problem? Please describe
>
> I'm trying to read a statsmodels.tsa.statespace.sarimax.SARIMAXResults
> pickle file I've saved. I'd like to pass a pathlib.Path object as I find it
> easier to handle (I work with different machines, Mac and Windows).
> Describe the solution you'd like
>
> The function open already supports pathlib objects. Why not using it?
>
> You could replace statsmodels.iolib.openfile.get_file_obj with:
>
> if _is_string_like(fname):
>     return _open(fname, mode, encoding)try:  # try opening it with 'open'
>     return open(fname, mode, encoding)except TypeError:  # else, maybe return context manager (please check if it's always TypeError)
>     try:
>         # Make sure the object has the write methods
>         if 'r' in mode:
>             fname.read
>         if 'w' in mode or 'a' in mode:
>             fname.write
>     except AttributeError:
>         raise ValueError('fname must be a string or a file-like object')
>     return EmptyContextManager(fname)
>
> This is just a suggestion though :)
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/statsmodels/statsmodels/issues/6652>, or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABKTSRPYR7MPDUCTIUYXJYDRNX4P7ANCNFSM4MNSR7KQ>
> .
>

I've never done it, but I can try. Is there a guide about it?

Commit message: Handle pathlib.Path objects

Patch to review:
diff --git a/statsmodels/iolib/openfile.py b/statsmodels/iolib/openfile.py
index 01e4b4edd17..17acf977286 100644
--- a/statsmodels/iolib/openfile.py
+++ b/statsmodels/iolib/openfile.py
@@ -56,11 +56,14 @@ def get_file_obj(fname, mode='r', encoding=None):
     if _is_string_like(fname):
         return _open(fname, mode, encoding)
     try:
-        # Make sure the object has the write methods
-        if 'r' in mode:
-            fname.read
-        if 'w' in mode or 'a' in mode:
-            fname.write
-    except AttributeError:
-        raise ValueError('fname must be a string or a file-like object')
-    return EmptyContextManager(fname)
+        return open(fname, mode, encoding=encoding)  # handle pathlib-like objs
+    except TypeError:
+        try:
+            # Make sure the object has the write methods
+            if "r" in mode:
+                fname.read
+            if "w" in mode or "a" in mode:
+                fname.write
+        except AttributeError:
+            raise ValueError("fname must be a string or a file-like object")
+        return EmptyContextManager(fname)
diff --git a/statsmodels/iolib/tests/test_pickle.py b/statsmodels/iolib/tests/test_pickle.py
index 0a449f40f65..3c167ef677d 100644
--- a/statsmodels/iolib/tests/test_pickle.py
+++ b/statsmodels/iolib/tests/test_pickle.py
@@ -1,4 +1,5 @@
 from io import BytesIO
+import pathlib
 import tempfile
 
 from numpy.testing import assert_equal
@@ -8,16 +9,26 @@
 
 
 def test_pickle():
-    tmpdir = tempfile.mkdtemp(prefix='pickle')
+    tmpdir = tempfile.mkdtemp(prefix="pickle")
     a = lrange(10)
-    save_pickle(a, tmpdir+'/res.pkl')
-    b = load_pickle(tmpdir+'/res.pkl')
+
+    # test with str
+    path_str = tmpdir + "/res.pkl"
+    save_pickle(a, path_str)
+    b = load_pickle(path_str)
+    assert_equal(a, b)
+
+    # test with pathlib
+    path_pathlib = pathlib.Path(tmpdir) / "res.pkl"
+    save_pickle(a, path_pathlib)
+    b = load_pickle(path_pathlib)
     assert_equal(a, b)
 
     # cleanup, tested on Windows
     try:
         import os
-        os.remove(tmpdir+'/res.pkl')
+
+        os.remove(tmpdir + "/res.pkl")
         os.rmdir(tmpdir)
     except (OSError, IOError):
         pass


Code review comments:
diff_hunk: @@ -8,16 +9,26 @@
 
 
 def test_pickle():
-    tmpdir = tempfile.mkdtemp(prefix='pickle')
+    tmpdir = tempfile.mkdtemp(prefix="pickle")
     a = lrange(10)
-    save_pickle(a, tmpdir+'/res.pkl')
-    b = load_pickle(tmpdir+'/res.pkl')
+
+    # test with str
+    path_str = tmpdir + "/res.pkl"
+    save_pickle(a, path_str)
+    b = load_pickle(path_str)
+    assert_equal(a, b)
+
+    # test with pathlib
+    path_pathlib = pathlib.Path(tmpdir) / "res.pkl"
path: statsmodels/iolib/tests/test_pickle.py
text: @user1:
Maybe use a different file name just to be sure it is writing the file. res2.pkl.

diff_hunk: @@ -8,16 +9,26 @@
 
 
 def test_pickle():
-    tmpdir = tempfile.mkdtemp(prefix='pickle')
+    tmpdir = tempfile.mkdtemp(prefix="pickle")
     a = lrange(10)
-    save_pickle(a, tmpdir+'/res.pkl')
-    b = load_pickle(tmpdir+'/res.pkl')
+
+    # test with str
+    path_str = tmpdir + "/res.pkl"
+    save_pickle(a, path_str)
+    b = load_pickle(path_str)
+    assert_equal(a, b)
+
+    # test with pathlib
+    path_pathlib = pathlib.Path(tmpdir) / "res.pkl"
+    save_pickle(a, path_pathlib)
+    b = load_pickle(path_pathlib)
     assert_equal(a, b)
 
     # cleanup, tested on Windows
     try:
         import os
-        os.remove(tmpdir+'/res.pkl')
+
+        os.remove(tmpdir + "/res.pkl")
path: statsmodels/iolib/tests/test_pickle.py
text: @user1:
Need to remove both here too then.