Commit 3b900b1c by willmcgugan

Python3 compatibility

parent 754fcc98
...@@ -66,8 +66,13 @@ from fs.errors import * ...@@ -66,8 +66,13 @@ from fs.errors import *
from fs.path import * from fs.path import *
from fs.local_functools import wraps from fs.local_functools import wraps
from six import PY3
try: try:
import fuse_ctypes as fuse if PY3:
import fuse3 as fuse
else:
import fuse
except NotImplementedError: except NotImplementedError:
raise ImportError("FUSE found but not usable") raise ImportError("FUSE found but not usable")
try: try:
...@@ -178,7 +183,7 @@ class FSOperations(Operations): ...@@ -178,7 +183,7 @@ class FSOperations(Operations):
# FUSE doesn't seem to pass correct mode information here - at least, # FUSE doesn't seem to pass correct mode information here - at least,
# I haven't figured out how to distinguish between "w" and "w+". # I haven't figured out how to distinguish between "w" and "w+".
# Go with the most permissive option. # Go with the most permissive option.
fh = self._reg_file(self.fs.open(path,"w+"),path) fh = self._reg_file(self.fs.open(path,"wb+"),path)
fi.fh = fh fi.fh = fh
fi.keep_cache = 0 fi.keep_cache = 0
...@@ -318,10 +323,10 @@ class FSOperations(Operations): ...@@ -318,10 +323,10 @@ class FSOperations(Operations):
def truncate(self, path, length, fh=None): def truncate(self, path, length, fh=None):
path = path.decode(NATIVE_ENCODING) path = path.decode(NATIVE_ENCODING)
if fh is None and length == 0: if fh is None and length == 0:
self.fs.open(path,"w").close() self.fs.open(path,"wb").close()
else: else:
if fh is None: if fh is None:
f = self.fs.open(path,"r+") f = self.fs.open(path,"rb+")
if not hasattr(f,"truncate"): if not hasattr(f,"truncate"):
raise UnsupportedError("truncate") raise UnsupportedError("truncate")
f.truncate(length) f.truncate(length)
...@@ -544,7 +549,7 @@ class MountProcess(subprocess.Popen): ...@@ -544,7 +549,7 @@ class MountProcess(subprocess.Popen):
os.close(w) os.close(w)
if os.read(r,1) != "S": if os.read(r,1) != "S":
self.terminate() self.terminate()
raise RuntimeError("FUSE error: " + os.read(r,20)).decode(NATIVE_ENCODING) raise RuntimeError("FUSE error: " + os.read(r,20).decode(NATIVE_ENCODING))
def unmount(self): def unmount(self):
"""Cleanly unmount the FUSE filesystem, terminating this subprocess.""" """Cleanly unmount the FUSE filesystem, terminating this subprocess."""
......
...@@ -42,6 +42,8 @@ from fs.opener import fsopendir, OpenerError ...@@ -42,6 +42,8 @@ from fs.opener import fsopendir, OpenerError
from fs.errors import * from fs.errors import *
from fs.path import * from fs.path import *
from six import b
class FSImportHook(object): class FSImportHook(object):
"""PEP-302-compliant module finder and loader for FS objects. """PEP-302-compliant module finder and loader for FS objects.
...@@ -204,9 +206,9 @@ class FSImportHook(object): ...@@ -204,9 +206,9 @@ class FSImportHook(object):
if info is None: if info is None:
info = self._get_module_info(fullname) info = self._get_module_info(fullname)
(path,type,ispkg) = info (path,type,ispkg) = info
code = self.fs.getcontents(path) code = self.fs.getcontents(path, 'rb')
if type == imp.PY_SOURCE: if type == imp.PY_SOURCE:
code = code.replace("\r\n","\n") code = code.replace(b("\r\n"),b("\n"))
return compile(code,path,"exec") return compile(code,path,"exec")
elif type == imp.PY_COMPILED: elif type == imp.PY_COMPILED:
if code[:4] != imp.get_magic(): if code[:4] != imp.get_magic():
...@@ -223,12 +225,12 @@ class FSImportHook(object): ...@@ -223,12 +225,12 @@ class FSImportHook(object):
(path,type,ispkg) = info (path,type,ispkg) = info
if type != imp.PY_SOURCE: if type != imp.PY_SOURCE:
return None return None
return self.fs.getcontents(path).replace("\r\n","\n") return self.fs.getcontents(path).replace(b("\r\n"),b("\n"))
def get_data(self,path): def get_data(self,path):
"""Read the specified data file.""" """Read the specified data file."""
try: try:
return self.fs.getcontents(path) return self.fs.getcontents(path, 'rb')
except FSError, e: except FSError, e:
raise IOError(str(e)) raise IOError(str(e))
......
...@@ -19,6 +19,9 @@ import xmlrpclib ...@@ -19,6 +19,9 @@ import xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer from SimpleXMLRPCServer import SimpleXMLRPCServer
from datetime import datetime from datetime import datetime
import six
from six import PY3, b
class RPCFSInterface(object): class RPCFSInterface(object):
"""Wrapper to expose an FS via a XML-RPC compatible interface. """Wrapper to expose an FS via a XML-RPC compatible interface.
...@@ -37,10 +40,14 @@ class RPCFSInterface(object): ...@@ -37,10 +40,14 @@ class RPCFSInterface(object):
must return something that can be represented in ASCII. The default must return something that can be represented in ASCII. The default
is base64-encoded UTF-8. is base64-encoded UTF-8.
""" """
if PY3:
return path
return path.encode("utf8").encode("base64") return path.encode("utf8").encode("base64")
def decode_path(self, path): def decode_path(self, path):
"""Decode paths arriving over the wire.""" """Decode paths arriving over the wire."""
if PY3:
return path
return path.decode("base64").decode("utf8") return path.decode("base64").decode("utf8")
def getmeta(self, meta_name): def getmeta(self, meta_name):
...@@ -54,9 +61,9 @@ class RPCFSInterface(object): ...@@ -54,9 +61,9 @@ class RPCFSInterface(object):
def hasmeta(self, meta_name): def hasmeta(self, meta_name):
return self.fs.hasmeta(meta_name) return self.fs.hasmeta(meta_name)
def get_contents(self, path): def get_contents(self, path, mode="rb"):
path = self.decode_path(path) path = self.decode_path(path)
data = self.fs.getcontents(path) data = self.fs.getcontents(path, mode)
return xmlrpclib.Binary(data) return xmlrpclib.Binary(data)
def set_contents(self, path, data): def set_contents(self, path, data):
...@@ -107,7 +114,8 @@ class RPCFSInterface(object): ...@@ -107,7 +114,8 @@ class RPCFSInterface(object):
def getinfo(self, path): def getinfo(self, path):
path = self.decode_path(path) path = self.decode_path(path)
return self.fs.getinfo(path) info = self.fs.getinfo(path)
return info
def desc(self, path): def desc(self, path):
path = self.decode_path(path) path = self.decode_path(path)
......
...@@ -502,7 +502,7 @@ class FileLikeBase(object): ...@@ -502,7 +502,7 @@ class FileLikeBase(object):
# If not found, return whole string up to <size> length # If not found, return whole string up to <size> length
# Any leftovers are pushed onto front of buffer # Any leftovers are pushed onto front of buffer
if indx == -1: if indx == -1:
data = "".join(bits) data = b("").join(bits)
if size > 0 and sizeSoFar > size: if size > 0 and sizeSoFar > size:
extra = data[size:] extra = data[size:]
data = data[:size] data = data[:size]
......
...@@ -29,9 +29,15 @@ import calendar ...@@ -29,9 +29,15 @@ import calendar
from socket import error as socket_error from socket import error as socket_error
from fs.local_functools import wraps from fs.local_functools import wraps
try: import six
from six import PY3
if PY3:
from six import BytesIO as StringIO
else:
try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO from StringIO import StringIO
import time import time
......
...@@ -23,7 +23,6 @@ import threading ...@@ -23,7 +23,6 @@ import threading
import six import six
def _check_mode(mode, mode_chars): def _check_mode(mode, mode_chars):
for c in mode_chars: for c in mode_chars:
if c not in mode: if c not in mode:
...@@ -76,7 +75,8 @@ class MemoryFile(object): ...@@ -76,7 +75,8 @@ class MemoryFile(object):
def __str__(self): def __str__(self):
return "<MemoryFile in %s %s>" % (self.memory_fs, self.path) return "<MemoryFile in %s %s>" % (self.memory_fs, self.path)
__repr__ = __str__ def __repr__(self):
return u"<MemoryFile in %s %s>" % (self.memory_fs, self.path)
def __unicode__(self): def __unicode__(self):
return u"<MemoryFile in %s %s>" % (self.memory_fs, self.path) return u"<MemoryFile in %s %s>" % (self.memory_fs, self.path)
...@@ -89,7 +89,9 @@ class MemoryFile(object): ...@@ -89,7 +89,9 @@ class MemoryFile(object):
pass pass
def __iter__(self): def __iter__(self):
return self self.mem_file.seek(self.pos)
for line in self.mem_file:
yield line
@seek_and_lock @seek_and_lock
def next(self): def next(self):
...@@ -614,10 +616,10 @@ class MemoryFS(FS): ...@@ -614,10 +616,10 @@ class MemoryFS(FS):
@synchronize @synchronize
def setcontents(self, path, data, chunk_size=1024*64): def setcontents(self, path, data, chunk_size=1024*64):
if not isinstance(data, str): if not isinstance(data, six.binary_type):
return super(MemoryFS, self).setcontents(path, data, chunk_size) return super(MemoryFS, self).setcontents(path, data, chunk_size)
if not self.exists(path): if not self.exists(path):
self.open(path, 'w').close() self.open(path, 'wb').close()
dir_entry = self._get_dir_entry(path) dir_entry = self._get_dir_entry(path)
if not dir_entry.isfile(): if not dir_entry.isfile():
......
...@@ -205,7 +205,6 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -205,7 +205,6 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
@convert_os_errors @convert_os_errors
def open(self, path, mode="r", **kwargs): def open(self, path, mode="r", **kwargs):
#mode = filter(lambda c: c in "rwabt+",mode)
mode = ''.join(c for c in mode if c in 'rwabt+') mode = ''.join(c for c in mode if c in 'rwabt+')
sys_path = self.getsyspath(path) sys_path = self.getsyspath(path)
try: try:
...@@ -331,7 +330,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -331,7 +330,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
@convert_os_errors @convert_os_errors
def getinfo(self, path): def getinfo(self, path):
stats = self._stat(path) stats = self._stat(path)
info = dict((k, getattr(stats, k)) for k in dir(stats) if not k.startswith('__') ) info = dict((k, getattr(stats, k)) for k in dir(stats) if k.startswith('st_'))
info['size'] = info['st_size'] info['size'] = info['st_size']
# TODO: this doesn't actually mean 'creation time' on unix # TODO: this doesn't actually mean 'creation time' on unix
ct = info.get('st_ctime', None) ct = info.get('st_ctime', None)
......
...@@ -94,7 +94,8 @@ class RemoteFileBuffer(FileWrapper): ...@@ -94,7 +94,8 @@ class RemoteFileBuffer(FileWrapper):
self._eof = True self._eof = True
if not hasattr(rfile, "read"): if not hasattr(rfile, "read"):
rfile = StringIO(unicode(rfile)) #rfile = StringIO(unicode(rfile))
rfile = StringIO(rfile)
self._rfile = rfile self._rfile = rfile
else: else:
......
...@@ -18,6 +18,8 @@ from fs.path import * ...@@ -18,6 +18,8 @@ from fs.path import *
from fs.filelike import StringIO from fs.filelike import StringIO
import six
from six import PY3, b
def re_raise_faults(func): def re_raise_faults(func):
"""Decorator to re-raise XML-RPC faults as proper exceptions.""" """Decorator to re-raise XML-RPC faults as proper exceptions."""
...@@ -127,6 +129,9 @@ class RPCFS(FS): ...@@ -127,6 +129,9 @@ class RPCFS(FS):
def __str__(self): def __str__(self):
return '<RPCFS: %s>' % (self.uri,) return '<RPCFS: %s>' % (self.uri,)
def __repr__(self):
return '<RPCFS: %s>' % (self.uri,)
@synchronize @synchronize
def __getstate__(self): def __getstate__(self):
state = super(RPCFS,self).__getstate__() state = super(RPCFS,self).__getstate__()
...@@ -147,10 +152,14 @@ class RPCFS(FS): ...@@ -147,10 +152,14 @@ class RPCFS(FS):
must return something that can be represented in ASCII. The default must return something that can be represented in ASCII. The default
is base64-encoded UTF8. is base64-encoded UTF8.
""" """
if PY3:
return path
return path.encode("utf8").encode("base64") return path.encode("utf8").encode("base64")
def decode_path(self, path): def decode_path(self, path):
"""Decode paths arriving over the wire.""" """Decode paths arriving over the wire."""
if PY3:
return path
return path.decode("base64").decode("utf8") return path.decode("base64").decode("utf8")
@synchronize @synchronize
...@@ -169,18 +178,18 @@ class RPCFS(FS): ...@@ -169,18 +178,18 @@ class RPCFS(FS):
# TODO: chunked transport of large files # TODO: chunked transport of large files
path = self.encode_path(path) path = self.encode_path(path)
if "w" in mode: if "w" in mode:
self.proxy.set_contents(path,xmlrpclib.Binary("")) self.proxy.set_contents(path,xmlrpclib.Binary(b("")))
if "r" in mode or "a" in mode or "+" in mode: if "r" in mode or "a" in mode or "+" in mode:
try: try:
data = self.proxy.get_contents(path).data data = self.proxy.get_contents(path, "rb").data
except IOError: except IOError:
if "w" not in mode and "a" not in mode: if "w" not in mode and "a" not in mode:
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
if not self.isdir(dirname(path)): if not self.isdir(dirname(path)):
raise ParentDirectoryMissingError(path) raise ParentDirectoryMissingError(path)
self.proxy.set_contents(path,xmlrpclib.Binary("")) self.proxy.set_contents(path,xmlrpclib.Binary(b("")))
else: else:
data = "" data = b("")
f = StringIO(data) f = StringIO(data)
if "a" not in mode: if "a" not in mode:
f.seek(0,0) f.seek(0,0)
......
...@@ -297,8 +297,8 @@ class FSTestCases(object): ...@@ -297,8 +297,8 @@ class FSTestCases(object):
self.assertRaises(ResourceInvalidError,self.fs.listdirinfo,"foo") self.assertRaises(ResourceInvalidError,self.fs.listdirinfo,"foo")
def test_walk(self): def test_walk(self):
self.fs.setcontents('a.txt', 'hello') self.fs.setcontents('a.txt', b('hello'))
self.fs.setcontents('b.txt', 'world') self.fs.setcontents('b.txt', b('world'))
self.fs.makeopendir('foo').setcontents('c', b('123')) self.fs.makeopendir('foo').setcontents('c', b('123'))
sorted_walk = sorted([(d,sorted(fs)) for (d,fs) in self.fs.walk()]) sorted_walk = sorted([(d,sorted(fs)) for (d,fs) in self.fs.walk()])
self.assertEquals(sorted_walk, self.assertEquals(sorted_walk,
...@@ -730,7 +730,8 @@ class FSTestCases(object): ...@@ -730,7 +730,8 @@ class FSTestCases(object):
checkcontents("hello",b("12345")) checkcontents("hello",b("12345"))
def test_truncate_to_larger_size(self): def test_truncate_to_larger_size(self):
print repr(self.fs)
print self.fs.__class__
with self.fs.open("hello","wb") as f: with self.fs.open("hello","wb") as f:
f.truncate(30) f.truncate(30)
self.assertEquals(self.fs.getsize("hello"), 30) self.assertEquals(self.fs.getsize("hello"), 30)
......
...@@ -10,6 +10,7 @@ from fs.expose.importhook import FSImportHook ...@@ -10,6 +10,7 @@ from fs.expose.importhook import FSImportHook
from fs.tempfs import TempFS from fs.tempfs import TempFS
from fs.zipfs import ZipFS from fs.zipfs import ZipFS
from six import b
class TestFSImportHook(unittest.TestCase): class TestFSImportHook(unittest.TestCase):
...@@ -32,23 +33,23 @@ class TestFSImportHook(unittest.TestCase): ...@@ -32,23 +33,23 @@ class TestFSImportHook(unittest.TestCase):
sys.path_importer_cache.clear() sys.path_importer_cache.clear()
def _init_modules(self,fs): def _init_modules(self,fs):
fs.setcontents("fsih_hello.py",dedent(""" fs.setcontents("fsih_hello.py",b(dedent("""
message = 'hello world!' message = 'hello world!'
""")) """)))
fs.makedir("fsih_pkg") fs.makedir("fsih_pkg")
fs.setcontents("fsih_pkg/__init__.py",dedent(""" fs.setcontents("fsih_pkg/__init__.py",b(dedent("""
a = 42 a = 42
""")) """)))
fs.setcontents("fsih_pkg/sub1.py",dedent(""" fs.setcontents("fsih_pkg/sub1.py",b(dedent("""
import fsih_pkg import fsih_pkg
from fsih_hello import message from fsih_hello import message
a = fsih_pkg.a a = fsih_pkg.a
""")) """)))
fs.setcontents("fsih_pkg/sub2.pyc",self._getpyc(dedent(""" fs.setcontents("fsih_pkg/sub2.pyc",self._getpyc(b(dedent("""
import fsih_pkg import fsih_pkg
from fsih_hello import message from fsih_hello import message
a = fsih_pkg.a * 2 a = fsih_pkg.a * 2
"""))) """))))
def _getpyc(self,src): def _getpyc(self,src):
"""Get the .pyc contents to match th given .py source code.""" """Get the .pyc contents to match th given .py source code."""
......
...@@ -178,7 +178,7 @@ class WatcherTestCases: ...@@ -178,7 +178,7 @@ class WatcherTestCases:
while not isinstance(event,CLOSED): while not isinstance(event,CLOSED):
event = changes.next(timeout=1) event = changes.next(timeout=1)
# That should be the last event in the list # That should be the last event in the list
self.assertRaises(StopIteration,changes.next,timeout=1) self.assertRaises(StopIteration,getattr(changes, "next"),timeout=1)
changes.close() changes.close()
......
...@@ -11,6 +11,7 @@ from fs.path import * ...@@ -11,6 +11,7 @@ from fs.path import *
from fs.errors import * from fs.errors import *
from fs.tests import FSTestCases from fs.tests import FSTestCases
from six import b
class XAttrTestCases: class XAttrTestCases:
"""Testcases for filesystems providing extended attribute support. """Testcases for filesystems providing extended attribute support.
...@@ -26,11 +27,11 @@ class XAttrTestCases: ...@@ -26,11 +27,11 @@ class XAttrTestCases:
self.assertEqual(self.fs.getxattr(p,"xattr1"),"value1") self.assertEqual(self.fs.getxattr(p,"xattr1"),"value1")
self.fs.delxattr(p,"xattr1") self.fs.delxattr(p,"xattr1")
self.assertEqual(self.fs.getxattr(p,"xattr1"),None) self.assertEqual(self.fs.getxattr(p,"xattr1"),None)
self.fs.setcontents("test.txt","hello") self.fs.setcontents("test.txt",b("hello"))
do_getsetdel("test.txt") do_getsetdel("test.txt")
self.assertRaises(ResourceNotFoundError,self.fs.getxattr,"test2.txt","xattr1") self.assertRaises(ResourceNotFoundError,self.fs.getxattr,"test2.txt","xattr1")
self.fs.makedir("mystuff") self.fs.makedir("mystuff")
self.fs.setcontents("/mystuff/test.txt","") self.fs.setcontents("/mystuff/test.txt",b(""))
do_getsetdel("mystuff") do_getsetdel("mystuff")
do_getsetdel("mystuff/test.txt") do_getsetdel("mystuff/test.txt")
...@@ -49,15 +50,15 @@ class XAttrTestCases: ...@@ -49,15 +50,15 @@ class XAttrTestCases:
self.assertEquals(sorted(self.fs.listxattrs(p)),["attr2"]) self.assertEquals(sorted(self.fs.listxattrs(p)),["attr2"])
self.fs.delxattr(p,"attr2") self.fs.delxattr(p,"attr2")
self.assertEquals(sorted(self.fs.listxattrs(p)),[]) self.assertEquals(sorted(self.fs.listxattrs(p)),[])
self.fs.setcontents("test.txt","hello") self.fs.setcontents("test.txt",b("hello"))
do_list("test.txt") do_list("test.txt")
self.fs.makedir("mystuff") self.fs.makedir("mystuff")
self.fs.setcontents("/mystuff/test.txt","") self.fs.setcontents("/mystuff/test.txt",b(""))
do_list("mystuff") do_list("mystuff")
do_list("mystuff/test.txt") do_list("mystuff/test.txt")
def test_copy_xattrs(self): def test_copy_xattrs(self):
self.fs.setcontents("a.txt","content") self.fs.setcontents("a.txt",b("content"))
self.fs.setxattr("a.txt","myattr","myvalue") self.fs.setxattr("a.txt","myattr","myvalue")
self.fs.setxattr("a.txt","testattr","testvalue") self.fs.setxattr("a.txt","testattr","testvalue")
self.fs.makedir("stuff") self.fs.makedir("stuff")
...@@ -75,7 +76,7 @@ class XAttrTestCases: ...@@ -75,7 +76,7 @@ class XAttrTestCases:
self.assertEquals(self.fs.getxattr("stuff","dirattr"),"a directory") self.assertEquals(self.fs.getxattr("stuff","dirattr"),"a directory")
def test_move_xattrs(self): def test_move_xattrs(self):
self.fs.setcontents("a.txt","content") self.fs.setcontents("a.txt",b("content"))
self.fs.setxattr("a.txt","myattr","myvalue") self.fs.setxattr("a.txt","myattr","myvalue")
self.fs.setxattr("a.txt","testattr","testvalue") self.fs.setxattr("a.txt","testattr","testvalue")
self.fs.makedir("stuff") self.fs.makedir("stuff")
......
...@@ -13,9 +13,10 @@ import shutil ...@@ -13,9 +13,10 @@ import shutil
import fs.tests import fs.tests
from fs.path import * from fs.path import *
from fs import zipfs
from six import b
from fs import zipfs
class TestReadZipFS(unittest.TestCase): class TestReadZipFS(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -24,11 +25,11 @@ class TestReadZipFS(unittest.TestCase): ...@@ -24,11 +25,11 @@ class TestReadZipFS(unittest.TestCase):
self.zf = zipfile.ZipFile(self.temp_filename, "w") self.zf = zipfile.ZipFile(self.temp_filename, "w")
zf = self.zf zf = self.zf
zf.writestr("a.txt", "Hello, World!") zf.writestr("a.txt", b("Hello, World!"))
zf.writestr("b.txt", "b") zf.writestr("b.txt", b("b"))
zf.writestr("1.txt", "1") zf.writestr("1.txt", b("1"))
zf.writestr("foo/bar/baz.txt", "baz") zf.writestr("foo/bar/baz.txt", b("baz"))
zf.writestr("foo/second.txt", "hai") zf.writestr("foo/second.txt", b("hai"))
zf.close() zf.close()
self.fs = zipfs.ZipFS(self.temp_filename, "r") self.fs = zipfs.ZipFS(self.temp_filename, "r")
...@@ -50,18 +51,18 @@ class TestReadZipFS(unittest.TestCase): ...@@ -50,18 +51,18 @@ class TestReadZipFS(unittest.TestCase):
return contents return contents
def check_contents(path, expected): def check_contents(path, expected):
self.assert_(read_contents(path)==expected) self.assert_(read_contents(path)==expected)
check_contents("a.txt", "Hello, World!") check_contents("a.txt", b("Hello, World!"))
check_contents("1.txt", "1") check_contents("1.txt", b("1"))
check_contents("foo/bar/baz.txt", "baz") check_contents("foo/bar/baz.txt", b("baz"))
def test_getcontents(self): def test_getcontents(self):
def read_contents(path): def read_contents(path):
return self.fs.getcontents(path) return self.fs.getcontents(path)
def check_contents(path, expected): def check_contents(path, expected):
self.assert_(read_contents(path)==expected) self.assert_(read_contents(path)==expected)
check_contents("a.txt", "Hello, World!") check_contents("a.txt", b("Hello, World!"))
check_contents("1.txt", "1") check_contents("1.txt", b("1"))
check_contents("foo/bar/baz.txt", "baz") check_contents("foo/bar/baz.txt", b("baz"))
def test_is(self): def test_is(self):
self.assert_(self.fs.isfile('a.txt')) self.assert_(self.fs.isfile('a.txt'))
...@@ -98,15 +99,15 @@ class TestWriteZipFS(unittest.TestCase): ...@@ -98,15 +99,15 @@ class TestWriteZipFS(unittest.TestCase):
def makefile(filename, contents): def makefile(filename, contents):
if dirname(filename): if dirname(filename):
zip_fs.makedir(dirname(filename), recursive=True, allow_recreate=True) zip_fs.makedir(dirname(filename), recursive=True, allow_recreate=True)
f = zip_fs.open(filename, 'w') f = zip_fs.open(filename, 'wb')
f.write(contents) f.write(contents)
f.close() f.close()
makefile("a.txt", "Hello, World!") makefile("a.txt", b("Hello, World!"))
makefile("b.txt", "b") makefile("b.txt", b("b"))
makefile(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", "this is the alpha and the omega") makefile(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", b("this is the alpha and the omega"))
makefile("foo/bar/baz.txt", "baz") makefile("foo/bar/baz.txt", b("baz"))
makefile("foo/second.txt", "hai") makefile("foo/second.txt", b("hai"))
zip_fs.close() zip_fs.close()
...@@ -123,11 +124,11 @@ class TestWriteZipFS(unittest.TestCase): ...@@ -123,11 +124,11 @@ class TestWriteZipFS(unittest.TestCase):
def check_contents(filename, contents): def check_contents(filename, contents):
zcontents = zf.read(filename.encode("CP437")) zcontents = zf.read(filename.encode("CP437"))
self.assertEqual(contents, zcontents) self.assertEqual(contents, zcontents)
check_contents("a.txt", "Hello, World!") check_contents("a.txt", b()"Hello, World!")
check_contents("b.txt", "b") check_contents("b.txt", b("b"))
check_contents("foo/bar/baz.txt", "baz") check_contents("foo/bar/baz.txt", b("baz"))
check_contents("foo/second.txt", "hai") check_contents("foo/second.txt", b("hai"))
check_contents(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", "this is the alpha and the omega") check_contents(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", b("this is the alpha and the omega"))
class TestAppendZipFS(TestWriteZipFS): class TestAppendZipFS(TestWriteZipFS):
...@@ -141,19 +142,19 @@ class TestAppendZipFS(TestWriteZipFS): ...@@ -141,19 +142,19 @@ class TestAppendZipFS(TestWriteZipFS):
def makefile(filename, contents): def makefile(filename, contents):
if dirname(filename): if dirname(filename):
zip_fs.makedir(dirname(filename), recursive=True, allow_recreate=True) zip_fs.makedir(dirname(filename), recursive=True, allow_recreate=True)
f = zip_fs.open(filename, 'w') f = zip_fs.open(filename, 'wb')
f.write(contents) f.write(contents)
f.close() f.close()
makefile("a.txt", "Hello, World!") makefile("a.txt", b("Hello, World!"))
makefile("b.txt", "b") makefile("b.txt", b("b"))
zip_fs.close() zip_fs.close()
zip_fs = zipfs.ZipFS(self.temp_filename, 'a') zip_fs = zipfs.ZipFS(self.temp_filename, 'a')
makefile("foo/bar/baz.txt", "baz") makefile("foo/bar/baz.txt", b("baz"))
makefile(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", "this is the alpha and the omega") makefile(u"\N{GREEK SMALL LETTER ALPHA}/\N{GREEK CAPITAL LETTER OMEGA}.txt", b("this is the alpha and the omega"))
makefile("foo/second.txt", "hai") makefile("foo/second.txt", b("hai"))
zip_fs.close() zip_fs.close()
...@@ -168,7 +169,7 @@ class TestZipFSErrors(unittest.TestCase): ...@@ -168,7 +169,7 @@ class TestZipFSErrors(unittest.TestCase):
def test_bogus_zipfile(self): def test_bogus_zipfile(self):
badzip = os.path.join(self.workdir,"bad.zip") badzip = os.path.join(self.workdir,"bad.zip")
f = open(badzip,"wb") f = open(badzip,"wb")
f.write("I'm not really a zipfile") f.write(b("I'm not really a zipfile"))
f.close() f.close()
self.assertRaises(zipfs.ZipOpenError,zipfs.ZipFS,badzip) self.assertRaises(zipfs.ZipOpenError,zipfs.ZipFS,badzip)
......
...@@ -19,6 +19,7 @@ from memoryfs import MemoryFS ...@@ -19,6 +19,7 @@ from memoryfs import MemoryFS
import tempfs import tempfs
from six import PY3
class ZipOpenError(CreateFailedError): class ZipOpenError(CreateFailedError):
"""Thrown when the zip file could not be opened""" """Thrown when the zip file could not be opened"""
...@@ -144,9 +145,20 @@ class ZipFS(FS): ...@@ -144,9 +145,20 @@ class ZipFS(FS):
def __unicode__(self): def __unicode__(self):
return u"<ZipFS: %s>" % self.zip_path return u"<ZipFS: %s>" % self.zip_path
def _decode_path(self, path):
if PY3:
return path
return path.decode(self.encoding)
def _encode_path(self, path):
if PY3:
return path
return path.encode(self.encoding)
def _parse_resource_list(self): def _parse_resource_list(self):
for path in self.zf.namelist(): for path in self.zf.namelist():
self._add_resource(path.decode(self.encoding)) #self._add_resource(path.decode(self.encoding))
self._add_resource(self._decode_path(path))
def _add_resource(self, path): def _add_resource(self, path):
if path.endswith('/'): if path.endswith('/'):
...@@ -185,9 +197,9 @@ class ZipFS(FS): ...@@ -185,9 +197,9 @@ class ZipFS(FS):
msg="1 Zip file must be opened for reading ('r') or appending ('a')") msg="1 Zip file must be opened for reading ('r') or appending ('a')")
try: try:
if hasattr(self.zf, 'open') and self._zip_file_string: if hasattr(self.zf, 'open') and self._zip_file_string:
return self.zf.open(path.encode(self.encoding)) return self.zf.open(self._encode_path(path), "r")
else: else:
contents = self.zf.read(path.encode(self.encoding)) contents = self.zf.read(self._encode_path(path))
except KeyError: except KeyError:
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
return StringIO(contents) return StringIO(contents)
...@@ -214,7 +226,7 @@ class ZipFS(FS): ...@@ -214,7 +226,7 @@ class ZipFS(FS):
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
path = normpath(relpath(path)) path = normpath(relpath(path))
try: try:
contents = self.zf.read(path.encode(self.encoding)) contents = self.zf.read(self._encode_path(path))
except KeyError: except KeyError:
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
except RuntimeError: except RuntimeError:
...@@ -224,7 +236,7 @@ class ZipFS(FS): ...@@ -224,7 +236,7 @@ class ZipFS(FS):
@synchronize @synchronize
def _on_write_close(self, filename): def _on_write_close(self, filename):
sys_path = self.temp_fs.getsyspath(filename) sys_path = self.temp_fs.getsyspath(filename)
self.zf.write(sys_path, filename.encode(self.encoding)) self.zf.write(sys_path, self._encode_path(filename))
def desc(self, path): def desc(self, path):
return "%s in zip file %s" % (path, self.zip_path) return "%s in zip file %s" % (path, self.zip_path)
...@@ -256,14 +268,14 @@ class ZipFS(FS): ...@@ -256,14 +268,14 @@ class ZipFS(FS):
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
path = normpath(path).lstrip('/') path = normpath(path).lstrip('/')
try: try:
zi = self.zf.getinfo(path.encode(self.encoding)) zi = self.zf.getinfo(self._encode_path(path))
zinfo = dict((attrib, getattr(zi, attrib)) for attrib in dir(zi) if not attrib.startswith('_')) zinfo = dict((attrib, getattr(zi, attrib)) for attrib in dir(zi) if not attrib.startswith('_'))
for k, v in zinfo.iteritems(): for k, v in zinfo.iteritems():
if callable(v): if callable(v):
zinfo[k] = v() zinfo[k] = v()
except KeyError: except KeyError:
zinfo = {'file_size':0} zinfo = {'file_size':0}
info = {'size' : zinfo['file_size'] } info = {'size' : zinfo['file_size']}
if 'date_time' in zinfo: if 'date_time' in zinfo:
info['created_time'] = datetime.datetime(*zinfo['date_time']) info['created_time'] = datetime.datetime(*zinfo['date_time'])
info.update(zinfo) info.update(zinfo)
......
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