Skip to main content

Clean error handling with Result pattern and @resilient decorators

Project description

resilient-result

PyPI version License: MIT Python 3.8+ Code style: black

Clean error handling with Result pattern and @resilient decorators that convert exceptions to Results for both sync and async functions, with full generic typing support.

Installation

pip install resilient-result

Quick Start

from resilient_result import Result, resilient

# Basic Result usage
def divide_safely(a: int, b: int) -> Result[int, str]:
    if b == 0:
        return Result.fail("Cannot divide by zero")
    return Result.ok(a // b)

result = divide_safely(10, 2)
if result.success:
    print(f"Result: {result.data}")  # Result: 5
else:
    print(f"Error: {result.error}")

# @resilient decorator converts exceptions to Results
@resilient
def divide(a: int, b: int) -> int:
    return a // b

result = divide(10, 0)  # Returns Result.fail("integer division or modulo by zero")
if not result.success:
    print(f"Division failed: {result.error}")

Core Concepts

Result Pattern

The Result type represents either success (Result.ok(data)) or failure (Result.fail(error)):

from resilient_result import Result

# Create results
success = Result.ok("data")
failure = Result.fail("error message")

# Check status
if success.success:  # True
    print(success.data)  # "data"

if failure.failure:  # True  
    print(failure.error)  # "error message"

# Boolean evaluation
if success:  # True (success)
    print("Operation succeeded")

if not failure:  # True (failure)
    print("Operation failed")

Guard Clause Pattern

Use guard clauses for clean early returns:

def multi_step_process(data: str) -> Result[str, str]:
    # Step 1
    step1_result = process_step1(data)
    if not step1_result.success:
        return step1_result  # Early return on failure
    
    # Step 2  
    step2_result = process_step2(step1_result.data)
    if not step2_result.success:
        return step2_result  # Early return on failure
        
    # Success path
    return Result.ok(step2_result.data + "_complete")

@resilient Decorator

Convert exception-throwing functions to Result-returning functions:

from resilient_result import resilient

@resilient
def risky_operation(value: str) -> dict:
    if not value:
        raise ValueError("Empty value not allowed")
    return {"processed": value.upper()}

# Usage
result = risky_operation("")  # Result.fail("Empty value not allowed")
result = risky_operation("hello")  # Result.ok({"processed": "HELLO"})

# Works with async functions too
@resilient  
async def async_operation() -> str:
    # ... async work that might raise
    return "success"

result = await async_operation()  # Returns Result

Rust-Style Aliases

For Rust developers, familiar Ok and Err constructors are available:

from resilient_result import Ok, Err

success = Ok("data")  # Same as Result.ok("data")
failure = Err("error")  # Same as Result.fail("error")

Real-World Example

from resilient_result import resilient, Result

@resilient
def fetch_user(user_id: int) -> dict:
    if user_id <= 0:
        raise ValueError("Invalid user ID")
    # Simulate API call that might fail
    return {"id": user_id, "name": f"User{user_id}"}

@resilient
def fetch_user_posts(user_id: int) -> list:
    if user_id <= 0:
        raise ValueError("Invalid user ID") 
    return [{"title": "Post 1"}, {"title": "Post 2"}]

def get_user_profile(user_id: int) -> Result[dict, str]:
    # Fetch user - early return on failure
    user_result = fetch_user(user_id)
    if not user_result.success:
        return user_result
    
    # Fetch posts - early return on failure  
    posts_result = fetch_user_posts(user_id)
    if not posts_result.success:
        return posts_result
    
    # Combine successful results
    profile = {
        "user": user_result.data,
        "posts": posts_result.data
    }
    return Result.ok(profile)

# Usage
profile_result = get_user_profile(123)
if profile_result.success:
    print(f"User: {profile_result.data['user']['name']}")
    print(f"Posts: {len(profile_result.data['posts'])}")
else:
    print(f"Failed to load profile: {profile_result.error}")

Why resilient-result?

  • Clean Error Handling: No more try/catch blocks everywhere
  • Explicit Error States: Errors are part of the type system
  • Composable: Chain operations with early returns on failure
  • Readable: Guard clauses make success/failure paths obvious
  • Zero Dependencies: Lightweight with no external dependencies
  • Type Safe: Full type hints for better IDE support

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

resilient_result-0.1.0.tar.gz (4.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

resilient_result-0.1.0-py3-none-any.whl (5.4 kB view details)

Uploaded Python 3

File details

Details for the file resilient_result-0.1.0.tar.gz.

File metadata

  • Download URL: resilient_result-0.1.0.tar.gz
  • Upload date:
  • Size: 4.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.10 Darwin/24.5.0

File hashes

Hashes for resilient_result-0.1.0.tar.gz
Algorithm Hash digest
SHA256 462f254058b2a0d824ec4ce7e8d99b05e4b30753c1fb870a35811e558828dd12
MD5 542bc5815614d7b74243b9325ea975ff
BLAKE2b-256 d3ed44459ef9b4eec75eed9e6524c8bae7dc225ca86c4a68db17fa253a8c718a

See more details on using hashes here.

File details

Details for the file resilient_result-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: resilient_result-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.10 Darwin/24.5.0

File hashes

Hashes for resilient_result-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b89e1bfcb4c1bd51268779f3b5ef239a8424cc2b835f210f777fc99411f05eb3
MD5 91e06b2d80778fe1b4b9e3b13d0f198c
BLAKE2b-256 883618886ef33b696be90633785feb2dbab70a4756d958401f94e92d56bdc90b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page