CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-aioftp

Asynchronous FTP client and server implementation for Python's asyncio framework

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

errors.mddocs/

Error Handling

Exception hierarchy for FTP operations including protocol errors, path validation, I/O errors, and connection issues. All exceptions inherit from AIOFTPException providing consistent error handling and detailed error information for debugging FTP operations.

Capabilities

Base Exception

Root exception class for all aioftp-related errors.

class AIOFTPException(Exception):
    """
    Base exception for all aioftp errors.
    
    This is the root exception class that all other aioftp exceptions inherit from.
    Catching this exception will catch all aioftp-specific errors.
    """

Protocol Errors

Exceptions related to FTP protocol violations and unexpected server responses.

class StatusCodeError(AIOFTPException):
    """
    Raised when FTP server returns unexpected status code.
    
    This exception is raised when the server returns a status code that doesn't
    match what was expected for a particular FTP command. It includes detailed
    information about both expected and received codes.
    """
    
    expected_codes: tuple[Code, ...]
    """Status codes that were expected from the server."""
    
    received_codes: tuple[Code, ...]  
    """Status codes actually received from the server."""
    
    info: Union[list[str], str]
    """Additional information from the server response."""
    
    def __init__(expected_codes, received_codes, info):
        """
        Initialize StatusCodeError.
        
        Parameters:
        - expected_codes: Codes that were expected
        - received_codes: Codes actually received  
        - info: Server response information
        """

Path Validation Errors

Exceptions for path-related validation and operations.

class PathIsNotAbsolute(AIOFTPException):
    """
    Raised when path is not absolute but should be.
    
    Some operations require absolute paths. This exception is raised when
    a relative path is provided where an absolute path is required.
    """
    
    def __init__(path):
        """
        Initialize PathIsNotAbsolute error.
        
        Parameters:
        - path: The invalid relative path that was provided
        """

class PathIOError(AIOFTPException):
    """
    Universal exception for path I/O operations.
    
    This exception wraps filesystem-related errors and provides additional
    context about the operation that failed. It preserves the original
    exception information for debugging.
    """
    
    reason: Union[tuple, None]
    """Original exception information from the wrapped error."""
    
    def __init__(reason):
        """
        Initialize PathIOError.
        
        Parameters:
        - reason: Original exception info (usually from sys.exc_info())
        """

Connection and Port Errors

Exceptions related to network connections and port availability.

class NoAvailablePort(AIOFTPException, OSError):
    """
    Raised when no data ports are available for FTP data connections.
    
    This exception is raised when the server cannot find an available port
    from the configured data port range for establishing data connections.
    Inherits from both AIOFTPException and OSError for compatibility.
    """
    
    def __init__(message="No available ports"):
        """
        Initialize NoAvailablePort error.
        
        Parameters:
        - message: Error message describing the port availability issue
        """

Usage Examples

Basic Error Handling

import aioftp
import asyncio

async def handle_basic_errors():
    try:
        async with aioftp.Client.context("ftp.example.com") as client:
            await client.upload("local_file.txt", "remote_file.txt")
    
    except aioftp.StatusCodeError as e:
        print(f"FTP protocol error: {e}")
        print(f"Expected: {e.expected_codes}, Got: {e.received_codes}")
        print(f"Server message: {e.info}")
    
    except aioftp.PathIOError as e:
        print(f"File system error: {e}")
        if e.reason:
            print(f"Original error: {e.reason}")
    
    except aioftp.AIOFTPException as e:
        print(f"General FTP error: {e}")
    
    except Exception as e:
        print(f"Unexpected error: {e}")

asyncio.run(handle_basic_errors())

Specific Error Types

import aioftp
import asyncio

async def handle_specific_errors():
    try:
        async with aioftp.Client.context("ftp.example.com") as client:
            # This might raise StatusCodeError if file doesn't exist
            await client.download("nonexistent_file.txt", "local_file.txt")
    
    except aioftp.StatusCodeError as e:
        if "550" in str(e.received_codes):
            print("File not found on server")
        elif "426" in str(e.received_codes):
            print("Connection closed during transfer")
        else:
            print(f"Other protocol error: {e}")
    
    except aioftp.PathIsNotAbsolute as e:
        print(f"Path must be absolute: {e}")
    
    except aioftp.NoAvailablePort as e:
        print(f"Server has no available data ports: {e}")

asyncio.run(handle_specific_errors())

Server Error Handling

import aioftp
import asyncio
from pathlib import Path

async def server_error_handling():
    try:
        users = [aioftp.User(
            login="test", 
            password="test",
            base_path=Path("/nonexistent/path")  # This might cause issues
        )]
        
        server = aioftp.Server(users=users)
        await server.run(host="localhost", port=21)
    
    except aioftp.PathIOError as e:
        print(f"Server path error: {e}")
        print("Check that base paths exist and are accessible")
    
    except aioftp.NoAvailablePort as e:
        print(f"Port binding error: {e}")
        print("Try a different port or check port availability")
    
    except aioftp.AIOFTPException as e:
        print(f"Server startup error: {e}")

asyncio.run(server_error_handling())

Custom Error Handling with Retry Logic

import aioftp
import asyncio
import logging

async def robust_ftp_operation():
    """Example with retry logic and comprehensive error handling."""
    
    max_retries = 3
    retry_delay = 1.0
    
    for attempt in range(max_retries):
        try:
            async with aioftp.Client.context("ftp.example.com") as client:
                await client.upload("important_file.txt", "backup.txt")
                print("Upload successful!")
                return
        
        except aioftp.StatusCodeError as e:
            if "550" in str(e.received_codes):
                # Permission denied or file not found - don't retry
                print(f"Permanent error: {e}")
                break
            elif "4" in str(e.received_codes)[0]:
                # Temporary error (4xx codes) - retry
                print(f"Temporary error (attempt {attempt + 1}): {e}")
                if attempt < max_retries - 1:
                    await asyncio.sleep(retry_delay)
                    continue
        
        except aioftp.PathIOError as e:
            print(f"Local file error: {e}")
            break  # Don't retry file system errors
        
        except (ConnectionError, OSError) as e:
            # Network errors - retry
            print(f"Network error (attempt {attempt + 1}): {e}")
            if attempt < max_retries - 1:
                await asyncio.sleep(retry_delay)
                continue
        
        except aioftp.AIOFTPException as e:
            print(f"FTP error: {e}")
            break
    
    print("All retry attempts failed")

asyncio.run(robust_ftp_operation())

Error Logging and Monitoring

import aioftp
import asyncio
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

async def monitored_ftp_operation():
    """Example with comprehensive error logging."""
    
    try:
        async with aioftp.Client.context("ftp.example.com") as client:
            # Log successful connection
            logger.info("Connected to FTP server")
            
            await client.upload("data.txt", "remote_data.txt") 
            logger.info("File uploaded successfully")
            
    except aioftp.StatusCodeError as e:
        logger.error(
            "FTP protocol error: expected %s, got %s, info: %s",
            e.expected_codes, e.received_codes, e.info
        )
        # Could send alert to monitoring system here
    
    except aioftp.PathIOError as e:
        logger.error("File system error: %s", e)
        if e.reason:
            logger.debug("Original exception: %s", e.reason)
    
    except aioftp.NoAvailablePort as e:
        logger.critical("No available ports for FTP data connection: %s", e)
        # Critical infrastructure issue
    
    except aioftp.AIOFTPException as e:
        logger.error("General FTP error: %s", e)
    
    except Exception as e:
        logger.exception("Unexpected error in FTP operation")

asyncio.run(monitored_ftp_operation())

Error Code Patterns

Common FTP status codes and their meanings:

  • 1xx (Positive Preliminary): Command accepted, another command expected
  • 2xx (Positive Completion): Command completed successfully
  • 3xx (Positive Intermediate): Command accepted, more information needed
  • 4xx (Transient Negative): Temporary failure, command may be retried
  • 5xx (Permanent Negative): Permanent failure, command should not be retried

Common specific codes:

  • 425: Can't open data connection
  • 426: Connection closed; transfer aborted
  • 450: File unavailable (e.g., file busy)
  • 451: Local error in processing
  • 550: File unavailable (e.g., file not found, no access)
  • 552: Exceeded storage allocation
  • 553: File name not allowed

Best Practices

  1. Always catch AIOFTPException as a fallback for any aioftp-specific errors
  2. Check status codes in StatusCodeError to determine if operations should be retried
  3. Log error details including expected vs received codes for debugging
  4. Handle PathIOError separately as it indicates local filesystem issues
  5. Use appropriate retry logic for temporary errors (4xx codes)
  6. Don't retry permanent errors (5xx codes) automatically

Install with Tessl CLI

npx tessl i tessl/pypi-aioftp

docs

client.md

errors.md

index.md

pathio.md

server.md

streaming.md

tile.json