Commits

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)

storages/backends/sftpstorage.py

 # SFTP storage backend for Django.
 # Author: Brent Tubbs <brent.tubbs@gmail.com>
 # License: MIT
-# 
+#
 # Modeled on the FTP storage by Rafal Jonca <jonca.rafal@gmail.com>
-# 
+#
 # 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
 # http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html#connect 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 'manage.py 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 http://docs.python.org/library/os.html#os.chmod for
 # acceptable values.
-# 
+#
 # SFTP_STORAGE_DIR_MODE (Optional) - A bitmask for setting permissions on
 # newly-created directories.  See
 # http://docs.python.org/library/os.html#os.chmod 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',
                                     False)
-
         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.
         self._ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
-        try: 
+        try:
             self._ssh.connect(self._host, **self._params)
         except paramiko.AuthenticationException, e:
             if self._interactive and 'password' not in self._params:
                 self._connect()
             else:
                 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 self._sftp.open(remote_path, 'rb')
+        return self.sftp.open(remote_path, '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._mkdir(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."""
-        content.open() 
+        content.open()
         path = self._remote_path(name)
         dirname = self._pathmod.dirname(path)
         if not self.exists(dirname):
             self._mkdir(dirname)
 
-        f = self._sftp.open(path, 'wb')
+        f = self.sftp.open(path, 'wb')
         f.write(content.file.read())
         f.close()
 
         # 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)
         try:
-            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):
                 dirs.append(item.filename)
             else:
 
     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
-    
+
     @property
     def size(self):
         if not hasattr(self, '_size'):
         if not self._is_read:
             self.file = self._storage._read(self._name)
             self._is_read = True
-            
+
         return self.file.read(num_bytes)
 
     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 ProjectModifiedEvent.java.
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.