Python’s ftplib lets you blast files around via FTP, but it’s not just a dumb pipe.

from ftplib import FTP

ftp = FTP('ftp.example.com')
ftp.login('user', 'password')

# Uploading a file
with open('local_file.txt', 'rb') as fp:
    ftp.storbinary('STOR remote_file.txt', fp)

# Downloading a file
with open('downloaded_file.txt', 'wb') as fp:
    ftp.retrbinary('RETR remote_file.txt', fp.write)

ftp.quit()

This code is the skeleton. The STOR command tells the FTP server to store the incoming data stream as a file named remote_file.txt. RETR is the opposite: retrieve the file named remote_file.txt and feed its data stream into the provided callback function (fp.write in this case, which writes chunks of data to the local file). The 'rb' and 'wb' modes are crucial here; FTP, especially for binary files, expects raw bytes, not decoded text.

The Problem It Solves: Remote File Management

Imagine you have a web server, a data logger, or any system that generates files you need to access or update from another machine. FTP is a common, albeit older, protocol for this. ftplib bridges the gap, letting your Python scripts act as an FTP client to interact with these remote resources. You can automate backups, distribute configuration files, or pull data for processing without manual intervention.

How It Works Internally: Control and Data Connections

When you connect to an FTP server, ftplib first establishes a control connection on port 21. This is where commands like USER, PASS, STOR, and RETR are sent, and where the server sends its responses (like "200 OK" or "550 File not found").

For actual file transfers, FTP opens a separate data connection. The mode of this connection (active or passive) dictates how this data connection is established.

  • Active Mode: The client tells the server its IP address and a port number. The server then initiates a connection back to the client on that specified port to send or receive data. This can be problematic with firewalls on the client side.
  • Passive Mode: The client sends a PASV command. The server responds with an IP address and port number that the client should connect to for the data transfer. This is generally preferred as it avoids firewall issues on the client. ftplib defaults to passive mode, which is why it usually "just works."

When you call ftp.storbinary or ftp.retrbinary, ftplib handles the negotiation of the data connection (usually passive), sending the necessary commands (PASV, PORT if active, STOR/RETR), and then streaming the data over that connection.

The Levers You Control

  • FTP('hostname', user, passwd): The constructor. You can pass credentials here directly, or login() separately.
  • ftp.login(user, passwd, account=''): Authenticates with the server. The account parameter is rarely used.
  • ftp.cwd(pathname): Change directory on the remote server.
  • ftp.nlst(): Get a list of filenames in the current directory.
  • ftp.dir(): Get a more detailed directory listing (like ls -l).
  • ftp.size(filename): Get the size of a remote file.
  • ftp.storbinary(command, file, blocksize=8192, callback=None, rest=None): Uploads a file. command is usually STOR filename. file is a file-like object opened in binary read mode ('rb'). blocksize controls how much data is read from file and sent at once. callback can be a function to show progress. rest is for resuming interrupted transfers.
  • ftp.retrbinary(command, callback, blocksize=8192, rest=None): Downloads a file. command is usually RETR filename. callback is a function that receives data chunks (e.g., file.write).
  • ftp.mkd(pathname): Make a new directory on the remote server.
  • ftp.rmd(pathname): Remove a directory on the remote server.
  • ftp.delete(filename): Delete a file on the remote server.
  • ftp.pwd(): Get the current working directory on the remote server.
  • ftp.quit(): Closes the control connection.

The One Thing Most People Don’t Know

The rest parameter in storbinary and retrbinary is incredibly powerful for resuming interrupted transfers. If a download or upload fails partway through, you don’t have to start from scratch. You can get the size of the partially transferred file on the remote server and then pass that size (in bytes) as the rest argument to the STOR or RETR command. ftplib will then tell the server to start sending/receiving from that byte offset, and you can continue writing/reading to/from your local file from where you left off.

The next step is often dealing with FTP servers that require explicit TLS/SSL encryption using ftplib.FTP_TLS.

Want structured learning?

Take the full Ftp course →