Blocking sockobj.makefile('w') objects

Stefan Schwarzer s.schwarzer at ndh.net
Sun Jan 13 16:28:35 CET 2002


Hello all

Please excuse the long posting.

I'm writing a module to make FTP connections appear as file-like
objects (I will upload the code to the Vaults when it's ready).
Example code (code that is used behind the scenes follows below):

import ftputil
host = ftputil.FTPHost('ftp.domain.com', 'user', 'password')
text = 'Some example text\nSecond line'
fp = host.file('remote_name', 'w')
fp.write(text)
fp.close
host.close()

With read access (read, readline, readlines) all goes very well.
(I'm impressed. :-) )

However, if I use write (or writelines) like above, the interpreter
blocks after the statement "fp = host.file('remote_name', 'w')". I've
noted that the ftplib module itself does the "STOR" transfers by using
sockobj.send directly while it uses makefile for the "RETR" transfers.
Is there a way to use sockobj.makefile with "STOR" transfers? What's
the problem with the code below; why do 'w' modes not work?

I hope to have extracted the relevant code from ftputil.py ...

-----
import ftplib

class _FTPFile:
    '''Represents a file-like object connected to an
    FTP host. File and socket are closed appropriately if
    the close operation is requested.'''

    def __init__(self, host, conn, mode):
        '''Construct the file(-like) object.'''
        self._host = host
        self._conn = conn
        self._mode = mode
        self._binary = 'b' in mode
        self._fp = conn.makefile(mode)
        
    #
    # Read and write operations with support for
    # line separator conversion for text modes.
    #
    # Note that we must convert line endings because
    # the FTP server expects the native line separator
    # format to be sent on ASCII transfers.
    #

    # read methods snipped

    def write(self, data):
        '''Write data to file. Do linesep conversion for
        text mode.'''
        if not self._binary:
            data = _python_to_native_linesep(data)
        #self._conn.send(data)
        self._fp.write(data)

    #
    # other attributes
    #
    def __getattr__(self, attr_name):
        '''Delegate unknown attribute requests to the file.'''
        if attr_name in ( 'flush isatty fileno seek tell '
          'truncate closed name softspace'.split() ):
            return eval('self._fp.%s' % attr_name)
        else:
            raise AttributeError("'FTPFile' object has no "
                  "attribute '%s'" % attr_name)

    # close and __del__ snipped


class FTPHost:
    '''FTP host class'''

    def __init__(self, *args, **kwargs):
        '''Abstract initialization of FTPHost object. At this
        stage I don't know if I need a new FTP connection for
        each file transfer.'''
        self._host = apply(ftplib.FTP, args, kwargs)
    
    def file(self, path, mode='r'):
        '''Return a file(-like) object that is connected to an
        FTP host.'''
        if '+' in mode:
            raise FTPIOError("append modes not supported")
        if mode not in ('r', 'rb', 'w', 'wb'):
            raise FTPIOError("invalid mode")
        # select ASCII or binary mode
        transfer_type = ('A', 'I')['b' in mode]
        command = 'TYPE %s' % transfer_type
        # this logic taken from ftplib;
        #  why this strange distinction?
        if mode == 'r':
            self._host.sendcmd(command)
        else:  # rb, w, wb
            self._host.voidcmd(command)
        # make transfer command
        command_type = ('STOR', 'RETR')['r' in mode]
        command = '%s %s' % (command_type, path)
        # get connection and file object
        conn = self._host.transfercmd(command)
        self._host.voidresp()
        ftp_file = _FTPFile(self._host, conn, mode)
        return ftp_file
-----

Many thanks for your advice in advance. :-)

Stefan



More information about the Python-list mailing list