Python Result Type

A functional programming Result type for Python, inspired by Rust's Result<T, E> and similar to PyMonad's Either, but with more intuitive naming (Success/Failure instead of Right/Left).
🚀 Features
- Intuitive API:
Success and Failure instead of cryptic Right/Left
- Rust-like Aliases: Optional
Ok and Err aliases for Rust developers
- Type Safe: Full generic type support with
Result[T, E]
- Chainable Operations: Use
.then() method or >> operator for clean chaining
- Exception Safety: Automatic exception handling in chained operations
- Async/Await Support: Full async support with
AsyncResult wrapper
- Zero Dependencies: Pure Python with no external dependencies
- Comprehensive: Includes helper functions and decorators for common patterns
- Well Tested: 100% test coverage with extensive edge case testing
📦 Installation
pip install python-result-type
🎯 Quick Start
Basic Usage
from result_type import Success, Failure, Result
def divide(a: float, b: float) -> Result[float, str]:
if b == 0:
return Failure("Division by zero")
return Success(a / b)
result = divide(10, 2)
if result.is_success():
print(f"Result: {result.value}")
else:
print(f"Error: {result.error}")
result = divide(10, 0)
if result.is_failure():
print(f"Error: {result.error}")
Chaining Operations
Chain operations that can fail using .then() or the >> operator:
from result_type import Success, Failure
def divide(a: float, b: float) -> Result[float, str]:
if b == 0:
return Failure("Division by zero")
return Success(a / b)
def multiply_by_2(x: float) -> Result[float, str]:
return Success(x * 2)
def subtract_1(x: float) -> Result[float, str]:
if x < 1:
return Failure("Result would be negative")
return Success(x - 1)
result = (
divide(10, 2)
.then(multiply_by_2)
.then(subtract_1)
.map(lambda x: x + 5)
)
result = divide(10, 2) >> multiply_by_2 >> subtract_1
result = (
divide(10, 2)
>> multiply_by_2
.map(lambda x: x + 10)
>> subtract_1
)
if result.is_success():
print(f"Final result: {result.value}")
else:
print(f"Error occurred: {result.error}")
Safe Function Calls
Automatically handle exceptions with safe_call:
from result_type import safe_call
result = safe_call(
lambda: 10 / 0,
"Math operation failed"
)
if result.is_failure():
print(result.error)
from result_type import safe_call_decorator
@safe_call_decorator("Database error")
def risky_database_operation():
return fetch_user_from_db()
result = risky_database_operation()
Rust-like Aliases (Ok/Err)
For developers familiar with Rust's Result<T, E>, this library provides Ok and Err aliases for Success and Failure:
from result_type import Ok, Err, ok, err
def divide_rust_style(a: float, b: float):
if b == 0:
return Err("Division by zero")
return Ok(a / b)
result = divide_rust_style(10, 2)
if result.is_success():
print(f"Result: {result.value}")
def multiply_by_2(x):
return Ok(x * 2)
def validate_positive(x):
if x > 0:
return Ok(x)
return Err("Must be positive")
result = Ok(5) >> multiply_by_2 >> validate_positive
if result.is_success():
print(f"Final: {result.value}")
success_result = ok(42)
error_result = err("oops")
mixed_result = Ok(10) >> (lambda x: Success(x * 2))
print(Ok(42) == Success(42))
📚 Complete API Reference
Core Types
Result[T, E]
Abstract base class representing either success or failure.
Methods:
is_success() -> bool - Check if result is Success
is_failure() -> bool - Check if result is Failure
then(func: Callable[[T], Result[U, E]]) -> Result[U, E] - Chain operations
map(func: Callable[[T], U]) -> Result[U, E] - Transform success value
map_error(func: Callable[[E], F]) -> Result[T, F] - Transform error value
unwrap() -> T - Extract value or raise exception
unwrap_or(default: T) -> T - Extract value or return default
unwrap_or_else(func: Callable[[E], T]) -> T - Extract value or compute from error
Success[T]
Represents successful result containing a value.
success_result = Success(42)
print(success_result.value)
print(success_result.is_success())
Failure[E]
Represents failed result containing an error.
failure_result = Failure("Something went wrong")
print(failure_result.error)
print(failure_result.is_failure())
Rust-like Aliases
Ok and Err
Type aliases for Success and Failure respectively.
from result_type import Ok, Err
ok_result = Ok(42)
err_result = Err("error")
ok(value: T) -> Success[T] and err(error: E) -> Failure[E]
Helper functions with Rust-style naming.
from result_type import ok, err
result = ok(42)
error = err("oops")
Helper Functions
success(value: T) -> Success[T]
Create a Success result.
from result_type import success
result = success(42)
failure(error: E) -> Failure[E]
Create a Failure result.
from result_type import failure
result = failure("error")
safe_call(func: Callable[[], T], error_msg: str = None) -> Result[T, str]
Safely call a function that might raise exceptions.
from result_type import safe_call
result = safe_call(lambda: risky_operation())
if result.is_failure():
print(f"Operation failed: {result.error}")
safe_call_decorator(error_msg: str = None)
Decorator version of safe_call.
from result_type import safe_call_decorator
@safe_call_decorator("API call failed")
def call_external_api():
return requests.get("https://api.example.com").json()
result = call_external_api()
🔄 Chaining Operations
Error Propagation
When chaining operations, errors automatically propagate:
result = (
Success(10)
>> (lambda x: Failure("Something went wrong"))
>> (lambda x: Success(x * 2))
>> (lambda x: Success(x + 1))
)
print(result.error)
Exception Handling in Chains
Exceptions in chained operations are automatically converted to Failure:
def risky_operation(x: int) -> Result[int, str]:
return Success(x / 0)
result = Success(10) >> risky_operation
print(result.is_failure())
print(type(result.error))
🔄 Async/Await Support
The library includes full async/await support for modern Python applications with the AsyncResult wrapper:
Basic Async Usage
import asyncio
from result_type import Success, Failure, Result
from result_type.async_result import AsyncResult, async_safe_call
async def fetch_user(user_id: int) -> Result[dict, str]:
await asyncio.sleep(0.1)
if user_id <= 0:
return Failure("Invalid user ID")
return Success({"id": user_id, "name": f"User {user_id}"})
result = await fetch_user(123)
if result.is_success():
print(f"Found user: {result.value}")
Async Chaining
Chain async and sync operations seamlessly:
async def fetch_user_posts(user: dict) -> Result[list, str]:
await asyncio.sleep(0.1)
return Success([f"Post {i}" for i in range(3)])
def format_summary(posts: list) -> Result[str, str]:
return Success(f"User has {len(posts)} posts")
pipeline = (AsyncResult(fetch_user(123))
.then_async(fetch_user_posts)
.then_sync(format_summary))
result = await pipeline.resolve()
if result.is_success():
print(result.value)
Async Safe Calls
Handle async exceptions safely:
from result_type.async_result import async_safe_call, async_safe_call_decorator
async def risky_api_call():
return await some_external_api()
result = await async_safe_call(risky_api_call, "API Error")
@async_safe_call_decorator("Database Error")
async def database_operation():
return await db.fetch_data()
result = await database_operation()
Gathering Multiple Async Results
Process multiple async operations concurrently:
from result_type.async_result import gather_results
async def fetch_data(source: str) -> Result[str, str]:
await asyncio.sleep(0.1)
return Success(f"Data from {source}")
async_operations = [
AsyncResult(fetch_data("source1")),
AsyncResult(fetch_data("source2")),
AsyncResult(fetch_data("source3")),
]
combined = await gather_results(*async_operations)
if combined.is_success():
print(combined.value)
Converting Regular Awaitables
Convert any awaitable to an AsyncResult:
from result_type.async_result import from_awaitable
async def regular_async_function():
return {"data": "success"}
async_result = await from_awaitable(regular_async_function(), "Operation failed")
result = await async_result.resolve()
🆚 Comparison with Alternatives
vs PyMonad Either
from pymonad.either import Left, Right
result = Right(42)
result = Left("error")
from result_type import Success, Failure
result = Success(42)
result = Failure("error")
vs Exception Handling
try:
result = risky_operation()
result = transform(result)
result = another_transform(result)
except Exception as e:
handle_error(e)
result = (
safe_call(risky_operation)
>> safe_transform
>> safe_another_transform
)
if result.is_failure():
handle_error(result.error)
🧪 Real-World Examples
Database Operations
from result_type import Result, Success, Failure
def fetch_user(user_id: str) -> Result[dict, str]:
try:
user = database.users.find_one({"_id": user_id})
if not user:
return Failure("User not found")
return Success(user)
except Exception as e:
return Failure(f"Database error: {e}")
def validate_user(user: dict) -> Result[dict, str]:
if not user.get("is_active"):
return Failure("User is inactive")
return Success(user)
def get_user_permissions(user: dict) -> Result[list, str]:
permissions = user.get("permissions", [])
if not permissions:
return Failure("User has no permissions")
return Success(permissions)
result = (
fetch_user("user123")
>> validate_user
>> get_user_permissions
)
if result.is_success():
print(f"User permissions: {result.value}")
else:
print(f"Failed to get permissions: {result.error}")
API Calls
import requests
from result_type import safe_call, Result, Success, Failure
def fetch_weather(city: str) -> Result[dict, str]:
return safe_call(
lambda: requests.get(f"http://api.weather.com/{city}").json(),
f"Failed to fetch weather for {city}"
)
def extract_temperature(weather_data: dict) -> Result[float, str]:
try:
temp = weather_data["current"]["temperature"]
return Success(float(temp))
except (KeyError, ValueError, TypeError) as e:
return Failure(f"Invalid weather data: {e}")
def celsius_to_fahrenheit(celsius: float) -> Result[float, str]:
return Success(celsius * 9/5 + 32)
result = (
fetch_weather("London")
>> extract_temperature
>> celsius_to_fahrenheit
)
if result.is_success():
print(f"Temperature in Fahrenheit: {result.value}")
else:
print(f"Error: {result.error}")
File Operations
from pathlib import Path
from result_type import safe_call, Result
def read_config_file(path: str) -> Result[dict, str]:
def _read_and_parse():
content = Path(path).read_text()
return json.loads(content)
return safe_call(_read_and_parse, f"Failed to read config from {path}")
def validate_config(config: dict) -> Result[dict, str]:
required_fields = ["api_key", "database_url", "port"]
missing = [field for field in required_fields if field not in config]
if missing:
return Failure(f"Missing required fields: {missing}")
return Success(config)
def start_application(config: dict) -> Result[str, str]:
return Success(f"Application started on port {config['port']}")
result = (
read_config_file("config.json")
>> validate_config
>> start_application
)
if result.is_success():
print(result.value)
else:
print(f"Startup failed: {result.error}")
🧪 Testing
pip install python-result-type[dev]
pytest
pytest --cov=result_type --cov-report=html
mypy result_type
black result_type tests
📄 Type Safety
This library is fully typed and compatible with mypy:
from result_type import Result
def typed_operation(x: int) -> Result[str, str]:
if x < 0:
return Failure("Negative numbers not allowed")
return Success(str(x))
result: Result[str, str] = typed_operation(42)
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
📜 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
📈 Changelog
1.2.1
- Fixed: Mypy warning
"Result[T, E]" has no attribute "error" when accessing result.error
- Fixed: Mypy warning
"Result[T, E]" has no attribute "value" when accessing result.value
- Enhanced: Added
value and error properties to the abstract Result class for better type checking
- Improved: Better mypy compatibility and type safety throughout the codebase
- Maintained: Full backward compatibility - no breaking changes to existing code
1.2.0
- New: Rust-like aliases
Ok and Err for Success and Failure
- New: Helper functions
ok() and err() with Rust-style naming
- Enhanced: Full compatibility between Rust aliases and original naming
- Improved: Better developer experience for Rust developers
- Added: Comprehensive tests and examples for Rust-style usage
1.1.0
- New: Full async/await support with
AsyncResult wrapper
- New:
async_safe_call and async_safe_call_decorator for async exception handling
- New: Async chaining operations with
.then_async() and .then_sync()
- New:
gather_results() for concurrent async operations
- New:
from_awaitable() to convert regular awaitables to AsyncResult
- Improved: Enhanced type annotations for better IDE support
- Enhanced: Better error messages and stack traces
1.0.0
- Initial release
- Core Result, Success, and Failure types
- Chaining with
.then() and >> operator
- Helper functions and decorators
- Comprehensive test suite
- Full type annotations