The file transfer connection API

Uploading, downloading, reading, writing and managing remote entries - the same API for SFTP and SMB.

SFTP and SMB connections share one API. The methods to upload, download, read, write, list and manage remote entries are the same for both, so everything in this chapter applies to either protocol unless a difference is called out.

Getting a connection

Obtain a connection object by name, then call its methods. The only difference between the protocols is the attribute the lookup goes through - self.sftp for one, self.smb for the other.

from zato.server.service import Service

class MyService(Service):

    def handle(self):

        # Get a connection by name - use self.smb['My SMB Connection'] for SMB
        conn = self.sftp['My SFTP Connection']

        # Confirm the remote server responds
        conn.ping()

Uploading and downloading

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Upload a local file to a remote path
        conn.upload('/local/path/file.txt', '/remote/path/file.txt')

        # Download a remote file to a local path
        conn.download_file('/remote/path/file.txt', '/local/path/file.txt')

        # Download an entry - with SFTP, this can be a whole directory downloaded
        # recursively, whereas with SMB it is an alias to .download_file.
        conn.download('/remote/path', '/local/path')

Reading and writing data directly

You do not need local files to exchange data with a remote server - remote files can be read into and written out of your service directly.

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Write data out to a remote file
        data = 'My data'
        conn.write(data, '/remote/path/file.txt')

        # Read the contents of a remote file
        out = conn.read('/remote/path/file.txt')
        self.logger.info(out)

Guaranteed delivery

Where .write sends data once and raises if the remote server is down, .publish queues it for delivery with automatic retries and backoff - the data is spooled locally first, so it survives until the remote side is reachable again. This is the method to use when a file must arrive even if the SFTP server happens to be offline at the moment of sending.

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Queue the data for delivery - returns immediately,
        # the transfer itself is retried until it succeeds
        conn.publish('My data', '/remote/path/file.txt')

Managing directories and entries

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Create a new directory - with SMB, any missing parent directories
        # are created too.
        conn.create_directory('/path/to/new/directory')

        # Delete a directory
        conn.delete_directory('/path/to/delete')

        # Delete a file
        conn.delete_file('/path/to/delete')

        # Move or rename remote files and directories
        conn.move('/from/path', '/to/path')

        # An alias to .move
        conn.rename('/from/path', '/to/path')

Getting information about entries

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Check if a path exists at all
        result = conn.exists('/remote/path')

        # Get information about an entry, e.g. its size and modification time
        info = conn.get_info('/remote/path')

        self.logger.info(info.name)
        self.logger.info(info.size)
        self.logger.info(info.size_human)
        self.logger.info(info.last_modified)

        # Whether the path is a directory
        result = conn.is_directory('/remote/path')

        # Whether the path is a file
        result = conn.is_file('/remote/path')

        # List the contents of a directory - items are in the same format .get_info uses
        items = conn.list('/remote/path')

        for item in items:
            self.logger.info(f'{item.name} {item.size_human}')

Differences between SFTP and SMB

Everything above is shared. What follows is the complete list of the differences between the two protocols.

With SMB, remote paths are always rooted in a share, e.g. MyShare/documents/invoice.pdf, whereas SFTP paths are ordinary remote file system paths, e.g. /home/user/documents/invoice.pdf.

With SMB, .get_info returns the entry's name, size and modification time. With SFTP, it additionally returns the owner and the permissions, e.g. info.owner and info.permissions_oct.

Everything below is available with SFTP only.

from zato.server.service import Service

class MyService(Service):

    def handle(self):
        conn = self.sftp['My SFTP Connection']

        # Execute a script of one or more SFTP commands, newline-separated -
        # the same functionality that the SFTP command shell in Dashboard offers.
        out = conn.execute('ls -la /remote/path')

        # Standard output of the commands executed
        self.logger.info(out.stdout)

        # The result also includes is_ok, stderr and response_time. By default,
        # a failed command raises - pass raise_on_error=False to inspect
        # the outcome yourself instead.
        out = conn.execute('rm /may/not/exist', raise_on_error=False)
        if not out.is_ok:
            self.logger.info(f'Command failed: {out.stderr}')

        # Create a new symlink
        conn.create_symlink('/path/to/point/to', '/path/to/new/symlink')

        # Create a new hard link
        conn.create_hardlink('/path/to/point/to', '/path/to/new/hardlink')

        # Delete an entry, possibly recursively, no matter what kind it is
        conn.delete('/path/to/delete')

        # Delete a symlink
        conn.delete_symlink('/path/to/delete')

        # Whether the path is a symlink
        result = conn.is_symlink('/remote/path')

        # Change the mode of the entry at path
        conn.chmod('600', '/path/to/entry')

        # Change the owner of the entry at path
        conn.chown('myuser', '/path/to/entry')

        # Change the group of the entry at path
        conn.chgrp('mygroup', '/path/to/entry')

Learn more