Skip to main content

rusty Option and Result container for python. Ready for pattern matching and asyncio

Project description

fateful

Option and Result container for python

fateful

documentation: https://jerkos.github.io/fateful/

Python has some great functional features. The most notable ones are list / dict comprehensions. However, when you start to chain function calls (or predicate or whatever), it becomes rapidly a pain.

In some famous languages, we have Option and Result monad helping user to handle optional values (i.e. None values) and possibly failing computation (Rust, Java, Kotlin...)

So, this very small Python library implements those monads - Option, Result or AsyncResult - providing a way to handle optional values / computation failures in a functional style.

A focus has been made to typing (but can still be improved of course !)

Installation

pip install fateful

pip install fateful[http] # install aiohttp to enable async api calls helper

pip install fateful[orjson] # install orjson a fast json implementation

pip install fateful[all] # install all optional dependencies

Option monad

The Option Monad, also known as the Maybe Monad, is a concept from functional programming that provides a way to handle optional values in a more controlled and expressive manner. It helps avoid null or undefined errors by encapsulating a value that may or may not be present. The key idea behind the Option Monad is to provide a container that can either hold a value (Some) or indicate the absence of a value (None).

The Option Monad offers several benefits, including:

  • Safety: It helps prevent null or undefined errors that can occur when working with optional values.
  • Clarity: It makes the presence or absence of a value explicit, enhancing the readability of code.
  • Composability: It allows for chaining operations on optional values without explicitly checking for null or undefined values.
  • Functional style: It promotes functional programming principles by encouraging pure functions and immutability.

When you do not know if a value is None or is actually a real value / object, use opt to wrap it into an option

from fateful import opt, Null, Some

# Null is a singleton which is basically Empty(None)
>>> opt('value').or_('new value')
value

>>> opt('value').or_else(lambda: 'another value)
value

>>> Null.or_else(lambda: 'new value')
new value

>>> opt(None).get() # same as none.get()
Traceback (most recent call last):
...
ValueError: Option is empty

Option monad implements the iteration protocl

>>> o = opt([1, 2, 3]).for_each(print)
[1, 2, 3]

>>> a = opt('optional option value')
>>> for i in a:
>>>     print(i)
optional option value

It supports also forwarding a value

>>> # forwarding value
>>> class A(object):
        def __init__(self, x):
            self.x = x
        def get_x(self):
            return self.x

>>> opt(A(1)).get_x().or_(0)
1
>>> opt(A(1)).get_y().or_(0)
0

A option value can be transformed into another enabling chaining operation

from fateful.monad.option import option
result = option("some").map(lambda x: len(x) * 2).or_(0)

An option can contain another option type but can be flattened

x = option(option(option(1))).flatten() # x is Some[Some[Some[int]]]

y: Some[Some[Some[int]]] = Some(Some(Some(1)))
x: Some[int] = Some(Some(Some(1))).flatten()

We can lift an function to return an option type using a decorator

from fateful.monad.option import lift_option

@lift_option
def maybe(value: int):
    if value >= 2:
        return 2
    return None

x: Some[int] = maybe(2)
x: Empty = maybe(0)

Result monad

The Result Monad is another concept from functional programming that is used to handle computations that may produce a successful result or a failure. It provides a way to encapsulate the outcome of an operation, allowing you to handle and propagate errors in a controlled manner.

The Result Monad typically has two possible states: Ok (representing a successful result) and Err (representing a failure or an error condition). The Ok state contains the successful result value, while the Err state contains information about the failure, such as an error message or an error object.

The Result Monad offers several benefits:

  • Explicit error handling: It makes error handling explicit and separates the handling of successful results from error conditions.
  • Propagation of errors: It allows errors to be easily propagated through a chain of operations, avoiding the need for explicit error-checking at each step.
  • Composition: It enables the chaining and composition of operations on results in a concise and expressive manner.
  • Error recovery: It provides mechanisms to handle and recover from errors, such as by mapping to alternative values or applying fallback strategies.
def may_fail(x: int) -> float:
    return 1 / x

from fateful.monad.resut import sync_try

r: Ok[float] | Err[ZeroDivisionError] = sync_try(may_fail, ZeroDivisionError)(1)
result = sync_try(may_fail, ZeroDivisionError)(1).or_(10.0)

assert result == 1.0

result = sync_try(may_fail, ZeroDivisonError)(0).or_(10.0)
assert result == 10.0

Async Result

Async result provides exactly same functionalites than regular Result monad

async def divide_async(a, b):
    await asyncio.sleep(0.1)
    return a / b

from fateful.monad import async_try

value = await async_try(add_async)( 1, 1).or_else(lambda: 4)

value = await async_try(add_async)(1, 1).or_(4)

divide: AsyncResult[..., float, ZeroDivisionError] = async_try(add_async, ZeroDivisionError)(1, 1)

result: Ok[float] | Err[ZeroDivisionError] = await divide..execute()

# transforming value
value = await divide.map(lambda r: r - 100).or_(0)

match (await divide.execute()):
    case Ok(val):
        return val
    case Err(err):
        raise err
    case _:
        raise TypeError("Never supposed to happen")

Http module

Miscellaneous functions dealing with http client functions

Wraps aiohttp for perform http call and parsing results optionnaly as json

aiohttp is a library of choice for making http requests. A good alternative is httpx but various benchmarks show that it is slightly slower.

page_content: str = await try_get("http://google.com").or_raise()

Container module

Simple subclass to return option monad when getting values implementation of list and dict

x = opt_list([1,2,3])
v: Some[int] = x.at(0)
v: Empty = x.at(12)

Json module

Miscellaneous functions dealing with json (parsing, manipulating...)

>>> from fateful.json import opt_dict
>>> i = opt_dict({'z-index': 1000})
>>> i.toto = [4, 5, 6]
>>> str(i)
'{"z-index": 1000, "toto": [4, 5, 6]}'

Notes: I found some python packages doing almost the same things. I did this essentially to learn and wanted to keep it simple.

Licenses

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fateful-0.4.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

fateful-0.4.0-py3-none-any.whl (18.5 kB view details)

Uploaded Python 3

File details

Details for the file fateful-0.4.0.tar.gz.

File metadata

  • Download URL: fateful-0.4.0.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.17

File hashes

Hashes for fateful-0.4.0.tar.gz
Algorithm Hash digest
SHA256 ed8b9955f6e9542de2cba862832d23059c1b30480cb73a7dcbf202afb75e91a1
MD5 a51fc99d8f1648503ed32bd29fc0b159
BLAKE2b-256 c928262ac96752c74afbdc20ee845ebee497aca5994bced74fbeb075a0a70983

See more details on using hashes here.

File details

Details for the file fateful-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: fateful-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 18.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.9.17

File hashes

Hashes for fateful-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c3f7d610213f7a6ed54e533ba583ad358fe89cac37abc9193e1cec08011a68a7
MD5 c02d8d210788439b09049bfbc3928f2d
BLAKE2b-256 6aeb96599b2305b9fc6ffb0ed1d697638254b001345f2d9f05029a3acbb03d5c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page