Exceptions#

Lima-API provides a comprehensive exception system that allows you to handle HTTP errors and validation issues in a structured way. All exceptions in Lima-API are built on top of the base LimaException class.

Base Exception Classes#

LimaException#

class lima_api.exceptions.LimaException(detail: str | None = None, status_code: codes | int | None = None, content: bytes | None = None, request: Request | None = None, response: Response | None = None)[source]#

Bases: Exception

model: T | None = None#
__init__(detail: str | None = None, status_code: codes | int | None = None, content: bytes | None = None, request: Request | None = None, response: Response | None = None)[source]#
detail: str = ''#
property http_request: Request | None#
property http_response: Response | None#
property status_code: codes | int | None#
property content: bytes | None#
json(default: ~typing.Any | None = <class 'dict'>)[source]#

Return the JSON-decoded content of the response.

If the content is None, return the provided default value. The default value can be a callable, in which case it is called to obtain the return value.

Parameters:

default – The default value to return if content is None.

Returns:

The JSON-decoded content or the default value.

Raises:

json.JSONDecodeError – If the content cannot be decoded.

object() BaseModel[source]#

Parse the response content into a Pydantic model.

Returns:

The parsed Pydantic model.

Raises:

pydantic.ValidationError – If the content cannot be parsed.

response(default: ~typing.Any | None = <class 'dict'>) bytes | Any | T[source]#

Parse the response content into a Pydantic model, json dump or default value.

Parameters:

default – The default value to return if content is None. Callable or Any.

Returns:

The parsed content or the default value.

LimaException is the base exception class for all Lima-API exceptions. It provides rich information about HTTP requests and responses, making it easier to debug and handle errors.

Key Features#

  • HTTP Context: Access to the original HTTP request and response objects

  • Status Code: Automatic extraction of HTTP status codes

  • Content Handling: Built-in methods to parse response content as JSON or custom objects

  • Flexible Initialization: Can be initialized with various combinations of parameters

Usage Examples#

import lima_api

try:
    # Your API call here
    result = await client.some_api_call()
except lima_api.LimaException as e:
    print(f"Status Code: {e.status_code}")
    print(f"Error Detail: {e.detail}")
    print(f"Response Content: {e.json()}")
    
    # Access the original HTTP objects
    if e.http_request:
        print(f"Request URL: {e.http_request.url}")
    if e.http_response:
        print(f"Response Headers: {e.http_response.headers}")

ValidationError#

class lima_api.exceptions.ValidationError(detail: str | None = None, status_code: codes | int | None = None, content: bytes | None = None, request: Request | None = None, response: Response | None = None)[source]#

Bases: LimaException

detail: str = 'Validation error'#

ValidationError is a specialized exception for handling validation errors that occur during request processing or response parsing.

Usage Examples#

import lima_api
from pydantic import ValidationError as PydanticValidationError

try:
    # API call that might have validation issues
    result = await client.create_user(invalid_data)
except lima_api.ValidationError as e:
    print(f"Validation failed: {e}")
    # The underlying pydantic error is available via __cause__
    if isinstance(e.__cause__, PydanticValidationError):
        for error in e.__cause__.errors():
            print(f"Field: {error['loc']}, Error: {error['msg']}")

Exception Handling Patterns#

Response Mapping#

Lima-API allows you to map specific HTTP status codes to custom exception classes using the response_mapping feature:

import lima_api

class CustomNotFoundError(lima_api.LimaException):
    detail = "Resource not found"

class CustomAuthError(lima_api.LimaException):
    detail = "Authentication failed"

class MyApiClient(lima_api.LimaApi):
    base_url = "https://api.example.com"
    response_mapping = {
        404: CustomNotFoundError,
        401: CustomAuthError,
    }

    @lima_api.get("/users/{user_id}")
    async def get_user(self, user_id: int) -> dict:
        pass

# Usage
with MyApiClient() as client:
    try:
        user = await client.get_user(999)
    except CustomNotFoundError:
        print("User not found!")
    except CustomAuthError:
        print("Authentication required!")

Method-Level Exception Mapping#

You can also define response mapping at the method level:

@lima_api.get(
    "/sensitive-data",
    response_mapping={
        403: CustomPermissionError,
        429: CustomRateLimitError,
    }
)
async def get_sensitive_data(self) -> dict:
    pass

Global Exception Handling#

For global exception handling, you can catch the base LimaException:

async def safe_api_call():
    try:
        return await client.some_method()
    except lima_api.LimaException as e:
        # Log the error with full context
        logger.error(
            "API call failed",
            extra={
                "status_code": e.status_code,
                "detail": e.detail,
                "url": e.http_request.url if e.http_request else None,
                "response_content": e.json() if e.content else None,
            }
        )
        # Re-raise or handle as needed
        raise

Best Practices#

Create Meaningful Exception Classes#

class UserNotFoundError(lima_api.LimaException):
    detail = "User not found"

class UserAlreadyExistsError(lima_api.LimaException):
    detail = "User already exists"

class InsufficientPermissionsError(lima_api.LimaException):
    detail = "Insufficient permissions to perform this action"

Use Response Models for Structured Error Handling#

from pydantic import BaseModel

class ErrorResponse(BaseModel):
    error_code: str
    message: str
    details: dict = {}

class ApiError(lima_api.LimaException):
    model = ErrorResponse

Read errors ussing response function#

async def safe_api_call():
    try:
        return await client.some_method()
    except ApiError as e:
        error = e.response(default=None)
        if isinstanceof(error, ErrorResponse):
            ...
        elif error is None:
            print("Empty body")
        else:
            print(f"Unexpected error {error}")
        raise

Implement Retry Logic with Exception Handling#

@lima_api.get(
    "/retry-data",
    response_mapping={
        403: CustomPermissionError,
        429: CustomRateLimitError,
    }
    retry_mapping={429: lima_api.retry_processors.RetryAfterProcessor},
)
async def get_retry_data(self) -> dict:
    pass

5. Testing Exception Scenarios#

For testing we recoment mock the client funtion directly result, not the http response body.