From 7beae4e0107393f55005d35f72e395c7d1e6c8b5 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 2 May 2018 07:35:59 +0100 Subject: [PATCH] Tox fixes (#164) * tox init update and fixes for 3.5.1 * fix pragma * pragma and MemoryFS.read * shameless coverage hack * no cover * coverage hack * move no cover * second stab at typing * ftp fix * _typing shim * enums test * enum tests * Reinstate typing * fix pragma * coverage * exclude overload * cover exclude --- fs/_typing.py | 21 +++++++++++++++++++++ fs/_version.py | 2 +- fs/copy.py | 2 +- fs/errors.py | 2 +- fs/ftpfs.py | 22 ++++++++++------------ fs/info.py | 24 +++++++++++++----------- fs/memoryfs.py | 15 +++++++-------- fs/mode.py | 7 +++++-- fs/opener/ftpfs.py | 2 -- fs/osfs.py | 12 ++++++------ fs/path.py | 2 +- fs/permissions.py | 5 ++++- fs/tarfs.py | 2 +- tests/test_enums.py | 14 ++++++++++++++ tests/test_ftpfs.py | 12 ++++++++++++ tests/test_info.py | 2 +- tox.ini | 19 ++++++------------- 17 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 fs/_typing.py create mode 100644 tests/test_enums.py diff --git a/fs/_typing.py b/fs/_typing.py new file mode 100644 index 00000000..12953e14 --- /dev/null +++ b/fs/_typing.py @@ -0,0 +1,21 @@ +""" +Typing objects missing from Python3.5.1 + +""" +import sys +import six + + +_PY = sys.version_info + + +if _PY.major == 3 and _PY.minor == 5 and _PY.micro in (0, 1): + def overload(func): # pragma: no cover + return func +else: + from typing import overload + +try: + from typing import Text +except ImportError: # pragma: no cover + Text = six.text_type diff --git a/fs/_version.py b/fs/_version.py index aff8a833..fb7a5c69 100644 --- a/fs/_version.py +++ b/fs/_version.py @@ -1,3 +1,3 @@ """Version, used in module and setup.py. """ -__version__ = "2.0.20" +__version__ = "2.0.21" diff --git a/fs/copy.py b/fs/copy.py index df2eff87..8eddcd43 100644 --- a/fs/copy.py +++ b/fs/copy.py @@ -96,7 +96,7 @@ def _source_is_newer(src_fs, src_path, dst_fs, dst_path): dst_modified = dst_fs.getinfo(dst_path, namespace).modified return dst_modified is None or src_modified > dst_modified return True - except FSError: # pragma: nocover + except FSError: # pragma: no cover #todo: should log something here return True diff --git a/fs/errors.py b/fs/errors.py index 9b042c5f..cfc3abcd 100644 --- a/fs/errors.py +++ b/fs/errors.py @@ -19,7 +19,7 @@ if False: # typing.TYPE_CHECKING from typing import Optional, Text - + __all__ = [ 'CreateFailed', diff --git a/fs/ftpfs.py b/fs/ftpfs.py index 81633464..4303a46a 100644 --- a/fs/ftpfs.py +++ b/fs/ftpfs.py @@ -76,12 +76,12 @@ def ftp_errors(fs, path=None): ) except error_perm as error: code, message = _parse_ftp_error(error) - if code == 552: + if code == '552': raise errors.InsufficientStorage( path=path, msg=message ) - elif code in (501, 550): + elif code in ('501', '550'): raise errors.ResourceNotFound(path=path) raise errors.PermissionDenied( msg=message @@ -96,16 +96,14 @@ def manage_ftp(ftp): finally: try: ftp.quit() - except: # pragma: nocover + except: # pragma: no cover pass def _parse_ftp_error(error): - # type: (ftplib.Error) -> Tuple[Union[int, Text], Text] + # type: (ftplib.Error) -> Tuple[Text, Text] """Extract code and message from ftp error.""" code, _, message = text_type(error).partition(' ') - if code.isdigit(): - return int(code), message return code, message @@ -189,7 +187,7 @@ def close(self): self._read_conn = None try: self.ftp.quit() - except error_temp: # pragma: nocover + except error_temp: # pragma: no cover pass finally: super(FTPFile, self).close() @@ -219,7 +217,7 @@ def read(self, size=-1): read_size = min(DEFAULT_CHUNK_SIZE, remaining) try: chunk = conn.recv(read_size) - except socket.error: # pragma: nocover + except socket.error: # pragma: no cover break if not chunk: break @@ -420,7 +418,7 @@ def _open_ftp(self): self._features = {} try: feat_response = _decode(_ftp.sendcmd("FEAT"), 'latin-1') - except error_perm: + except error_perm: # pragma: no cover self.encoding = 'latin-1' else: self._features = self._parse_features(feat_response) @@ -660,7 +658,7 @@ def makedir(self, # type: _F self.ftp.mkd(_encode(_path, self.ftp.encoding)) except error_perm as error: code, _ = _parse_ftp_error(error) - if code == 550: + if code == '550': if self.isdir(path): raise errors.DirectoryExists(path) else: @@ -712,7 +710,7 @@ def removedir(self, path): self.ftp.rmd(_encode(_path, self.ftp.encoding)) except error_perm as error: code, _ = _parse_ftp_error(error) - if code == 550: + if code == '550': if self.isfile(path): raise errors.DirectoryExpected(path) if not self.isempty(path): @@ -795,7 +793,7 @@ def getbytes(self, path): ) except error_perm as error: code, _ = _parse_ftp_error(error) - if code == 550: + if code == '550': if self.isdir(path): raise errors.FileExpected(path) raise diff --git a/fs/info.py b/fs/info.py index b224878d..55a3e09c 100644 --- a/fs/info.py +++ b/fs/info.py @@ -13,10 +13,12 @@ from .errors import MissingInfoNamespace from .permissions import Permissions from .time import epoch_to_datetime +from ._typing import overload if False: # typing.TYPE_CHECKING from datetime import datetime - from typing import Callable, Mapping, Optional, Text + from typing import Callable, List, Mapping, Optional + from ._typing import Text RawInfo = Mapping[Text, Mapping[Text, object]] ToDatetime = Callable[[int], datetime] T = typing.TypeVar("T") @@ -58,13 +60,13 @@ def __eq__(self, other): # type: (object) -> bool return self.raw == getattr(other, 'raw', None) - @typing.overload - def _make_datetime(self, t): + @overload + def _make_datetime(self, t): # pragma: no cover # type: (None) -> None pass - @typing.overload - def _make_datetime(self, t): + @overload + def _make_datetime(self, t): # pragma: no cover # type: (int) -> datetime pass @@ -75,18 +77,18 @@ def _make_datetime(self, t): else: return None - @typing.overload - def get(self, namespace, key, default=None): + @overload + def get(self, namespace, key, default=None): # pragma: no cover # type: (Text, Text, Optional[T]) -> Optional[T] pass - @typing.overload - def get(self, namespace, key): + @overload + def get(self, namespace, key): # pragma: no cover # type: (Text, Text) -> Optional[T] pass - @typing.overload - def get(self, namespace, key, default): + @overload + def get(self, namespace, key, default): # pragma: no cover # type: (Text, Text, T) -> T pass diff --git a/fs/memoryfs.py b/fs/memoryfs.py index 1e81f0b6..2fabcefd 100644 --- a/fs/memoryfs.py +++ b/fs/memoryfs.py @@ -21,6 +21,7 @@ from .path import iteratepath from .path import normpath from .path import split +from ._typing import overload if False: # typing.TYPE_CHECKING from typing import ( @@ -120,8 +121,6 @@ def read(self, size=-1): # type: (Optional[int]) -> bytes if not self._mode.reading: raise IOError('File not open for reading') - if size is None: - size = -1 with self._seek_lock(): self.on_access() return self._bytes_io.read(size) @@ -222,18 +221,18 @@ def size(self): _bytes_file.seek(0, os.SEEK_END) return _bytes_file.tell() - @typing.overload - def get_entry(self, name, default): + @overload + def get_entry(self, name, default): # pragma: no cover # type: (Text, _DirEntry) -> _DirEntry pass - @typing.overload - def get_entry(self, name): + @overload + def get_entry(self, name): # pragma: no cover # type: (Text) -> Optional[_DirEntry] pass - @typing.overload - def get_entry(self, name, default): + @overload + def get_entry(self, name, default): # pragma: no cover # type: (Text, None) -> Optional[_DirEntry] pass diff --git a/fs/mode.py b/fs/mode.py index 152e8f82..c4c338d0 100644 --- a/fs/mode.py +++ b/fs/mode.py @@ -12,8 +12,11 @@ import six +from ._typing import Text + + if False: # typing.TYPE_CHECKING - from typing import Container, FrozenSet, Set, Text, Union + from typing import Container, FrozenSet, Set, Union __all__ = ["Mode", @@ -24,7 +27,7 @@ # https://docs.python.org/3/library/functions.html#open @six.python_2_unicode_compatible -class Mode(typing.Container[typing.Text]): +class Mode(typing.Container[Text]): """An abstraction for I/O modes. A mode object provides properties that can be used to interrogate the diff --git a/fs/opener/ftpfs.py b/fs/opener/ftpfs.py index 5d98ade4..dc72d08f 100644 --- a/fs/opener/ftpfs.py +++ b/fs/opener/ftpfs.py @@ -43,8 +43,6 @@ def open_fs(self, passwd=parse_result.password, proxy=parse_result.params.get('proxy') ) - if dir_path: return ftp_fs.opendir(dir_path, factory=ClosingSubFS) - return ftp_fs diff --git a/fs/osfs.py b/fs/osfs.py index 22916bb7..9aa0f85f 100644 --- a/fs/osfs.py +++ b/fs/osfs.py @@ -25,7 +25,7 @@ except ImportError: try: from scandir import scandir # type: ignore - except ImportError: # pragma: nocover + except ImportError: # pragma: no cover scandir = None from . import errors @@ -120,7 +120,7 @@ def __init__(self, 'virtual': False, } - if _WINDOWS_PLATFORM: # pragma: nocover + if _WINDOWS_PLATFORM: # pragma: no cover _meta["invalid_path_chars"] =\ ''.join(six.unichr(n) for n in range(31)) + '\\:*?"<>|' else: @@ -196,12 +196,12 @@ def _make_access_from_stat(cls, stat_result): import pwd try: access['group'] = grp.getgrgid(gid).gr_name - except KeyError: # pragma: nocover + except KeyError: # pragma: no cover pass try: access['user'] = pwd.getpwuid(uid).pw_name - except KeyError: # pragma: nocover + except KeyError: # pragma: no cover pass return access @@ -339,11 +339,11 @@ def remove(self, path): except OSError as error: if error.errno == errno.EACCES and sys.platform == "win32": # sometimes windows says this for attempts to remove a dir - if os.path.isdir(sys_path): # pragma: nocover + if os.path.isdir(sys_path): # pragma: no cover raise errors.FileExpected(path) if error.errno == errno.EPERM and sys.platform == "darwin": # sometimes OSX says this for attempts to remove a dir - if os.path.isdir(sys_path): # pragma: nocover + if os.path.isdir(sys_path): # pragma: no cover raise errors.FileExpected(path) raise diff --git a/fs/path.py b/fs/path.py index 026a307c..9819fb58 100644 --- a/fs/path.py +++ b/fs/path.py @@ -461,7 +461,7 @@ def isbase(path1, path2): """ _path1 = forcedir(abspath(path1)) _path2 = forcedir(abspath(path2)) - return _path2.startswith(_path1) # longer one is child + return _path2.startswith(_path1) # longer one is child def isparent(path1, path2): diff --git a/fs/permissions.py b/fs/permissions.py index 5a9542bc..433dce12 100644 --- a/fs/permissions.py +++ b/fs/permissions.py @@ -5,10 +5,13 @@ from __future__ import unicode_literals import typing -from typing import Container, Iterable, Text +from typing import Container, Iterable import six +from ._typing import Text + + if False: # typing.TYPE_CHECKING from typing import ( Iterator, List, Optional, Tuple, Type, Union) diff --git a/fs/tarfs.py b/fs/tarfs.py index 44b7c0b5..fc7ae658 100644 --- a/fs/tarfs.py +++ b/fs/tarfs.py @@ -430,7 +430,7 @@ def isclosed(self): return self._tar.closed # type: ignore -if __name__ == "__main__": # pragma: nocover +if __name__ == "__main__": # pragma: no cover from fs.tree import render from fs.opener import open_fs diff --git a/tests/test_enums.py b/tests/test_enums.py new file mode 100644 index 00000000..fd323e60 --- /dev/null +++ b/tests/test_enums.py @@ -0,0 +1,14 @@ +import os + +from fs import enums + +import unittest + + +class TestEnums(unittest.TestCase): + + def test_enums(self): + self.assertEqual(enums.Seek.current, os.SEEK_CUR) + self.assertEqual(enums.Seek.end, os.SEEK_END) + self.assertEqual(enums.Seek.set, os.SEEK_SET) + self.assertEqual(enums.ResourceType.unknown, 0) diff --git a/tests/test_ftpfs.py b/tests/test_ftpfs.py index 17b8843a..e5fa4e69 100644 --- a/tests/test_ftpfs.py +++ b/tests/test_ftpfs.py @@ -25,6 +25,7 @@ from fs import errors from fs.opener import open_fs from fs.ftpfs import FTPFS, ftp_errors +from fs.subfs import SubFS from fs.test import FSTestCases @@ -151,6 +152,17 @@ def test_getmeta_unicode_path(self): del self.fs.features['UTF8'] self.assertFalse(self.fs.getmeta().get('unicode_paths')) + def test_opener_path(self): + self.fs.makedir('foo') + self.fs.settext('foo/bar', 'baz') + ftp_fs = open_fs( + 'ftp://user:1234@{}:{}/foo'.format( + self.server.host, self.server.port + ) + ) + self.assertIsInstance(ftp_fs, SubFS) + self.assertEqual(ftp_fs.gettext('bar'), 'baz') + ftp_fs.close() class TestFTPFSNoMLSD(TestFTPFS): diff --git a/tests/test_info.py b/tests/test_info.py index 0de4804e..c7b2cddd 100644 --- a/tests/test_info.py +++ b/tests/test_info.py @@ -157,4 +157,4 @@ def test_suffix(self): }) self.assertEqual(info.suffix, '') self.assertEqual(info.suffixes, []) - self.assertEqual(info.stem, '.foo') \ No newline at end of file + self.assertEqual(info.stem, '.foo') diff --git a/tox.ini b/tox.ini index d705e8b3..8d311660 100644 --- a/tox.ini +++ b/tox.ini @@ -1,23 +1,16 @@ [tox] -envlist = py27,py34,py35,py36,pypy +envlist = {py27,py34,py35,py36}{,-scandir},pypy sitepackages = False +skip_missing_interpreters=True [testenv] deps = appdirs + coverage + enum34 nose mock pyftpdlib pytz - scandir -#changedir=.tox -commands = nosetests tests -v \ + scandir: scandir +commands = nosetests --with-coverage --cover-package=fs --cover-package=fs.opener tests \ [] - -[testenv:pure-python] -deps = appdirs - nose - mock - pyftpdlib - pytz -commands = nosetests tests -v \ - []