safeguard


Namesafeguard JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryRailway Oriented Programming Library
upload_time2024-06-10 07:18:01
maintainerNone
docs_urlNone
authorDemetre Dzmanashvili
requires_python<4.0,>=3.9
licenseMIT
keywords result maybe railway-oriented-programming functional-programming safeguard
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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](https://github.com/dry-python/returns)
and [result](https://github.com/rustedpy/result).

## TODO

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

## Installation

Latest release:

``` sh
$ pip install safeguard
```

Latest GitHub `main` branch version:

``` sh
$ 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

``` python
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:

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

or we can use `Maybe` like this:

``` python
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:

``` python
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:

``` python
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:

``` python
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:

``` python
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:

- [returns](https://github.com/dry-python/returns)
- [result](https://github.com/rustedpy/result)

## License

MIT License
            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "safeguard",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.9",
    "maintainer_email": null,
    "keywords": "result, maybe, railway-oriented-programming, functional-programming, safeguard",
    "author": "Demetre Dzmanashvili",
    "author_email": "demetredzmanashvili@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/b2/99/9566eb7097dbbf078e1d8c9e2de953d30a7e04a8ceb08daa7455efaef55f/safeguard-0.1.0.tar.gz",
    "platform": null,
    "description": "# safeguard\n\nThis is a simple library for building happy path in python, this looks like a Rust's `Result` type.\nThis library is inspired by two other libraries that are [returns](https://github.com/dry-python/returns)\nand [result](https://github.com/rustedpy/result).\n\n## TODO\n\n- update README to provide more examples\n- generate Documentation\n- add more tests\n- add tox for testing\n- add CI/CD\n\n## Installation\n\nLatest release:\n\n``` sh\n$ pip install safeguard\n```\n\nLatest GitHub `main` branch version:\n\n``` sh\n$ pip install git+https://github.com/demetere/safeguard\n```\n\n## TL/DR\n\nThe main purpose of this library is to provide a way to build happy path without thinking about exceptions. Because of\nthat we have two main Containers `Maybe` and `Result`.\n\n### `Maybe`\n\n`Maybe` is a way to work with optional values. It can be either `Some(value)` or `Nothing`.\n`Some` is a class encapsulating an arbitrary value. `Maybe[T]` is a generic type alias\nfor `typing.Union[Some[T], None]`. Lets imagine having function for division\n\n``` python\ndef divide(a: int, b: int) -> Optional[int]:\n    if b == 0:\n        return None\n    return a // b\n```\n\nWe want to do some operation on the result but we dont know if it returned None or not. We can do it like this:\n\n``` python\ndef upstream_function():\n    result = divide(10, 0)\n    if result is None:\n        return None\n    return result * 2\n```\n\nor we can use `Maybe` like this:\n\n``` python\nfrom safeguard.maybe import Maybe, Some, Nothing, maybe\n\ndef divide(a: int, b: int) -> Maybe[int]:\n    if b == 0:\n        return Nothing\n    return Some(a // b)\n    \ndef upstream_function():\n    result = divide(10, 0)\n    return result.map(lambda x: x * 2)\n```\n\nWhat maybe does in this context is that it will return `Nothing` if the result is `None` and `Some` if the result is\nnot `None`.\nThis way we can chain operations on the result without thinking about handling `None` values. Alternatively we can\nuse decorators to use Maybe in a more elegant way:\n\n``` python\nfrom safeguard.maybe import maybe\n\n@maybe\ndef divide(a: int, b: int) -> int:\n    if b == 0:\n        return None\n    return a // b\n    \ndef upstream_function():\n    return divide(10, 0).map(lambda x: x * 2)\n```\n\nThis will automatically handle None values and return `Nothing` if the result is `None`.\n\n### Result\n\nThe idea is that a result value can be either `Ok(value)` or\n`Err(error)`, with a way to differentiate between the two. `Ok` and\n`Err` are both classes inherited from `Result[T, E]`.\nWe can use `Result` to handle errors in a more elegant way. Lets imagine having function for fetching user by email:\n\n``` python\ndef get_user_by_email(email: str) -> Tuple[Optional[User], Optional[str]]:\n    \"\"\"\n    Return the user instance or an error message.\n    \"\"\"\n    if not user_exists(email):\n        return None, 'User does not exist'\n    if not user_active(email):\n        return None, 'User is inactive'\n    user = get_user(email)\n    return user, None\n\nuser, reason = get_user_by_email('ueli@example.com')\nif user is None:\n    raise RuntimeError('Could not fetch user: %s' % reason)\nelse:\n    do_something(user)\n```\n\nWe can refactor this code to use `Result` like this:\n\n``` python\nfrom safeguard.result import Ok, Err, Result\n\ndef get_user_by_email(email: str) -> Result[User, str]:\n    \"\"\"\n    Return the user instance or an error message.\n    \"\"\"\n    if not user_exists(email):\n        return Err('User does not exist')\n    if not user_active(email):\n        return Err('User is inactive')\n    user = get_user(email)\n    return Ok(user)\n\nuser_result = get_user_by_email(email)\nif isinstance(user_result, Ok): # or `user_result.is_ok()`\n    # type(user_result.unwrap()) == User\n    do_something(user_result.unwrap())\nelse: # or `elif user_result.is_err()`\n    # type(user_result.unwrap_err()) == str\n    raise RuntimeError('Could not fetch user: %s' % user_result.unwrap_err())\n```\n\nIf you're using python version `3.10` or later, you can use the\nelegant `match` statement as well:\n\n``` python\nfrom result import Result, Ok, Err\n\ndef divide(a: int, b: int) -> Result[int, str]:\n    if b == 0:\n        return Err(\"Cannot divide by zero\")\n    return Ok(a // b)\n\nvalues = [(10, 0), (10, 5)]\nfor a, b in values:\n    match divide(a, b):\n        case Ok(value):\n            print(f\"{a} // {b} == {value}\")\n        case Err(e):\n            print(e)\n```\n\n## Contributing\n\nEveryone is welcome to contribute.\n\nYou just need to fork the repository, run `poetry install` so you can have the same environment,\nmake your changes and create a pull request. We will review your changes and merge them if they are good.\n\n## Related Projects\n\nThe inspiration was taken from following libraries, some of the ideas and code fragments are from their codebase:\n\n- [returns](https://github.com/dry-python/returns)\n- [result](https://github.com/rustedpy/result)\n\n## License\n\nMIT License",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Railway Oriented Programming Library",
    "version": "0.1.0",
    "project_urls": null,
    "split_keywords": [
        "result",
        " maybe",
        " railway-oriented-programming",
        " functional-programming",
        " safeguard"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "178f12d24894b7af7e67bc1e76e067dbaa1aece5d0ca4d954c522c366dd4c9b7",
                "md5": "b7d47a7f6e451292be95b29f6365b3da",
                "sha256": "e0ffb620a10ff3ed22004b82bc4404e4ceae2776fa7e8f613bdf138d59ece981"
            },
            "downloads": -1,
            "filename": "safeguard-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b7d47a7f6e451292be95b29f6365b3da",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.9",
            "size": 15233,
            "upload_time": "2024-06-10T07:17:59",
            "upload_time_iso_8601": "2024-06-10T07:17:59.542288Z",
            "url": "https://files.pythonhosted.org/packages/17/8f/12d24894b7af7e67bc1e76e067dbaa1aece5d0ca4d954c522c366dd4c9b7/safeguard-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b2999566eb7097dbbf078e1d8c9e2de953d30a7e04a8ceb08daa7455efaef55f",
                "md5": "7984d050b1d57bd9c65109482f625bf7",
                "sha256": "b8cc27b1c99fb272d74ed4039c55c5104ca3e3bbfeecb7b5e46aff46d25129ee"
            },
            "downloads": -1,
            "filename": "safeguard-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7984d050b1d57bd9c65109482f625bf7",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.9",
            "size": 15342,
            "upload_time": "2024-06-10T07:18:01",
            "upload_time_iso_8601": "2024-06-10T07:18:01.073284Z",
            "url": "https://files.pythonhosted.org/packages/b2/99/9566eb7097dbbf078e1d8c9e2de953d30a7e04a8ceb08daa7455efaef55f/safeguard-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-06-10 07:18:01",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "safeguard"
}
        
Elapsed time: 3.78165s