aioftp
aioftp is an asynchronous FTP client and server library for Python's asyncio framework, providing a simple yet extensible API for file transfer operations. It is currently at version 0.27.2 and is actively maintained, with updates released irregularly as needed.
Common errors
-
RuntimeError: This event loop is already running
cause Attempting to run `asyncio.run()` or `loop.run_until_complete()` when an `asyncio` event loop is already active in the current thread, often seen in environments like Jupyter notebooks or when embedding async code.fixIf an event loop is already running, avoid calling `asyncio.run()`. Instead, use `await` if within an `async` function, or use libraries like `nest_asyncio` to allow nested event loops for specific environments. If starting the main application, ensure `asyncio.run()` is called only once at the top level. -
aioftp.StatusCodeError: 550 Requested action not taken: file not found. (550)
cause The specified file or directory path does not exist on the FTP server or the user lacks the necessary permissions to access it. This is a common FTP protocol response for non-existent resources.fixVerify the exact path and filename on the FTP server. Check the user's permissions for the requested operation and path. Use `client.list()` or `client.stat()` to inspect the remote file system before attempting operations like download or rename. -
ConnectionResetError: Connection lost
cause The FTP server unexpectedly closed the connection, often due to a timeout, an invalid operation, or network issues.fixCheck for server-side timeouts or specific server error logs. Ensure your client operations are timely and adhere to FTP protocol expectations. For long-running operations, consider implementing retry logic or keeping the connection alive with periodic NOOP commands if the server supports it. Review network stability.
Warnings
- gotcha When using `client.list()` in multithreaded applications or with servers that don't support `MLSD`, the fallback `LIST` command parsing can fail due to locale-dependent date formatting. This is especially prevalent on Windows.
- gotcha Using `aioftp.AsyncPathIO` for file system operations, which wraps blocking calls with `asyncio.BaseEventLoop.run_in_executor()`, can be significantly slower than direct blocking `aioftp.PathIO` or in-memory `aioftp.MemoryPathIO` for certain use cases.
- gotcha While string paths often work, `aioftp` methods, especially for download/upload, internally benefit from or explicitly expect `pathlib.Path` instances. Mixing raw strings with `pathlib.Path` or making assumptions can lead to unexpected behavior or errors.
Install
-
pip install aioftp -
pip install aioftp[socks]
Imports
- Client
from aioftp import Client
- Server
from aioftp import Server
- Client.context
from aioftp import Client
- setlocale
from aioftp import setlocale
Quickstart
import asyncio
import aioftp
import os
async def download_mp3_files(host, port, user, password):
"""Connects to an FTP server, lists files, and downloads MP3s."""
print(f"Connecting to ftp://{user}@{host}:{port}...")
try:
async with aioftp.Client.context(host, port, user, password) as client:
print("Connected and logged in.")
async for path, info in client.list(recursive=True):
if info.get("type") == "file" and path.suffix == ".mp3":
print(f"Downloading {path.name}...")
await client.download(path, path.name)
print(f"Downloaded {path.name}.")
except aioftp.StatusCodeError as e:
print(f"FTP error: {e}")
except ConnectionRefusedError:
print(f"Connection refused by the FTP server at {host}:{port}.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
async def main():
FTP_HOST = os.environ.get('FTP_HOST', 'ftp.example.com') # Replace with a real FTP server for testing
FTP_PORT = int(os.environ.get('FTP_PORT', '21'))
FTP_USER = os.environ.get('FTP_USER', 'anonymous')
FTP_PASS = os.environ.get('FTP_PASS', 'anonymous@example.com')
# Example with a mock server if you don't have a real one
# For a real server, ensure FTP_HOST, FTP_PORT, FTP_USER, FTP_PASS are set correctly
# For testing, you might need a local FTP server or a public test server (be cautious)
print("Starting aioftp client example...")
await download_mp3_files(FTP_HOST, FTP_PORT, FTP_USER, FTP_PASS)
if __name__ == "__main__":
# To run this, you need an accessible FTP server. For anonymous access:
# export FTP_HOST='test.rebex.net' # or another public test server
# export FTP_USER='demo'
# export FTP_PASS='password'
# Then run 'python your_script.py'
asyncio.run(main())