CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-restnavigator

A python library for interacting with HAL+JSON APIs

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

Comprehensive exception hierarchy for handling HTTP errors, parsing failures, and navigation issues. RestNavigator provides detailed error information to help diagnose and handle API interaction problems.

Capabilities

Main Exception Class

Primary exception raised for HTTP error responses with detailed context information.

class HALNavigatorError(Exception):
    def __init__(self, message: str, nav: 'HALNavigator', response):
        """
        Exception raised for HTTP error responses (4xx, 5xx).
        
        Parameters:
        - message: Human-readable error message
        - nav: HALNavigator instance that caused the error
        - response: HTTP response object from requests
        """
    
    @property
    def nav(self) -> 'HALNavigator':
        """Navigator that caused the error"""
    
    @property
    def response(self):
        """HTTP response object"""
    
    @property
    def message(self) -> str:
        """Error message"""
    
    @property
    def status(self) -> int:
        """HTTP status code"""

URL and Scheme Validation Errors

Exceptions for invalid URLs and protocol issues.

class WileECoyoteException(ValueError):
    """
    Raised for bad URL schemes.
    
    Occurs when URL has invalid or missing scheme.
    """

class ZachMorrisException(ValueError):
    """
    Raised for URLs with too many schemes.
    
    Occurs when URL has multiple conflicting schemes.
    """

Resource State Errors

Exceptions for accessing uninitialized or unavailable resource data.

class NoResponseError(ValueError):
    """
    Raised when accessing unfetched navigator fields.
    
    Occurs when trying to access state/response data
    before navigator has been resolved.
    """

JSON Parsing Errors

Exception for non-JSON response handling.

class UnexpectedlyNotJSON(TypeError):
    def __init__(self, uri: str, response):
        """
        Raised for non-JSON parseable responses.
        
        Parameters:
        - uri: Resource URI that returned non-JSON
        - response: HTTP response object
        """
    
    @property
    def uri(self) -> str:
        """Resource URI"""
    
    @property
    def response(self):
        """HTTP response object"""

Navigation Traversal Errors

Exception for invalid navigation operations and traversal failures.

class OffTheRailsException(TypeError):
    def __init__(self, traversal: list, index: int, intermediates: list, exception: Exception):
        """
        Raised for invalid navigation traversals.
        
        Parameters:
        - traversal: The attempted traversal path
        - index: Where in the traversal the failure occurred
        - intermediates: Successfully traversed navigators
        - exception: Original exception that caused the failure
        """
    
    @property
    def traversal(self) -> list:
        """The attempted traversal path"""
    
    @property
    def index(self) -> int:
        """Index where traversal failed"""
    
    @property
    def intermediates(self) -> list:
        """Intermediate navigators before failure"""
    
    @property
    def exception(self) -> Exception:
        """Original exception"""

Error Handling Patterns

Basic Error Handling

from restnavigator import Navigator
from restnavigator.exc import HALNavigatorError

api = Navigator.hal('https://api.example.com/')

try:
    user = api['users', 'nonexistent']()
except HALNavigatorError as e:
    print(f"HTTP Error {e.status}: {e.message}")
    print(f"Failed URL: {e.nav.uri}")
    print(f"Response: {e.response.text}")

Graceful Error Handling

# Disable automatic exception raising
result = api['users'].create(invalid_data, raise_exc=False)

if not result:  # Check if operation succeeded
    status_code, reason = result.status
    print(f"Creation failed: {status_code} {reason}")
    
    # Access error details from response
    if hasattr(result, 'state') and result.state:
        print("Error details:", result.state)

Specific Status Code Handling

try:
    user_data = api['users', user_id]()
except HALNavigatorError as e:
    if e.status == 404:
        print("User not found")
        # Handle missing resource
    elif e.status == 403:
        print("Access denied")
        # Handle permission error
    elif e.status >= 500:
        print("Server error, retrying...")
        # Handle server errors
    else:
        print(f"Unexpected error: {e.status}")
        raise

Navigation Error Handling

from restnavigator.exc import OffTheRailsException

try:
    # Complex navigation that might fail
    data = api['users', 0, 'posts', 'comments', 'invalid']()
except OffTheRailsException as e:
    print(f"Navigation failed at step {e.index}")
    print(f"Attempted path: {e.traversal}")
    print(f"Successfully navigated: {len(e.intermediates)} steps")
    print(f"Error: {e.exception}")
    
    # Work with successful intermediate results
    if e.intermediates:
        last_successful = e.intermediates[-1]
        print(f"Last successful resource: {last_successful.uri}")

JSON Parsing Error Handling

from restnavigator.exc import UnexpectedlyNotJSON

try:
    # Request might return non-JSON (e.g., HTML error page)
    data = api['csv-export']()
except UnexpectedlyNotJSON as e:
    print(f"Expected JSON from {e.uri}")
    print(f"Got content type: {e.response.headers.get('content-type')}")
    
    # Handle non-JSON response appropriately
    if 'text/csv' in e.response.headers.get('content-type', ''):
        csv_data = e.response.text
        # Process CSV data
    else:
        print(f"Unexpected content: {e.response.text[:200]}...")

URL Validation Error Handling

from restnavigator.exc import WileECoyoteException, ZachMorrisException

try:
    # This might have URL issues
    api = Navigator.hal('invalid-url')
except WileECoyoteException:
    print("Invalid URL scheme - make sure URL starts with http:// or https://")
except ZachMorrisException:
    print("Multiple conflicting schemes in URL")

Resource State Error Handling

from restnavigator.exc import NoResponseError

# Create navigator but don't fetch yet
user = api['users', user_id]

try:
    # This will fail because we haven't fetched the resource
    print(user.state)
except NoResponseError:
    print("Resource not yet fetched")
    # Fetch the resource first
    user_data = user()
    print(user.state)  # Now this works

Comprehensive Error Handling Strategy

def safe_api_call(navigator_func):
    """Wrapper for safe API calls with comprehensive error handling"""
    try:
        return navigator_func()
    except HALNavigatorError as e:
        if e.status in [401, 403]:
            # Authentication/authorization error
            handle_auth_error(e)
        elif e.status == 404:
            # Resource not found
            return None
        elif e.status == 429:
            # Rate limiting
            handle_rate_limit(e)
        elif e.status >= 500:
            # Server error
            handle_server_error(e)
        else:
            # Other HTTP errors
            log_error(f"HTTP {e.status}: {e.message}")
            raise
    except OffTheRailsException as e:
        # Navigation error
        log_error(f"Navigation failed: {e.traversal}")
        return None
    except UnexpectedlyNotJSON as e:
        # Content type error
        log_error(f"Non-JSON response from {e.uri}")
        return e.response.text
    except (WileECoyoteException, ZachMorrisException) as e:
        # URL validation error
        log_error(f"Invalid URL: {e}")
        raise ValueError("Invalid API URL configuration")
    except NoResponseError:
        # Resource state error
        log_error("Attempted to access unfetched resource")
        return None

# Usage
user_data = safe_api_call(lambda: api['users', user_id]())
if user_data is None:
    print("Failed to retrieve user data")

Error Context and Debugging

def debug_error(exception):
    """Extract comprehensive error information for debugging"""
    if isinstance(exception, HALNavigatorError):
        print(f"HTTP Error: {exception.status}")
        print(f"URL: {exception.nav.uri}")
        print(f"Message: {exception.message}")
        print(f"Response Headers: {dict(exception.response.headers)}")
        print(f"Response Body: {exception.response.text}")
        
        # Check request details
        request = exception.response.request
        print(f"Request Method: {request.method}")
        print(f"Request Headers: {dict(request.headers)}")
        if request.body:
            print(f"Request Body: {request.body}")
    
    elif isinstance(exception, OffTheRailsException):
        print(f"Navigation Error at step {exception.index}")
        print(f"Full path: {exception.traversal}")
        print(f"Completed steps: {len(exception.intermediates)}")
        for i, nav in enumerate(exception.intermediates):
            print(f"  Step {i}: {nav.uri}")
        print(f"Underlying error: {exception.exception}")

# Usage in exception handler
try:
    data = api['complex', 'navigation', 'path']()
except Exception as e:
    debug_error(e)
    raise

Install with Tessl CLI

npx tessl i tessl/pypi-restnavigator

docs

authentication.md

exceptions.md

http-operations.md

index.md

navigation.md

templated-links.md

utilities.md

tile.json