Skip to main content

Railway Oriented Programming Library

Project description

safeguard

This is a simple library for building happy path in python, this looks like a Rust's Result type. This library is inspired by two other libraries that are returns and result.

TODO

  • update README to provide more examples
  • generate Documentation
  • add more tests
  • add tox for testing
  • add CI/CD

Installation

Latest release:

$ pip install safeguard

Latest GitHub main branch version:

$ pip install git+https://github.com/demetere/safeguard

TL/DR

The main purpose of this library is to provide a way to build happy path without thinking about exceptions. Because of that we have two main Containers Maybe and Result.

Maybe

Maybe is a way to work with optional values. It can be either Some(value) or Nothing. Some is a class encapsulating an arbitrary value. Maybe[T] is a generic type alias for typing.Union[Some[T], None]. Lets imagine having function for division

def divide(a: int, b: int) -> Optional[int]:
    if b == 0:
        return None
    return a // b

We want to do some operation on the result but we dont know if it returned None or not. We can do it like this:

def upstream_function():
    result = divide(10, 0)
    if result is None:
        return None
    return result * 2

or we can use Maybe like this:

from safeguard.maybe import Maybe, Some, Nothing, maybe

def divide(a: int, b: int) -> Maybe[int]:
    if b == 0:
        return Nothing
    return Some(a // b)
    
def upstream_function():
    result = divide(10, 0)
    return result.map(lambda x: x * 2)

What maybe does in this context is that it will return Nothing if the result is None and Some if the result is not None. This way we can chain operations on the result without thinking about handling None values. Alternatively we can use decorators to use Maybe in a more elegant way:

from safeguard.maybe import maybe

@maybe
def divide(a: int, b: int) -> int:
    if b == 0:
        return None
    return a // b
    
def upstream_function():
    return divide(10, 0).map(lambda x: x * 2)

This will automatically handle None values and return Nothing if the result is None.

Result

The idea is that a result value can be either Ok(value) or Err(error), with a way to differentiate between the two. Ok and Err are both classes inherited from Result[T, E]. We can use Result to handle errors in a more elegant way. Lets imagine having function for fetching user by email:

def get_user_by_email(email: str) -> Tuple[Optional[User], Optional[str]]:
    """
    Return the user instance or an error message.
    """
    if not user_exists(email):
        return None, 'User does not exist'
    if not user_active(email):
        return None, 'User is inactive'
    user = get_user(email)
    return user, None

user, reason = get_user_by_email('ueli@example.com')
if user is None:
    raise RuntimeError('Could not fetch user: %s' % reason)
else:
    do_something(user)

We can refactor this code to use Result like this:

from safeguard.result import Ok, Err, Result

def get_user_by_email(email: str) -> Result[User, str]:
    """
    Return the user instance or an error message.
    """
    if not user_exists(email):
        return Err('User does not exist')
    if not user_active(email):
        return Err('User is inactive')
    user = get_user(email)
    return Ok(user)

user_result = get_user_by_email(email)
if isinstance(user_result, Ok): # or `user_result.is_ok()`
    # type(user_result.unwrap()) == User
    do_something(user_result.unwrap())
else: # or `elif user_result.is_err()`
    # type(user_result.unwrap_err()) == str
    raise RuntimeError('Could not fetch user: %s' % user_result.unwrap_err())

If you're using python version 3.10 or later, you can use the elegant match statement as well:

from result import Result, Ok, Err

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

values = [(10, 0), (10, 5)]
for a, b in values:
    match divide(a, b):
        case Ok(value):
            print(f"{a} // {b} == {value}")
        case Err(e):
            print(e)

Contributing

Everyone is welcome to contribute.

You just need to fork the repository, run poetry install so you can have the same environment, make your changes and create a pull request. We will review your changes and merge them if they are good.

Related Projects

The inspiration was taken from following libraries, some of the ideas and code fragments are from their codebase:

License

MIT License

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

safeguard-0.1.0.tar.gz (15.3 kB view details)

Uploaded Source

Built Distribution

safeguard-0.1.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file safeguard-0.1.0.tar.gz.

File metadata

  • Download URL: safeguard-0.1.0.tar.gz
  • Upload date:
  • Size: 15.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.2 CPython/3.12.0 Linux/6.5.0-35-generic

File hashes

Hashes for safeguard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b8cc27b1c99fb272d74ed4039c55c5104ca3e3bbfeecb7b5e46aff46d25129ee
MD5 7984d050b1d57bd9c65109482f625bf7
BLAKE2b-256 b2999566eb7097dbbf078e1d8c9e2de953d30a7e04a8ceb08daa7455efaef55f

See more details on using hashes here.

File details

Details for the file safeguard-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: safeguard-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.2 CPython/3.12.0 Linux/6.5.0-35-generic

File hashes

Hashes for safeguard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e0ffb620a10ff3ed22004b82bc4404e4ceae2776fa7e8f613bdf138d59ece981
MD5 b7d47a7f6e451292be95b29f6365b3da
BLAKE2b-256 178f12d24894b7af7e67bc1e76e067dbaa1aece5d0ca4d954c522c366dd4c9b7

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