Skip to content
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [2.4.3] - 2019-02-23

### Fixed

- Fixed broken "case_insensitive" check
- Fixed Windows test fails

## [2.4.2] - 2019-02-22

### Fixed
Expand Down
25 changes: 13 additions & 12 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,25 @@ environment:
# The list here is complete (excluding Python 2.6, which
# isn't covered by this document) at the time of writing.

- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python33"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python27-x64"
- PYTHON: "C:\\Python33-x64"
DISTUTILS_USE_SDK: "1"
- PYTHON: "C:\\Python34-x64"
DISTUTILS_USE_SDK: "1"
- PYTHON: "C:\\Python35-x64"
# - PYTHON: "C:\\Python27"
# - PYTHON: "C:\\Python33"
# - PYTHON: "C:\\Python34"
# - PYTHON: "C:\\Python35"
# - PYTHON: "C:\\Python27-x64"
# - PYTHON: "C:\\Python33-x64"
# DISTUTILS_USE_SDK: "1"
# - PYTHON: "C:\\Python34-x64"
# DISTUTILS_USE_SDK: "1"
# - PYTHON: "C:\\Python35-x64"
- PYTHON: "C:\\Python36-x64"
- PYTHON: "C:\\Python37-x64"

install:
# We need wheel installed to build wheels
- "%PYTHON%\\python.exe -m pip install nose pyftpdlib mock"
- "%PYTHON%\\python.exe -m pip install nose psutil pyftpdlib mock"
- "%PYTHON%\\python.exe setup.py install"

build: off

test_script:
- "%PYTHON%\\python.exe -m nose tests"
- "%PYTHON%\\python.exe -m nose tests -v"
2 changes: 1 addition & 1 deletion fs/_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Version, used in module and setup.py.
"""
__version__ = "2.4.2"
__version__ = "2.4.3"
32 changes: 23 additions & 9 deletions fs/osfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from .permissions import Permissions
from .error_tools import convert_os_errors
from .mode import Mode, validate_open_mode
from .errors import NoURL
from .errors import FileExpected, NoURL

if False: # typing.TYPE_CHECKING
from typing import (
Expand Down Expand Up @@ -134,7 +134,7 @@ def __init__(
raise errors.CreateFailed("root path does not exist")

_meta = self._meta = {
"case_insensitive": os.path.normcase("Aa") != "aa",
"case_insensitive": os.path.normcase("Aa") == "aa",
"network": False,
"read_only": False,
"supports_rename": True,
Expand Down Expand Up @@ -245,12 +245,17 @@ def _get_type_from_stat(cls, _stat):

def _gettarget(self, sys_path):
# type: (Text) -> Optional[Text]
try:
target = os.readlink(fsencode(sys_path))
except OSError:
return None
else:
return target
if hasattr(os, "readlink"):
try:
if _WINDOWS_PLATFORM: # pragma: no cover
target = os.readlink(sys_path)
else:
target = os.readlink(fsencode(sys_path))
except OSError:
pass
else:
return target
return None

def _make_link_info(self, sys_path):
# type: (Text) -> Dict[Text, object]
Expand Down Expand Up @@ -328,6 +333,8 @@ def openbin(self, path, mode="r", buffering=-1, **options):
_mode.validate_bin()
self.check()
_path = self.validatepath(path)
if _path == "/":
raise errors.FileExpected(path)
sys_path = self._to_sys_path(_path)
with convert_os_errors("openbin", path):
if six.PY2 and _mode.exclusive:
Expand Down Expand Up @@ -454,7 +461,12 @@ def _scandir(self, path, namespaces=None):
self.check()
namespaces = namespaces or ()
_path = self.validatepath(path)
sys_path = self._to_sys_path(_path)
if _WINDOWS_PLATFORM:
sys_path = os.path.join(
self._root_path, path.lstrip("/").replace("/", os.sep)
)
else:
sys_path = self._to_sys_path(_path)
with convert_os_errors("scandir", path, directory=True):
for dir_entry in scandir(sys_path):
info = {
Expand Down Expand Up @@ -595,6 +607,8 @@ def open(
validate_open_mode(mode)
self.check()
_path = self.validatepath(path)
if _path == "/":
raise FileExpected(path)
sys_path = self._to_sys_path(_path)
with convert_os_errors("open", path):
if six.PY2 and _mode.exclusive:
Expand Down
7 changes: 4 additions & 3 deletions tests/test_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ def test_getinfo(self):
except errors.NoSysPath:
pass
else:
self.assertEqual(
top.permissions.mode, stat.S_IMODE(os.stat(source_syspath).st_mode)
)
if top.has_namespace("access"):
self.assertEqual(
top.permissions.mode, stat.S_IMODE(os.stat(source_syspath).st_mode)
)

self.assertEqual(top.get("details", "type"), ResourceType.file)

Expand Down
85 changes: 45 additions & 40 deletions tests/test_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,48 @@
from fs.osfs import OSFS


@unittest.skipIf(platform.system() == "Darwin", "Bad unicode not possible on OSX")
class TestEncoding(unittest.TestCase):

TEST_FILENAME = b"foo\xb1bar"
TEST_FILENAME_UNICODE = fs.fsdecode(TEST_FILENAME)

def setUp(self):
dir_path = self.dir_path = tempfile.mkdtemp()
if six.PY2:
with open(os.path.join(dir_path, self.TEST_FILENAME), "wb") as f:
f.write(b"baz")
else:
with open(os.path.join(dir_path, self.TEST_FILENAME_UNICODE), "wb") as f:
f.write(b"baz")

def tearDown(self):
shutil.rmtree(self.dir_path)

def test_open(self):
with OSFS(self.dir_path) as test_fs:
self.assertTrue(test_fs.exists(self.TEST_FILENAME_UNICODE))
self.assertTrue(test_fs.isfile(self.TEST_FILENAME_UNICODE))
self.assertFalse(test_fs.isdir(self.TEST_FILENAME_UNICODE))
with test_fs.open(self.TEST_FILENAME_UNICODE, "rb") as f:
self.assertEqual(f.read(), b"baz")
self.assertEqual(test_fs.readtext(self.TEST_FILENAME_UNICODE), "baz")
test_fs.remove(self.TEST_FILENAME_UNICODE)
self.assertFalse(test_fs.exists(self.TEST_FILENAME_UNICODE))

def test_listdir(self):
with OSFS(self.dir_path) as test_fs:
dirlist = test_fs.listdir("/")
self.assertEqual(dirlist, [self.TEST_FILENAME_UNICODE])
self.assertEqual(test_fs.readtext(dirlist[0]), "baz")

def test_scandir(self):
with OSFS(self.dir_path) as test_fs:
for info in test_fs.scandir("/"):
self.assertIsInstance(info.name, six.text_type)
self.assertEqual(info.name, self.TEST_FILENAME_UNICODE)
if platform.system() != "Windows":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not wrap in two unittest.skipIf decorators with two different conditions ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was breaking at the module level. Fsencode uses 'strict' error handler and was throwing an exception on the classvar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@willmcgugan : my bad !

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries! I've got Windows tests running with appveyor now, so we can catch these things in future.


@unittest.skipIf(platform.system() == "Darwin", "Bad unicode not possible on OSX")
class TestEncoding(unittest.TestCase):

TEST_FILENAME = b"foo\xb1bar"
# fsdecode throws error on Windows
TEST_FILENAME_UNICODE = fs.fsdecode(TEST_FILENAME)

def setUp(self):
dir_path = self.dir_path = tempfile.mkdtemp()
if six.PY2:
with open(os.path.join(dir_path, self.TEST_FILENAME), "wb") as f:
f.write(b"baz")
else:
with open(
os.path.join(dir_path, self.TEST_FILENAME_UNICODE), "wb"
) as f:
f.write(b"baz")

def tearDown(self):
shutil.rmtree(self.dir_path)

def test_open(self):
with OSFS(self.dir_path) as test_fs:
self.assertTrue(test_fs.exists(self.TEST_FILENAME_UNICODE))
self.assertTrue(test_fs.isfile(self.TEST_FILENAME_UNICODE))
self.assertFalse(test_fs.isdir(self.TEST_FILENAME_UNICODE))
with test_fs.open(self.TEST_FILENAME_UNICODE, "rb") as f:
self.assertEqual(f.read(), b"baz")
self.assertEqual(test_fs.readtext(self.TEST_FILENAME_UNICODE), "baz")
test_fs.remove(self.TEST_FILENAME_UNICODE)
self.assertFalse(test_fs.exists(self.TEST_FILENAME_UNICODE))

def test_listdir(self):
with OSFS(self.dir_path) as test_fs:
dirlist = test_fs.listdir("/")
self.assertEqual(dirlist, [self.TEST_FILENAME_UNICODE])
self.assertEqual(test_fs.readtext(dirlist[0]), "baz")

def test_scandir(self):
with OSFS(self.dir_path) as test_fs:
for info in test_fs.scandir("/"):
self.assertIsInstance(info.name, six.text_type)
self.assertEqual(info.name, self.TEST_FILENAME_UNICODE)
5 changes: 3 additions & 2 deletions tests/test_ftpfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from fs import errors
from fs.opener import open_fs
from fs.ftpfs import FTPFS, ftp_errors
from fs.path import join
from fs.subfs import SubFS
from fs.test import FSTestCases

Expand Down Expand Up @@ -200,7 +201,7 @@ def test_opener_path(self):

def test_create(self):

directory = os.path.join("home", self.user, "test", "directory")
directory = join("home", self.user, "test", "directory")
base = "ftp://user:1234@{}:{}/foo".format(self.server.host, self.server.port)
url = "{}/{}".format(base, directory)

Expand All @@ -215,7 +216,7 @@ def test_create(self):
# Open the base filesystem and check the subdirectory exists
with open_fs(base) as ftp_fs:
self.assertTrue(ftp_fs.isdir(directory))
self.assertTrue(ftp_fs.isfile(os.path.join(directory, "foo")))
self.assertTrue(ftp_fs.isfile(join(directory, "foo")))

# Open without `create` and check the file exists
with open_fs(url) as ftp_fs:
Expand Down
1 change: 1 addition & 0 deletions tests/test_osfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def test_unicode_paths(self):
finally:
shutil.rmtree(dir_path)

@unittest.skipIf(not hasattr(os, "symlink"), "No symlink support")
def test_symlinks(self):
with open(self._get_real_path("foo"), "wb") as f:
f.write(b"foobar")
Expand Down
1 change: 1 addition & 0 deletions tests/test_tarfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
class TestWriteReadTarFS(unittest.TestCase):
def setUp(self):
fh, self._temp_path = tempfile.mkstemp()
os.close(fh)

def tearDown(self):
os.remove(self._temp_path)
Expand Down
1 change: 1 addition & 0 deletions tests/test_zipfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
class TestWriteReadZipFS(unittest.TestCase):
def setUp(self):
fh, self._temp_path = tempfile.mkstemp()
os.close(fh)

def tearDown(self):
os.remove(self._temp_path)
Expand Down