Commit 63ae726c by willmcgugan@gmail.com

Test fixes and preparations for 0.5.0 release

parent 0cae6ca1
Copyright (c) 2009-2014, Will McGugan <will@willmcgugan.com> and contributors. Copyright (c) 2009-2015, Will McGugan <will@willmcgugan.com> and contributors.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
......
include AUTHORS include AUTHORS
include README.txt
include LICENSE.txt
include CHANGES.txt
\ No newline at end of file
PyFilesystem
============
PyFilesystem is an abstraction layer for *filesystems*. In the same way that Python's file-like objects provide a common way of accessing files, PyFilesystem provides a common way of accessing entire filesystems. You can write platform-independent code to work with local files, that also works with any of the supported filesystems (zip, ftp, S3 etc.).
Pyfilesystem works with Linux, Windows and Mac.
Suported Filesystems
---------------------
Here are a few of the filesystems that can be accessed with Pyfilesystem:
* **DavFS** access files & directories on a WebDAV server
* **FTPFS** access files & directories on an FTP server
* **MemoryFS** access files & directories stored in memory (non-permanent but very fast)
* **MountFS** creates a virtual directory structure built from other filesystems
* **MultiFS** a virtual filesystem that combines a list of filesystems in to one, and checks them in order when opening files
* **OSFS** the native filesystem
* **SFTPFS** access files & directores stored on a Secure FTP server
* **S3FS** access files & directories stored on Amazon S3 storage
* **TahoeLAFS** access files & directories stored on a Tahoe distributed filesystem
* **ZipFS** access files and directories contained in a zip file
Example
-------
The following snippet prints the total number of bytes contained in all your Python files in `C:/projects` (including sub-directories)::
from fs.osfs import OSFS
projects_fs = OSFS('C:/projects')
print sum(projects_fs.getsize(path)
for path in projects_fs.walkfiles(wildcard="*.py"))
That is, assuming you are on Windows and have a directory called 'projects' in your C drive. If you are on Linux / Mac, you might replace the second line with something like::
projects_fs = OSFS('~/projects')
If you later want to display the total size of Python files stored in a zip file, you could make the following change to the first two lines::
from fs.zipfs import ZipFS
projects_fs = ZipFS('source.zip')
In fact, you could use any of the supported filesystems above, and the code would continue to work as before.
An alternative to explicity importing the filesystem class you want, is to use an FS opener which opens a filesystem from a URL-like syntax::
from fs.opener import fsopendir
projects_fs = fsopendir('C:/projects')
You could change ``C:/projects`` to ``zip://source.zip`` to open the zip file, or even ``ftp://ftp.example.org/code/projects/`` to sum up the bytes of Python stored on an ftp server.
Screencast
----------
This is from an early version of PyFilesystem, but still relevant
http://vimeo.com/12680842
Discussion Group
----------------
http://groups.google.com/group/pyfilesystem-discussion
Further Information
-------------------
http://www.willmcgugan.com/tag/fs/
\ No newline at end of file
...@@ -21,6 +21,7 @@ __all__ = ['UserDataFS', ...@@ -21,6 +21,7 @@ __all__ = ['UserDataFS',
'UserCacheFS', 'UserCacheFS',
'UserLogFS'] 'UserLogFS']
class UserDataFS(OSFS): class UserDataFS(OSFS):
"""A filesystem for per-user application data.""" """A filesystem for per-user application data."""
def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True): def __init__(self, appname, appauthor=None, version=None, roaming=False, create=True):
......
import warnings import warnings
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
import sys
from optparse import OptionParser
from fs.opener import opener, OpenerError, Opener from fs.opener import opener, OpenerError, Opener
from fs.errors import FSError from fs.errors import FSError
from fs.path import splitext, pathsplit, isdotfile, iswildcard from fs.path import splitext, pathsplit, isdotfile, iswildcard
import re
import sys
import platform import platform
from collections import defaultdict
import six import six
from optparse import OptionParser
from collections import defaultdict
if platform.system() == 'Windows':
if platform.system() == 'Windows':
def getTerminalSize(): def getTerminalSize():
try: try:
## {{{ http://code.activestate.com/recipes/440694/ (r3) ## {{{ http://code.activestate.com/recipes/440694/ (r3)
...@@ -32,13 +34,12 @@ if platform.system() == 'Windows': ...@@ -32,13 +34,12 @@ if platform.system() == 'Windows':
sizex = right - left + 1 sizex = right - left + 1
sizey = bottom - top + 1 sizey = bottom - top + 1
else: else:
sizex, sizey = 80, 25 # can't determine actual size - return default values sizex, sizey = 80, 25 # can't determine actual size - return default values
return sizex, sizey return sizex, sizey
except: except:
return 80, 25 return 80, 25
else: else:
def getTerminalSize(): def getTerminalSize():
def ioctl_GWINSZ(fd): def ioctl_GWINSZ(fd):
try: try:
...@@ -65,11 +66,13 @@ else: ...@@ -65,11 +66,13 @@ else:
pass pass
return 80, 25 return 80, 25
def _unicode(text): def _unicode(text):
if not isinstance(text, unicode): if not isinstance(text, unicode):
return text.decode('ascii', 'replace') return text.decode('ascii', 'replace')
return text return text
class Command(object): class Command(object):
usage = '' usage = ''
...@@ -146,6 +149,7 @@ class Command(object): ...@@ -146,6 +149,7 @@ class Command(object):
if not self.terminal_colors: if not self.terminal_colors:
return text return text
re_fs = r'(\S*?://\S*)' re_fs = r'(\S*?://\S*)'
def repl(matchobj): def repl(matchobj):
fs_url = matchobj.group(0) fs_url = matchobj.group(0)
return self.wrap_link(fs_url) return self.wrap_link(fs_url)
......
...@@ -31,6 +31,16 @@ class RPCFSInterface(object): ...@@ -31,6 +31,16 @@ class RPCFSInterface(object):
the contents of files. the contents of files.
""" """
# info keys are restricted to a subset known to work over xmlrpc
# This fixes an issue with transporting Longs on Py3
_allowed_info = ["size",
"created_time",
"modified_time",
"accessed_time",
"st_size",
"st_mode",
"type"]
def __init__(self, fs): def __init__(self, fs):
super(RPCFSInterface, self).__init__() super(RPCFSInterface, self).__init__()
self.fs = fs self.fs = fs
...@@ -118,6 +128,8 @@ class RPCFSInterface(object): ...@@ -118,6 +128,8 @@ class RPCFSInterface(object):
def getinfo(self, path): def getinfo(self, path):
path = self.decode_path(path) path = self.decode_path(path)
info = self.fs.getinfo(path) info = self.fs.getinfo(path)
info = dict((k, v) for k, v in info.iteritems()
if k in self._allowed_info)
return info return info
def desc(self, path): def desc(self, path):
......
...@@ -188,8 +188,6 @@ def make_bytes_io(data, encoding=None, errors=None): ...@@ -188,8 +188,6 @@ def make_bytes_io(data, encoding=None, errors=None):
return io.BytesIO(data) return io.BytesIO(data)
def copy_file_to_fs(f, fs, path, encoding=None, errors=None, progress_callback=None, chunk_size=64 * 1024): def copy_file_to_fs(f, fs, path, encoding=None, errors=None, progress_callback=None, chunk_size=64 * 1024):
"""Copy an open file to a path on an FS""" """Copy an open file to a path on an FS"""
if progress_callback is None: if progress_callback is None:
......
...@@ -31,6 +31,7 @@ def _check_mode(mode, mode_chars): ...@@ -31,6 +31,7 @@ def _check_mode(mode, mode_chars):
return False return False
return True return True
class MemoryFile(object): class MemoryFile(object):
def seek_and_lock(f): def seek_and_lock(f):
...@@ -71,7 +72,6 @@ class MemoryFile(object): ...@@ -71,7 +72,6 @@ class MemoryFile(object):
finally: finally:
lock.release() lock.release()
assert self.mem_file is not None, "self.mem_file should have a value" assert self.mem_file is not None, "self.mem_file should have a value"
def __str__(self): def __str__(self):
...@@ -163,7 +163,7 @@ class MemoryFile(object): ...@@ -163,7 +163,7 @@ class MemoryFile(object):
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self,exc_type,exc_value,traceback): def __exit__(self, exc_type, exc_value, traceback):
self.close() self.close()
return False return False
...@@ -218,7 +218,7 @@ class DirEntry(object): ...@@ -218,7 +218,7 @@ class DirEntry(object):
if self.isfile(): if self.isfile():
return "<file %s>" % self.name return "<file %s>" % self.name
elif self.isdir(): elif self.isdir():
return "<dir %s>" % "".join( "%s: %s" % (k, v.desc_contents()) for k, v in self.contents.iteritems()) return "<dir %s>" % "".join("%s: %s" % (k, v.desc_contents()) for k, v in self.contents.iteritems())
def isdir(self): def isdir(self):
return self.type == "dir" return self.type == "dir"
...@@ -248,24 +248,23 @@ class DirEntry(object): ...@@ -248,24 +248,23 @@ class DirEntry(object):
self.mem_file = StringIO() self.mem_file = StringIO()
self.mem_file.write(data) self.mem_file.write(data)
class MemoryFS(FS):
class MemoryFS(FS):
"""An in-memory filesystem. """An in-memory filesystem.
""" """
_meta = {'thread_safe' : True, _meta = {'thread_safe': True,
'network' : False, 'network': False,
'virtual': False, 'virtual': False,
'read_only' : False, 'read_only': False,
'unicode_paths' : True, 'unicode_paths': True,
'case_insensitive_paths' : False, 'case_insensitive_paths': False,
'atomic.move' : False, 'atomic.move': False,
'atomic.copy' : False, 'atomic.copy': False,
'atomic.makedir' : True, 'atomic.makedir': True,
'atomic.rename' : True, 'atomic.rename': True,
'atomic.setcontents' : False, 'atomic.setcontents': False}
}
def _make_dir_entry(self, *args, **kwargs): def _make_dir_entry(self, *args, **kwargs):
return self.dir_entry_factory(*args, **kwargs) return self.dir_entry_factory(*args, **kwargs)
......
...@@ -305,7 +305,8 @@ class RPCFS(FS): ...@@ -305,7 +305,8 @@ class RPCFS(FS):
@synchronize @synchronize
def getinfo(self, path): def getinfo(self, path):
path = self.encode_path(path) path = self.encode_path(path)
return self.proxy.getinfo(path) info = self.proxy.getinfo(path)
return info
@synchronize @synchronize
def desc(self, path): def desc(self, path):
......
...@@ -163,8 +163,8 @@ class FSTestCases(object): ...@@ -163,8 +163,8 @@ class FSTestCases(object):
b("to you, good sir!")), chunk_size=2) b("to you, good sir!")), chunk_size=2)
self.assertEquals(self.fs.getcontents( self.assertEquals(self.fs.getcontents(
"hello", "rb"), b("to you, good sir!")) "hello", "rb"), b("to you, good sir!"))
self.fs.setcontents("hello", b"") self.fs.setcontents("hello", b(""))
self.assertEquals(self.fs.getcontents("hello", "rb"), "") self.assertEquals(self.fs.getcontents("hello", "rb"), b(""))
def test_setcontents_async(self): def test_setcontents_async(self):
# setcontents() should accept both a string... # setcontents() should accept both a string...
......
...@@ -7,7 +7,7 @@ from setuptools import setup ...@@ -7,7 +7,7 @@ from setuptools import setup
import sys import sys
PY3 = sys.version_info >= (3,) PY3 = sys.version_info >= (3,)
VERSION = "0.4.1" VERSION = "0.5.0"
COMMANDS = ['fscat', COMMANDS = ['fscat',
'fscp', 'fscp',
...@@ -34,10 +34,9 @@ classifiers = [ ...@@ -34,10 +34,9 @@ classifiers = [
'Topic :: System :: Filesystems', 'Topic :: System :: Filesystems',
] ]
long_desc = """Pyfilesystem is a module that provides a simplified common interface to many types of filesystem. Filesystems exposed via Pyfilesystem can also be served over the network, or 'mounted' on the native filesystem. with open('README.txt', 'r') as f:
long_desc = f.read()
Even if you only need to work with file and directories on the local hard-drive, Pyfilesystem can simplify your code and make it more robust -- with the added advantage that you can change where the files are located by changing a single line of code.
"""
extra = {} extra = {}
if PY3: if PY3:
...@@ -46,13 +45,14 @@ if PY3: ...@@ -46,13 +45,14 @@ if PY3:
setup(install_requires=['distribute', 'six'], setup(install_requires=['distribute', 'six'],
name='fs', name='fs',
version=VERSION, version=VERSION,
description="Filesystem abstraction", description="Filesystem abstraction layer",
long_description=long_desc, long_description=long_desc,
license="BSD", license="BSD",
author="Will McGugan", author="Will McGugan",
author_email="will@willmcgugan.com", author_email="will@willmcgugan.com",
url="http://code.google.com/p/pyfilesystem/", #url="http://code.google.com/p/pyfilesystem/",
download_url="http://code.google.com/p/pyfilesystem/downloads/list", #download_url="http://code.google.com/p/pyfilesystem/downloads/list",
url="http://pypi.python.org/pypi/fs/"
platforms=['any'], platforms=['any'],
packages=['fs', packages=['fs',
'fs.expose', 'fs.expose',
......
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