Skip to content

Commit e6f0c8f

Browse files
authored
Merge pull request #475 from PyFilesystem/fix-scandir
Fix minor issues in `OSFS.scandir`
2 parents b88f432 + 8aff8be commit e6f0c8f

File tree

4 files changed

+60
-36
lines changed

4 files changed

+60
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
4040
- `WrapCachedDir.isdir` and `WrapCachedDir.isfile` raising a `ResourceNotFound` error on non-existing path ([#470](https://github.com/PyFilesystem/pyfilesystem2/pull/470)).
4141
- `FTPFS` not listing certain entries with sticky/SUID/SGID permissions set by Linux server ([#473](https://github.com/PyFilesystem/pyfilesystem2/pull/473)).
4242
Closes [#451](https://github.com/PyFilesystem/pyfilesystem2/issues/451).
43+
- `scandir` iterator not being closed explicitly in `OSFS.scandir`, occasionally causing a `ResourceWarning`
44+
to be thrown. Closes [#311](https://github.com/PyFilesystem/pyfilesystem2/issues/311).
4345

4446

4547
## [2.4.12] - 2021-01-14

fs/osfs.py

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ def _scandir(self, path, namespaces=None):
475475
# type: (Text, Optional[Collection[Text]]) -> Iterator[Info]
476476
self.check()
477477
namespaces = namespaces or ()
478+
requires_stat = not {"details", "stat", "access"}.isdisjoint(namespaces)
478479
_path = self.validatepath(path)
479480
if _WINDOWS_PLATFORM:
480481
sys_path = os.path.join(
@@ -483,39 +484,47 @@ def _scandir(self, path, namespaces=None):
483484
else:
484485
sys_path = self._to_sys_path(_path) # type: ignore
485486
with convert_os_errors("scandir", path, directory=True):
486-
for dir_entry in scandir(sys_path):
487-
info = {
488-
"basic": {
489-
"name": fsdecode(dir_entry.name),
490-
"is_dir": dir_entry.is_dir(),
491-
}
492-
}
493-
if "details" in namespaces:
494-
stat_result = dir_entry.stat()
495-
info["details"] = self._make_details_from_stat(stat_result)
496-
if "stat" in namespaces:
497-
stat_result = dir_entry.stat()
498-
info["stat"] = {
499-
k: getattr(stat_result, k)
500-
for k in dir(stat_result)
501-
if k.startswith("st_")
502-
}
503-
if "lstat" in namespaces:
504-
lstat_result = dir_entry.stat(follow_symlinks=False)
505-
info["lstat"] = {
506-
k: getattr(lstat_result, k)
507-
for k in dir(lstat_result)
508-
if k.startswith("st_")
487+
scandir_iter = scandir(sys_path)
488+
try:
489+
for dir_entry in scandir_iter:
490+
info = {
491+
"basic": {
492+
"name": fsdecode(dir_entry.name),
493+
"is_dir": dir_entry.is_dir(),
494+
}
509495
}
510-
if "link" in namespaces:
511-
info["link"] = self._make_link_info(
512-
os.path.join(sys_path, dir_entry.name)
513-
)
514-
if "access" in namespaces:
515-
stat_result = dir_entry.stat()
516-
info["access"] = self._make_access_from_stat(stat_result)
517-
518-
yield Info(info)
496+
if requires_stat:
497+
stat_result = dir_entry.stat()
498+
if "details" in namespaces:
499+
info["details"] = self._make_details_from_stat(
500+
stat_result
501+
)
502+
if "stat" in namespaces:
503+
info["stat"] = {
504+
k: getattr(stat_result, k)
505+
for k in dir(stat_result)
506+
if k.startswith("st_")
507+
}
508+
if "access" in namespaces:
509+
info["access"] = self._make_access_from_stat(
510+
stat_result
511+
)
512+
if "lstat" in namespaces:
513+
lstat_result = dir_entry.stat(follow_symlinks=False)
514+
info["lstat"] = {
515+
k: getattr(lstat_result, k)
516+
for k in dir(lstat_result)
517+
if k.startswith("st_")
518+
}
519+
if "link" in namespaces:
520+
info["link"] = self._make_link_info(
521+
os.path.join(sys_path, dir_entry.name)
522+
)
523+
524+
yield Info(info)
525+
finally:
526+
if sys.version_info >= (3, 6):
527+
scandir_iter.close()
519528

520529
else:
521530

fs/test.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import os
1717
import time
1818
import unittest
19+
import warnings
1920

2021
import fs.copy
2122
import fs.move
@@ -884,8 +885,9 @@ def test_open_files(self):
884885
self.assertFalse(f.closed)
885886
self.assertTrue(f.closed)
886887

887-
iter_lines = iter(self.fs.open("text"))
888-
self.assertEqual(next(iter_lines), "Hello\n")
888+
with self.fs.open("text") as f:
889+
iter_lines = iter(f)
890+
self.assertEqual(next(iter_lines), "Hello\n")
889891

890892
with self.fs.open("unicode", "w") as f:
891893
self.assertEqual(12, f.write("Héllo\nWörld\n"))
@@ -1594,8 +1596,10 @@ def test_files(self):
15941596
self.assert_bytes("foo2", b"help")
15951597

15961598
# Test __del__ doesn't throw traceback
1597-
f = self.fs.open("foo2", "r")
1598-
del f
1599+
with warnings.catch_warnings():
1600+
warnings.simplefilter("ignore")
1601+
f = self.fs.open("foo2", "r")
1602+
del f
15991603

16001604
with self.assertRaises(IOError):
16011605
with self.fs.open("foo2", "r") as f:

tests/test_osfs.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import tempfile
99
import sys
1010
import unittest
11+
import warnings
1112

1213
from fs import osfs, open_fs
1314
from fs.path import relpath, dirname
@@ -25,6 +26,14 @@
2526
class TestOSFS(FSTestCases, unittest.TestCase):
2627
"""Test OSFS implementation."""
2728

29+
@classmethod
30+
def setUpClass(cls):
31+
warnings.simplefilter("error")
32+
33+
@classmethod
34+
def tearDownClass(cls):
35+
warnings.simplefilter(warnings.defaultaction)
36+
2837
def make_fs(self):
2938
temp_dir = tempfile.mkdtemp("fstestosfs")
3039
return osfs.OSFS(temp_dir)

0 commit comments

Comments
 (0)