Package: orz
orzaims 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 usingtry … exceptor special values(e.g.-1,0orNone). 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 SwiftandResult type in OCaml. And there’s aproposal in Go. AlthoughPromise in Javascriptis not a Result type, it handles errors fluently in a similar way.Existing Result type Python libraries, such asdbrgn/result,arcrose/result_py, andipconfiger/result2did 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.orztrying to make Result more pythonic and readable, useful in most cases.Install OrzJust like other Python package, install it bypipinto avirtualenv, or usepoetryto automatically create and manage the
virtualenv.$pipinstallorzCheat Sheetorz.Ok(value)orz.Err(error)Create a Result objectorz.catch(raises=(Exception,))(func)Wrap a function to return anOkwhen success, or return anErrwhen exception is raised[Ok|Err].then(func,catch_raises=None)[Ok|Err].err_then(func,catch_raises=None)Transform the wrapped value/error throughfunc.[Ok|Err].then_unpack(func,catch_raises=None)[Ok|Err].err_then_unpack(func,catch_raises=None)Same asthen()anderr_then(), but values are unpacked as arguments offunc.[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 functionpred, or return anErrobject.Err: Return self.[Ok|Err].fill(pred,value)Ok: Return self.Err: ReturnOk(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 Errorz.any(results)Get an Ok which contains a list of Ok values, or get last Err if all results are Errorz.first_ok(results)Get first ok or last errorz.ensure(obj)Ensure object is an instance of Result.Getting StartCreate aorz.ResultobjectWrap the return value withorz.Okexplicitly for indicating success. And
return anorz.Errobject when something went wrong. Normally, the value wraped withErris an error message or an exception object.>>>importorz>>>defget_score_rz(subj):...score_db={'math':80,'physics':95}...ifsubjinscore_db:...returnorz.Ok(score_db[subj])...else:...returnorz.Err('subj does not exist: '+subj)>>>get_score_rz('math')Ok(80)>>>get_score_rz('bio')Err('subj does not exist: bio')A handy decoratororz.catchcan transform normal function into a
Result-oriented function. The return value would be wraped withorz.Okautomatically, and exceptions would be captured and wraped withorz.Err.>>>@orz.catch(raises=KeyError)...defget_score_rz(subj):...score_db={'math':80,'physics':95}...returnscore_db[subj]>>>get_score_rz('math')Ok(80)>>>get_score_rz('bio')Err(KeyError('bio',))Processing PipelineBothOkandErrare ofResulttype, they have the same set of methods for further processing. The value inOkwould be transformed withthen(func). AndErrwould skip the transformation, and propogate the error to the next stage.>>>defget_letter_grade_rz(score):...if90<=score<=100:returnorz.Ok('A')...elif80<=score<90:returnorz.Ok('B')...elif70<=score<80:returnorz.Ok('C')...elif60<=score<70:returnorz.Ok('D')...elif0<=score<=60:returnorz.Ok('F')...else:returnorz.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',))Thefuncpass to thethen(func, catch_raises=None)can be a normal
function which returns an ordinary value. The returned value would be wraped withOkautomatically. Usecatch_raisesto capture exceptions and returned as anErrobject.>>>letter_grade_rz=get_score_rz('math').then(get_letter_grade_rz)>>>msg_rz=letter_grade_rz.then(lambdaletter_grade:'your grade is{}'.format(letter_grade))>>>msg_rzOk('your grade is B')Connect all thethen(func)calls together. And useResult.get_or(default)to get the final
value.>>>defget_grade_msg(subj):...return(...get_score_rz(subj)....then(get_letter_grade_rz)....then(lambdaletter_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, useget_or_raise(error)instead.>>>defget_grade_msg(subj):...return(...get_score_rz(subj)....then(get_letter_grade_rz)....then(lambdaletter_grade:'your grade is{}'.format(letter_grade))....get_or_raise())>>>get_grade_msg('math')'your grade is B'>>>get_grade_msg('bio')Traceback(mostrecentcalllast):...KeyError:'bio'Handling ErrorUseResult.err_then(func, catch_raises)to convertErrback toOkor to otherErr.>>>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(lambdaerror:0ifisinstance(error,KeyError)elseerror))Ok(0)>>>(get_score_rz('bio')....err_then(lambdaerror:0ifisinstance(error,KeyError)elseerror)....then(get_letter_grade_rz))Ok('F')>>>(get_score_rz('bio')....then(get_letter_grade_rz)....err_then(lambdaerror:'F'ifisinstance(error,KeyError)elseerror))Ok('F')Most of the time,fill()is more concise to turn someErrback.>>>get_score_rz('bio').fill(lambdaerror:isinstance(error,KeyError),0)Ok(0)Check whether the returned value isErrorOk.>>>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 OkTrue>>>bool(orz.Ok(False))# you always get True for OkTrue>>>bool(orz.Err(True))# you always get True for ErrFalseMore in OrzProcess Multiple Result objectsTo ensure all values areOkand 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(lambdavalues:sum(values))Ok(42)>>>orz.all([orz.Ok(40),orz.Ok(2)]).then_unpack(lambdan1,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(lambdan:n+2,lambdan:n+1)Ok([5,4])>>>orz.Ok(3).then_all(lambdan:n+2,lambdan:n+1).then_unpack(lambdan1,n2:n1+n2)Ok(9)Usefirst_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(...lambdav:2if(v%2)==0elseorz.Err('not a factor'),...lambdav:3if(v%3)==0elseorz.Err('not a factor'),...lambdav:5if(v%5)==0elseorz.Err('not a factor'))Ok(3)Guard value>>>orz.Ok(3).guard(lambdav:v>0)Ok(3)>>>orz.Ok(-3).guard(lambdav:v>0)Err(GuardError('Ok(-3) was failed to pass the guard: <function <lambda> at ...>',))>>>orz.Ok(-3).guard(lambdav: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 ofthen().>>>(orz.Ok(-3)....then(lambdav:...orz.Ok(v)ifv>0...elseorz.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 typeorz.ensurealways 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