Repository: PyFilesystem/pyfilesystem2 (python)

Issue:
convert_os_errors error when converting errno.ENAMETOOLONG to PathError
Using convert_os_errors to convert errno.ENAMETOOLONG to PathError results in unexpected keyword arguement 'exc' error. Seems that [this line](https://github.com/PyFilesystem/pyfilesystem2/blob/8784fd61797fac654511f16fd50c38c8a1675f16/fs/error_tools.py#L89) will set exc=exc_value however PathError does not take in exc parameter.

The following example code:
```
import errno
from fs.error_tools import convert_os_errors
from fs import errors as fserrors

try:
	from fs import open_fs
	f = open_fs('/tmp')
	f.open('x'*500, mode='w')
except OSError as e:
    with convert_os_errors("foo", "test"):
        raise e
```
results in: TypeError: __init__() got an unexpected keyword argument 'exc'

Pull request:
Add missing `exc` argument to `PathError`
## Type of changes

- Bug fix

## Checklist

- [x] I've run the latest [black](https://github.com/ambv/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @PyFilesystem/maintainers may be pedantic in the code review.

## Description

`fs.errors.PathError` was missing an `exc` argument, so it couldn't be used in `fs.error_tools.convert_os_errors`. This prevented the conversion of `OSError(errno.ENAMETOOLONG)` errors to `PathError`. Closes #453.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aacded9..62cc085 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Added FTP over TLS (FTPS) support to FTPFS.
   Closes [#437](https://github.com/PyFilesystem/pyfilesystem2/issues/437),
   [#449](https://github.com/PyFilesystem/pyfilesystem2/pull/449).
+- `PathError` now supports wrapping an exception using the `exc` argument.
+  Closes [#453](https://github.com/PyFilesystem/pyfilesystem2/issues/453).
 
 ### Changed
 
diff --git a/fs/errors.py b/fs/errors.py
index 25625e2..2448c7a 100644
--- a/fs/errors.py
+++ b/fs/errors.py
@@ -139,13 +139,14 @@ class PathError(FSError):
 
     default_message = "path '{path}' is invalid"
 
-    def __init__(self, path, msg=None):  # noqa: D107
-        # type: (Text, Optional[Text]) -> None
+    def __init__(self, path, msg=None, exc=None):  # noqa: D107
+        # type: (Text, Optional[Text], Optional[Exception]) -> None
         self.path = path
+        self.exc = exc
         super(PathError, self).__init__(msg=msg)
 
     def __reduce__(self):
-        return type(self), (self.path, self._msg)
+        return type(self), (self.path, self._msg, self.exc)
 
 
 class NoSysPath(PathError):


Test patch:
diff --git a/tests/test_error_tools.py b/tests/test_error_tools.py
index b9ac25c..4f6aa32 100644
--- a/tests/test_error_tools.py
+++ b/tests/test_error_tools.py
@@ -3,13 +3,23 @@ from __future__ import unicode_literals
 import errno
 import unittest
 
+import fs.errors
 from fs.error_tools import convert_os_errors
-from fs import errors as fserrors
 
 
 class TestErrorTools(unittest.TestCase):
-    def assert_convert_os_errors(self):
+    def test_convert_enoent(self):
+        exception = OSError(errno.ENOENT, "resource not found")
+        with self.assertRaises(fs.errors.ResourceNotFound) as ctx:
+            with convert_os_errors("stat", "/tmp/test"):
+                raise exception
+        self.assertEqual(ctx.exception.exc, exception)
+        self.assertEqual(ctx.exception.path, "/tmp/test")
 
-        with self.assertRaises(fserrors.ResourceNotFound):
-            with convert_os_errors("foo", "test"):
-                raise OSError(errno.ENOENT)
+    def test_convert_enametoolong(self):
+        exception = OSError(errno.ENAMETOOLONG, "File name too long: test")
+        with self.assertRaises(fs.errors.PathError) as ctx:
+            with convert_os_errors("stat", "/tmp/test"):
+                raise exception
+        self.assertEqual(ctx.exception.exc, exception)
+        self.assertEqual(ctx.exception.path, "/tmp/test")