Skip to main content
Join the official 2019 Python Developers SurveyStart the survey!

A Result(Either) like library to handle error fluently

Project description

orz: Result type

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

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)

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(CheckError('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(CheckError('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


Download files

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

Files for orz, version 0.3.1
Filename, size File type Python version Upload date Hashes
Filename, size orz-0.3.1-py2.py3-none-any.whl (10.6 kB) File type Wheel Python version py2.py3 Upload date Hashes View hashes
Filename, size orz-0.3.1.tar.gz (11.5 kB) File type Source Python version None Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN SignalFx SignalFx Supporter DigiCert DigiCert EV certificate StatusPage StatusPage Status page