A Result(Either) like library to handle error fluently
Project description
orz aims to provide a more pythonic and mature Result type(or similar to Result type) for Python.
Result is a Monad type for handling success response and errors without using try … except or special values(e.g. -1, 0 or None). It makes your code more readable and more elegant.
Many langauges already have a builtin Result type. e.g. Result in Rust, Either type in Haskell , Result type in Swift and Result type in OCaml. And there’s a proposal in Go. Although Promise in Javascript is not a Result type, it handles errors fluently in a similar way.
Existing Result type Python libraries, such as dbrgn/result, arcrose/result_py, and ipconfiger/result2 did great job on porting Result from other languages. However, most of these libraries doesn’t support Python 2(sadly, I still have to use it). And because of the syntax limitation of Python, like lack of pattern matching, it’s not easy to show all the strength of Result type.
orz trying to make Result more pythonic and readable, useful in most cases.
Install Orz
Just like other Python package, install it by pip into a virtualenv, or use poetry to automatically create and manage the virtualenv.
$ pip install orz
Cheat Sheet
orz.Ok(value) orz.Err(error) |
Create a Result object |
orz.catch(raises=(Exception,))(func) |
Wrap a function to return an Ok when success, or return an Err when exception is raised |
[Ok|Err].then(func, catch_raises=None) [Ok|Err].err_then(func, catch_raises=None) |
Transform the wrapped value/error through func. |
[Ok|Err].then_unpack(func, catch_raises=None) [Ok|Err].err_then_unpack(func, catch_raises=None) |
Same as then() and err_then(), but values are unpacked as arguments of func. |
[Ok|Err].get_or(default) [Ok|Err].get_or_raise(self, error=None) |
Ok: Get the wrapped value. Err: Raise excetpion or get default value. |
[Ok|Err].guard(pred, err=UnSet) [Ok|Err].guard_none(err=UnSet) |
Ok: Make sure value in Ok pass the predicate function pred, or return an Err object. Err: Return self. |
[Ok|Err].fill(pred, value) |
Ok: Return self. Err: Return Ok(value) if the wrapped error pass the predicate function. |
bool([Ok|Err]) [Ok|Err].is_ok() [Ok|Err].is_err() isinstance(obj, orz.Ok) isinstance(obj, orz.Err) |
Check whether the object is Ok or Err. |
orz.is_result(obj) isinstance(obj, orz.Result) |
Check if the object is a Result object(Ok or Err). |
orz.all(results) |
Get an Ok which contains a list of values if all are Ok, or an Err of first Err |
orz.any(results) |
Get an Ok which contains a list of Ok values, or get last Err if all results are Err |
orz.first_ok(results) |
Get first ok or last err |
orz.ensure(obj) |
Ensure object is an instance of Result. |
Getting Start
Create a orz.Result object
Wrap the return value with orz.Ok explicitly for indicating success. And return an orz.Err object when something went wrong. Normally, the value wraped with Err is an error message or an exception object.
>>> import orz
>>> def get_score_rz(subj):
... score_db = {'math': 80, 'physics': 95}
... if subj in score_db:
... return orz.Ok(score_db[subj])
... else:
... return orz.Err('subj does not exist: ' + subj)
>>> get_score_rz('math')
Ok(80)
>>> get_score_rz('bio')
Err('subj does not exist: bio')
A handy decorator orz.catch can transform normal function into a Result-oriented function. The return value would be wraped with orz.Ok automatically, and exceptions would be captured and wraped with orz.Err.
>>> @orz.catch(raises=KeyError)
... def get_score_rz(subj):
... score_db = {'math': 80, 'physics': 95}
... return score_db[subj]
>>> get_score_rz('math')
Ok(80)
>>> get_score_rz('bio')
Err(KeyError('bio',))
Processing Pipeline
Both Ok and Err are of Result type, they have the same set of methods for further processing. The value in Ok would be transformed with then(func). And Err would skip the transformation, and propogate the error to the next stage.
>>> def get_letter_grade_rz(score):
... if 90 <= score <= 100: return orz.Ok('A')
... elif 80 <= score < 90: return orz.Ok('B')
... elif 70 <= score < 80: return orz.Ok('C')
... elif 60 <= score < 70: return orz.Ok('D')
... elif 0 <= score <= 60: return orz.Ok('F')
... else: return orz.Err('Wrong value range')
>>> get_score_rz('math')
Ok(80)
>>> get_score_rz('math').then(get_letter_grade_rz)
Ok('B')
>>> get_score_rz('bio')
Err(KeyError('bio',))
>>> get_score_rz('bio').then(get_letter_grade_rz)
Err(KeyError('bio',))
The func pass to the then(func, catch_raises=None) can be a normal function which returns an ordinary value. The returned value would be wraped with Ok automatically. Use catch_raises to capture exceptions and returned as an Err object.
>>> letter_grade_rz = get_score_rz('math').then(get_letter_grade_rz)
>>> msg_rz = letter_grade_rz.then(lambda letter_grade: 'your grade is {}'.format(letter_grade))
>>> msg_rz
Ok('your grade is B')
Connect all the then(func) calls together. And use Result.get_or(default) to get the final value.
>>> def get_grade_msg(subj):
... return (
... get_score_rz(subj)
... .then(get_letter_grade_rz)
... .then(lambda letter_grade: 'your grade is {}'.format(letter_grade))
... .get_or('something went wrong'))
>>> get_grade_msg('math')
'your grade is B'
>>> get_grade_msg('bio')
'something went wrong'
If you prefer to raise an exception rather than get a fallback value, use get_or_raise(error) instead.
>>> def get_grade_msg(subj):
... return (
... get_score_rz(subj)
... .then(get_letter_grade_rz)
... .then(lambda letter_grade: 'your grade is {}'.format(letter_grade))
... .get_or_raise())
>>> get_grade_msg('math')
'your grade is B'
>>> get_grade_msg('bio')
Traceback (most recent call last):
...
KeyError: 'bio'
Handling Error
Use Result.err_then(func, catch_raises) to convert Err back to Ok or to other Err.
>>> get_score_rz('bio')
Err(KeyError('bio',))
>>> get_score_rz('bio').then(get_letter_grade_rz)
Err(KeyError('bio',))
>>> (get_score_rz('bio')
... .err_then(lambda error: 0 if isinstance(error, KeyError) else error))
Ok(0)
>>> (get_score_rz('bio')
... .err_then(lambda error: 0 if isinstance(error, KeyError) else error)
... .then(get_letter_grade_rz))
Ok('F')
>>> (get_score_rz('bio')
... .then(get_letter_grade_rz)
... .err_then(lambda error: 'F' if isinstance(error, KeyError) else error))
Ok('F')
Most of the time, fill() is more concise to turn some Err back.
>>> get_score_rz('bio').fill(lambda error: isinstance(error, KeyError), 0)
Ok(0)
Check whether the returned value is Err or Ok.
>>> num_rz = orz.Ok(42)
>>> num_rz.is_ok()
True
>>> num_rz.is_err()
False
>>> isinstance(num_rz, orz.Ok)
True
>>> bool(num_rz)
True
>>> bool(orz.Ok(True)) # you always get True for Ok
True
>>> bool(orz.Ok(False)) # you always get True for Ok
True
>>> bool(orz.Err(True)) # you always get True for Err
False
More in Orz
Process Multiple Result objects
To ensure all values are Ok and handle them together.
>>> orz.all([orz.Ok(39), orz.Ok(2), orz.Ok(1)])
Ok([39, 2, 1])
>>> orz.all([orz.Ok(40), orz.Err('wrong value'), orz.Ok(1)])
Err('wrong value')
>>> orz.all([orz.Ok(40), orz.Ok(2)]).then(lambda values: sum(values))
Ok(42)
>>> orz.all([orz.Ok(40), orz.Ok(2)]).then_unpack(lambda n1, n2: n1 + n2)
Ok(42)
then_all() is useful when you want to apply multiple functions to the same value.
>>> orz.Ok(3).then_all(lambda n: n+2, lambda n: n+1)
Ok([5, 4])
>>> orz.Ok(3).then_all(lambda n: n+2, lambda n: n+1).then_unpack(lambda n1, n2: n1 + n2)
Ok(9)
Use first_ok() To get the first available value.
>>> orz.first_ok([orz.Err('E1'), orz.Ok(42), orz.Ok(3)])
Ok(42)
>>> orz.first_ok([orz.Err('E1'), orz.Err('E2'), orz.Err('E3')])
Err('E3')
>>> orz.Ok(15).then_first_ok(
... lambda v: 2 if (v % 2) == 0 else orz.Err('not a factor'),
... lambda v: 3 if (v % 3) == 0 else orz.Err('not a factor'),
... lambda v: 5 if (v % 5) == 0 else orz.Err('not a factor'))
Ok(3)
Guard value
>>> orz.Ok(3).guard(lambda v: v > 0)
Ok(3)
>>> orz.Ok(-3).guard(lambda v: v > 0)
Err(GuardError('Ok(-3) was failed to pass the guard: <function <lambda> at ...>',))
>>> orz.Ok(-3).guard(lambda v: v > 0, err=orz.Err('value should be greater than zero'))
Err('value should be greater than zero')
In fact, guard is a short-hand for a pattern of then().
>>> (orz.Ok(-3)
... .then(lambda v:
... orz.Ok(v) if v > 0
... else orz.Err('value should be greater than zero')))
Err('value should be greater than zero')
>>> orz.Ok(3).guard_none()
Ok(3)
>>> orz.Ok(None).guard_none()
Err(GuardError('failed to pass not None guard: ...',))
Convert any value to Result type
orz.ensure always returns a Result object.
>>> orz.ensure(42)
Ok(42)
>>> orz.ensure(orz.Ok(42))
Ok(42)
>>> orz.ensure(orz.Ok(orz.Ok(42)))
Ok(42)
>>> orz.ensure(orz.Err('failed'))
Err('failed')
>>> orz.ensure(KeyError('a'))
Err(KeyError('a',))
Check if object is a Result
>>> orz.is_result(orz.Ok(3))
True
>>> isinstance(orz.Ok(3), orz.Result)
True
>>> orz.Ok(3).is_ok()
True
>>> orz.Ok(3).is_err()
False
>>> orz.Err('E').is_ok()
False
>>> orz.Err('E').is_err()
True
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file orz-0.3.3.tar.gz
.
File metadata
- Download URL: orz-0.3.3.tar.gz
- Upload date:
- Size: 14.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/0.12.16 CPython/3.7.1 Darwin/19.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2ab6561fe9b586dbe0d11c358581f9d90991265b5b783b4e4e79b8b92de959d3 |
|
MD5 | 798fbc1cb1c88322b92bd2ec3cdf9938 |
|
BLAKE2b-256 | 972e09ac759a8810691dd656dc01e379c212f7d638d297a95d49b9f45c3db659 |
File details
Details for the file orz-0.3.3-py2.py3-none-any.whl
.
File metadata
- Download URL: orz-0.3.3-py2.py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/0.12.16 CPython/3.7.1 Darwin/19.6.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c6b8fd87a1eec0b51dd284c0d7ba0164d52e1786ec6a6d290294030ec910bacf |
|
MD5 | 530d036b790fb5991e3f2adfeebb7bf4 |
|
BLAKE2b-256 | 2ec56a06f0c1fc6605ba6b3fa8c995cdde0175729d6f89720e9dd2f815155541 |