unwrappy¶
Rust-inspired Result and Option types for Python
Safe, expressive error handling with errors as values.
-
Explicit Error Handling
No hidden exceptions. Errors are values that must be handled, making your code's error paths visible and type-checkable.
-
Type Safe
Full generic type support with proper inference. Works with pyright, mypy, and ty out of the box.
-
Functional Composition
Rich combinator API (
map,and_then,or_else, etc.) for elegant transformation chains. -
Async First
LazyResultandLazyOptionfor clean async operation chaining without nested awaits.
Quick Example¶
from unwrappy import Ok, Err, Result
def divide(a: int, b: int) -> Result[float, str]:
if b == 0:
return Err("division by zero")
return Ok(a / b)
# Pattern matching (Python 3.10+)
match divide(10, 2):
case Ok(value):
print(f"Result: {value}")
case Err(error):
print(f"Error: {error}")
# Combinator chaining
result = (
divide(10, 2)
.map(lambda x: x * 2)
.and_then(lambda x: Ok(int(x)) if x < 100 else Err("too large"))
)
Installation¶
Or with uv:
Why unwrappy?¶
Python's exception-based error handling has fundamental issues for complex applications:
| Problem | With Exceptions | With unwrappy |
|---|---|---|
| Hidden control flow | Exceptions can be raised anywhere and caught anywhere | Errors are explicit values in function signatures |
| Unclear contracts | Function signatures don't declare what exceptions they raise | Return types show exactly what can fail |
| Silent failures | Forgotten exception handling leads to runtime crashes | Type checker enforces handling |
unwrappy provides Result[T, E] and Option[T] types that make errors explicit values in your code.
Core Types¶
Result[T, E]¶
Represents either success (Ok) or failure (Err):
from unwrappy import Ok, Err, Result
def parse_int(s: str) -> Result[int, str]:
try:
return Ok(int(s))
except ValueError:
return Err(f"invalid number: {s}")
result = parse_int("42")
result.unwrap() # 42
result.is_ok() # True
Option[T]¶
Represents an optional value (Some or Nothing):
from unwrappy import Some, NOTHING, Option, from_nullable
def find_user(user_id: int) -> Option[User]:
user = db.get(user_id) # Returns User | None
return from_nullable(user)
email = find_user(123).map(lambda u: u.email).unwrap_or("unknown")
LazyResult & LazyOption¶
Deferred execution for clean async chaining:
from unwrappy import LazyResult
async def fetch_user(id: int) -> Result[User, str]: ...
async def fetch_profile(user: User) -> Result[Profile, str]: ...
# Build pipeline, execute once - no nested awaits!
result = await (
LazyResult.from_awaitable(fetch_user(42))
.and_then(fetch_profile)
.map(lambda p: p.name)
.collect()
)
Learn more about Lazy Evaluation
Design Philosophy¶
unwrappy is designed for practical use in real Python codebases:
- Zero dependencies - Just Python stdlib
- Incremental adoption - Use in a single module or throughout your codebase
- Framework friendly - Works alongside FastAPI, Django, Flask, etc.
- No magic - No decorators, no metaclasses, no import hooks
Next Steps¶
-
Getting Started
Installation, basic usage, and your first Result-based function.
-
Guide
Deep dive into Result, Option, and async patterns.
-
API Reference
Complete API documentation with all methods and functions.
-
Examples
Real-world patterns and code examples.