CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pycares

Python interface for c-ares asynchronous DNS library

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling and Constants

Comprehensive error handling for DNS operations including error codes, utility functions, and debugging support. All pycares operations use consistent error reporting through callback functions and exception classes.

Capabilities

Error Codes

DNS resolution can fail for various reasons. pycares provides detailed error codes to help diagnose and handle different failure scenarios.

# Success code
ARES_SUCCESS = 0

# DNS-specific errors
ARES_ENODATA = 1        # No data in DNS response
ARES_EFORMERR = 2       # DNS format error
ARES_ESERVFAIL = 3      # DNS server failure
ARES_ENOTFOUND = 4      # Domain name not found
ARES_ENOTIMP = 5        # Query type not implemented
ARES_EREFUSED = 6       # DNS query refused by server
ARES_EBADQUERY = 7      # Malformed DNS query
ARES_EBADNAME = 8       # Malformed domain name
ARES_EBADFAMILY = 9     # Bad address family
ARES_EBADRESP = 10      # Bad DNS response
ARES_ECONNREFUSED = 11  # Connection refused
ARES_ETIMEOUT = 12      # Query timeout
ARES_EOF = 13           # End of file
ARES_EFILE = 14         # File I/O error
ARES_ENOMEM = 15        # Out of memory
ARES_EDESTRUCTION = 16  # Channel destroyed
ARES_EBADSTR = 17       # Bad string
ARES_EBADFLAGS = 18     # Bad flags
ARES_ENONAME = 19       # No name
ARES_EBADHINTS = 20     # Bad hints
ARES_ENOTINITIALIZED = 21    # Not initialized
ARES_ELOADIPHLPAPI = 22      # Failed to load iphlpapi
ARES_EADDRGETNETWORKPARAMS = 23  # Failed to get network parameters
ARES_ECANCELLED = 24    # Query cancelled
ARES_ESERVICE = 25      # Service error

Error Code Mapping

Mapping dictionary for converting error codes to symbolic names.

errorcode: dict[int, str]  # Maps error codes to string names

Usage Example:

import pycares.errno

# Print all available error codes
for code, name in pycares.errno.errorcode.items():
    print(f'{code}: {name}')

# Check if an error code is known
error_code = 4
if error_code in pycares.errno.errorcode:
    print(f'Error {error_code} is {pycares.errno.errorcode[error_code]}')

Error Messages

Convert error codes to human-readable error messages.

def strerror(code: int) -> str:
    """
    Get human-readable error message for an error code.
    
    Args:
        code: int - Error code from callback or exception
        
    Returns:
        str: Human-readable error message
    """

Usage Example:

import pycares
import pycares.errno

def query_callback(result, error):
    if error is not None:
        error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
        error_msg = pycares.errno.strerror(error)
        print(f'DNS query failed: {error_name} - {error_msg}')
        return
        
    print(f'Query successful: {result}')

channel = pycares.Channel()
channel.query('nonexistent.invalid', pycares.QUERY_TYPE_A, query_callback)

Exception Classes

AresError

Primary exception class for c-ares related errors.

class AresError(Exception):
    """
    Exception raised for c-ares library errors.
    
    This exception is raised for synchronous errors during channel creation,
    configuration, or other immediate operations. Asynchronous query errors
    are reported via callback functions, not exceptions.
    """

Usage Example:

import pycares

try:
    # This might raise AresError if initialization fails
    channel = pycares.Channel(servers=['invalid.ip.address'])
except pycares.AresError as e:
    print(f'Failed to create channel: {e}')
    
try:
    channel = pycares.Channel()
    # This might raise AresError if reinit fails
    channel.reinit()
except pycares.AresError as e:
    print(f'Failed to reinitialize channel: {e}')

Error Handling Patterns

Basic Error Handling

import pycares
import pycares.errno

def handle_dns_result(result, error):
    """Standard DNS result handler with error checking."""
    if error is not None:
        if error == pycares.errno.ARES_ENOTFOUND:
            print('Domain not found')
        elif error == pycares.errno.ARES_ETIMEOUT:
            print('Query timed out')
        elif error == pycares.errno.ARES_ESERVFAIL:
            print('DNS server failure')
        else:
            print(f'DNS error: {pycares.errno.strerror(error)}')
        return
        
    # Process successful result
    print(f'DNS query successful: {result}')

channel = pycares.Channel()
channel.query('example.com', pycares.QUERY_TYPE_A, handle_dns_result)

Retry Logic

class DNSResolver:
    def __init__(self, max_retries=3):
        self.channel = pycares.Channel(timeout=5.0, tries=2)
        self.max_retries = max_retries
        
    def query_with_retry(self, name, query_type, callback, retries=0):
        def retry_callback(result, error):
            if error is not None and retries < self.max_retries:
                # Retry on timeout or server failure
                if error in (pycares.errno.ARES_ETIMEOUT, pycares.errno.ARES_ESERVFAIL):
                    print(f'Retrying query ({retries + 1}/{self.max_retries})')
                    self.query_with_retry(name, query_type, callback, retries + 1)
                    return
                    
            # Final result (success or non-retryable error)
            callback(result, error)
            
        self.channel.query(name, query_type, retry_callback)

resolver = DNSResolver()
resolver.query_with_retry('google.com', pycares.QUERY_TYPE_A, handle_dns_result)

Error Categorization

import pycares.errno

def categorize_error(error_code):
    """Categorize DNS errors for appropriate handling."""
    if error_code == pycares.errno.ARES_SUCCESS:
        return 'success'
    elif error_code in (pycares.errno.ARES_ENOTFOUND, pycares.errno.ARES_ENODATA):
        return 'not_found'
    elif error_code in (pycares.errno.ARES_ETIMEOUT, pycares.errno.ARES_ECONNREFUSED):
        return 'network'
    elif error_code in (pycares.errno.ARES_ESERVFAIL, pycares.errno.ARES_EREFUSED):
        return 'server'
    elif error_code in (pycares.errno.ARES_EBADNAME, pycares.errno.ARES_EBADQUERY):
        return 'invalid_input'
    elif error_code == pycares.errno.ARES_ECANCELLED:
        return 'cancelled'
    else:
        return 'unknown'

def handle_categorized_error(result, error):
    if error is None:
        print(f'Success: {result}')
        return
        
    category = categorize_error(error)
    error_msg = pycares.errno.strerror(error)
    
    if category == 'not_found':
        print(f'Domain not found: {error_msg}')
    elif category == 'network':
        print(f'Network error (check connectivity): {error_msg}')
    elif category == 'server':
        print(f'DNS server error (try different server): {error_msg}')
    elif category == 'invalid_input':
        print(f'Invalid input (check domain name): {error_msg}')
    elif category == 'cancelled':
        print('Query was cancelled')
    else:
        print(f'Unknown error: {error_msg}')

Timeout Handling

import time
import threading
import pycares

class TimeoutHandler:
    def __init__(self, channel, timeout_seconds=10):
        self.channel = channel
        self.timeout_seconds = timeout_seconds
        self.active_queries = set()
        
    def query_with_timeout(self, name, query_type, callback):
        query_id = id(callback)  # Simple query identifier
        self.active_queries.add(query_id)
        
        # Start timeout timer
        def timeout_callback():
            if query_id in self.active_queries:
                self.active_queries.remove(query_id)
                callback(None, pycares.errno.ARES_ETIMEOUT)
                
        timer = threading.Timer(self.timeout_seconds, timeout_callback)
        timer.start()
        
        # Wrap original callback
        def wrapped_callback(result, error):
            if query_id in self.active_queries:
                timer.cancel()
                self.active_queries.remove(query_id)
                callback(result, error)
                
        self.channel.query(name, query_type, wrapped_callback)

# Usage
handler = TimeoutHandler(pycares.Channel(), timeout_seconds=5)
handler.query_with_timeout('slow-server.com', pycares.QUERY_TYPE_A, handle_dns_result)

Debugging Support

Verbose Error Logging

import logging
import pycares.errno

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def debug_callback(name, query_type):
    """Create a callback with debug logging."""
    def callback(result, error):
        if error is not None:
            error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
            error_msg = pycares.errno.strerror(error)
            logger.error(f'Query failed: {name} ({query_type}) - {error_name}: {error_msg}')
        else:
            logger.info(f'Query successful: {name} ({query_type}) - {len(result) if isinstance(result, list) else 1} records')
            logger.debug(f'Result: {result}')
    return callback

channel = pycares.Channel()
channel.query('google.com', pycares.QUERY_TYPE_A, debug_callback('google.com', 'A'))

Error Statistics

class ErrorStats:
    def __init__(self):
        self.error_counts = {}
        self.success_count = 0
        
    def record_result(self, error):
        if error is None:
            self.success_count += 1
        else:
            error_name = pycares.errno.errorcode.get(error, f'UNKNOWN_{error}')
            self.error_counts[error_name] = self.error_counts.get(error_name, 0) + 1
            
    def print_stats(self):
        total = self.success_count + sum(self.error_counts.values())
        print(f'DNS Query Statistics (Total: {total})')
        print(f'  Successful: {self.success_count}')
        for error_name, count in sorted(self.error_counts.items()):
            print(f'  {error_name}: {count}')

stats = ErrorStats()

def stats_callback(result, error):
    stats.record_result(error)
    if error is None:
        print(f'Success: {result}')
    else:
        print(f'Error: {pycares.errno.strerror(error)}')

# After running queries...
stats.print_stats()

Common Error Scenarios

Network Connectivity Issues

# DNS server unreachable
# Error: ARES_ECONNREFUSED or ARES_ETIMEOUT

# Firewall blocking DNS
# Error: ARES_ETIMEOUT

# No internet connection
# Error: ARES_ECONNREFUSED or ARES_ETIMEOUT

DNS Configuration Problems

# Invalid DNS server configured
# Error: ARES_ESERVFAIL or ARES_ETIMEOUT

# DNS server not responding
# Error: ARES_ETIMEOUT

# DNS server refusing queries
# Error: ARES_EREFUSED

Query Issues

# Domain doesn't exist
# Error: ARES_ENOTFOUND

# Record type doesn't exist for domain
# Error: ARES_ENODATA

# Malformed domain name
# Error: ARES_EBADNAME

# Invalid query parameters
# Error: ARES_EBADQUERY

All error handling in pycares follows the pattern of passing error codes to callback functions rather than raising exceptions for asynchronous operations. Synchronous configuration errors raise AresError exceptions.

Install with Tessl CLI

npx tessl i tessl/pypi-pycares

docs

channel-management.md

dns-queries.md

error-handling.md

host-resolution.md

index.md

utilities.md

tile.json