Minimalist Result Type Handling with Pure Python Typing
Tired of boilerplate classes just to represent success or failure? Looking for Rust-like Result
expressiveness without adopting a whole new type system or custom classes?
Resultite offers a clean, lightweight approach using Python's native Union[T, Exception]
type hint pattern. Capture function outcomes transparently, handle errors gracefully, and chain operations—all with standard Python functions and type safety. It's the Pythonic way to handle results.
- ✅ Purely Type-Based: Uses
typing.Union[T, Exception]
(aliased asResult[T]
) – no custom classes needed. - ✅ Type-Safe: Leverages Python's type hinting for robust error handling patterns.
- ✅ Sync & Async: Seamlessly works with both synchronous and asynchronous functions.
- ✅ Composable: Functions designed for easy chaining and transformation of results.
- ✅ Minimal & Lightweight: No external dependencies beyond standard Python.
- ✅ Easy Handling: Utility functions to extract values, provide defaults, map results, or raise errors.
- ✅ Testable: Simple functional units make testing straightforward.
- ✅ Pythonic Philosophy: Embraces Python's built-in features over introducing complex abstractions.
pip install resultite
Or using uv:
uv pip install resultite
Basic Sync Example:
from resultite import run_catching, get_or_throw, get_or_default, Result
def might_fail(data: str) -> int:
if not data:
raise ValueError("Input cannot be empty")
return int(data) * 2
# Capture the outcome
result: Result[int] = run_catching(might_fail, "10") # -> 20
error_result: Result[int] = run_catching(might_fail, "") # -> ValueError("Input cannot be empty")
# Handle the result
try:
value = get_or_throw(error_result) # Raises ValueError
except ValueError as e:
print(f"Caught expected error: {e}")
# Provide a default
safe_value = get_or_default(error_result, -1) # -> -1
print(f"Safe value: {safe_value}")
# Get None on error
maybe_value = get_or_none(result) # -> 20
maybe_error = get_or_none(error_result) # -> None
Async Example:
import asyncio
from resultite import async_run_catching, map_result_async, get_or_else_async
async def fetch_data(url: str) -> dict:
# In a real scenario, this would use a library like aiohttp
await asyncio.sleep(0.1)
if "error" in url:
raise ConnectionError("Failed to connect")
return {"data": url}
async def process_data(data: dict) -> str:
await asyncio.sleep(0.1)
return data.get("data", "default").upper()
async def main():
result: Result[dict] = await async_run_catching(fetch_data, "http://example.com")
# -> {"data": "http://example.com"}
error_result: Result[dict] = await async_run_catching(fetch_data, "http://error.com")
# -> ConnectionError("Failed to connect")
# Map the successful result asynchronously
processed_result: Result[str] = await map_result_async(result, process_data)
# -> "HTTP://EXAMPLE.COM"
# Handle error result with an async fallback
final_value = await get_or_else_async(
processed_result,
lambda e: f"Async fallback for error: {e}" # Fallback if process_data failed
)
print(f"Processed value: {final_value}") # -> Processed value: HTTP://EXAMPLE.COM
final_error_value = await get_or_else_async(
error_result, # Original fetch error
lambda e: f"Async fallback for error: {e}"
)
# -> Async fallback for error: Failed to connect
print(f"Processed error value: {final_error_value}")
asyncio.run(main())
The core type is Result[T] = Union[T, Exception]
.
Function | Description | Sync/Async |
---|---|---|
run_catching(func, *args, **kwargs) |
Executes func(*args, **kwargs) and returns Result[T] . |
Sync |
async_run_catching(func, *args, **kwargs) |
Awaits func(*args, **kwargs) and returns Result[T] . |
Async |
get_or_throw(result) |
Returns T if result is T , otherwise raises the Exception . |
Sync |
get_or_none(result) |
Returns T if result is T , otherwise returns None . |
Sync |
get_or_default(result, default) |
Returns T if result is T , otherwise returns default . |
Sync |
get_or_else(result, func) |
Returns T if result is T , otherwise calls func(exception) to get a fallback T . |
Sync |
get_or_else_async(result, func) |
Returns T if result is T , otherwise awaits func(exception) to get a fallback T . |
Async |
map_result(result, func) |
If result is T , returns run_catching(func, result) . If result is Exception , returns the exception. Output is Result[U] . |
Sync |
map_result_async(result, func) |
If result is T , returns await async_run_catching(func, result) . If result is Exception , returns the exception. Output is Result[U] . |
Async |
Tests use Python's built-in unittest
module.
First, install test dependencies (e.g., if you need specific libraries for test functions):
# Using pip
pip install .[test]
# Using uv
uv pip install .[test]
(Note: Add a [test]
extra in your pyproject.toml
if you have test-specific dependencies).
Then, run tests:
# Discover and run tests using unittest's discovery
python -m unittest discover tests
# Or use pytest (it can run unittest tests)
pytest
No DSLs. No magic classes. No frameworks. Just Python's strengths, applied cleanly.
Resultite embraces Python's existing type system to provide a robust yet minimal way to handle function outcomes. It avoids introducing new abstractions often seen elsewhere, focusing instead on simple, composable functions that operate on the standard Union[T, Exception]
pattern. Inspired by the clarity of Result
patterns, but executed with Pythonic simplicity and pragmatism.
Crafted with a love for minimalism and functional programming principles in Python.
MIT License