or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/pypi-result

A Rust-like result type for Python that enables robust error handling through explicit Ok/Err variants

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
pypipkg:pypi/result@0.17.x

To install, run

npx @tessl/cli install tessl/pypi-result@0.17.0

index.mddocs/

Result

A Rust-inspired Result type for Python that enables robust error handling through explicit Ok/Err variants. The library provides type-safe value encapsulation with full type annotation support, allowing developers to replace traditional exception-based error handling with functional programming patterns.

Package Information

  • Package Name: result
  • Language: Python
  • Installation: pip install result
  • Python Requirements: >=3.8
  • Type Support: Full type annotations with py.typed marker

Core Imports

from result import Ok, Err, Result

For type checking and guards:

from result import is_ok, is_err, OkErr

For functional utilities:

from result import as_result, as_async_result, do, do_async

For exception handling:

from result import UnwrapError

For version information:

from result import __version__
print(__version__)  # "0.17.0"

Basic Usage

from result import Ok, Err, Result, is_ok, is_err

def divide(a: int, b: int) -> Result[int, str]:
    """Divide two numbers, returning a Result type."""
    if b == 0:
        return Err("Cannot divide by zero")
    return Ok(a // b)

# Using type guards for safe access
result = divide(10, 2)
if is_ok(result):
    print(f"Success: {result.ok_value}")  # Success: 5
elif is_err(result):
    print(f"Error: {result.err_value}")

# Using isinstance for type checking
result = divide(10, 0)
if isinstance(result, Ok):
    print(f"Success: {result.ok_value}")
elif isinstance(result, Err):
    print(f"Error: {result.err_value}")  # Error: Cannot divide by zero

# Python 3.10+ pattern matching
match divide(10, 3):
    case Ok(value):
        print(f"Result: {value}")
    case Err(error):
        print(f"Error: {error}")

Architecture

The Result type implements a functional approach to error handling inspired by Rust's Result enum. Instead of relying on exceptions for control flow, it provides explicit success (Ok) and failure (Err) variants that must be handled consciously.

Core Design Principles

  • Explicit Error Handling: All potential failures are encoded in the type system, making error states visible and mandatory to handle
  • Type Safety: Full generic type support ensures that both success values and error types are preserved through transformations
  • Immutability: Result instances are immutable, preventing accidental state mutations
  • Composability: Operations can be chained safely with short-circuiting behavior on errors

Error Handling Philosophy

Traditional Python exception handling can lead to uncaught errors and unclear control flow:

# Traditional approach - easy to miss error cases
def risky_operation():
    if something_wrong():
        raise ValueError("Something went wrong")
    return success_value

try:
    result = risky_operation()
    # Easy to forget error handling
    process(result)
except ValueError:
    # Error handling often added as afterthought
    handle_error()

The Result pattern makes error handling explicit and type-safe:

# Result pattern - errors must be handled
def safe_operation() -> Result[SuccessType, ErrorType]:
    if something_wrong():
        return Err("Something went wrong")
    return Ok(success_value)

result = safe_operation()
if is_ok(result):
    process(result.ok_value)  # Type-safe access
else:
    handle_error(result.err_value)  # Must handle error case

Key Components

  • Result[T, E]: Union type representing either success (Ok[T]) or failure (Err[E])
  • Ok[T]: Container for successful values with transformation methods
  • Err[E]: Container for error values with recovery methods
  • Type Guards: is_ok() and is_err() functions provide type-safe discrimination
  • Transformations: map(), and_then(), or_else() enable safe operation chaining
  • Do Notation: Generator-based syntax for complex operation sequences

This architecture enables building robust error-handling pipelines where failures are handled explicitly at each step, reducing bugs and improving code clarity.

Capabilities

Result Type Construction

Create Ok and Err instances to represent success and failure states.

class Ok[T]:
    def __init__(self, value: T) -> None:
        """Create an Ok instance containing a success value."""

class Err[E]:
    def __init__(self, value: E) -> None:
        """Create an Err instance containing an error value."""

Type Checking and Guards

Check Result types safely with type guards or isinstance.

def is_ok(result: Result[T, E]) -> TypeGuard[Ok[T]]:
    """Type guard to check if a result is an Ok."""

def is_err(result: Result[T, E]) -> TypeGuard[Err[E]]:
    """Type guard to check if a result is an Err."""

Usage examples:

result = Ok("success")

# Using type guard functions (recommended for type safety)
if is_ok(result):
    # result is now typed as Ok[str]
    value = result.ok_value
elif is_err(result):
    # result is now typed as Err[E]
    error = result.err_value

# Using isinstance checks
if isinstance(result, Ok):
    value = result.ok_value
elif isinstance(result, Err):
    error = result.err_value

# Using the OkErr convenience type
if isinstance(result, OkErr):
    # result is either Ok or Err, but we don't know which
    pass

State Inspection Methods

Check the state of Result instances without type narrowing.

# Ok methods
def is_ok(self) -> Literal[True]:
    """Return True for Ok instances."""

def is_err(self) -> Literal[False]:
    """Return False for Ok instances."""

# Err methods  
def is_ok(self) -> Literal[False]:
    """Return False for Err instances."""

def is_err(self) -> Literal[True]:
    """Return True for Err instances."""

Value Extraction

Extract success and error values from Result instances.

# Ok value access
@property
def ok_value(self) -> T:
    """Access the success value (Ok only)."""

def ok(self) -> T:
    """Return the success value (Ok only)."""

def err(self) -> None:
    """Return None (Ok only)."""

# Err value access
@property
def err_value(self) -> E:
    """Access the error value (Err only)."""

def ok(self) -> None:
    """Return None (Err only)."""

def err(self) -> E:
    """Return the error value (Err only)."""

Usage examples:

ok_result = Ok("success")
err_result = Err("failure")

# Direct property access (type-safe only after type checking)
if isinstance(ok_result, Ok):
    value = ok_result.ok_value  # "success"

if isinstance(err_result, Err):
    error = err_result.err_value  # "failure"

# Using ok() and err() methods
success_value = ok_result.ok()  # "success" 
error_value = err_result.err()  # "failure"

Object Protocol Methods

Standard Python object methods for comparison, hashing, and iteration.

# Ok class methods
def __repr__(self) -> str:
    """Return string representation of Ok instance."""

def __eq__(self, other: Any) -> bool:
    """Check equality with another object."""
    
def __ne__(self, other: Any) -> bool:
    """Check inequality with another object."""
    
def __hash__(self) -> int:
    """Return hash value for Ok instance."""
    
def __iter__(self) -> Iterator[T]:
    """Allow iteration over Ok value (used in do notation)."""

# Err class methods  
def __repr__(self) -> str:
    """Return string representation of Err instance."""
    
def __eq__(self, other: Any) -> bool:
    """Check equality with another object."""
    
def __ne__(self, other: Any) -> bool: 
    """Check inequality with another object."""
    
def __hash__(self) -> int:
    """Return hash value for Err instance."""
    
def __iter__(self) -> Iterator[NoReturn]:
    """Special iterator that raises DoException (used in do notation)."""

Usage examples:

ok1 = Ok("hello")
ok2 = Ok("hello")
ok3 = Ok("world")

print(repr(ok1))        # Ok('hello')
print(ok1 == ok2)       # True
print(ok1 == ok3)       # False  
print(ok1 != ok3)       # True
print(hash(ok1))        # Some hash value

err1 = Err("error")
err2 = Err("error")  
print(repr(err1))       # Err('error')
print(err1 == err2)     # True

# Iteration is used internally by do notation
for value in Ok("test"):
    print(value)        # "test"

Deprecated Properties

Legacy properties maintained for backward compatibility.

# Ok class
@property
def value(self) -> T:
    """
    Return the inner value.
    
    Warning:
        Deprecated. Use `ok_value` or `err_value` instead. 
        This property will be removed in a future version.
    """

# Err class  
@property  
def value(self) -> E:
    """
    Return the inner value.
    
    Warning:
        Deprecated. Use `ok_value` or `err_value` instead.
        This property will be removed in a future version.
    """

Usage (not recommended):

import warnings

ok_result = Ok("success")
with warnings.catch_warnings():
    warnings.simplefilter("ignore", DeprecationWarning)
    value = ok_result.value  # "success" (but will show deprecation warning)

Unwrap Operations

Extract values with various unwrap strategies and error handling.

def unwrap(self) -> T:
    """
    Return the value if Ok, raise UnwrapError if Err.
    
    Raises:
        UnwrapError: If called on an Err instance.
    """

def unwrap_err(self) -> E:
    """
    Return the error if Err, raise UnwrapError if Ok.
    
    Raises:
        UnwrapError: If called on an Ok instance.
    """

def expect(self, message: str) -> T:
    """
    Return the value if Ok, raise UnwrapError with custom message if Err.
    
    Args:
        message: Custom error message for UnwrapError.
        
    Raises:
        UnwrapError: If called on an Err instance.
    """

def expect_err(self, message: str) -> E:
    """
    Return the error if Err, raise UnwrapError with custom message if Ok.
    
    Args:
        message: Custom error message for UnwrapError.
        
    Raises:
        UnwrapError: If called on an Ok instance.
    """

def unwrap_or(self, default: U) -> T | U:
    """
    Return the value if Ok, return default if Err.
    
    Args:
        default: Default value to return for Err instances.
    """

def unwrap_or_else(self, op: Callable[[E], T]) -> T:
    """
    Return the value if Ok, apply function to error if Err.
    
    Args:
        op: Function to apply to error value for Err instances.
    """

def unwrap_or_raise(self, e: Type[TBE]) -> T:
    """
    Return the value if Ok, raise custom exception with error if Err.
    
    Args:
        e: Exception class to raise with the error value.
        
    Raises:
        e: Custom exception with error value as message.
    """

Usage examples:

ok_result = Ok("success")
err_result = Err("failure")

# Basic unwrap (raises UnwrapError on failure)
try:
    value = ok_result.unwrap()  # "success"
    value = err_result.unwrap()  # raises UnwrapError
except UnwrapError as e:
    print(f"Unwrap failed: {e}")

# Unwrap with custom message
try:
    value = err_result.expect("Expected success")  # raises UnwrapError
except UnwrapError as e:
    print(f"Custom message: {e}")

# Unwrap with default value
value = ok_result.unwrap_or("default")   # "success"
value = err_result.unwrap_or("default")  # "default"

# Unwrap with function fallback
value = err_result.unwrap_or_else(lambda e: f"Error: {e}")  # "Error: failure"

# Unwrap or raise custom exception
try:
    value = err_result.unwrap_or_raise(ValueError)  # raises ValueError("failure")
except ValueError as e:
    print(f"Custom exception: {e}")

Transformation Operations

Transform values and errors using map operations.

def map(self, op: Callable[[T], U]) -> Result[U, E]:
    """
    Transform the value if Ok, pass through if Err.
    
    Args:
        op: Function to transform the success value.
        
    Returns:
        Ok(op(value)) if Ok, unchanged Err if Err.
    """

async def map_async(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
    """
    Async transform the value if Ok, pass through if Err.
    
    Args:
        op: Async function to transform the success value.
        
    Returns:
        Ok(await op(value)) if Ok, unchanged Err if Err.
    """

def map_or(self, default: U, op: Callable[[T], U]) -> U:
    """
    Transform the value if Ok, return default if Err.
    
    Args:
        default: Default value to return for Err instances.
        op: Function to transform the success value.
        
    Returns:
        op(value) if Ok, default if Err.
    """

def map_or_else(self, default_op: Callable[[], U], op: Callable[[T], U]) -> U:
    """
    Transform the value if Ok, apply default function if Err.
    
    Args:
        default_op: Function to call for Err instances.
        op: Function to transform the success value.
        
    Returns:
        op(value) if Ok, default_op() if Err.
    """

def map_err(self, op: Callable[[E], F]) -> Result[T, F]:
    """
    Transform the error if Err, pass through if Ok.
    
    Args:
        op: Function to transform the error value.
        
    Returns:
        Unchanged Ok if Ok, Err(op(error)) if Err.
    """

Usage examples:

ok_result = Ok(5)
err_result = Err("not a number")

# Transform success values
doubled = ok_result.map(lambda x: x * 2)      # Ok(10)
failed = err_result.map(lambda x: x * 2)      # Err("not a number")

# Transform with default value
result1 = ok_result.map_or(0, lambda x: x * 2)   # 10
result2 = err_result.map_or(0, lambda x: x * 2)  # 0

# Transform errors
better_err = err_result.map_err(lambda e: f"Error: {e}")  # Err("Error: not a number")

Chain Operations

Chain multiple Result-returning operations together.

def and_then(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
    """
    Chain another Result-returning operation if Ok, pass through if Err.
    
    Args:
        op: Function that takes the success value and returns a Result.
        
    Returns:
        op(value) if Ok, unchanged Err if Err.
    """

async def and_then_async(self, op: Callable[[T], Awaitable[Result[U, E]]]) -> Result[U, E]:
    """
    Async chain another Result-returning operation if Ok, pass through if Err.
    
    Args:
        op: Async function that takes the success value and returns a Result.
        
    Returns:
        await op(value) if Ok, unchanged Err if Err.
    """

def or_else(self, op: Callable[[E], Result[T, F]]) -> Result[T, F]:
    """
    Chain alternative Result-returning operation if Err, pass through if Ok.
    
    Args:
        op: Function that takes the error value and returns a Result.
        
    Returns:
        Unchanged Ok if Ok, op(error) if Err.
    """

Usage examples:

def parse_int(s: str) -> Result[int, str]:
    try:
        return Ok(int(s))
    except ValueError:
        return Err(f"'{s}' is not a valid integer")

def divide_by_two(n: int) -> Result[float, str]:
    return Ok(n / 2.0)

# Chain operations
result = Ok("10").and_then(parse_int).and_then(divide_by_two)  # Ok(5.0)

# Handle errors in chain
result = Err("failed").or_else(lambda e: Ok(f"Recovered from: {e}"))  # Ok("Recovered from: failed")

Inspection Operations

Inspect values without consuming the Result.

def inspect(self, op: Callable[[T], Any]) -> Result[T, E]:
    """
    Call function with the value if Ok, pass through unchanged.
    
    Args:
        op: Function to call with the success value (for side effects).
        
    Returns:
        The original Result unchanged.
    """

def inspect_err(self, op: Callable[[E], Any]) -> Result[T, E]:
    """
    Call function with the error if Err, pass through unchanged.
    
    Args:
        op: Function to call with the error value (for side effects).
        
    Returns:
        The original Result unchanged.
    """

Usage examples:

# Log success values without changing the Result
result = Ok("success").inspect(lambda x: print(f"Got value: {x}"))
# Prints: Got value: success
# result is still Ok("success")

# Log errors without changing the Result  
result = Err("failed").inspect_err(lambda e: print(f"Error occurred: {e}"))
# Prints: Error occurred: failed
# result is still Err("failed")

Function Decorators

Convert regular functions to return Result types.

def as_result(*exceptions: Type[TBE]) -> Callable[[Callable[P, R]], Callable[P, Result[R, TBE]]]:
    """
    Decorator to convert a function to return Result instead of raising exceptions.
    
    Args:
        *exceptions: Exception types to catch and convert to Err.
        
    Returns:
        Decorator function that wraps the original function.
        
    Example:
        @as_result(ValueError, TypeError)
        def parse_int(s: str) -> int:
            return int(s)
            
        result = parse_int("123")  # Ok(123)
        result = parse_int("abc")  # Err(ValueError(...))
    """

def as_async_result(*exceptions: Type[TBE]) -> Callable[[Callable[P, Awaitable[R]]], Callable[P, Awaitable[Result[R, TBE]]]]:
    """
    Decorator to convert an async function to return Result instead of raising exceptions.
    
    Args:  
        *exceptions: Exception types to catch and convert to Err.
        
    Returns:
        Decorator function that wraps the original async function.
        
    Example:
        @as_async_result(ValueError, TypeError)
        async def fetch_data(url: str) -> str:
            # ... async code that might raise exceptions
            return data
            
        result = await fetch_data("http://example.com")  # Ok(data) or Err(exception)
    """

Usage examples:

@as_result(ValueError, TypeError)
def safe_int_conversion(value: str) -> int:
    return int(value)

# Returns Ok(42) instead of int
result = safe_int_conversion("42")

# Returns Err(ValueError(...)) instead of raising
result = safe_int_conversion("not_a_number")

@as_async_result(ConnectionError, TimeoutError)
async def fetch_data(url: str) -> str:
    # Some async operation that might fail
    return "data"

# Usage
result = await fetch_data("http://example.com")

Do Notation

Syntactic sugar for chaining Result operations using generator syntax.

def do(gen: Generator[Result[T, E], None, None]) -> Result[T, E]:
    """
    Do notation for Result - syntactic sugar for sequence of and_then() calls.
    
    Args:
        gen: Generator expression using Result values.
        
    Returns:
        The final Result from the generator.
        
    Note:
        Type annotation on the result is recommended for proper type inference.
    """

async def do_async(gen: Union[Generator[Result[T, E], None, None], AsyncGenerator[Result[T, E], None]]) -> Result[T, E]:
    """
    Async version of do notation supporting both sync and async generators.
    
    Args:
        gen: Generator or async generator expression using Result values.
        
    Returns:
        The final Result from the generator.
    """

Usage examples:

# Basic do notation
result: Result[int, str] = do(
    Ok(x + y + z)
    for x in Ok(1)
    for y in Ok(2) 
    for z in Ok(3)
)  # Ok(6)

# With error propagation - stops at first Err
result: Result[int, str] = do(
    Ok(x + y)
    for x in Ok(1)
    for y in Err("failed")  # This error propagates
)  # Err("failed")

# Access previous values in later iterations
result: Result[str, str] = do(
    Ok(f"{x}-{y}-{z}")
    for x in Ok("a")
    for y in Ok(f"{x}b")  # Can use x from previous line
    for z in Ok(f"{y}c")  # Can use both x and y
)  # Ok("a-ab-abc")

# Async do notation
async def get_async_result(x: int) -> Result[int, str]:
    return Ok(x * 2)

result: Result[int, str] = await do_async(
    Ok(x + y)
    for x in await get_async_result(5)  # Ok(10)
    for y in Ok(3)
)  # Ok(13)

Exception Handling

Handle unwrap failures with custom exception types.

class UnwrapError(Exception):
    """
    Exception raised from unwrap and expect calls.
    
    The original Result can be accessed via the .result attribute.
    """
    
    def __init__(self, result: Result[object, object], message: str) -> None:
        """
        Initialize UnwrapError with the original result and message.
        
        Args:
            result: The original Result that failed to unwrap.
            message: Error message describing the failure.
        """
    
    @property
    def result(self) -> Result[Any, Any]:
        """Returns the original result that caused the unwrap failure."""

Usage examples:

try:
    value = Err("failed").unwrap()
except UnwrapError as e:
    print(f"Unwrap failed: {e}")
    original_result = e.result  # Access the original Err("failed")

Types

# Type alias for Result
Result[T, E] = Union[Ok[T], Err[E]]

# Convenience constant for isinstance checks
OkErr: Final = (Ok, Err)

# Type variables
T = TypeVar("T", covariant=True)  # Success type
E = TypeVar("E", covariant=True)  # Error type
U = TypeVar("U")                  # Generic type variable
F = TypeVar("F")                  # Generic type variable
P = ParamSpec("P")                # Function parameters
R = TypeVar("R")                  # Return type
TBE = TypeVar("TBE", bound=BaseException)  # Exception type

# Package version
__version__: str = "0.17.0"      # Package version string

Pattern Matching (Python 3.10+)

The Result types support pattern matching with __match_args__:

from result import Ok, Err

def handle_result(r: Result[int, str]) -> str:
    match r:
        case Ok(value):
            return f"Success: {value}"
        case Err(error):
            return f"Error: {error}"

# Usage
print(handle_result(Ok(42)))      # "Success: 42"
print(handle_result(Err("oops"))) # "Error: oops"

Error Handling Best Practices

  1. Use type guards for safe access: Always use is_ok(), is_err(), or isinstance() before accessing values.

  2. Prefer unwrap_or() over unwrap(): Provide default values rather than risking exceptions.

  3. Chain operations with and_then(): Build error-handling pipelines that short-circuit on failure.

  4. Use do notation for complex chains: Simplify multiple sequential operations.

  5. Convert exceptions with decorators: Use @as_result() to convert existing functions.

# Good: Safe value access
if is_ok(result):
    process_value(result.ok_value)

# Good: Default value handling
value = result.unwrap_or("default")

# Good: Operation chaining
final_result = (
    initial_value
    .and_then(validate)
    .and_then(transform)
    .map(format_output)
)