Exceptions

django-api-orm provides a comprehensive exception hierarchy similar to Django’s ORM exceptions, making error handling familiar and predictable.

Exception Hierarchy

All exceptions inherit from APIException:

APIException
├── ValidationError (from Pydantic)
├── DoesNotExist
├── MultipleObjectsReturned
├── APIConnectionError
├── APITimeoutError
├── APIHTTPError
│   ├── APIClientError (4xx)
│   │   ├── BadRequestError (400)
│   │   ├── UnauthorizedError (401)
│   │   ├── ForbiddenError (403)
│   │   ├── NotFoundError (404)
│   │   └── TooManyRequestsError (429)
│   └── APIServerError (5xx)
│       ├── InternalServerError (500)
│       ├── BadGatewayError (502)
│       ├── ServiceUnavailableError (503)
│       └── GatewayTimeoutError (504)
└── ConfigurationError

Common Exceptions

DoesNotExist

Raised when a query returns no results when exactly one was expected:

from django_api_orm.exceptions import DoesNotExist

try:
    user = User.objects.get(id=999)
except DoesNotExist:
    print("User not found")

# Or catch model-specific exception
try:
    user = User.objects.get(id=999)
except User.DoesNotExist:
    print("User not found")

Common scenarios:

  • Model.objects.get() with no matching record

  • API returns 404 for a specific resource

MultipleObjectsReturned

Raised when a query returns multiple results when exactly one was expected:

from django_api_orm.exceptions import MultipleObjectsReturned

try:
    # Multiple users with role="admin" exist
    user = User.objects.get(role="admin")
except MultipleObjectsReturned:
    print("Multiple users found, expected one")

# Or catch model-specific exception
try:
    user = User.objects.get(role="admin")
except User.MultipleObjectsReturned:
    print("Multiple users found")

Common scenarios:

  • Model.objects.get() with multiple matching records

  • API returns a list when a single item was expected

ValidationError

Raised when data validation fails (from Pydantic):

from pydantic import ValidationError

try:
    user = User.objects.create(
        name="",  # Empty name
        email="invalid-email",  # Invalid email
        age=-5  # Invalid age
    )
except ValidationError as e:
    print(f"Validation failed: {e}")
    for error in e.errors():
        print(f"  {error['loc']}: {error['msg']}")

Common scenarios:

  • Invalid field values during creation

  • Missing required fields

  • Type mismatches

  • Failed Pydantic validators

HTTP Exceptions

APIHTTPError

Base class for all HTTP-related errors:

from django_api_orm.exceptions import APIHTTPError

try:
    user = User.objects.get(id=1)
except APIHTTPError as e:
    print(f"HTTP error: {e.status_code} - {e.message}")

Client Errors (4xx)

BadRequestError (400): Invalid request data

from django_api_orm.exceptions import BadRequestError

try:
    user = User.objects.create(name="Alice", email="alice@example.com")
except BadRequestError as e:
    print(f"Bad request: {e.message}")

UnauthorizedError (401): Authentication required or failed

from django_api_orm.exceptions import UnauthorizedError

try:
    user = User.objects.get(id=1)
except UnauthorizedError:
    print("Authentication required or token expired")

ForbiddenError (403): Authenticated but not authorized

from django_api_orm.exceptions import ForbiddenError

try:
    user.delete()
except ForbiddenError:
    print("You don't have permission to delete this user")

NotFoundError (404): Resource not found

from django_api_orm.exceptions import NotFoundError

try:
    user = User.objects.get(id=999)
except NotFoundError:
    print("User not found")

TooManyRequestsError (429): Rate limit exceeded

from django_api_orm.exceptions import TooManyRequestsError

try:
    users = User.objects.all()
except TooManyRequestsError as e:
    print(f"Rate limit exceeded. Retry after: {e.retry_after}")

Server Errors (5xx)

InternalServerError (500): Server error

from django_api_orm.exceptions import InternalServerError

try:
    user = User.objects.create(name="Alice", email="alice@example.com")
except InternalServerError:
    print("Server encountered an error")

BadGatewayError (502): Bad gateway

from django_api_orm.exceptions import BadGatewayError

try:
    users = User.objects.all()
except BadGatewayError:
    print("Gateway error - upstream server issue")

ServiceUnavailableError (503): Service unavailable

from django_api_orm.exceptions import ServiceUnavailableError

try:
    users = User.objects.all()
except ServiceUnavailableError as e:
    print(f"Service unavailable. Retry after: {e.retry_after}")

GatewayTimeoutError (504): Gateway timeout

from django_api_orm.exceptions import GatewayTimeoutError

try:
    users = User.objects.all()
except GatewayTimeoutError:
    print("Gateway timeout - upstream server too slow")

Connection Exceptions

APIConnectionError

Raised when a connection to the API cannot be established:

from django_api_orm.exceptions import APIConnectionError

try:
    user = User.objects.get(id=1)
except APIConnectionError as e:
    print(f"Connection failed: {e.message}")

Common scenarios:

  • Network is down

  • Invalid base URL

  • DNS resolution failed

  • SSL/TLS errors

APITimeoutError

Raised when a request times out:

from django_api_orm.exceptions import APITimeoutError

try:
    user = User.objects.get(id=1)
except APITimeoutError as e:
    print(f"Request timed out after {e.timeout} seconds")

Common scenarios:

  • API is slow to respond

  • Large response taking too long

  • Network latency issues

Configuration Exceptions

ConfigurationError

Raised when there’s a configuration issue:

from django_api_orm.exceptions import ConfigurationError

try:
    # Model not registered with client
    user = User.objects.get(id=1)
except ConfigurationError as e:
    print(f"Configuration error: {e.message}")

Common scenarios:

  • Model not registered with a client

  • Invalid endpoint configuration

  • Missing required configuration

Error Handling Patterns

Specific Exception Handling

Handle specific exceptions for fine-grained control:

from django_api_orm.exceptions import (
    DoesNotExist,
    UnauthorizedError,
    NotFoundError,
    APIServerError
)

try:
    user = User.objects.get(id=user_id)
except DoesNotExist:
    print("User not found")
except UnauthorizedError:
    print("Please log in")
except NotFoundError:
    print("API endpoint not found")
except APIServerError:
    print("Server error, try again later")

Generic Exception Handling

Catch all API exceptions with APIException:

from django_api_orm.exceptions import APIException

try:
    user = User.objects.get(id=user_id)
except APIException as e:
    print(f"API error: {e}")
    # Log error, notify admin, etc.

HTTP Status Code Handling

Handle errors by HTTP status code:

from django_api_orm.exceptions import APIHTTPError, APIClientError, APIServerError

try:
    user = User.objects.get(id=user_id)
except APIClientError as e:
    # 4xx errors - client issue
    if e.status_code == 401:
        print("Please log in")
    elif e.status_code == 403:
        print("Permission denied")
    elif e.status_code == 404:
        print("Not found")
    else:
        print(f"Client error: {e.status_code}")
except APIServerError as e:
    # 5xx errors - server issue
    print(f"Server error: {e.status_code}")

Retry Logic

Implement retry logic for transient errors:

import time
from django_api_orm.exceptions import (
    APIServerError,
    APITimeoutError,
    TooManyRequestsError
)

max_retries = 3
retry_delay = 1  # seconds

for attempt in range(max_retries):
    try:
        user = User.objects.get(id=user_id)
        break
    except (APIServerError, APITimeoutError) as e:
        if attempt < max_retries - 1:
            time.sleep(retry_delay * (attempt + 1))
            continue
        raise
    except TooManyRequestsError as e:
        if attempt < max_retries - 1 and e.retry_after:
            time.sleep(e.retry_after)
            continue
        raise

Fallback Values

Provide fallback values when resources don’t exist:

from django_api_orm.exceptions import DoesNotExist

try:
    user = User.objects.get(email=email)
except DoesNotExist:
    # Create a default user
    user = User.objects.create(
        email=email,
        name="New User",
        active=False
    )

# Or use get_or_create
user, created = User.objects.get_or_create(
    email=email,
    defaults={"name": "New User", "active": False}
)

Logging Errors

Log errors for debugging and monitoring:

import logging
from django_api_orm.exceptions import APIException

logger = logging.getLogger(__name__)

try:
    user = User.objects.get(id=user_id)
except APIException as e:
    logger.error(
        f"API error getting user {user_id}: {e}",
        exc_info=True,
        extra={
            "user_id": user_id,
            "exception_type": type(e).__name__,
        }
    )
    raise

Async Exception Handling

Exception handling in async code works the same way:

from django_api_orm.exceptions import DoesNotExist, APIHTTPError

try:
    user = await User.objects.get(id=user_id)
except DoesNotExist:
    print("User not found")
except APIHTTPError as e:
    print(f"HTTP error: {e.status_code}")

Best Practices

  1. Be specific: Catch specific exceptions when possible

  2. Log errors: Always log unexpected errors for debugging

  3. Provide context: Include relevant information in error messages

  4. Handle validation: Validate data before sending to API

  5. Implement retries: Retry transient errors (5xx, timeouts)

  6. Respect rate limits: Honor retry_after headers

  7. Use fallbacks: Provide sensible defaults when appropriate

  8. Don’t catch too broadly: Only catch exceptions you can handle

Exception Attributes

All exceptions have useful attributes:

from django_api_orm.exceptions import APIHTTPError

try:
    user = User.objects.get(id=user_id)
except APIHTTPError as e:
    print(f"Status code: {e.status_code}")
    print(f"Message: {e.message}")
    print(f"Response: {e.response}")

from django_api_orm.exceptions import TooManyRequestsError

try:
    users = User.objects.all()
except TooManyRequestsError as e:
    print(f"Retry after: {e.retry_after} seconds")

Complete Example

import logging
from django_api_orm.exceptions import (
    DoesNotExist,
    MultipleObjectsReturned,
    ValidationError,
    UnauthorizedError,
    ForbiddenError,
    NotFoundError,
    TooManyRequestsError,
    APIServerError,
    APITimeoutError,
    APIConnectionError,
)

logger = logging.getLogger(__name__)

def get_user_safely(user_id: int):
    """Get a user with comprehensive error handling."""
    try:
        return User.objects.get(id=user_id)

    except DoesNotExist:
        logger.warning(f"User {user_id} not found")
        return None

    except UnauthorizedError:
        logger.error("Authentication failed")
        raise  # Re-raise to caller

    except ForbiddenError:
        logger.error(f"Access denied to user {user_id}")
        raise

    except NotFoundError:
        logger.error("API endpoint not found")
        return None

    except TooManyRequestsError as e:
        logger.warning(f"Rate limited. Retry after {e.retry_after}s")
        raise

    except APIServerError as e:
        logger.error(f"Server error: {e.status_code} - {e.message}")
        return None

    except APITimeoutError:
        logger.error(f"Request timed out for user {user_id}")
        return None

    except APIConnectionError as e:
        logger.error(f"Connection failed: {e.message}")
        return None

    except Exception as e:
        logger.exception(f"Unexpected error getting user {user_id}")
        raise

Next Steps