server/sources/storages.py
changeset 10706 b261d90149d0
parent 10591 8e46ed1a0b8a
child 10996 dc572d116731
--- 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))