High-level FTP client library (virtual file system and more)
ftputil is a high-level FTP client library for the Python programming language. It implements a virtual file system for accessing FTP servers, providing many functions similar to those in the `os`, `os.path`, and `shutil` modules. It also offers convenience functions for conditional uploads and downloads, and handles FTP clients and servers in different timezones. The current stable version is 5.1.0, released on 2024-01-06. The project follows semantic versioning, with major version changes indicating backward incompatibility. Releases are announced on its mailing list.
Warnings
- breaking ftputil 4.0.0 changed the default behavior of the `DIR` command by no longer sending the `-a` option. This means 'hidden' files/directories (starting with a dot) are no longer included in listings by default, which may alter `listdir` or `walk` results.
- breaking The definition of 'time shift' used for `upload_if_newer` and `download_if_newer` changed in ftputil 4.0.0. Previously it was `server_time - local_client_time`; now it's defined as `server_listing_time - UTC`. This is backward-incompatible and may require adjusting existing code, especially if `synchronize_times` or explicit `set_time_shift` calls are used.
- breaking ftputil 5.0.0 is not backward-compatible with 4.0.0 and earlier due to compatibility changes with `ftplib` in Python 3.9. This primarily affects internal workings and session factory implementations.
- gotcha Calling `isdir`, `isfile`, or `islink` on path names returned by `listdir` can incorrectly return `False` if the `listdir` call was not for the current directory. This happens because these methods expect a full path or a path relative to the current working directory of the `FTPHost` instance.
- gotcha The `FTPHost.lstat('/')` method (for the root directory) will raise a `RootDirError`. This is a known limitation of the underlying algorithm. You can `stat` items *in* the root directory, but not the root directory itself.
- gotcha The methods `upload_if_newer` and `download_if_newer` may behave unexpectedly (e.g., unnecessary transfers or failing to transfer) due to subtle differences in server timestamp precision (often only to the minute) or incorrect time zone synchronization between the client and server.
Install
-
pip install ftputil
Imports
- FTPHost
import ftputil ftp_host = ftputil.FTPHost(...)
Quickstart
import ftputil
import os
FTP_HOST = os.environ.get('FTP_HOST', 'ftp.example.com')
FTP_USER = os.environ.get('FTP_USER', 'your_username')
FTP_PASSWORD = os.environ.get('FTP_PASSWORD', 'your_password')
# Example: Download files from the login directory
with ftputil.FTPHost(FTP_HOST, FTP_USER, FTP_PASSWORD) as ftp_host:
print(f"Connected to {FTP_HOST}. Current directory: {ftp_host.getcwd()}")
names = ftp_host.listdir(ftp_host.curdir)
for name in names:
if ftp_host.path.isfile(name):
print(f"Downloading {name}...")
# remote name, local name
ftp_host.download(name, name)
# Example: Create a new directory and upload a file
LOCAL_FILE_CONTENT = b"This is a test file.\n"
LOCAL_FILE_NAME = "local_test.txt"
REMOTE_DIR_NAME = "new_remote_dir"
REMOTE_FILE_NAME = f"{REMOTE_DIR_NAME}/remote_test.txt"
with open(LOCAL_FILE_NAME, 'wb') as f:
f.write(LOCAL_FILE_CONTENT)
with ftputil.FTPHost(FTP_HOST, FTP_USER, FTP_PASSWORD) as ftp_host:
print(f"Creating remote directory {REMOTE_DIR_NAME}...")
ftp_host.makedirs(REMOTE_DIR_NAME, exist_ok=True)
print(f"Uploading {LOCAL_FILE_NAME} to {REMOTE_FILE_NAME}...")
ftp_host.upload(LOCAL_FILE_NAME, REMOTE_FILE_NAME)
print("Upload complete.")
# Clean up local file
os.remove(LOCAL_FILE_NAME)