Repository: PyFilesystem/pyfilesystem2
Pull request #281 (2019-05-05 15:30:27)

fix for tarfs '.' exception
## Type of changes

- [x] Bug fix
- [ ] New feature
- [ ] Documentation / docstrings
- [ ] Tests
- [ ] Other

## 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 @willmcgugan may be pedantic in the code review.

## Description

Fix exception when opening tar files (https://github.com/PyFilesystem/pyfilesystem2/issues/275)



coveralls: 
[![Coverage Status](https://coveralls.io/builds/23198431/badge)](https://coveralls.io/builds/23198431)

Coverage decreased (-0.7%) to 98.965% when pulling **e67a01f41d7df27c228e11e444a031dd13baab8b on tarfs-fix** into **5f0a47530d0407b386442c991dae3584580a68c3 on master**.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 651cea7..d1be3b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 - Restored deprecated `setfile` method with deprecation warning to change to `writefile`
+- Fixed exception when a tarfile contains a path called '.' https://github.com/PyFilesystem/pyfilesystem2/issues/275
+- Made TarFS directory loading lazy
+
+### Changed
+
+- Removed case_insensitive meta value from OSFS meta on OSX. normcase check doesn't work on OSX (https://stackoverflow.com/questions/7870041/check-if-file-system-is-case-insensitive-in-python)
 
 ## [2.4.4] - 2019-02-23
 
diff --git a/fs/_version.py b/fs/_version.py
index 88ac354..9d1a374 100644
--- a/fs/_version.py
+++ b/fs/_version.py
@@ -1,3 +1,3 @@
 """Version, used in module and setup.py.
 """
-__version__ = "2.4.5a0"
+__version__ = "2.4.5"
diff --git a/fs/osfs.py b/fs/osfs.py
index 72cab39..5877fc7 100644
--- a/fs/osfs.py
+++ b/fs/osfs.py
@@ -143,6 +143,11 @@ class OSFS(FS):
             "virtual": False,
         }
 
+        if platform.system() == "Darwin":
+            # Standard test doesn't work on OSX.
+            # Best we can say is we don't know.
+            del _meta["case_insensitive"]
+
         if _WINDOWS_PLATFORM:  # pragma: no cover
             _meta["invalid_path_chars"] = (
                 "".join(six.unichr(n) for n in range(31)) + '\\:*?"<>|'
diff --git a/fs/tarfs.py b/fs/tarfs.py
index 6dc4b1a..fcd7ceb 100644
--- a/fs/tarfs.py
+++ b/fs/tarfs.py
@@ -20,7 +20,7 @@ from .enums import ResourceType
 from .info import Info
 from .iotools import RawWrapper
 from .opener import open_fs
-from .path import dirname, relpath, basename, isbase, parts, frombase
+from .path import dirname, relpath, basename, isbase, normpath, parts, frombase
 from .wrapfs import WrapFS
 from .permissions import Permissions
 
@@ -272,9 +272,18 @@ class ReadTarFS(FS):
         else:
             self._tar = tarfile.open(fileobj=file, mode="r")
 
-        self._directory = OrderedDict(
-            (relpath(self._decode(info.name)).rstrip("/"), info) for info in self._tar
-        )
+        self._directory_cache = None
+
+    @property
+    def _directory(self):
+        """Lazy directory cache."""
+        if self._directory_cache is None:
+            _decode = self._decode
+            _directory = ((_decode(info.name).strip("/"), info) for info in self._tar)
+            self._directory_cache = OrderedDict(
+                (name, info) for name, info in _directory if normpath(name)
+            )
+        return self._directory_cache
 
     def __repr__(self):
         # type: () -> Text


Test patch:
diff --git a/tests/test_tarfs.py b/tests/test_tarfs.py
index 0ce0960..2a1dc82 100644
--- a/tests/test_tarfs.py
+++ b/tests/test_tarfs.py
@@ -188,6 +188,30 @@ class TestReadTarFS(ArchiveTestCases, unittest.TestCase):
         self.assertTrue(top.get("tar", "is_file"))
 
 
+class TestBrokenDir(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.tmpfs = open_fs("temp://tarfstest")
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tmpfs.close()
+
+    def setUp(self):
+        self.tempfile = self.tmpfs.open("test.tar", "wb+")
+        with tarfile.open(mode="w", fileobj=self.tempfile) as tf:
+            tf.addfile(tarfile.TarInfo("."), io.StringIO)
+        self.tempfile.seek(0)
+        self.fs = tarfs.TarFS(self.tempfile)
+
+    def tearDown(self):
+        self.fs.close()
+        self.tempfile.close()
+
+    def test_listdir(self):
+        self.assertEqual(self.fs.listdir("/"), [])
+
+
 class TestImplicitDirectories(unittest.TestCase):
     """Regression tests for #160.
     """