Repository: networkx/networkx

Issue:
nx.write_gpickle and nx.read_gpickle not implementing os.PathLike
If you use pathlib in python3, networkx.write_gpickle and networkx.read_gpickle will not accept these as paths.


Discussion:
This is easily fixable (I think) just by using str(x) whenever you expect a string path.
Each of these functions uses the ```@open_file``` decorator to determine if the argument is a string-like name of the file or a file object. Your suggestion would lead to a change in that decorator so that if it is a pathlib object, a reasonable file object is created.

Would you be able to take a look at open_file and see if there is a good way to identify the argument as a pathlib object? Then pathlib would work for all networkx functions. 
Actually, I think it might be better/faster/cheaper just to have the user put ```str(p)``` when they are calling the function, instead of adding a bunch of code to NetworkX which would eventually need to be changed to keep up with pathlib. The pathlib library helps you manipulate the path, but when you want to use it you turn it into a string by using ```str```.  

So I think I will close this issue in favor of leaving specific code out of networkx and instead requiring the user to turn the argument into a string before calling these functions.
I think keeping up with pathlib is pretty important, as it's a base package and is trying to create a standard for how to handle paths in python. Part of python 3 compatibility should be compatibility with all base packages and compatibility with PEP paradigms.

Not to mention that compatibility is just basically easy to implement as you've said.

I can look at it someday.
I agree with keeping up with all base packages. 
But isn't the proper use of pathlib to turn it into a string when you want to use it?

    f = open(str(p))

or in a more sophisticated way:

    with p.open('wb') as f:
        nx.write_pickle(G, path=f)

How are you envisioning people use the path objects?
I'm not sure I can cite the book, but my impression was that anything that implements os.pathlike should be considered a path, so any modules that take paths should handle things that are pathlike.
I understand that it's easy on either the user or the developer end just by calling str, but sometimes it's hard to know as a user which methods can take pathlikes and which require strings. So slowly identifying and converting anything that cant take pathlikes will improve user experience in the distant future when 3.6 is the new 2.7 
It's also been awhile but I think I recall opening this issue bc when I was debugging some code where I passed a pathlike to networkx it gave some horrid traceback that took a bit to realize what was wrong. So at least maybe we should add a typecheck assert. But at that point it's just as easy to cast to string.

Resolving patch:
diff --git a/networkx/utils/decorators.py b/networkx/utils/decorators.py
--- a/networkx/utils/decorators.py
+++ b/networkx/utils/decorators.py
@@ -3,6 +3,11 @@
 from collections import defaultdict
 from os.path import splitext
 from contextlib import contextmanager
+try:
+    from pathlib import Path
+except ImportError:
+    # Use Path to indicate if pathlib exists (like numpy does)
+    Path = None
 
 import networkx as nx
 from decorator import decorator
@@ -208,6 +213,10 @@ def _open_file(func_to_be_decorated, *args, **kwargs):
             # path is already a file-like object
             fobj = path
             close_fobj = False
+        elif Path is not None and isinstance(path, Path):
+            # path is a pathlib reference to a filename
+            fobj = _dispatch_dict[path.suffix](str(path), mode=mode)
+            close_fobj = True
         else:
             # could be None, in which case the algorithm will deal with it
             fobj = path


Test patch:
diff --git a/networkx/utils/tests/test_decorators.py b/networkx/utils/tests/test_decorators.py
--- a/networkx/utils/tests/test_decorators.py
+++ b/networkx/utils/tests/test_decorators.py
@@ -87,6 +87,13 @@ def test_writer_arg0_str(self):
     def test_writer_arg0_fobj(self):
         self.writer_arg0(self.fobj)
 
+    def test_writer_arg0_pathlib(self):
+        try:
+            import pathlib
+            self.writer_arg0(pathlib.Path(self.name))
+        except ImportError:
+            return
+
     def test_writer_arg1_str(self):
         self.writer_arg1(self.name)
         assert_equal(self.read(self.name), ''.join(self.text))