aioftp

raw JSON →
0.27.2 verified Thu Apr 16 auth: no python

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.

pip install aioftp
error 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.
fix
If 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.
error 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.
fix
Verify 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.
error ConnectionResetError: Connection lost
cause The FTP server unexpectedly closed the connection, often due to a timeout, an invalid operation, or network issues.
fix
Check 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.
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.
fix Ensure the C locale is set for `datetime.strftime` using `aioftp.setlocale()` as a context manager, especially in multithreaded environments. Alternatively, update your FTP server to support `MLSx` commands or provide a custom `parse_list_line_custom` routine.
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.
fix Avoid `AsyncPathIO` if performance is critical for local file operations. Consider `aioftp.PathIO` if blocking is acceptable, or `aioftp.MemoryPathIO` for in-memory file systems, or custom non-blocking IO implementations if specific performance requirements exist.
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.
fix Prefer `pathlib.Path` objects for all path-related arguments in `aioftp` methods to ensure consistent and robust behavior, e.g., `await client.download(Path('remote_file.txt'), Path('local_file.txt'))`.
pip install aioftp[socks]

This quickstart demonstrates how to use `aioftp.Client` with its `context` manager to connect to an FTP server, list files recursively, and download specific files (e.g., MP3s). It gracefully handles connection and FTP status errors. Remember to replace placeholder credentials and host with actual FTP server details for real-world usage, ideally through environment variables.

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())