# 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"
}