--- a/server/sources/storages.py Thu Sep 17 15:35:26 2015 +0200
+++ b/server/sources/storages.py Fri Sep 18 11:54:12 2015 +0200
@@ -23,6 +23,10 @@
from contextlib import contextmanager
import tempfile
+from six import PY2, PY3, text_type, binary_type
+
+from logilab.common import nullobject
+
from yams.schema import role_name
from cubicweb import Binary, ValidationError
@@ -103,13 +107,22 @@
del cnx.transaction_data['fs_importing']
+_marker = nullobject()
+
+
class BytesFileSystemStorage(Storage):
"""store Bytes attribute value on the file system"""
- def __init__(self, defaultdir, fsencoding='utf-8', wmode=0o444):
- if type(defaultdir) is unicode:
- defaultdir = defaultdir.encode(fsencoding)
+ def __init__(self, defaultdir, fsencoding=_marker, wmode=0o444):
+ if PY3:
+ if not isinstance(defaultdir, text_type):
+ raise TypeError('defaultdir must be a unicode object in python 3')
+ if fsencoding is not _marker:
+ raise ValueError('fsencoding is no longer supported in python 3')
+ else:
+ self.fsencoding = fsencoding or 'utf-8'
+ if isinstance(defaultdir, text_type):
+ defaultdir = defaultdir.encode(fsencoding)
self.default_directory = defaultdir
- self.fsencoding = fsencoding
# extra umask to use when creating file
# 0444 as in "only allow read bit in permission"
self._wmode = wmode
@@ -145,7 +158,8 @@
binary = entity.cw_edited.pop(attr)
fd, fpath = self.new_fs_path(entity, attr)
# bytes storage used to store file's path
- entity.cw_edited.edited_attribute(attr, Binary(fpath))
+ binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8'))
+ entity.cw_edited.edited_attribute(attr, binary_obj)
self._writecontent(fd, binary)
AddFileOp.get_instance(entity._cw).add_data(fpath)
return binary
@@ -187,7 +201,8 @@
entity.cw_edited.edited_attribute(attr, None)
else:
# register the new location for the file.
- entity.cw_edited.edited_attribute(attr, Binary(fpath))
+ binary_obj = Binary(fpath if PY2 else fpath.encode('utf-8'))
+ entity.cw_edited.edited_attribute(attr, binary_obj)
if oldpath is not None and oldpath != fpath:
# Mark the old file as useless so the file will be removed at
# commit.
@@ -206,16 +221,19 @@
# available. Keeping the extension is useful for example in the case of
# PIL processing that use filename extension to detect content-type, as
# well as providing more understandable file names on the fs.
+ if PY2:
+ attr = attr.encode('ascii')
basename = [str(entity.eid), attr]
name = entity.cw_attr_metadata(attr, 'name')
if name is not None:
- basename.append(name.encode(self.fsencoding))
+ basename.append(name.encode(self.fsencoding) if PY2 else name)
fd, fspath = uniquify_path(self.default_directory,
'_'.join(basename))
if fspath is None:
msg = entity._cw._('failed to uniquify path (%s, %s)') % (
self.default_directory, '_'.join(basename))
raise ValidationError(entity.eid, {role_name(attr, 'subject'): msg})
+ assert isinstance(fspath, str) # bytes on py2, unicode on py3
return fd, fspath
def current_fs_path(self, entity, attr):
@@ -229,8 +247,12 @@
rawvalue = cu.fetchone()[0]
if rawvalue is None: # no previous value
return None
- return sysource._process_value(rawvalue, cu.description[0],
- binarywrap=str)
+ fspath = sysource._process_value(rawvalue, cu.description[0],
+ binarywrap=binary_type)
+ if PY3:
+ fspath = fspath.decode('utf-8')
+ assert isinstance(fspath, str) # bytes on py2, unicode on py3
+ return fspath
def migrate_entity(self, entity, attribute):
"""migrate an entity attribute to the storage"""
@@ -248,15 +270,17 @@
class AddFileOp(hook.DataOperationMixIn, hook.Operation):
def rollback_event(self):
for filepath in self.get_data():
+ assert isinstance(filepath, str) # bytes on py2, unicode on py3
try:
unlink(filepath)
except Exception as ex:
- self.error('cant remove %s: %s' % (filepath, ex))
+ self.error("can't remove %s: %s" % (filepath, ex))
class DeleteFileOp(hook.DataOperationMixIn, hook.Operation):
def postcommit_event(self):
for filepath in self.get_data():
+ assert isinstance(filepath, str) # bytes on py2, unicode on py3
try:
unlink(filepath)
except Exception as ex:
- self.error('cant remove %s: %s' % (filepath, ex))
+ self.error("can't remove %s: %s" % (filepath, ex))