Package: safetywrap
safetywrapFully typesafe, Rust-inspired wrapper types for Python valuesSummaryThis library provides two main wrappers:ResultandOption. These types
allow you to specify typesafe code that effectively handles errors or
absent data, without resorting to deeply nested if-statements and lots
of try-except blocks.This is accomplished by allowing you to operate on anOptionorResultin a sort of quantum superposition, where anOptioncould beSomeorNothingor aResultcould beOkorErr. In either case, all of the
methods on the type work just the same, allowing you to handle both cases
elegantly.AResult[T, E]may be an instance ofOk[T]orErr[E], while anOption[T]may be an instance ofSome[T]orNothing. Either way, you get to treat
them just the same until you really need to get the wrapped value.So, rather than this:forsomethingin"value",None:ifsomethingisnotNone:val=something.upper()else:val="DEFAULT"print(val)You can do this:forsomethinginSome("value"),Nothing():print(something.map(str.upper).unwrap_or("DEFAULT"))And rather than this:forjsondatain'{"value": "myvalue"}','{badjson':try:config=capitalize_keys(json.loads(jsondata))exceptException:config=get_default_config()print(config["value"])You can do this:forjsondatain'{"value": "myvalue"}','{badjson':print(Result.of(json.loads,jsondata).map(capitalize_keys).unwrap_or_else(get_default_config)["value"])These two examples are super minimal samples of how using these typesafe
wrappers can make things easier to write and reason about. Please see theExamplessection for more, andUsagefor the full
suite of offered functionality.These types are heavily influenced by theResultandOptiontypes in Rust.Thorough type specifications for mypy or your favorite python type-checker
are provided, so that you can decorate function inputs and outputs as
returningResultandOptiontypes and get useful feedback when supplying
arguments or passing return values.SponsorshipThis project was developed for and is graciously sponsored by my employer,Bestow, Inc.. At Bestow, we aim to democratize life
insurance by providing simple, easy coverage, purchasable online in five minutes
with no doctors' visits and no hassles.We're pretty much always hiring great developers, so if you'd like to work
with us, please check outour careers page!Table of ContentssafetywrapSummarySponsorshipTable of ContentsExamplesGet an enum member by its value, returning the member or NoneGet an enum member by its value, returning an OptionSerialize a dict that may be missing keys, using default valuesMake an HTTP request, and if the status code is 200, convert the body to JSON and return thedatakey. If there is an error or thedatakey does not exist, return an error stringUsageResult[T, E]Result ConstructorsOkErrResult.ofResult.collectResult.err_ifResult.ok_ifResult MethodsResult.and_Result.or_Result.and_thenResult.flatmapResult.or_elseResult.errResult.okResult.expectResult.raise_if_errResult.expect_errResult.is_errResult.is_okResult.iterResult.mapResult.map_errResult.unwrapResult.unwrap_errResult.unwrap_orResult.unwrap_or_elseResult Magic MethodsOption[T]Option ConstructorsSomeNothingOption.ofOption.nothing_ifOption.some_ifOption.collectOption MethodsOption.and_Option.or_Option.xorOption.and_thenOption.flatmapOption.or_elseOption.expectOption.raise_if_nothingOption.filterOption.is_nothingOption.is_someOption.iterOption.mapOption.map_orOption.map_or_elseOption.ok_orOption.ok_or_elseOption.unwrapOption.unwrap_orOption.unwrap_or_elseOption Magic MethodsPerformanceResultsDiscussionContributingExamplesIn general, these examples build from simple to complex. SeeUsagebelow for the full API specification.Get an enum member by its value, returning the member or NoneimporttypingastfromenumimportEnumfromresult_typesimportOption,Result,SomeT=t.TypeVar("T",bound=Enum)defenum_member_for_val(enum:t.Type[T],value:t.Any)->t.Optional[t.Any]:"""Return Some(enum_member) or Nothing()."""# Enums throw a `ValueError` if the value isn't present, so# we'll either have `Ok(enum_member)` or `Err(ValueError)`.# We unwrap and return the member if it's Ok, otherwise, we just# return NonereturnResult.of(enum,value).unwrap_or(None)Get an enum member by its value, returning an OptionimporttypingastfromenumimportEnumfromresult_typesimportOption,Result,SomeT=t.TypeVar("T",bound=Enum)defenum_member_for_val(enum:t.Type[T],value:t.Any)->Option[T]:"""Return Some(enum_member) or Nothing()."""# Enums throw a `ValueError` if the value isn't present, so# we'll either have `Ok(enum_member)` or `Err(ValueError)`.# Calling `ok()` on a `Result` returns an `Option`returnResult.of(enum,value).ok()Serialize a dict that may be missing keys, using default valuesimportjsonfromresult_typesimportResultdefserialize(data:t.Dict[str,t.Union[int,str,float]])->str:"""Serialize the data.Absent keys are "[absent]", rather than null. This allows us to maintaininformation about whether a key was present or actually set to None."""keys=("first","second","third","fourth")# We can even use Result to catch any JSON serialization errors, so that# this function will _always_ return a string!Result.of(json.dumps,# Result.of() will intercept the KeyError and return an Err. We use# `unwrap_or()` to discard the error and return the "[absent]" string# instead; if the key was present, the Result was Ok, and we just# return that value.{k:Result.of(lambda:data[k]).unwrap_or("[absent]")forkinkeys}).unwrap_or("Could not serialize JSON from data!")Make an HTTP request, and if the status code is 200, convert the body to JSON and return thedatakey. If there is an error or thedatakey does not exist, return an error stringfromfunctoolsimportpartialimportrequestsfromrequestsimportResponsefromresult_typesimportOption,Resultdefget_data(url:str)->str:"""Get the data!"""# We need to do manual type assignment sometimes when the code# we're wrapping does not provide types.# If the wrapped function raises any Exception, `res` will be# Err(Exception). Otherwise it will be `Ok(Response)`.res:Result[Response,Exception]=Result.of(requests.get,url)return(# We start as a `Result[Response, Exception]`res# And if we were an Err, map to a `Result[Response, str]`.map_err(str)# If we were Ok, and_then (aka flatmap) to a new `Result[Response, str]`.and_then(lambdares:(# Our return value starts as a `Result[Response, Response]`Result.ok_if(lambdar:r.status_code==200,res).map_err(# So we map it to a `Result[Response, str]`lambdar:str(f"Bad status code:{r.status_code}"))))# We are now a `Result[Response, str]`, where we are only Ok if# our status code was 200.# Now we transition to a `Result[dict, str]`.and_then(lambdares:Result.of(res.json).map_err(str))# And to a `Result[Option[str], str]`.map(lambdajs:Option.of(js.get("data")).map(str))# And to a `Result[str, str]`.and_then(lambdadata:data.ok_or("No data key in JSON!"))# If we are an error, convert us to an Ok with the error string.or_else(Ok)# And now we get either the Ok string or the Err string!.unwrap())UsageResult[T, E]A Result represents some value that may either be in anOkstate or
anErrstate.Result ConstructorsOkOk(value: T) -> Result[T, E]Construct anOkResult directly with the value.Example:defcheck_value_not_negative(val:int)->Result[int,str]:"""Check that a value is not negative, or return an Err."""ifval>=0:returnOk(val)returnErr(f"{val}is negative!")ErrErr(value: E) -> Result[T, E]Construct anErrResult directly with the value.Example:defcheck_value_not_negative(val:int)->Result[int,str]:"""Check that a value is not negative, or return an Err."""ifval>=0:returnOk(val)returnErr(f"{val}is negative!")Result.ofResult.of(fn: Callable[..., T], *args: t.Any, catch: t.Type[E], **kwargs) -> Result[T, E]Call a function with the provided arguments. If no error is thrown, returnOk(result). Otherwise, returnErr(exception). By default,Exceptionis caught, but different error types may be provided with thecatchkeyword argument.The type ofEMUST beExceptionor one of its subclasses.This constructor is designed to be useful in wrapping other APIs, builtin
functions, etc.Note that due to a bug in mypy (seehttps://github.com/python/mypy/issues/3737),
sometimes you need to explicitly specify thecatchkeyword argument,
even if you're setting it to its default (Exception). This does not
happen consistently, but when it does, you will see mypy thinking
that the type of theResultisResult[SomeType, <nothing>].Example:importjsondefparse_json(string:str)->Result[dict,Exception]:"""Parse a JSON object into a dict."""returnResult.of(json.loads,string)Result.collectResult.collect(iterable: Iterable[T, E]) -> Result[Tuple[T, ...], E]Convert an iterable of Results into a single Result. If all Results were
Ok, Ok values are collected into a Tuple in the final Result. If any Results
were Err, the Err result is returned directly.Example:assertResult.collect([Ok(1),Ok(2),Ok(3)])==Ok((1,2,3))assertResult.collect([Ok(1),Err("no"),Ok(3)])==Err("no")Result.err_ifResult.err_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]Run a predicate on some value, and returnErr(val)if the predicate returns
True, orOk(val)if the predicate returns False.Example:fromrequestsimportResponsedefchecked_response(response:Response)->Result[Response,Response]:"""Try to get a response from the server."""returnResult.err_if(lambdar:r.status_code>=300,response)Result.ok_ifResult.ok_if(predicate: t.Callable[[T], bool], value: T) -> Result[T, T]Run a predicate on some value, and returnOk(val)if the predicate returns
True, orErr(val)if the predicate returns False.Example:defchecked_data(data:dict)->Result[dict,dict]:"""Check if data has expected keys."""expected_keys=("one","two","three")returnResult.ok_if(lambdad:all(kindforkinexpected_keys),data)Result MethodsResult.and_Result.and_(self, res: Result[U, E]) -> Result[U, E]If this Result isOk, returnres. If this result isErr, return this
Result. This can be used to short circuit a chain of Results on encountering
the first error.Example:assertOk(5).and_(Ok(6))==Ok(6)assertErr(1).and_(Ok(6))==Err(1)assertErr(1).and_(Err(2)).and_(Ok(5))==Err(1)assertOk(5).and_(Err(1)).and_(Ok(6))==Err(1)Result.or_Result.or_(self, res: Result[T, F]) -> Result[T, F]If this Result isErr, returnres. Otherwise, return this Result.Example:assertErr(1).or_(Ok(5))==Ok(5)assertErr(1).or_(Err(2))==Err(2)assertOk(5).or_(Ok(6))==Ok(5)assertOk(5).or_(Err(1))==Ok(5)Result.and_thenResult.and_then(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]If this Result isOk, call the provided function with the wrapped value of
this Result and return the Result of that function. This allows easily
chaining multiple Result-generating calls together to yield a final
Result. This method is an alias ofResult.flatmapExample:assertOk(5).and_then(lambdaval:Ok(val+1))==Ok(6)assertErr(1).and_then(lambdaval:Ok(val+1))==Err(1)Result.flatmapResult.flatmap(self, fn: t.Callable[[T], Result[U, E]]) -> Result[U, E]If this Result isOk, call the provided function with the wrapped value of
this Result and return the Result of that function. This allows easily
chaining multiple Result-generating calls together to yield a final
Result. This method is an alias ofResult.and_thenExample:assertOk(5).flatmap(lambdaval:Ok(val+1))==Ok(6)assertErr(1).flatmap(lambdaval:Ok(val+1))==Err(1)Result.or_elseResult.or_else(self, fn: t.Callable[[E], Result[T, F]]) -> Result[T, F])If this result isErr, call the provided function with the wrapped error
value of this Result and return the Result of that function. This allows
easily handling potential errors in a way that still returns a final Result.Example:assertOk(5).or_else(Ok)==Ok(5)assertErr(1).or_else(Ok)==Ok(1)Result.errResult.err(self) -> Option[E]Convert this Result into an Option, returning Some(err_val) if this Result
isErr, or Nothing() if this Result isOk.Example:assertOk(5).err()==Nothing()assertErr(1).err()==Some(1)Result.okResult.ok(self) -> Option[T]Convert this Result into an Option, returningSome(val)if this Result isOk, orNothing()if this result isErr.Example:assertOk(5).ok()==Some(5)assertErr(1).ok()==Nothing()Result.expectResult.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> TReturn the wrapped value if this Result isOk. Otherwise, raise an error,
instantiated with the provided message and the stringified error value.
By default, aRuntimeErroris raised, but an alternative error may be
provided using theexc_clskeyword argument. This method is an alias forResult.raise_if_err.Example:importpytestwithpytest.raises(RuntimeError)asexc:Err(5).expect("Bad value")assertstr(exc.value)=="Bad value: 5"assertOk(1).expect("Bad value")==1Result.raise_if_errResult.raise_if_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> TReturn the wrapped value if this Result isOk. Otherwise, raise an error,
instantiated with the provided message and the stringified error value.
By default, aRuntimeErroris raised, but an alternative error may be
provided using theexc_clskeyword argument. This method is an alias forResult.expect.Example:importpytestwithpytest.raises(RuntimeError)asexc:Err(5).raise_if_err("Bad value")assertstr(exc.value)=="Bad value: 5"assertOk(1).raise_if_err("Bad value")==1Result.expect_errResult.expect_err(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> EReturn the wrapped value if this Result isErr. Otherwise, raise an error,
instantiated with the provided message and the stringified Ok value.
By default, aRuntimeErroris raised, but an alternative error may be
provided using theexc_clskeyword argument.Example:importpytestwithpytest.raises(RuntimeError)asexc:Ok(5).expect_err("Unexpected good value")assertstr(exc.value)=="Unexpected good value: 5"assertErr(1).expect_err("Unexpected good value")==1Result.is_errResult.is_err(self) -> boolReturn True if this Result isErr, orFalseif this Result isOk.Example:assertErr(1).is_err()isTrueassertOk(1).is_err()isFalseResult.is_okResult.is_ok(self) -> boolReturn True if this Result isOk, orFalseif this Result isErr.Example:assertOk(1).is_err()isTrueassertErr(1).is_err()isFalseResult.iterResult.iter(self) -> Iterator[T]Return an iterator with length 1 over the wrapped value if this Result isOk.
Otherwise, return a 0-length iterator.Example:asserttuple(Ok(1).iter())==(1,)asserttuple(Err(1).iter())==()Result.mapResult.map(self, fn: t.Callable[[T], U]) -> Result[U, E]If this Result isOk, apply the provided function to the wrapped value,
and return a newOkResult with the result of the function. If this Result
isErr, do not apply the function and return this Result unchanged.Warning:no error checking is performed while applying the provided
function, and exceptions applying the function are not caught. If you need
to map with error handling, consider usingand_then(akaflatmap) in
conjunction with theResult.ofconstructor, e.g.assert Ok(0).and_then(partial(Result.of, lambda i: 10 / i)) == Err(ZeroDivisionError('division by zero'))Example:assertOk(1).map(str)==Ok("1")assertErr(1).map(str)==Err(1)Result.map_errResult.map_err(self, fn: t.Callable[[E], F]) -> Result[T, F]If this Result isErr, apply the provided function to the wrapped value,
and return a newErrResult with the result of the function. If this Result
isOk, do not apply the function and return this Result unchanged.Warning:no error checking is performed while applying the provided
function, and exceptions applying the function are not caught.Example:assertErr(1).map_err(lambdai:i+1)==Err(2)assertOk(1).map_err(lambdai:i+1)==Ok(1)Result.unwrapResult.unwrap(self) -> TIf this Result isOk, return the wrapped value. If this Result isErr,
throw aRuntimeError.Example:importpytestassertOk(1).unwrap()==1withpytest.raises(RuntimeError):Err(1).unwrap()Result.unwrap_errResult.unwrap_err(self) -> EIf this Result isErr, return the wrapped value. If this Result isOk,
throw aRuntimeError.Example:importpytestassertErr(1).unwrap()==1withpytest.raises(RuntimeError):Ok(1).unwrap()Result.unwrap_orResult.unwrap_or(self, alternative: U) -> t.Union[T, U]If this Result isOk, return the wrapped value. Otherwise, if this Result
isErr, return the provided alternative.Example:assertOk(1).unwrap_or(5)==1assertErr(1).unwrap_or(5)==5Result.unwrap_or_elseResult.unwrap_or_else(self, fn: t.Callable[[E], U]) -> t.Union[T, U]If this Result isOk, return the wrapped value. Otherwise, if this Result
isErr, call the supplied function with the wrapped error value and return
the result.Example:assertOk(1).unwrap_or_else(str)==1assertErr(1).unwrap_or_else(str)=="1"Result Magic MethodsResult.iterResult.__iter__(self) -> t.Iterator[T]Implement the iterator protocol, allowing iteration over the results ofResult.iter. If this Result isOk, return an iterator
of length 1 containing the wrapped value. Otherwise, if this Result isErr,
return a 0-length iterator.Example:# Can be passed to methods that take iteratorsasserttuple(Ok(1))==(1,)asserttuple(Err(1))==()# Can be used in `for in` constructs, including comprehensionsassert[valforvalinOk(5)]==[5]assert[valforvalinErr(5)]==[]# More for-in usage.forvalinOk(5):passassertval==5val=NoneforvalinErr(1):passassertvalisNoneResult.eqResult.__eq__(self, other: Any) -> boolEnable equality checking using==.Compare the Result withother. Return True ifotheris the same type of
Result with the same wrapped value. Otherwise, return False.Example:assert(Ok(5)==Ok(5))isTrueassert(Ok(5)==Ok(6))isFalseassert(Ok(5)==Err(5))isFalseassert(Ok(5)==5)isFalseResult.neResult.__ne__(self, other: Any) -> boolEnable inequality checking using!=.Compare the Result withother. Return False ifotheris the same type of
Result with the same wrapped value. Otherwise, return True.Example:assert(Ok(5)!=Ok(5))isFalseassert(Ok(5)!=Ok(6))isTrueassert(Ok(5)!=Err(5))isTrueassert(Ok(5)!=5)isTrueResult.strResult.__str__(self) -> strEnable useful stringification viastr().Example:assertstr(Ok(5))=="Ok(5)"assertstr(Err(5))=="Err(5)"Result.reprResult.__repr__(self) -> strEnable useful stringification viarepr().Example:assertrepr(Ok(5))=="Ok(5)"assertrepr(Err(5))=="Err(5)"Option[T]An Option represents eitherSomevalue orNothing.Option ConstructorsSomeSome(value: T) -> Option[T]Construct aSomeOption directly with a value.Example:deffile_contents(path:str)->Option[str]:"""Return the file contents or Nothing."""try:withopen(path)asf:returnSome(f.read())exceptIOError:returnNothing()NothingNothing() -> Option[T]Construct aNothingOption directly with a value.Note: as an implementation detail,Nothingis implemented as a singleton,
to avoid instantiation time for anyNothingcreated after the first.
However since this is an implementation detail,NothingOptions should
still be compared with==rather thanis.Example:deffile_contents(path:str)->Option[str]:"""Return the file contents or Nothing."""try:withopen(path)asf:returnSome(f.read())exceptIOError:returnNothing()Option.ofOption.of(value: t.Optional[T]) -> Option[T]Convert an optional value into an Option. If the value is notNone, returnSome(value). Otherwise, if the value isNone, returnNothing().Example:assertOption.of(None)==Nothing()assertOption.of({}.get("a"))==Nothing()assertOption.of("a")==Some("a")assertOption.of({"a":"b"})==Some("b")Option.nothing_ifOption.nothing_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]Call the provided predicate function with the provided value. If the predicate
returns True, returnNothing(). If the predicate returns False, returnSome(value).Example:assertOption.nothing_if(lambdaval:val.startswith("_"),"_private")==Nothing()assertOption.nothing_if(lambdaval:val.startswith("_"),"public")==Some("public")Option.some_ifOption.some_if(predicate: t.Callable[[T], bool], value: T) -> Option[T]Call the provided predicate function with the provided value. If the predicate
returns True, returnSome(value). If the predicate returns False, returnNothing().Example:assertOption.some_if(bool,[1,2,3])==Some([1,2,3])assertOption.some_if(bool,[])==Nothing()Option.collectOption.collect(options: t.Iterable[Option[T]]) -> Option[t.Tuple[T, ...]]Collect a series of Options into single Option.If all options areSome[T], the result isSome[Tuple[T, ...j]]. If
any options areNothing, the result isNothing.Example:assertOption.collect([Some(1),Some(2),Some(3)])==Some((1,2,3))assertOption.collect([Some(1),Nothing(),Some(3)])==Nothing()Option MethodsOption.and_Option.and_(alternative: Option[U]) -> Option[U]If this Option isNothing, return it unchanged. Otherwise, if this Option
isSome, return the providedalternativeOption.Example:assertSome(1).and_(Some(2))==Some(2)assertNothing().and_(Some(2))==Nothing()assertSome(1).and_(Nothing())==Nothing()assertNothing().and_(Nothing())==Nothing()assertSome(1).and_(Nothing()).and_(Some(2))==Nothing()Option.or_Option.or_(alternative: Option[T]) -> Option[T]If this Option isNothing, return the providedalternativeOption.
Otherwise, if this Option isSome, return it unchanged.Example:assertSome(1).or_(Some(2))==Some(1)assertSome(1).or_(Nothing())==Some(1)assertNothing().or_(Some(1))==Some(1)assertNothing().or_(Nothing())==Nothing()Option.xorOption.xor(alternative: Option[T]) -> Option[T]Exclusive or. ReturnSomeOption iff (if and only if) exactly one of
this Option and hte providedalternativeare Some. Otherwise, returnNothing.Example:assertSome(1).xor(Nothing())==Some(1)assertNothing().xor(Some(1))==Some(1)assertSome(1).xor(Some(2))==Nothing()assertNothing().xor(Nothing())==Nothing()Option.and_thenOption.and_then(self, fn: t.Callable[[T], Option[U]]) -> Option[U]If this Option isSome, call the provided, Option-returning function with
the contained value and return whatever Option it returns. If this Option
isNothing, return it unchanged. This method is an alias forOption.flatmapExample:assertSome(1).and_then(lambdai:Some(i+1))==Some(2)assertNothing().and_then(lambdai:Some(i+1))==Nothing()Option.flatmapOption.flatmap(self, fn: t.Callable[[T], Option[U]]) -> Option[U]If this Option isSome, call the provided, Option-returning function with
the contained value and return whatever Option it returns. If this Option
isNothing, return it unchanged. This method is an alias forOption.and_thenExample:assertSome(1).flatmap(Some)==Some(1)assertNothing().flatmap(Some)==Nothing()Option.or_elseOption.or_else(self, fn: t.Callable[[], Option[T]]) -> Option[T]If this Option isNothing, call the provided, Option-returning function
and return whatever Option it returns. If this Option isSome, return it
unchanged.Example:assertNothing().or_else(lambda:Some(1))==Some(1)assertSome(1).or_else(lambda:Some(2))==Some(1)Option.expectOption.expect(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> TIf this Option isSome, return the wrapped value. Otherwise, if this
Option isNothing, raise an error instantiated with the provided message.
By default, aRuntimeErroris raised, but a custom exception class may be
provided via theexc_clskeyword argument. This method is an alias
ofOption.raise_if_nothing.Example:importpytestwithpytest.raises(RuntimeError)asexc:Nothing().expect("Nothing here")assertstr(exc.value)=="Nothing here"assertSome(1).expect("Nothing here")==1Option.raise_if_nothingOption.raise_if_nothing(self, msg: str, exc_cls: t.Type[Exception] = RuntimeError) -> TIf this Option isSome, return the wrapped value. Otherwise, if this
Option isNothing, raise an error instantiated with the provided message.
By default, aRuntimeErroris raised, but a custom exception class may be
provided via theexc_clskeyword argument. This method is an alias
ofOption.expect.Example:importpytestwithpytest.raises(RuntimeError)asexc:Nothing().raise_if_nothing("Nothing here")assertstr(exc.value)=="Nothing here"assertSome(1).raise_if_nothing("Nothing here")==1Option.filterOption.filter(self, predicate: t.Callable[[T], bool]) -> Option[T]If this Option isSome, call the provided predicate function with the wrapped
value. If the predicate returns True, returnSomecontaining the wrapped
value of this Option. If the predicate returns False, returnNothing. If
this Option isNothing, return it unchanged.Example:defis_even(val:int)->bool:"""Return whether the value is even."""returnval%2==0assertSome(2).filter(is_even)==Some(2)assertSome(1).filter(is_even)==Nothing()assertNothing().filter(is_even)==Nothing()Option.is_nothingOption.is_nothing(self) -> boolIf this Option isNothing, return True. Otherwise, if this Option isSome, return False.Example:assertNothing().is_nothing()isTrueassertSome(1).is_nothing()isFalseOption.is_someOption.is_some(self) -> boolIf this Option isSome. Otherwise, if this Option isNothing, return False.Example:assertSome(1).is_some()isTrueassertNothing().is_some()isFalseOption.iterOption.iter(self) -> t.Iterator[T]If this Option isSome, return an iterator of length one over the wrapped
value. Otherwise, if this Option isNothing, return a 0-length iterator.Example:asserttuple(Some(1).iter())==(1,)asserttuple(Nothing().iter())==()Option.mapOption.map(self, fn: t.Callable[[T], U]) -> Option[U]If this Option isSome, apply the provided function to the wrapped value,
and returnSomewrapping the result of the function. If this Option isNothing, return this Option unchanged.Example:assertSome(1).map(str)==Some("1")assertNothing().map(str)==Nothing()assertSome(1).map(str).map(lambdax:x+"a").map(str.upper)==Some("1A")Option.map_orOption.map_or(self, default: U, fn: t.Callable[[T], U]) -> UIf this Option isSome, apply the provided function to the wrapped value
and return the result. If this Option isNothing, return the provided
default value.Example:assertSome(1).map_or("no value",str)=="1"assertNothing().map_or("no value",str)=="no value"Option.map_or_elseOption.map_or_else(self, default: t.Callable[[], U], fn: t.Callable[[T], U]) -> UIf this Option isSome, apply the provided function to the wrapped value and
return the result. If this Option isNothing, call the provided default
function with no arguments and return the result.Example:fromdatetimeimportdatetime,dateassertSome("2005-08-28").map_or_else(date.today,lambdat:datetime.strptime(t,"%Y-%m-%d").date())==datetime(2005,8,28).date()assertNothing().map_or_else(date.today,lambdat:datetime.strptime(t,"%Y-%m-%d").date())==date.today()Option.ok_orOption.ok_or(self, err: E) -> Result[T, E]If this Option isSome, return anOkResult wrapping the contained
value. Otherwise, return anErrresult wrapping the provided error.Example:assertSome(1).ok_or("no value!")==Ok(1)assertNothing().ok_or("no value!")==Err("no value!")Option.ok_or_elseOption.ok_or_else(self, err_fn: t.Callable[[], E]) -> Result[T, E]If this Option isSome, return anOkResult wrapping the contained
value. Otherwise, call the providederr_fnand wrap its return value
in anErrResult.Example:fromfunctoolsimportpartialdefmake_err_msg(msg:str)->str:"""Make an error message with some starting text."""returnf"[MY_APP_ERROR] --{msg}"assertSome(1).ok_or_else(partial(make_err_msg,"no value!"))==Ok(1)assertNothing().ok_or_else(partial(make_err_msg,"no value!"))==Err("[MY_APP_ERROR] -- no value!")Option.unwrapOption.unwrap(self) -> TIf this Option isSome, return the wrapped value. Otherwise, raise aRuntimeError.Example:importpytestassertSome(1).unwrap()==1withpytest.raises(RuntimeError):Nothing().unwrap()Option.unwrap_orOption.unwrap_or(self, default: U) -> t.Union[T, U]If this Option isSome, return the wrapped value. Otherwise, return the
provided default.Example:assertSome(1),unwrap_or(-1)==1assertNothing().unwrap_or(-1)==-1Option.unwrap_or_elseOption.unwrap_or_else(self, fn: t.Callable[[], U]) -> t.Union[T, U]If this Option isSome, return the wrapped value. Otherwise, return the
result of the provided function.Example:fromdatetimeimportdateassertSome(date(2001,1,1)).unwrap_or_else(date.today)==date(2001,1,1)assertNothing().unwrap_or_else(date.today)==date.today()Option Magic MethodsOption.iterOption.__iter__(self) -> t.Iterator[T]Implement the iterator protocol, allowing iteration over the results ofOption.iter. If this Option isOk, return an iterator
of length 1 containing the wrapped value. Otherwise, if this Option isNothing,
return a 0-length iterator.Example:# Can be passed to methods that take iteratorsasserttuple(Some(1))==(1,)asserttuple(Nothing()j)==()# Can be used in `for in` constructs, including comprehensionsassert[valforvalinSome(1)]==[1]assert[valforvalinNothing()]==[]# More for-in usage.forvalinSome(1):passassertval==1val=NoneforvalinNothing():passassertvalisNoneOption.eqOption.__eq__(self, other: Any) -> boolEnable equality checking using==.Compare this Option withother. Return True ifotheris the same type of
Option with the same wrapped value. Otherwise, return False.Example:assert(Some(1)==Some(1))isTrueassert(Some(1)==Some(2))isFalseassert(Some(1)==Nothing())isFalseassert(Some(1)==1)isFalseOption.neOption.__ne__(self, other: Any) -> boolEnable inequality checking using!=.Compare the Option withother. Return False ifotheris the same type of
Option with the same wrapped value. Otherwise, return True.Example:assert(Some(1)!=Some(1))isFalseassert(Some(1)!=Some(2))isTrueassert(Some(1)!=Nothing())isTrueassert(Some(1)!=1)isTrueOption.strOption.__str__(self) -> strEnable useful stringification viastr().Example:assertstr(Some(1))=="Some(1)"assertstr(Nothing())=="Nothing()"Option.reprOption.__repr__(self) -> strEnable useful stringification viarepr().Example:assertrepr(Some(1))=="Some(1)"assertrepr(Nothing())=="Nothing()"PerformanceBenchmarks may be run withmake bench. Benchmarking utilities are provided
inbench/.Currently, thesample.pybenchmark defines two data
stores, one using classical python error handling (or lack thereof), and
the other using this library's wrapper types. Some simple operations
are performed using each data store for comparison.runner.shruns the benchmarks two ways. First, it useshyperfineto run the benchmarks as a normal python script 100 times and
display information about the run time. It then uses python's builtintimeitmodule to measure
the code execution time in isolation over one million runs, without the
added overhead of spinning up the interpreter to parse and run the script.ResultsTheResultandOptionwrapper types add minimal overhead to
execution time, which will not be noticeable for most real-world workloads.
However, care should be taken if using these types in "hot paths."Run in isolation, the sample code usingResultandOptiontypes is
about six times slower than builtin exception handling:MethodNumber of ExecutionsAverage Execution TimeRelative to ClassicalClassical1,000,000 (1E6)3.79E-6 s (3.79 μs)1xWrapper1,000,000 (1E6)2.31E-5 s (23.1 μs)6.09xWhen run as part of a Python script, there is no significant difference
between using code with these wrapper types versus code that uses builtin
exception handling and nested if statements.MethodNumber of ExecutionsAverage Execution TimeRelative to ClassicalClassical10032.2 ms1xWrapper10032.5 ms1.01xDiscussionCare has been taken to make the wrapper types in this library as performant
as possible. All types use__slots__to avoid allocating a dictionary for
instance variables, and wrapper variants (e.g.OkandErrforResult)
are implemented as separate subclasses ofResultrather than a shared
class in order to avoid needing to perform if/else branching orisinstance()checks, which are notoriously slow in Python.That being said, using these typesisdoing more than the builtin error
handling! Instances are being constructed and methods are being accessed.
Both of these are relatively quick in Python, but definitely not quicker
than doing nothing, so this library will probably never be quite as performant
as raw exception handling. That being said, that is not its aim! The goal
is to be as quick as possible, preferably within striking distance of
regular old idiomatic python, while providing significantly more ergonomics
and type safety around handling errors and absent data.ContributingContributions are welcome! To get started, you'll just need a local install
of Python 3.Once you've forked and cloned the repo, you can run:make test- run tests using your local interpretermake fmt- format code usingblackmake lint- check code with a variety of analysis toolsmake bench- run benchmarksSee theMakefilefor other commands.The CI system requires thatmake lintandmake testrun successfully
(exit status of 0) in order to merge code.result_typesis compatible with Python >= 3.6. You can run against
all supported python versions withmake test-all-versions. This requires
thatdockerbe installed on your local system. Alternatively, if you
have all required Python versions installed, you may runmake toxto
run against your local interpreters.