Commit fb95d7be by willmcgugan

Added better zip exceptions, and added __all__ to core classes

parent 8a504419
...@@ -10,6 +10,13 @@ start by sublcassing the base FS class. ...@@ -10,6 +10,13 @@ start by sublcassing the base FS class.
""" """
__all__ = ['DummyLock',
'silence_fserrors',
'NullFile',
'synchronize',
'FS',
'flags_to_mode']
import os, os.path import os, os.path
import sys import sys
import shutil import shutil
...@@ -23,9 +30,10 @@ except ImportError: ...@@ -23,9 +30,10 @@ except ImportError:
from fs.path import * from fs.path import *
from fs.errors import * from fs.errors import *
from fs.functools import wraps
class DummyLock: class DummyLock(object):
"""A dummy lock object that doesn't do anything. """A dummy lock object that doesn't do anything.
This is used as a placeholder when locking is disabled. We can't This is used as a placeholder when locking is disabled. We can't
...@@ -102,11 +110,6 @@ class NullFile(object): ...@@ -102,11 +110,6 @@ class NullFile(object):
def writelines(self, *args, **kwargs): def writelines(self, *args, **kwargs):
pass pass
try:
from functools import wraps
except ImportError:
wraps = lambda f: lambda f: f
def synchronize(func): def synchronize(func):
"""Decorator to synchronize a method on self._lock.""" """Decorator to synchronize a method on self._lock."""
...@@ -119,6 +122,7 @@ def synchronize(func): ...@@ -119,6 +122,7 @@ def synchronize(func):
self._lock.release() self._lock.release()
return acquire_lock return acquire_lock
class FS(object): class FS(object):
"""The base class for Filesystem abstraction objects. """The base class for Filesystem abstraction objects.
...@@ -489,7 +493,7 @@ class FS(object): ...@@ -489,7 +493,7 @@ class FS(object):
f = None f = None
try: try:
f = self.open(path, 'wb') f = self.open(path, 'wb')
if hasattr(data,"read"): if hasattr(data, "read"):
chunk = data.read(1024*512) chunk = data.read(1024*512)
while chunk: while chunk:
f.write(chunk) f.write(chunk)
......
...@@ -5,15 +5,33 @@ All Exception classes are derived from `FSError` which can be used as a catch-al ...@@ -5,15 +5,33 @@ All Exception classes are derived from `FSError` which can be used as a catch-al
""" """
__all__ = ['FSError',
'CreateFailedError',
'PathError',
'OperationFailedError',
'UnsupportedError',
'RemoteConnectionError',
'StorageSpaceError',
'PermissionDeniedError',
'FSClosedError',
'OperationTimeoutError',
'ResourceError',
'NoSysPathError',
'ResourceNotFoundError',
'ResourceInvalidError',
'DestinationExistsError',
'DirectoryNotEmptyError',
'ParentDirectoryMissingError',
'ResourceLockedError',
'convert_fs_errors',
'convert_os_errors'
]
import sys import sys
import errno import errno
from fs.path import * from fs.path import *
from fs.functools import wraps
try:
from functools import wraps
except ImportError:
wraps = lambda f: lambda f: f
class FSError(Exception): class FSError(Exception):
...@@ -41,6 +59,11 @@ class FSError(Exception): ...@@ -41,6 +59,11 @@ class FSError(Exception):
return self.__dict__.copy() return self.__dict__.copy()
class CreateFailedError(FSError):
"""An exception thrown when a FS could not be created"""
default_message = "Unable to create filesystem"
class PathError(FSError): class PathError(FSError):
"""Exception for errors to do with a path string. """Exception for errors to do with a path string.
""" """
......
...@@ -58,6 +58,7 @@ import pickle ...@@ -58,6 +58,7 @@ import pickle
from fs.base import flags_to_mode, threading from fs.base import flags_to_mode, threading
from fs.errors import * from fs.errors import *
from fs.path import * from fs.path import *
from fs.functools import wraps
try: try:
import fuse_ctypes as fuse import fuse_ctypes as fuse
......
...@@ -36,9 +36,7 @@ import paramiko ...@@ -36,9 +36,7 @@ import paramiko
from fs.base import flags_to_mode from fs.base import flags_to_mode
from fs.path import * from fs.path import *
from fs.errors import * from fs.errors import *
from fs.functools import wraps
from fs.errors import wraps
# Default host key used by BaseSFTPServer # Default host key used by BaseSFTPServer
......
...@@ -10,7 +10,8 @@ __all__ = ['FTPFS'] ...@@ -10,7 +10,8 @@ __all__ = ['FTPFS']
import fs import fs
from fs.base import * from fs.base import *
from fs.path import pathsplit from fs.errors import *
from fs.path import pathsplit, abspath, dirname, recursepath, normpath
from ftplib import FTP, error_perm, error_temp, error_proto, error_reply from ftplib import FTP, error_perm, error_temp, error_proto, error_reply
...@@ -26,10 +27,7 @@ from time import sleep ...@@ -26,10 +27,7 @@ from time import sleep
import datetime import datetime
import re import re
from socket import error as socket_error from socket import error as socket_error
try: from fs.functools import wraps
from functools import wraps
except ImportError:
wraps = lambda f: lambda f: f
try: try:
from cStringIO import StringIO from cStringIO import StringIO
......
...@@ -11,8 +11,9 @@ If you open a file from a `memoryfs` you will get back a StringIO object from th ...@@ -11,8 +11,9 @@ If you open a file from a `memoryfs` you will get back a StringIO object from th
""" """
import datetime import datetime
from fs.path import iteratepath from fs.path import iteratepath, pathsplit, normpath
from fs.base import * from fs.base import *
from fs.errors import *
from fs import _thread_synchronize_default from fs import _thread_synchronize_default
try: try:
...@@ -131,7 +132,6 @@ class MemoryFile(object): ...@@ -131,7 +132,6 @@ class MemoryFile(object):
return False return False
class DirEntry(object): class DirEntry(object):
def __init__(self, type, name, contents=None): def __init__(self, type, name, contents=None):
...@@ -507,8 +507,7 @@ class MemoryFS(FS): ...@@ -507,8 +507,7 @@ class MemoryFS(FS):
info['size'] = len(dir_entry.data or '') info['size'] = len(dir_entry.data or '')
info['st_mode'] = 0666 info['st_mode'] = 0666
return info return info
@synchronize @synchronize
def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384):
......
...@@ -43,6 +43,8 @@ Now both filesystems can be accessed with the same path structure:: ...@@ -43,6 +43,8 @@ Now both filesystems can be accessed with the same path structure::
""" """
from fs.base import * from fs.base import *
from fs.errors import *
from fs.path import *
from fs.objecttree import ObjectTree from fs.objecttree import ObjectTree
from fs import _thread_synchronize_default from fs import _thread_synchronize_default
......
...@@ -54,6 +54,7 @@ directories:: ...@@ -54,6 +54,7 @@ directories::
from fs.base import FS, FSError, synchronize from fs.base import FS, FSError, synchronize
from fs.path import * from fs.path import *
from fs.errors import *
from fs import _thread_synchronize_default from fs import _thread_synchronize_default
from fs.errors import ResourceNotFoundError from fs.errors import ResourceNotFoundError
......
...@@ -16,8 +16,10 @@ For example, to print all the files and directories in the OS root:: ...@@ -16,8 +16,10 @@ For example, to print all the files and directories in the OS root::
import os import os
import sys import sys
import errno import errno
import datetime
from fs.base import * from fs.base import *
from fs.errors import *
from fs.path import * from fs.path import *
from fs import _thread_synchronize_default from fs import _thread_synchronize_default
......
...@@ -269,6 +269,7 @@ def forcedir(path): ...@@ -269,6 +269,7 @@ def forcedir(path):
return path + '/' return path + '/'
return path return path
def frombase(path1, path2): def frombase(path1, path2):
if not isprefix(path1, path2): if not isprefix(path1, path2):
raise ValueError("path1 must be a prefix of path2") raise ValueError("path1 must be a prefix of path2")
......
...@@ -29,6 +29,7 @@ from fs.wrapfs import WrapFS, wrap_fs_methods ...@@ -29,6 +29,7 @@ from fs.wrapfs import WrapFS, wrap_fs_methods
from fs.wrapfs.lazyfs import LazyFS from fs.wrapfs.lazyfs import LazyFS
from fs.path import * from fs.path import *
from fs.errors import * from fs.errors import *
from fs.functools import wraps
try: try:
from tempfile import SpooledTemporaryFile from tempfile import SpooledTemporaryFile
......
...@@ -11,6 +11,8 @@ class from the :mod:`fs.expose.xmlrpc` module. ...@@ -11,6 +11,8 @@ class from the :mod:`fs.expose.xmlrpc` module.
import xmlrpclib import xmlrpclib
from fs.base import * from fs.base import *
from fs.errors import *
from fs.path import *
from StringIO import StringIO from StringIO import StringIO
if hasattr(StringIO,"__exit__"): if hasattr(StringIO,"__exit__"):
......
...@@ -19,6 +19,8 @@ from boto.s3.prefix import Prefix ...@@ -19,6 +19,8 @@ from boto.s3.prefix import Prefix
from boto.exception import S3ResponseError from boto.exception import S3ResponseError
from fs.base import * from fs.base import *
from fs.path import *
from fs.errors import *
from fs.remote import * from fs.remote import *
......
...@@ -8,13 +8,16 @@ Filesystem accessing an SFTP server (via paramiko) ...@@ -8,13 +8,16 @@ Filesystem accessing an SFTP server (via paramiko)
import datetime import datetime
import stat as statinfo import stat as statinfo
import threading
import paramiko import paramiko
from fs.base import * from fs.base import *
from fs.path import *
from fs.errors import *
# SFTPClient appears to not be thread-safe, so we use an instance per thread # SFTPClient appears to not be thread-safe, so we use an instance per thread
if hasattr(threading,"local"): if hasattr(threading, "local"):
thread_local = threading.local thread_local = threading.local
else: else:
class thread_local(object): class thread_local(object):
......
...@@ -12,6 +12,7 @@ import tempfile ...@@ -12,6 +12,7 @@ import tempfile
from fs.osfs import OSFS from fs.osfs import OSFS
from fs.errors import * from fs.errors import *
from fs import _thread_synchronize_default from fs import _thread_synchronize_default
class TempFS(OSFS): class TempFS(OSFS):
......
...@@ -12,6 +12,8 @@ import logging ...@@ -12,6 +12,8 @@ import logging
logging.basicConfig(level=logging.ERROR, stream=sys.stdout) logging.basicConfig(level=logging.ERROR, stream=sys.stdout)
from fs.base import * from fs.base import *
from fs.path import *
from fs.errors import *
import datetime import datetime
import unittest import unittest
......
...@@ -46,12 +46,12 @@ class TestPathFunctions(unittest.TestCase): ...@@ -46,12 +46,12 @@ class TestPathFunctions(unittest.TestCase):
for testpaths in tests: for testpaths in tests:
paths = testpaths[:-1] paths = testpaths[:-1]
result = testpaths[-1] result = testpaths[-1]
self.assertEqual(fs.pathjoin(*paths), result) self.assertEqual(pathjoin(*paths), result)
self.assertRaises(ValueError, fs.pathjoin, "../") self.assertRaises(ValueError, pathjoin, "../")
self.assertRaises(ValueError, fs.pathjoin, "./../") self.assertRaises(ValueError, pathjoin, "./../")
self.assertRaises(ValueError, fs.pathjoin, "a/b", "../../..") self.assertRaises(ValueError, pathjoin, "a/b", "../../..")
self.assertRaises(ValueError, fs.pathjoin, "a/b/../../../d") self.assertRaises(ValueError, pathjoin, "a/b/../../../d")
def test_relpath(self): def test_relpath(self):
tests = [ ("/a/b", "a/b"), tests = [ ("/a/b", "a/b"),
...@@ -59,7 +59,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -59,7 +59,7 @@ class TestPathFunctions(unittest.TestCase):
("/", "") ] ("/", "") ]
for path, result in tests: for path, result in tests:
self.assertEqual(fs.relpath(path), result) self.assertEqual(relpath(path), result)
def test_abspath(self): def test_abspath(self):
tests = [ ("/a/b", "/a/b"), tests = [ ("/a/b", "/a/b"),
...@@ -67,7 +67,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -67,7 +67,7 @@ class TestPathFunctions(unittest.TestCase):
("/", "/") ] ("/", "/") ]
for path, result in tests: for path, result in tests:
self.assertEqual(fs.abspath(path), result) self.assertEqual(abspath(path), result)
def test_iteratepath(self): def test_iteratepath(self):
tests = [ ("a/b", ["a", "b"]), tests = [ ("a/b", ["a", "b"]),
...@@ -92,7 +92,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -92,7 +92,7 @@ class TestPathFunctions(unittest.TestCase):
("foo/bar/baz", ("foo/bar", "baz")), ("foo/bar/baz", ("foo/bar", "baz")),
] ]
for path, result in tests: for path, result in tests:
self.assertEqual(fs.pathsplit(path), result) self.assertEqual(pathsplit(path), result)
def test_recursepath(self): def test_recursepath(self):
self.assertEquals(recursepath("/"),["/"]) self.assertEquals(recursepath("/"),["/"])
......
...@@ -10,12 +10,14 @@ import unittest ...@@ -10,12 +10,14 @@ import unittest
import threading import threading
import random import random
import time import time
import sys
from fs.remote import * from fs.remote import *
from fs.wrapfs import WrapFS, wrap_fs_methods from fs.wrapfs import WrapFS, wrap_fs_methods
from fs.tempfs import TempFS from fs.tempfs import TempFS
from fs.path import * from fs.path import *
from fs.functools import wraps
class TestCacheFS(unittest.TestCase,FSTestCases,ThreadingTestCases): class TestCacheFS(unittest.TestCase,FSTestCases,ThreadingTestCases):
......
...@@ -4,6 +4,14 @@ The `utils` module provides a number of utility functions that don't belong in t ...@@ -4,6 +4,14 @@ The `utils` module provides a number of utility functions that don't belong in t
""" """
__all__ = ['copyfile',
'movefile',
'movedir',
'copydir',
'countbytes',
'find_duplicates',
'print_fs']
import shutil import shutil
import os import os
import sys import sys
...@@ -12,6 +20,7 @@ from fs.path import pathjoin, pathsplit ...@@ -12,6 +20,7 @@ from fs.path import pathjoin, pathsplit
from fs.errors import DestinationExistsError from fs.errors import DestinationExistsError
def copyfile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=16384): def copyfile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=16384):
"""Copy a file from one filesystem to another. Will use system copyfile, if both files have a syspath. """Copy a file from one filesystem to another. Will use system copyfile, if both files have a syspath.
Otherwise file will be copied a chunk at a time. Otherwise file will be copied a chunk at a time.
......
...@@ -20,6 +20,8 @@ from fnmatch import fnmatch ...@@ -20,6 +20,8 @@ from fnmatch import fnmatch
from fs.base import FS, threading, synchronize from fs.base import FS, threading, synchronize
from fs.errors import * from fs.errors import *
from fs.functools import wraps
def rewrite_errors(func): def rewrite_errors(func):
"""Re-write paths in errors raised by wrapped FS objects.""" """Re-write paths in errors raised by wrapped FS objects."""
......
...@@ -7,6 +7,7 @@ An FS wrapper class for hiding dot-files in directory listings. ...@@ -7,6 +7,7 @@ An FS wrapper class for hiding dot-files in directory listings.
""" """
from fs.wrapfs import WrapFS from fs.wrapfs import WrapFS
from fs.path import *
class HideDotFilesFS(WrapFS): class HideDotFilesFS(WrapFS):
......
...@@ -16,6 +16,7 @@ except ImportError: ...@@ -16,6 +16,7 @@ except ImportError:
from fs.base import FS from fs.base import FS
from fs.wrapfs import WrapFS from fs.wrapfs import WrapFS
from fs.path import *
class LazyFS(WrapFS): class LazyFS(WrapFS):
......
...@@ -11,6 +11,7 @@ total size of files stored in the wrapped FS. ...@@ -11,6 +11,7 @@ total size of files stored in the wrapped FS.
# for Python2.5 compatibility # for Python2.5 compatibility
from __future__ import with_statement from __future__ import with_statement
from fs.errors import * from fs.errors import *
from fs.path import *
from fs.base import FS, threading, synchronize from fs.base import FS, threading, synchronize
from fs.wrapfs import WrapFS from fs.wrapfs import WrapFS
......
...@@ -7,8 +7,10 @@ A FS object that represents the contents of a Zip file ...@@ -7,8 +7,10 @@ A FS object that represents the contents of a Zip file
""" """
from fs.base import * from fs.base import *
from fs.path import *
from fs.errors import *
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED, BadZipfile, LargeZipFile
from memoryfs import MemoryFS from memoryfs import MemoryFS
try: try:
...@@ -18,6 +20,17 @@ except ImportError: ...@@ -18,6 +20,17 @@ except ImportError:
import tempfs import tempfs
class ZipOpenError(CreateFailedError):
"""Thrown when the zip file could not be opened"""
pass
class ZipMissingError(CreateFailedError):
"""Thrown when the requested zip file does not exist"""
pass
class _TempWriteFile(object): class _TempWriteFile(object):
"""Proxies a file object and calls a callback when the file is closed.""" """Proxies a file object and calls a callback when the file is closed."""
...@@ -38,6 +51,7 @@ class _TempWriteFile(object): ...@@ -38,6 +51,7 @@ class _TempWriteFile(object):
self._file.close() self._file.close()
self.close_callback(self.filename) self.close_callback(self.filename)
class _ExceptionProxy(object): class _ExceptionProxy(object):
"""A placeholder for an object that may no longer be used.""" """A placeholder for an object that may no longer be used."""
...@@ -51,6 +65,7 @@ class _ExceptionProxy(object): ...@@ -51,6 +65,7 @@ class _ExceptionProxy(object):
def __nonzero__(self): def __nonzero__(self):
return False return False
class ZipFS(FS): class ZipFS(FS):
"""A FileSystem that represents a zip file.""" """A FileSystem that represents a zip file."""
...@@ -64,6 +79,8 @@ class ZipFS(FS): ...@@ -64,6 +79,8 @@ class ZipFS(FS):
:param allow_zip_64: -- Set to True to use zip files greater than 2 GB, default is False :param allow_zip_64: -- Set to True to use zip files greater than 2 GB, default is False
:param encoding: -- The encoding to use for unicode filenames :param encoding: -- The encoding to use for unicode filenames
:param thread_synchronize: -- Set to True (default) to enable thread-safety :param thread_synchronize: -- Set to True (default) to enable thread-safety
:raises ZipOpenError: Thrown when the zip file could not be opened
:raises ZipMissingError: Thrown when the zip file does not exist (derived from ZipOpenError)
""" """
super(ZipFS, self).__init__(thread_synchronize=thread_synchronize) super(ZipFS, self).__init__(thread_synchronize=thread_synchronize)
...@@ -81,8 +98,16 @@ class ZipFS(FS): ...@@ -81,8 +98,16 @@ class ZipFS(FS):
self.encoding = encoding self.encoding = encoding
try: try:
self.zf = ZipFile(zip_file, mode, compression_type, allow_zip_64) self.zf = ZipFile(zip_file, mode, compression_type, allow_zip_64)
except IOError: except BadZipfile, bzf:
raise ResourceNotFoundError(str(zip_file), msg="Zip file does not exist: %(path)s") raise ZipOpenError("Not a zip file or corrupt (%s)" % str(zip_file),
details=bzf)
except IOError, ioe:
if str(ioe).startswith('[Errno 22] Invalid argument'):
raise ZipOpenError("Not a zip file or corrupt (%s)" % str(zip_file),
details=bzf)
raise ZipMissingError("Zip file not found (%s)" % str(zip_file),
details=ioe)
self.zip_path = str(zip_file) self.zip_path = str(zip_file)
self.temp_fs = None self.temp_fs = None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment