Commit 6bcbae3c by willmcgugan@gmail.com

Fix for missing ResourceNotFound error in fs.walk

parent dc1fe703
...@@ -47,7 +47,7 @@ class DummyLock(object): ...@@ -47,7 +47,7 @@ class DummyLock(object):
directly use the Lock class from the dummy_threading module, since directly use the Lock class from the dummy_threading module, since
it attempts to sanity-check the sequence of acquire/release calls it attempts to sanity-check the sequence of acquire/release calls
in a way that breaks when real threading is available. in a way that breaks when real threading is available.
""" """
def acquire(self, blocking=1): def acquire(self, blocking=1):
...@@ -160,11 +160,11 @@ class FS(object): ...@@ -160,11 +160,11 @@ class FS(object):
:param thread_synconize: If True, a lock object will be created for the object, otherwise a dummy lock will be used. :param thread_synconize: If True, a lock object will be created for the object, otherwise a dummy lock will be used.
:type thread_synchronize: bool :type thread_synchronize: bool
""" """
self.closed = False self.closed = False
super(FS, self).__init__() super(FS, self).__init__()
self.thread_synchronize = thread_synchronize self.thread_synchronize = thread_synchronize
if thread_synchronize: if thread_synchronize:
self._lock = threading.RLock() self._lock = threading.RLock()
...@@ -176,7 +176,7 @@ class FS(object): ...@@ -176,7 +176,7 @@ class FS(object):
try: try:
self.close() self.close()
except: except:
pass pass
def __enter__(self): def __enter__(self):
return self return self
...@@ -204,7 +204,7 @@ class FS(object): ...@@ -204,7 +204,7 @@ class FS(object):
the filesystem object is garbage collected, but it is good practice the filesystem object is garbage collected, but it is good practice
to call it explicitly so that any attached resourced are freed when they to call it explicitly so that any attached resourced are freed when they
are no longer required. are no longer required.
""" """
self.closed = True self.closed = True
...@@ -235,33 +235,33 @@ class FS(object): ...@@ -235,33 +235,33 @@ class FS(object):
Meta values are a way for an FS implementation to report potentially Meta values are a way for an FS implementation to report potentially
useful information associated with the file system. useful information associated with the file system.
A meta key is a lower case string with no spaces. Meta keys may also A meta key is a lower case string with no spaces. Meta keys may also
be grouped in namespaces in a dotted notation, e.g. 'atomic.namespaces'. be grouped in namespaces in a dotted notation, e.g. 'atomic.namespaces'.
FS implementations aren't obliged to return any meta values, but the FS implementations aren't obliged to return any meta values, but the
following are common: following are common:
* *read_only* True if the file system cannot be modified * *read_only* True if the file system cannot be modified
* *thread_safe* True if the implementation is thread safe * *thread_safe* True if the implementation is thread safe
* *network* True if the file system requires network access * *network* True if the file system requires network access
* *unicode_paths* True if the file system supports unicode paths * *unicode_paths* True if the file system supports unicode paths
* *case_insensitive_paths* True if the file system ignores the case of paths * *case_insensitive_paths* True if the file system ignores the case of paths
* *atomic.makedir* True if making a directory is an atomic operation * *atomic.makedir* True if making a directory is an atomic operation
* *atomic.rename* True if rename is an atomic operation, (and not implemented as a copy followed by a delete) * *atomic.rename* True if rename is an atomic operation, (and not implemented as a copy followed by a delete)
* *atomic.setcontents* True if the implementation supports setting the contents of a file as an atomic operation (without opening a file) * *atomic.setcontents* True if the implementation supports setting the contents of a file as an atomic operation (without opening a file)
* *free_space* The free space (in bytes) available on the file system * *free_space* The free space (in bytes) available on the file system
* *total_space* The total space (in bytes) available on the file system * *total_space* The total space (in bytes) available on the file system
* *virtual* True if the filesystem defers to other filesystems * *virtual* True if the filesystem defers to other filesystems
FS implementations may expose non-generic meta data through a self-named namespace. e.g. ``"somefs.some_meta"`` FS implementations may expose non-generic meta data through a self-named namespace. e.g. ``"somefs.some_meta"``
Since no meta value is guaranteed to exist, it is advisable to always supply a Since no meta value is guaranteed to exist, it is advisable to always supply a
default value to ``getmeta``. default value to ``getmeta``.
:param meta_name: The name of the meta value to retrieve :param meta_name: The name of the meta value to retrieve
:param default: An option default to return, if the meta value isn't present :param default: An option default to return, if the meta value isn't present
:raises `fs.errors.NoMetaError`: If specified meta value is not present, and there is no default :raises `fs.errors.NoMetaError`: If specified meta value is not present, and there is no default
""" """
if meta_name not in self._meta: if meta_name not in self._meta:
if default is not NoDefaultMeta: if default is not NoDefaultMeta:
...@@ -271,10 +271,10 @@ class FS(object): ...@@ -271,10 +271,10 @@ class FS(object):
def hasmeta(self, meta_name): def hasmeta(self, meta_name):
"""Check that a meta value is supported """Check that a meta value is supported
:param meta_name: The name of a meta value to check :param meta_name: The name of a meta value to check
:rtype: bool :rtype: bool
""" """
try: try:
self.getmeta(meta_name) self.getmeta(meta_name)
...@@ -295,7 +295,7 @@ class FS(object): ...@@ -295,7 +295,7 @@ class FS(object):
:type allow_none: bool :type allow_none: bool
:raises `fs.errors.NoSysPathError`: if the path does not map on to a system path, and allow_none is set to False (default) :raises `fs.errors.NoSysPathError`: if the path does not map on to a system path, and allow_none is set to False (default)
:rtype: unicode :rtype: unicode
""" """
if not allow_none: if not allow_none:
raise NoSysPathError(path=path) raise NoSysPathError(path=path)
...@@ -306,25 +306,25 @@ class FS(object): ...@@ -306,25 +306,25 @@ class FS(object):
:param path: path to check :param path: path to check
:returns: True if `path` maps to a system path :returns: True if `path` maps to a system path
:rtype: bool :rtype: bool
""" """
return self.getsyspath(path, allow_none=True) is not None return self.getsyspath(path, allow_none=True) is not None
def getpathurl(self, path, allow_none=False): def getpathurl(self, path, allow_none=False):
"""Returns a url that corresponds to the given path, if one exists. """Returns a url that corresponds to the given path, if one exists.
If the path does not have an equivalent URL form (and allow_none is False) If the path does not have an equivalent URL form (and allow_none is False)
then a :class:`~fs.errors.NoPathURLError` exception is thrown. Otherwise the URL will be then a :class:`~fs.errors.NoPathURLError` exception is thrown. Otherwise the URL will be
returns as an unicode string. returns as an unicode string.
:param path: a path within the filesystem :param path: a path within the filesystem
:param allow_none: if true, this method can return None if there is no :param allow_none: if true, this method can return None if there is no
URL form of the given path URL form of the given path
:type allow_none: bool :type allow_none: bool
:raises `fs.errors.NoPathURLError`: If no URL form exists, and allow_none is False (the default) :raises `fs.errors.NoPathURLError`: If no URL form exists, and allow_none is False (the default)
:rtype: unicode :rtype: unicode
""" """
if not allow_none: if not allow_none:
raise NoPathURLError(path=path) raise NoPathURLError(path=path)
...@@ -336,7 +336,7 @@ class FS(object): ...@@ -336,7 +336,7 @@ class FS(object):
:param path: path to check :param path: path to check
:returns: True if `path` has a URL form :returns: True if `path` has a URL form
:rtype: bool :rtype: bool
""" """
return self.getpathurl(path, allow_none=True) is not None return self.getpathurl(path, allow_none=True) is not None
...@@ -445,7 +445,7 @@ class FS(object): ...@@ -445,7 +445,7 @@ class FS(object):
:param dirs_only: if True, only return directories :param dirs_only: if True, only return directories
:type dirs_only: bool :type dirs_only: bool
:param files_only: if True, only return files :param files_only: if True, only return files
:type files_only: bool :type files_only: bool
:rtype: iterable of paths :rtype: iterable of paths
...@@ -514,7 +514,7 @@ class FS(object): ...@@ -514,7 +514,7 @@ class FS(object):
that directory, this method applies the semantics of the listdir() that directory, this method applies the semantics of the listdir()
keyword arguments. An appropriately modified and filtered list of keyword arguments. An appropriately modified and filtered list of
directory entries is returned. directory entries is returned.
""" """
path = normpath(path) path = normpath(path)
if dirs_only and files_only: if dirs_only and files_only:
...@@ -552,7 +552,7 @@ class FS(object): ...@@ -552,7 +552,7 @@ class FS(object):
This method behaves identically to :py:meth:`fs.base.FS.listdir` but returns an generator This method behaves identically to :py:meth:`fs.base.FS.listdir` but returns an generator
instead of a list. Depending on the filesystem this may be more instead of a list. Depending on the filesystem this may be more
efficient than calling :py:meth:`fs.base.FS.listdir` and iterating over the resulting list. efficient than calling :py:meth:`fs.base.FS.listdir` and iterating over the resulting list.
""" """
return iter(self.listdir(path, return iter(self.listdir(path,
wildcard=wildcard, wildcard=wildcard,
...@@ -573,7 +573,7 @@ class FS(object): ...@@ -573,7 +573,7 @@ class FS(object):
instead of a list. Depending on the filesystem this may be more instead of a list. Depending on the filesystem this may be more
efficient than calling :py:meth:`~fs.base.listdirinfo` and iterating over the resulting efficient than calling :py:meth:`~fs.base.listdirinfo` and iterating over the resulting
list. list.
""" """
return iter(self.listdirinfo(path, return iter(self.listdirinfo(path,
wildcard, wildcard,
...@@ -660,7 +660,7 @@ class FS(object): ...@@ -660,7 +660,7 @@ class FS(object):
:type modified_time: datetime :type modified_time: datetime
""" """
with self._lock: with self._lock:
sys_path = self.getsyspath(path, allow_none=True) sys_path = self.getsyspath(path, allow_none=True)
if sys_path is not None: if sys_path is not None:
...@@ -684,7 +684,7 @@ class FS(object): ...@@ -684,7 +684,7 @@ class FS(object):
* "size" - Number of bytes used to store the file or directory * "size" - Number of bytes used to store the file or directory
* "created_time" - A datetime object containing the time the resource was created * "created_time" - A datetime object containing the time the resource was created
* "accessed_time" - A datetime object containing the time the resource was last accessed * "accessed_time" - A datetime object containing the time the resource was last accessed
* "modified_time" - A datetime object containing the time the resource was modified * "modified_time" - A datetime object containing the time the resource was modified
:param path: a path to retrieve information for :param path: a path to retrieve information for
...@@ -705,7 +705,7 @@ class FS(object): ...@@ -705,7 +705,7 @@ class FS(object):
:param path: A path to describe :param path: A path to describe
:rtype: str :rtype: str
""" """
if not self.exists(path): if not self.exists(path):
return '' return ''
...@@ -731,14 +731,14 @@ class FS(object): ...@@ -731,14 +731,14 @@ class FS(object):
finally: finally:
if f is not None: if f is not None:
f.close() f.close()
def setcontents(self, path, data, chunk_size=1024 * 64): def setcontents(self, path, data, chunk_size=1024 * 64):
"""A convenience method to create a new file from a string or file-like object """A convenience method to create a new file from a string or file-like object
:param path: a path of the file to create :param path: a path of the file to create
:param data: a string or a file-like object containing the contents for the new file :param data: a string or a file-like object containing the contents for the new file
:param chunk_size: Number of bytes to read in a chunk, if the implementation has to resort to a read / copy loop :param chunk_size: Number of bytes to read in a chunk, if the implementation has to resort to a read / copy loop
""" """
if not data: if not data:
...@@ -754,10 +754,10 @@ class FS(object): ...@@ -754,10 +754,10 @@ class FS(object):
finished_callback=None, finished_callback=None,
error_callback=None): error_callback=None):
"""Create a new file from a string or file-like object asynchronously """Create a new file from a string or file-like object asynchronously
This method returns a ``threading.Event`` object. Call the ``wait`` method on the event object This method returns a ``threading.Event`` object. Call the ``wait`` method on the event object
to block until all data has been written, or simply ignore it. to block until all data has been written, or simply ignore it.
:param path: a path of the file to create :param path: a path of the file to create
:param data: a string or a file-like object containing the contents for the new file :param data: a string or a file-like object containing the contents for the new file
:param chunk_size: Number of bytes to read and write in a chunk :param chunk_size: Number of bytes to read and write in a chunk
...@@ -768,7 +768,7 @@ class FS(object): ...@@ -768,7 +768,7 @@ class FS(object):
object if any error occurs during the copy process. object if any error occurs during the copy process.
:returns: An event object that is set when the copy is complete, call :returns: An event object that is set when the copy is complete, call
the `wait` method of this object to block until the data is written the `wait` method of this object to block until the data is written
""" """
finished_event = threading.Event() finished_event = threading.Event()
...@@ -780,17 +780,17 @@ class FS(object): ...@@ -780,17 +780,17 @@ class FS(object):
error_callback(e) error_callback(e)
finally: finally:
finished_event.set() finished_event.set()
threading.Thread(target=do_setcontents).start() threading.Thread(target=do_setcontents).start()
return finished_event return finished_event
def createfile(self, path, wipe=False): def createfile(self, path, wipe=False):
"""Creates an empty file if it doesn't exist """Creates an empty file if it doesn't exist
:param path: path to the file to create :param path: path to the file to create
:param wipe: if True, the contents of the file will be erased :param wipe: if True, the contents of the file will be erased
""" """
if not wipe and self.isfile(path): if not wipe and self.isfile(path):
return return
...@@ -850,6 +850,10 @@ class FS(object): ...@@ -850,6 +850,10 @@ class FS(object):
""" """
path = normpath(path) path = normpath(path)
if not self.exists(path):
raise ResourceNotFoundError(path)
def listdir(path, *args, **kwargs): def listdir(path, *args, **kwargs):
if ignore_errors: if ignore_errors:
try: try:
...@@ -1008,10 +1012,10 @@ class FS(object): ...@@ -1008,10 +1012,10 @@ class FS(object):
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
if not overwrite and self.exists(dst): if not overwrite and self.exists(dst):
raise DestinationExistsError(dst) raise DestinationExistsError(dst)
src_syspath = self.getsyspath(src, allow_none=True) src_syspath = self.getsyspath(src, allow_none=True)
dst_syspath = self.getsyspath(dst, allow_none=True) dst_syspath = self.getsyspath(dst, allow_none=True)
if src_syspath is not None and dst_syspath is not None: if src_syspath is not None and dst_syspath is not None:
self._shutil_copyfile(src_syspath, dst_syspath) self._shutil_copyfile(src_syspath, dst_syspath)
else: else:
...@@ -1052,7 +1056,7 @@ class FS(object): ...@@ -1052,7 +1056,7 @@ class FS(object):
:param dst: destination path :param dst: destination path
:type dst: string :type dst: string
:param overwrite: When True the destination will be overwritten (if it exists), :param overwrite: When True the destination will be overwritten (if it exists),
otherwise a DestinationExistsError will be thrown otherwise a DestinationExistsError will be thrown
:type overwrite: bool :type overwrite: bool
:param chunk_size: Size of chunks to use when copying, if a simple copy :param chunk_size: Size of chunks to use when copying, if a simple copy
is required is required
...@@ -1065,7 +1069,7 @@ class FS(object): ...@@ -1065,7 +1069,7 @@ class FS(object):
with self._lock: with self._lock:
src_syspath = self.getsyspath(src, allow_none=True) src_syspath = self.getsyspath(src, allow_none=True)
dst_syspath = self.getsyspath(dst, allow_none=True) dst_syspath = self.getsyspath(dst, allow_none=True)
# Try to do an os-level rename if possible. # Try to do an os-level rename if possible.
# Otherwise, fall back to copy-and-remove. # Otherwise, fall back to copy-and-remove.
if src_syspath is not None and dst_syspath is not None: if src_syspath is not None and dst_syspath is not None:
...@@ -1110,17 +1114,17 @@ class FS(object): ...@@ -1110,17 +1114,17 @@ class FS(object):
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
if not overwrite and self.exists(dst): if not overwrite and self.exists(dst):
raise DestinationExistsError(dst) raise DestinationExistsError(dst)
src_syspath = self.getsyspath(src, allow_none=True) src_syspath = self.getsyspath(src, allow_none=True)
dst_syspath = self.getsyspath(dst, allow_none=True) dst_syspath = self.getsyspath(dst, allow_none=True)
if src_syspath is not None and dst_syspath is not None: if src_syspath is not None and dst_syspath is not None:
try: try:
os.rename(src_syspath, dst_syspath) os.rename(src_syspath, dst_syspath)
return return
except OSError: except OSError:
pass pass
def movefile_noerrors(src, dst, **kwargs): def movefile_noerrors(src, dst, **kwargs):
try: try:
return self.move(src, dst, **kwargs) return self.move(src, dst, **kwargs)
...@@ -1130,27 +1134,27 @@ class FS(object): ...@@ -1130,27 +1134,27 @@ class FS(object):
movefile = movefile_noerrors movefile = movefile_noerrors
else: else:
movefile = self.move movefile = self.move
src = abspath(src) src = abspath(src)
dst = abspath(dst) dst = abspath(dst)
if dst: if dst:
self.makedir(dst, allow_recreate=overwrite) self.makedir(dst, allow_recreate=overwrite)
for dirname, filenames in self.walk(src, search="depth"): for dirname, filenames in self.walk(src, search="depth"):
dst_dirname = relpath(frombase(src, abspath(dirname))) dst_dirname = relpath(frombase(src, abspath(dirname)))
dst_dirpath = pathjoin(dst, dst_dirname) dst_dirpath = pathjoin(dst, dst_dirname)
self.makedir(dst_dirpath, allow_recreate=True, recursive=True) self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
for filename in filenames: for filename in filenames:
src_filename = pathjoin(dirname, filename) src_filename = pathjoin(dirname, filename)
dst_filename = pathjoin(dst_dirpath, filename) dst_filename = pathjoin(dst_dirpath, filename)
movefile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size) movefile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
self.removedir(dirname) self.removedir(dirname)
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):
"""copies a directory from one location to another. """copies a directory from one location to another.
...@@ -1179,24 +1183,24 @@ class FS(object): ...@@ -1179,24 +1183,24 @@ class FS(object):
copyfile = copyfile_noerrors copyfile = copyfile_noerrors
else: else:
copyfile = self.copy copyfile = self.copy
src = abspath(src) src = abspath(src)
dst = abspath(dst) dst = abspath(dst)
if not overwrite and self.exists(dst): if not overwrite and self.exists(dst):
raise DestinationExistsError(dst) raise DestinationExistsError(dst)
if dst: if dst:
self.makedir(dst, allow_recreate=True) self.makedir(dst, allow_recreate=True)
for dirname, filenames in self.walk(src): for dirname, filenames in self.walk(src):
dst_dirname = relpath(frombase(src, abspath(dirname))) dst_dirname = relpath(frombase(src, abspath(dirname)))
dst_dirpath = pathjoin(dst, dst_dirname) dst_dirpath = pathjoin(dst, dst_dirname)
self.makedir(dst_dirpath, allow_recreate=True, recursive=True) self.makedir(dst_dirpath, allow_recreate=True, recursive=True)
for filename in filenames: for filename in filenames:
src_filename = pathjoin(dirname, filename) src_filename = pathjoin(dirname, filename)
dst_filename = pathjoin(dst_dirpath, filename) dst_filename = pathjoin(dst_dirpath, filename)
copyfile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size) copyfile(src_filename, dst_filename, overwrite=overwrite, chunk_size=chunk_size)
...@@ -1228,7 +1232,7 @@ class FS(object): ...@@ -1228,7 +1232,7 @@ class FS(object):
:return: the opened dir :return: the opened dir
:rtype: an FS object :rtype: an FS object
""" """
with self._lock: with self._lock:
self.makedir(path, allow_recreate=True, recursive=recursive) self.makedir(path, allow_recreate=True, recursive=recursive)
dir_fs = self.opendir(path) dir_fs = self.opendir(path)
...@@ -1236,10 +1240,10 @@ class FS(object): ...@@ -1236,10 +1240,10 @@ class FS(object):
def printtree(self, max_levels=5): def printtree(self, max_levels=5):
"""Prints a tree structure of the FS object to the console """Prints a tree structure of the FS object to the console
:param max_levels: The maximum sub-directories to display, defaults to :param max_levels: The maximum sub-directories to display, defaults to
5. Set to None for no limit 5. Set to None for no limit
""" """
from fs.utils import print_fs from fs.utils import print_fs
print_fs(self, max_levels=max_levels) print_fs(self, max_levels=max_levels)
...@@ -1247,23 +1251,23 @@ class FS(object): ...@@ -1247,23 +1251,23 @@ class FS(object):
def browse(self, hide_dotfiles=False): def browse(self, hide_dotfiles=False):
"""Displays the FS tree in a graphical window (requires wxPython) """Displays the FS tree in a graphical window (requires wxPython)
:param hide_dotfiles: If True, files and folders that begin with a dot will be hidden :param hide_dotfiles: If True, files and folders that begin with a dot will be hidden
""" """
from fs.browsewin import browse from fs.browsewin import browse
browse(self, hide_dotfiles) browse(self, hide_dotfiles)
def getmmap(self, path, read_only=False, copy=False): def getmmap(self, path, read_only=False, copy=False):
"""Returns a mmap object for this path. """Returns a mmap object for this path.
See http://docs.python.org/library/mmap.html for more details on the mmap module. See http://docs.python.org/library/mmap.html for more details on the mmap module.
:param path: A path on this filesystem :param path: A path on this filesystem
:param read_only: If True, the mmap may not be modified :param read_only: If True, the mmap may not be modified
:param copy: If False then changes wont be written back to the file :param copy: If False then changes wont be written back to the file
:raises `fs.errors.NoMMapError`: Only paths that have a syspath can be opened as a mmap :raises `fs.errors.NoMMapError`: Only paths that have a syspath can be opened as a mmap
""" """
syspath = self.getsyspath(path, allow_none=True) syspath = self.getsyspath(path, allow_none=True)
if syspath is None: if syspath is 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