kotresult
A Python implementation of the Result monad pattern, inspired by Kotlin's Result class. This library provides a way to handle operations that might succeed or fail without using exceptions for control flow.
Installation
You can install the package via pip:
pip install kotresult
Usage
Result Class
The Result class represents an operation that might succeed or fail. It can contain either a successful value or an
exception.
from kotresult import Result
# Create a success result
success = Result.success("Hello, World!")
print(success.is_success) # True
print(success.get_or_none()) # "Hello, World!"
# Create a failure result
failure = Result.failure(ValueError("Something went wrong"))
print(failure.is_failure) # True
print(failure.exception_or_none()) # ValueError("Something went wrong")
Getting Values Safely
# Get the value or a default
value = success.get_or_default("Default value") # "Hello, World!"
value = failure.get_or_default("Default value") # "Default value"
# Get the value or throw the exception
try:
value = failure.get_or_throw() # Raises ValueError("Something went wrong")
except ValueError as e:
print(f"Caught exception: {e}")
# Throw on failure
success.throw_on_failure() # Does nothing
try:
failure.throw_on_failure() # Raises ValueError("Something went wrong")
except ValueError as e:
print(f"Caught exception: {e}")
# Python naming convention aliases
# get_or_raise() is an alias for get_or_throw()
try:
value = failure.get_or_raise() # More Pythonic name
except ValueError as e:
print(f"Caught exception: {e}")
# raise_on_failure() is an alias for throw_on_failure()
success.raise_on_failure() # Does nothing
try:
failure.raise_on_failure() # More Pythonic name
except ValueError as e:
print(f"Caught exception: {e}")
run_catching Function
The run_catching function executes a function and returns a Result object containing either the return value or any
exception that was raised.
from kotresult import run_catching
# With a function that succeeds
def add(a, b):
return a + b
result = run_catching(add, 2, 3)
print(result.is_success) # True
print(result.get_or_none()) # 5
# With a function that fails
def divide(a, b):
return a / b
result = run_catching(divide, 1, 0) # ZeroDivisionError
print(result.is_failure) # True
print(type(result.exception_or_none())) # <class 'ZeroDivisionError'>
# With keyword arguments
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
result = run_catching(greet, name="World", greeting="Hi")
print(result.get_or_none()) # "Hi, World!"
Using Result as a Function Return
You can use the Result class as a return type for your functions to handle operations that might fail:
from kotresult import Result
# Function that returns a Result
def parse_int(value: str) -> Result[int]:
try:
return Result.success(int(value))
except ValueError as e:
return Result.failure(e)
# Using the function
result = parse_int("42")
if result.is_success:
print(f"Parsed value: {result.get_or_none()}") # Parsed value: 42
else:
print(f"Failed to parse: {result.exception_or_none()}")
# With a value that can't be parsed
result = parse_int("not_a_number")
if result.is_success:
print(f"Parsed value: {result.get_or_none()}")
else:
print(f"Failed to parse: {result.exception_or_none()}") # Failed to parse: ValueError("invalid literal for int() with base 10: 'not_a_number'")
# You can also chain operations that return Result
def double_parsed_int(value: str) -> Result[int]:
result = parse_int(value)
if result.is_success:
return Result.success(result.get_or_none() * 2)
return result # Return the failure result as is
result = double_parsed_int("21")
print(result.get_or_default(0)) # 42
result = double_parsed_int("not_a_number")
print(result.get_or_default(0)) # 0
# Using on_success and on_failure for handling results
def process_result(value: str):
parse_int(value).on_success(
lambda x: print(f"Successfully parsed {value} to {x}")
).on_failure(
lambda e: print(f"Failed to parse {value}: {e}")
)
process_result("42") # Successfully parsed 42 to 42
process_result("not_a_number") # Failed to parse not_a_number: invalid literal for int() with base 10: 'not_a_number'
Advanced Methods
Transforming Results with map and mapCatching
from kotresult import Result, run_catching
# map(): Transform success values
result = Result.success(5)
squared = result.map(lambda x: x ** 2)
print(squared.get_or_none()) # 25
# map() on failure returns the same failure
failure = Result.failure(ValueError("error"))
mapped = failure.map(lambda x: x * 2)
print(mapped.is_failure) # True
# mapCatching(): Transform values and catch exceptions
def risky_transform(x):
if x > 10:
raise ValueError("Too large")
return x * 2
result1 = Result.success(5).map_catching(risky_transform)
print(result1.get_or_none()) # 10
result2 = Result.success(15).map_catching(risky_transform)
print(result2.is_failure) # True
print(type(result2.exception_or_none())) # <class 'ValueError'>
Recovering from Failures
# recover(): Transform failures to successes
failure = Result.failure(ValueError("error"))
recovered = failure.recover(lambda e: "Default value")
print(recovered.get_or_none()) # "Default value"
# recover() on success returns the same success
success = Result.success(42)
recovered = success.recover(lambda e: 0)
print(recovered.get_or_none()) # 42
# recoverCatching(): Recover with exception handling
def risky_recovery(e):
if "critical" in str(e):
raise RuntimeError("Cannot recover")
return "Recovered"
result1 = Result.failure(ValueError("error")).recover_catching(risky_recovery)
print(result1.get_or_none()) # "Recovered"
result2 = Result.failure(ValueError("critical error")).recover_catching(risky_recovery)
print(result2.is_failure) # True
print(type(result2.exception_or_none())) # <class 'RuntimeError'>
Folding Results
# fold(): Handle both success and failure cases with one call
def handle_result(value: str) -> str:
return parse_int(value).fold(
on_success=lambda x: f"The number is {x}",
on_failure=lambda e: f"Invalid input: {e}"
)
print(handle_result("42")) # "The number is 42"
print(handle_result("abc")) # "Invalid input: invalid literal for int() with base 10: 'abc'"
# getOrElse(): Get value or compute alternative from exception
result = parse_int("not_a_number")
value = result.get_or_else(lambda e: len(str(e)))
print(value) # Length of the error message
Chaining Operations
# Chain multiple transformations
result = (
run_catching(int, "42")
.map(lambda x: x * 2)
.map(lambda x: x + 10)
.map_catching(lambda x: 100 / x)
)
print(result.get_or_none()) # 1.0526315789473684
# Complex error handling chain
def process_data(data: str) -> str:
return (
run_catching(int, data)
.map(lambda x: x * 2)
.recover_catching(lambda e: 0) # Default to 0 on parse error
.map(lambda x: f"Result: {x}")
.get_or_else(lambda e: "Processing failed")
)
print(process_data("21")) # "Result: 42"
print(process_data("abc")) # "Result: 0"
run_catching_with Function
The run_catching_with function executes a function with a receiver object as the first argument. This is similar to Kotlin's extension function version of runCatching.
Note: In Kotlin, there are two versions of runCatching:
- Regular function:
runCatching { ... } - Extension function:
someObject.runCatching { ... }
Since Python doesn't have extension functions, we implement the extension function version as a separate function called run_catching_with, where the receiver object is explicitly passed as the first parameter.
from kotresult import run_catching_with
# Basic usage with string operations
# Kotlin: "hello".runCatching { toUpperCase() }
# Python equivalent:
result = run_catching_with("hello", str.upper)
print(result.get_or_null()) # "HELLO"
# With a custom function
def add_prefix(text, prefix):
return prefix + text
result = run_catching_with("world", add_prefix, "Hello, ")
print(result.get_or_null()) # "Hello, world"
# With lambda functions
result = run_catching_with(42, lambda x: x * 2)
print(result.get_or_null()) # 84
# Type conversion with error handling
result = run_catching_with("123", int)
print(result.get_or_null()) # 123
result = run_catching_with("not a number", int)
print(result.is_failure) # True
print(type(result.exception_or_null())) # <class 'ValueError'>
# Chaining operations with receiver
def process_text(text):
return text.strip().lower().replace(" ", "_")
result = run_catching_with(" Hello World ", process_text)
print(result.get_or_null()) # "hello_world"
API Reference
Result Class
Static Methods
Result.success(value): Creates a success result with the given valueResult.failure(exception): Creates a failure result with the given exception
Properties
is_success: ReturnsTrueif the result is a success,Falseotherwiseis_failure: ReturnsTrueif the result is a failure,Falseotherwise
Methods
get_or_null(): Returns the value if success,Noneif failureget_or_none(): Alias forget_or_null()for Python naming conventionexception_or_null(): Returns the exception if failure,Noneif successexception_or_none(): Alias forexception_or_null()for Python naming conventionto_string(): Returns a string representation of the resultget_or_default(default_value): Returns the value if success, the default value if failureget_or_throw(): Returns the value if success, throws the exception if failureget_or_raise(): Alias forget_or_throw()for Python naming conventionthrow_on_failure(): Throws the exception if failure, does nothing if successraise_on_failure(): Alias forthrow_on_failure()for Python naming conventionon_success(callback): Executes the callback with the value if success, returns the Result object for chainingon_failure(callback): Executes the callback with the exception if failure, returns the Result object for chainingmap(transform): Transforms the success value with the given function, returns a new Resultmap_catching(transform): Like map(), but catches exceptions thrown by the transform functionrecover(transform): Transforms the failure exception to a success value, returns a new Resultrecover_catching(transform): Like recover(), but catches exceptions thrown by the transform functionfold(on_success, on_failure): Applies the appropriate function based on success/failure and returns the result directly (not wrapped in Result)get_or_else(on_failure): Returns the success value or computes an alternative value from the exception
run_catching Function
run_catching(func, *args, **kwargs): Executes the function with the given arguments and returns aResultobject
run_catching_with Function
run_catching_with(receiver, func, *args, **kwargs): Executes the function with a receiver object as the first argument and returns aResultobject
License
This project is licensed under the MIT License - see the LICENSE file for details.
Release files for kotresult 1.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kotresult-1.0.1.tar.gz | 6.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kotresult-1.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 14.6 kB
Release files / kotresult-1.0.1.tar.gz
| Download URL | kotresult-1.0.1.tar.gz |
|---|---|
| Size | 6.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4f398a2e3b21f941d5ace5611ecc19fe75a369988622c856aeea705fe5f35054
|
|
BLAKE2b-256 checksum How to use checksums |
b560c0393a61a8ac81d20a31ababb66972e01d3844d10e7c3d609f047b4b362b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.5 Linux/6.11.0-1018-azure
|
Release files / kotresult-1.0.1-py3-none-any.whl
| Download URL | kotresult-1.0.1-py3-none-any.whl |
|---|---|
| Size | 7.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f605a30e88f35d9f6b97533f67540e3e20f5c5798ea3b8343e3d17c8afa11321
|
|
BLAKE2b-256 checksum How to use checksums |
0771e51c8de5fd9c17dd86c3d71e2be91465a52a34eb70cd184e38b04cb1d185
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.3 CPython/3.13.5 Linux/6.11.0-1018-azure
|