Brent Tubbs avatar Brent Tubbs committed 8f043af

use lazy SFTP connection to avoid paramiko hanging on import when storage is passed into file field init

Comments (0)

Files changed (1)


 # SFTP storage backend for Django.
 # Author: Brent Tubbs <>
 # License: MIT
 # Modeled on the FTP storage by Rafal Jonca <>
 # Settings:
 # SFTP_STORAGE_HOST - The hostname where you want the files to be saved.
 # SFTP_STORAGE_ROOT - The root directory on the remote host into which files
 # paramiko.SSHClient().connect() (do not include hostname here).  See
 # for
 # details
 # SFTP_STORAGE_INTERACTIVE (Optional) - A boolean indicating whether to prompt
 # for a password if the connection cannot be made using keys, and there is not
 # already a password in SFTP_STORAGE_PARAMS.  You can set this to True to
 # enable interactive login when running ' collectstatic', for example.
 #   DO NOT set SFTP_STORAGE_INTERACTIVE to True if you are using this storage
 #   for files being uploaded to your site by users, because you'll have no way
 #   to enter the password when they submit the form..
 # SFTP_STORAGE_FILE_MODE (Optional) - A bitmask for setting permissions on
 # newly-created files.  See for
 # acceptable values.
 # SFTP_STORAGE_DIR_MODE (Optional) - A bitmask for setting permissions on
 # newly-created directories.  See
 # for acceptable values.
 #   Hint: if you start the mode number with a 0 you can express it in octal
 #   just like you would when doing "chmod 775 myfile" from bash.
     from StringIO import StringIO
 class SFTPStorage(Storage):
     def __init__(self):
         self._host = settings.SFTP_STORAGE_HOST
         self._params = getattr(settings, 'SFTP_STORAGE_PARAMS', {})
         self._interactive = getattr(settings, 'SFTP_STORAGE_INTERACTIVE',
         self._file_mode = getattr(settings, 'SFTP_STORAGE_FILE_MODE', None)
         self._dir_mode = getattr(settings, 'SFTP_STORAGE_DIR_MODE', None)
         self._uid = getattr(settings, 'SFTP_STORAGE_UID', None)
         self._gid = getattr(settings, 'SFTP_STORAGE_GID', None)
         self._root_path = settings.SFTP_STORAGE_ROOT
         # for now it's all posix paths.  Maybe someday we'll support figuring
         # out if the remote host is windows.
         self._pathmod = posixpath
-        # set up connection
-        self._connect()
     def _connect(self):
         self._ssh = paramiko.SSHClient()
         # automatically add host keys from current user.
         self._ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
         # and automatically add new host keys for hosts we haven't seen before.
-        try: 
+        try:
             self._ssh.connect(self._host, **self._params)
         except paramiko.AuthenticationException, e:
             if self._interactive and 'password' not in self._params:
                 raise paramiko.AuthenticationException, e
+        except Exception, e:
+            print e
         if not hasattr(self, '_sftp'):
             self._sftp = self._ssh.open_sftp()
+    @property
+    def sftp(self):
+        """Lazy SFTP connection"""
+        if not hasattr(self, '_sftp'):
+            self._connect()
+        return self._sftp
     def _join(self, *args):
         # Use the path module for the remote host type to join a path together
         return self._pathmod.join(*args)
     def _open(self, name, mode='rb'):
         return SFTPStorageFile(name, self, mode)
     def _read(self, name):
         remote_path = self._remote_path(name)
-        return, 'rb')
+        return, 'rb')
     def _chown(self, path, uid=None, gid=None):
         """Set uid and/or gid for file at path."""
         # Paramiko's chown requires both uid and gid, so look them up first if
         # we're only supposed to set one.
         if uid is None or gid is None:
-            attr = self._sftp.stat(path)
+            attr = self.sftp.stat(path)
             uid = uid or attr.st_uid
             gid = gid or attr.st_gid
-        self._sftp.chown(path, uid, gid)
+        self.sftp.chown(path, uid, gid)
     def _mkdir(self, path):
         """Create directory, recursing up to create parent dirs if
         parent = self._pathmod.dirname(path)
         if not self.exists(parent):
-        self._sftp.mkdir(path)
+        self.sftp.mkdir(path)
         if self._dir_mode is not None:
-            self._sftp.chmod(path, self._dir_mode)
+            self.sftp.chmod(path, self._dir_mode)
         if self._uid or self._gid:
             self._chown(path, uid=self._uid, gid=self._gid)
     def _save(self, name, content):
         """Save file via SFTP."""
         path = self._remote_path(name)
         dirname = self._pathmod.dirname(path)
         if not self.exists(dirname):
-        f =, 'wb')
+        f =, 'wb')
         # set file permissions if configured
         if self._file_mode is not None:
-            self._sftp.chmod(path, self._file_mode)
+            self.sftp.chmod(path, self._file_mode)
         if self._uid or self._gid:
             self._chown(path, uid=self._uid, gid=self._gid)
         return name
     def delete(self, name):
         remote_path = self._remote_path(name)
-        self._sftp.remove(remote_path)
+        self.sftp.remove(remote_path)
     def exists(self, name):
         # Try to retrieve file info.  Return true on success, false on failure.
         remote_path = self._remote_path(name)
-            self._sftp.stat(remote_path)
+            self.sftp.stat(remote_path)
             return True
         except IOError:
             return False
     def listdir(self, path):
         remote_path = self._remote_path(path)
         dirs, files = [], []
-        for item in self._sftp.listdir_attr(remote_path):
+        for item in self.sftp.listdir_attr(remote_path):
             if self._isdir_attr(item):
     def size(self, name):
         remote_path = self._remote_path(name)
-        return self._sftp.stat(remote_path).st_size
+        return self.sftp.stat(remote_path).st_size
     def accessed_time(self, name):
         remote_path = self._remote_path(name)
-        utime = self._sftp.stat(remote_path).st_atime
-        return datetime.fromtimestamp(utime) 
+        utime = self.sftp.stat(remote_path).st_atime
+        return datetime.fromtimestamp(utime)
     def modified_time(self, name):
         remote_path = self._remote_path(name)
-        utime = self._sftp.stat(remote_path).st_mtime
-        return datetime.fromtimestamp(utime) 
+        utime = self.sftp.stat(remote_path).st_mtime
+        return datetime.fromtimestamp(utime)
     def url(self, name):
         remote_path = self._remote_path(name)
         return 'sftp://%s/%s' % (self._host, remote_path)
         self._is_dirty = False
         self.file = StringIO()
         self._is_read = False
     def size(self):
         if not hasattr(self, '_size'):
         if not self._is_read:
             self.file = self._storage._read(self._name)
             self._is_read = True
     def write(self, content):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.