class TestOptionConstructors: """Test option constructors.""" @pytest.mark.parametrize( "val, exp", ( (5, Some(5)), (None, Nothing()), ("", Some("")), (False, Some(False)), ({}, Some({})), ([], Some([])), ), ) def test_of(self, val: t.Any, exp: Option) -> None: """Option.of() returns an Option from an Optional.""" assert Option.of(val) == exp @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Some(True)), (lambda x: x is True, False, Nothing()), (lambda x: x > 0, 1, Some(1)), (lambda x: x > 0, -2, Nothing()), ), ) def test_some_if(self, predicate: t.Callable, val: t.Any, exp: Option) -> None: """Test constructing based on some predicate.""" assert Option.some_if(predicate, val) == exp @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Nothing()), (lambda x: x is True, False, Some(False)), (lambda x: x > 0, 1, Nothing()), (lambda x: x > 0, -2, Some(-2)), ), ) def test_nothing_if(self, predicate: t.Callable, val: t.Any, exp: Option) -> None: """Test constructing based on some predicate.""" assert Option.nothing_if(predicate, val) == exp @pytest.mark.parametrize( "options, exp", ( ([Some(1), Some(2), Some(3)], Some((1, 2, 3))), ([Some(1), Nothing(), Some(3)], Nothing()), ([Nothing()], Nothing()), ([Some(1)], Some((1, ))), ((Some(1), Some(2), Some(3)), Some((1, 2, 3))), ), ) def test_collect(self, options: t.Iterable[Option], exp: Option) -> None: """Test constructing from an iterable of options.""" assert Option.collect(options) == exp
def test_raise_if_err(self) -> None: """This method is deprecated.""" with pytest.deprecated_call(): assert Some("hello").raise_if_err("error") == "hello" with pytest.deprecated_call(): with pytest.raises(RuntimeError): Nothing().raise_if_err("error")
def __init__(self, sql: Sql, dbname: str): self.sql = sql self.conn = psycopg3.connect(dbname=dbname) self.cursor = self.conn.cursor() if sql.select_sql != '': self.cursor.execute(sql.select_sql) self.select_result = Some(self.cursor.fetchall()) else: self.select_result = Nothing()
class TestOptionConstructors: """Test option constructors.""" @pytest.mark.parametrize( "val, exp", ( (5, Some(5)), (None, Nothing()), ("", Some("")), (False, Some(False)), ({}, Some({})), ([], Some([])), ), ) def test_of(self, val: t.Any, exp: Option) -> None: """Option.of() returns an Option from an Optional.""" assert Option.of(val) == exp @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Some(True)), (lambda x: x is True, False, Nothing()), (lambda x: x > 0, 1, Some(1)), (lambda x: x > 0, -2, Nothing()), ), ) def test_ok_if(self, predicate: t.Callable, val: t.Any, exp: Option) -> None: """Test constructing based on some predicate.""" assert Option.some_if(predicate, val) == exp @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Nothing()), (lambda x: x is True, False, Some(False)), (lambda x: x > 0, 1, Nothing()), (lambda x: x > 0, -2, Some(-2)), ), ) def test_err_if(self, predicate: t.Callable, val: t.Any, exp: Option) -> None: """Test constructing based on some predicate.""" assert Option.nothing_if(predicate, val) == exp
class TestImplementationDetails: """Some implementation details need to be tested.""" def test_nothing_singleton(self) -> None: """Ensure Nothing() is a singleton.""" assert Nothing() is Nothing() is Nothing() @pytest.mark.parametrize("obj", (Some(1), Nothing(), Ok(1), Err(1))) def test_all_slotted(self, obj: t.Any) -> None: """All implementations use __slots__.""" assert not hasattr(obj, "__dict__")
def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None: """Can specify exception msg/cls if value is not Some().""" exp_exc = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Nothing().expect(msg, **kwargs) assert msg in str(exc_info.value)
def run(folder: Path, options: RunOptions, limit: Option[int], other_args): sql = Sql.load_files(folder) sys.path.append(str(folder)) import script #type: ignore log_path = folder.joinpath('errors.log') if options != RunOptions.NODB: if script.DBNAME != '': transform(script, Some(script.DBNAME), sql, options == RunOptions.FULL, limit, log_path, other_args) else: print( 'DBNAME not specified. Run with --nodb or fill DBNAME in script/__init__.py' ) else: transform(script, Nothing(), sql, False, limit, log_path, other_args)
async def async_transform(script, dbname: Option[str], sql: Sql, export: bool, limit: Option[int], log_path: Path, other_args): with log_path.open('w') as log_file: with MaybeDb(dbname.map(lambda name: Db(sql, name))) as maybe_db: input_rows = maybe_db.select_result().unwrap_or([]) input_rows = clamp_to_limit(input_rows, limit) with MaybeExport( (maybe_db.export() if export else Nothing())) as maybe_export: sender, receiver = trio.open_memory_channel(0) async with trio.open_nursery() as nurs: nurs.start_soon(run_script, script, input_rows, sender, other_args) async with receiver: async for output_result in receiver: handle_result(output_result, maybe_export, log_file) if export: maybe_db.commit()
class TestOption: """Test the option type.""" @pytest.mark.parametrize( "left, right, exp", ( (Some(2), Nothing(), Nothing()), (Nothing(), Some(2), Nothing()), (Some(1), Some(2), Some(2)), (Nothing(), Nothing(), Nothing()), ), ) def test_and(self, left: Option[int], right: Option[int], exp: Option[int]) -> None: """Returns Some() if both options are Some().""" assert left.and_(right) == exp @pytest.mark.parametrize( "left, right, exp", ( (Some(2), Nothing(), Some(2)), (Nothing(), Some(2), Some(2)), (Some(1), Some(2), Some(1)), (Nothing(), Nothing(), Nothing()), ), ) def test_or(self, left: Option[int], right: Option[int], exp: Option[int]) -> None: """Returns Some() if either or both is Some().""" assert left.or_(right) == exp @pytest.mark.parametrize( "left, right, exp", ( (Some(2), Nothing(), Some(2)), (Nothing(), Some(2), Some(2)), (Some(1), Some(2), Nothing()), (Nothing(), Nothing(), Nothing()), ), ) def test_xor(self, left: Option[int], right: Option[int], exp: Option[int]) -> None: """Returns Some() IFF only one option is Some().""" assert left.xor(right) == exp @pytest.mark.parametrize( "start, first, second, exp", ( (Some(2), _sq, _sq, Some(16)), (Some(2), _sq, _nothing, Nothing()), (Some(2), _nothing, _sq, Nothing()), (Nothing(), _sq, _sq, Nothing()), ), ) def test_and_then( self, start: Option[int], first: t.Callable[[int], Option[int]], second: t.Callable[[int], Option[int]], exp: Option[int], ) -> None: """Chains option-generating functions if results are `Some`.""" assert start.and_then(first).and_then(second) == exp @pytest.mark.parametrize( "start, fn, exp", ( (Some("one"), lambda: Some("one else"), Some("one")), (Nothing(), lambda: Some("one else"), Some("one else")), (Nothing(), lambda: Nothing(), Nothing()), ), ) def test_or_else( self, start: Option[str], fn: t.Callable[[], Option[str]], exp: Option[str], ) -> None: """Chains option-generating functions if results are `None`.""" assert start.or_else(fn) == exp @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None: """Can specify exception msg/cls if value is not Some().""" exp_exc = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Nothing().expect(msg, **kwargs) assert msg in str(exc_info.value) def test_expect_not_raising(self) -> None: """Expecting on a Some() returns the value.""" assert Some("hello").expect("not what I expected") == "hello" @pytest.mark.parametrize( "start, exp", ((Nothing(), Nothing()), (Some(3), Nothing()), (Some(4), Some(4))), ) def test_filter(self, start: Option[int], exp: Option[int]) -> None: """A satisfied predicate returns `Some()`, otherwise `None()`.""" def is_even(val: int) -> bool: return val % 2 == 0 assert start.filter(is_even) == exp @pytest.mark.parametrize("opt, exp", ((Nothing(), True), (Some(1), False))) def test_is_nothing(self, opt: Option[int], exp: bool) -> None: """"Nothings() are nothing, Some()s are not.""" assert opt.is_nothing() is exp @pytest.mark.parametrize("opt, exp", ((Nothing(), False), (Some(1), True))) def test_is_some(self, opt: Option[int], exp: bool) -> None: """"Nothings() are nothing, Some()s are not.""" assert opt.is_some() is exp @pytest.mark.parametrize("opt, exp", ((Nothing(), ()), (Some(5), (5, )))) def test_iter(self, opt: Option[int], exp: t.Tuple[int, ...]) -> None: """Iterating on a Some() yields the Some(); on a None() nothing.""" assert tuple(opt.iter()) == tuple(iter(opt)) == exp @pytest.mark.parametrize("opt, exp", ((Some("hello"), Some(5)), (Nothing(), Nothing()))) def test_map(self, opt: Option[str], exp: Option[int]) -> None: """Maps fn() onto `Some()` to make a new option, or ignores None().""" assert opt.map(len) == exp @pytest.mark.parametrize("opt, exp", ((Some("hello"), 5), (Nothing(), -1))) def test_map_or(self, opt: Option[str], exp: Option[int]) -> None: """Maps fn() onto `Some()` & return the value, or return a default.""" assert opt.map_or(-1, lambda s: len(s)) == exp @pytest.mark.parametrize("opt, exp", ((Some("hello"), 5), (Nothing(), -1))) def test_map_or_else(self, opt: Option[str], exp: Option[int]) -> None: """Maps fn() onto `Some()` & return the value, or return a default.""" assert opt.map_or_else(lambda: -1, lambda s: len(s)) == exp @pytest.mark.parametrize("opt, exp", ((Some(2), Ok(2)), (Nothing(), Err("oh no")))) def test_ok_or(self, opt: Option[str], exp: Result[str, int]) -> None: """Map `Some(t)` to `Ok(t)`, or `Nothing()` to an `Err()`.""" assert opt.ok_or("oh no") == exp @pytest.mark.parametrize("opt, exp", ((Some(2), Ok(2)), (Nothing(), Err("oh no")))) def test_ok_or_else(self, opt: Option[str], exp: Result[str, int]) -> None: """Map `Some(t)` to `Ok(t)`, or `Nothing()` to an `Err()`.""" assert opt.ok_or_else(lambda: "oh no") == exp def test_unwrap_raising(self) -> None: """Unwraping a Nothing() raises an error.""" with pytest.raises(RuntimeError): Nothing().unwrap() def test_unwrap_success(self) -> None: """Unwrapping a Some() returns the wrapped value.""" assert Some("thing").unwrap() == "thing" @pytest.mark.parametrize("opt, exp", ((Some(2), 2), (Nothing(), 42))) def test_unwrap_or(self, opt: Option[int], exp: int) -> None: """Unwraps a `Some()` or returns a default.""" assert opt.unwrap_or(42) == exp @pytest.mark.parametrize("opt, exp", ((Some(2), 2), (Nothing(), 42))) def test_unwrap_or_else(self, opt: Option[int], exp: int) -> None: """Unwraps a `Some()` or returns a default.""" assert opt.unwrap_or_else(lambda: 42) == exp @pytest.mark.parametrize( "inst, other, eq", ( (Some(1), Some(1), True), (Some(1), Some(2), False), (Some(1), Nothing(), False), (Some(1), 1, False), (Nothing(), Nothing(), True), (Nothing(), Some(1), False), (Nothing(), None, False), ), ) def test_equality_inequality(self, inst: t.Any, other: t.Any, eq: bool) -> None: """Test equality and inequality of results.""" assert (inst == other) is eq assert (inst != other) is not eq def test_stringify(self) -> None: """Repr and str representations are equivalent.""" assert repr(Some(1)) == str(Some(1)) == "Some(1)" assert repr(Nothing()) == str(Nothing()) == "Nothing()"
def test_stringify(self) -> None: """Repr and str representations are equivalent.""" assert repr(Some(1)) == str(Some(1)) == "Some(1)" assert repr(Nothing()) == str(Nothing()) == "Nothing()"
def test_unwrap_raising(self) -> None: """Unwraping a Nothing() raises an error.""" with pytest.raises(RuntimeError): Nothing().unwrap()
class TestResultConstructors: """Test Result constructors.""" @pytest.mark.parametrize( "fn, exp", ( (lambda: 5, Ok(5)), (lambda: _raises(TypeError), Err(TypeError)), (Nothing, Ok(Nothing())), ), ) def test_of(self, fn: t.Callable, exp: Result) -> None: """Test getting a result from a callable.""" if exp.is_err(): assert isinstance(Result.of(fn).unwrap_err(), exp.unwrap_err()) else: assert Result.of(fn) == exp def test_of_with_args(self) -> None: """Test getting a result from a callable with args.""" assert Result.of(lambda x: bool(x > 0), 1).unwrap() is True def test_of_with_kwargs(self) -> None: """Test getting a result from a callable with args.""" def foo(a: int, b: str = None) -> t.Optional[str]: return b assert Result.of(foo, 1, b="a").unwrap() == "a" @pytest.mark.parametrize( "iterable, exp", ( ((Ok(1), Ok(2), Ok(3)), Ok((1, 2, 3))), ((Ok(1), Err("no"), Ok(3)), Err("no")), (iter([Ok(1), Ok(2)]), Ok((1, 2))), ([Err("no")], Err("no")), ([Ok(1)], Ok((1, ))), ([], Ok(())), ), ) def test_collect(self, iterable: t.Iterable[Result[int, str]], exp: Result[int, str]) -> None: """Test collecting an iterable of results into a single result.""" assert Result.collect(iterable) == exp def test_collect_short_circuits(self) -> None: """Ensure collect does not iterate after an err is reached.""" until_err: t.List[Result[int, str]] = [Ok(1), Ok(2), Err("no")] def _iterable() -> t.Iterable[Result[int, str]]: yield from until_err # If we continue iterating after the err, we will raise a # runtime Error. assert False, "Result.collect() did not short circuit on err!" assert Result.collect(_iterable()) == Err("no") @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Ok(True)), (lambda x: x is True, False, Err(False)), (lambda x: x > 0, 1, Ok(1)), (lambda x: x > 0, -2, Err(-2)), ), ) def test_ok_if(self, predicate: t.Callable, val: t.Any, exp: Result) -> None: """Test constructing based on some predicate.""" assert Result.ok_if(predicate, val) == exp @pytest.mark.parametrize( "predicate, val, exp", ( (lambda x: x is True, True, Err(True)), (lambda x: x is True, False, Ok(False)), (lambda x: x > 0, 1, Err(1)), (lambda x: x > 0, -2, Ok(-2)), ), ) def test_err_if(self, predicate: t.Callable, val: t.Any, exp: Result) -> None: """Test constructing based on some predicate.""" assert Result.err_if(predicate, val) == exp
class TestResult: """Test the result type.""" @pytest.mark.parametrize( "left, right, exp", ( (Ok(2), Err("late error"), Err("late error")), (Err("early error"), Ok(2), Err("early error")), (Err("early error"), Err("late error"), Err("early error")), (Ok(2), Ok(3), Ok(3)), ), ) def test_and( self, left: Result[int, str], right: Result[int, str], exp: Result[int, str], ) -> None: """Test that `and` returns an alternative for `Ok` values.""" assert left.and_(right) == exp def test_and_multichain(self) -> None: """.and() calls can be chained indefinitely.""" assert Ok(2).and_(Ok(3)).and_(Ok(4)).and_(Ok(5)) == Ok(5) @pytest.mark.parametrize( "start, first, second, exp", ( (Ok(2), _sq, _sq, Ok(16)), (Ok(2), _sq, _err, Err(4)), (Ok(2), _err, _sq, Err(2)), (Ok(2), _err, _err, Err(2)), (Err(3), _sq, _sq, Err(3)), ), ) def test_and_then( self, start: Result[int, int], first: t.Callable[[int], Result[int, int]], second: t.Callable[[int], Result[int, int]], exp: Result[int, int], ) -> None: """Test that and_then chains result-generating functions.""" assert start.and_then(first).and_then(second) == exp def test_flatmap(self) -> None: """Flatmap is an alias for and_then""" ok: Result[int, int] = Ok(2) err: Result[int, int] = Err(2) assert ok.flatmap(_sq) == Ok(4) assert err.flatmap(_sq) == Err(2) @pytest.mark.parametrize( "start, first, second, exp", ( (Ok(2), _sq, _sq, Ok(2)), (Ok(2), _err, _sq, Ok(2)), (Err(3), _sq, _err, Ok(9)), (Err(3), _err, _err, Err(3)), ), ) def test_or_else( self, start: Result[int, int], first: t.Callable[[int], Result[int, int]], second: t.Callable[[int], Result[int, int]], exp: Result[int, int], ) -> None: """Test that and_then chains result-generating functions.""" assert start.or_else(first).or_else(second) == exp @pytest.mark.parametrize("start, exp", ((Ok(2), Nothing()), (Err("err"), Some("err")))) def test_err(self, start: Result[int, str], exp: Option[str]) -> None: """Test converting a result to an option.""" assert start.err() == exp @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None: """Test expecting a value to be Ok().""" exp_exc = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Err(2).expect(msg, **kwargs) assert msg in str(exc_info.value) def test_expect_ok(self) -> None: """Expecting an Ok() value returns the value.""" assert Ok(2).expect("err") == 2 @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_expect_err_raising(self, exc_cls: t.Type[Exception]) -> None: """Test expecting a value to be Ok().""" exp_exc = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Ok(2).expect_err(msg, **kwargs) assert msg in str(exc_info.value) def test_expect_err_err(self) -> None: """Expecting an Ok() value returns the value.""" assert Err(2).expect_err("err") == 2 def test_is_err(self) -> None: """Err() returns true for is_err().""" assert Err(1).is_err() assert not Ok(1).is_err() def test_is_ok(self) -> None: """Ok() returns true for is_ok().""" assert Ok(1).is_ok() assert not Err(1).is_ok() @pytest.mark.parametrize("start, exp", ((Ok(1), (1, )), (Err(1), ()))) def test_iter(self, start: Result[int, int], exp: t.Tuple[int, ...]) -> None: """iter() returns a 1-member iterator on Ok(), 0-member for Err().""" assert tuple(start.iter()) == exp @pytest.mark.parametrize("start, exp", ((Ok(2), Ok(4)), (Err("foo"), Err("foo")))) def test_map(self, start: Result[int, str], exp: Result[int, str]) -> None: """.map() will map onto Ok() and ignore Err().""" assert start.map(lambda x: int(x**2)) == exp @pytest.mark.parametrize("start, exp", ((Ok("foo"), Ok("foo")), (Err(2), Err("2")))) def test_map_err(self, start: Result[str, int], exp: Result[str, str]) -> None: """.map_err() will map onto Err() and ignore Ok().""" assert start.map_err(str) == exp @pytest.mark.parametrize("start, exp", ((Ok(1), Some(1)), (Err(1), Nothing()))) def test_ok(self, start: Result[int, int], exp: Option[int]) -> None: """.ok() converts a result to an Option.""" assert start.ok() == exp @pytest.mark.parametrize( "left, right, exp", ( (Ok(5), Ok(6), Ok(5)), (Ok(5), Err(6), Ok(5)), (Err(5), Ok(6), Ok(6)), (Err(5), Err(6), Err(6)), ), ) def test_or( self, left: Result[int, str], right: Result[int, str], exp: Result[int, str], ) -> None: """.or_() returns the first available non-err value.""" assert left.or_(right) == exp def test_or_multichain(self) -> None: """.or_() calls can be chained indefinitely.""" err: Result[int, int] = Err(5) assert err.or_(Err(6)).or_(Err(7)).or_(Ok(8)) == Ok(8) def test_unwrap_is_ok(self) -> None: """.unwrap() returns an ok() value.""" assert Ok(5).unwrap() == 5 def test_unwrap_is_err(self) -> None: """.unwrap() raises for an error value.""" with pytest.raises(RuntimeError): Err(5).unwrap() def test_unwrap_err_is_ok(self) -> None: """.unwrap_err() raises for an ok value.""" with pytest.raises(RuntimeError): Ok(5).unwrap_err() def test_unwrap_err_is_err(self) -> None: """.unwrap_err() returns an error value.""" assert Err(5).unwrap_err() == 5 @pytest.mark.parametrize("start, alt, exp", ((Ok(5), 6, 5), (Err(5), 6, 6))) def test_unwrap_or(self, start: Result[int, int], alt: int, exp: int) -> None: """.unwrap_or() provides the default if the result is Err().""" assert start.unwrap_or(alt) == exp @pytest.mark.parametrize( "start, fn, exp", ((Ok(5), lambda i: i + 2, 5), (Err(5), lambda i: i + 2, 7)), ) def test_unwrap_or_else(self, start: Result[int, int], fn: t.Callable[[int], int], exp: int) -> None: """Calculates a result from Err() value if present.""" assert start.unwrap_or_else(fn) == exp @pytest.mark.parametrize( "inst, other, eq", ( (Ok(1), Ok(1), True), (Ok(1), Ok(2), False), (Ok(1), Err(1), False), (Ok(1), 1, False), (Err(1), Err(1), True), (Err(1), Err(2), False), (Err(1), Ok(1), False), (Err(1), 1, False), ), ) def test_equality_inequality(self, inst: t.Any, other: t.Any, eq: bool) -> None: """Test equality and inequality of results.""" assert (inst == other) is eq assert (inst != other) is not eq def test_stringify(self) -> None: """Repr and str representations are equivalent.""" assert repr(Ok(1)) == str(Ok(1)) == "Ok(1)" assert repr(Err(1)) == str(Err(1)) == "Err(1)"
def get(self, key: str) -> Option[t.Any]: """Return a value from the store.""" if key in self._values: return Some(self._values[key]) return Nothing()
def test_nothing_singleton(self) -> None: """Ensure Nothing() is a singleton.""" assert Nothing() is Nothing() is Nothing()
def _nothing(_: int) -> Option[int]: """Just return nothing.""" return Nothing()
class TestResult: """Test the result type.""" @pytest.mark.parametrize( "left, right, exp", ( (Ok(2), Err("late error"), Err("late error")), (Err("early error"), Ok(2), Err("early error")), (Err("early error"), Err("late error"), Err("early error")), (Ok(2), Ok(3), Ok(3)), ), ) def test_and( self, left: Result[int, str], right: Result[int, str], exp: Result[int, str], ) -> None: """Test that `and` returns an alternative for `Ok` values.""" assert left.and_(right) == exp def test_and_multichain(self) -> None: """.and() calls can be chained indefinitely.""" assert Ok(2).and_(Ok(3)).and_(Ok(4)).and_(Ok(5)) == Ok(5) @pytest.mark.parametrize( "start, first, second, exp", ( (Ok(2), _sq, _sq, Ok(16)), (Ok(2), _sq, _err, Err(4)), (Ok(2), _err, _sq, Err(2)), (Ok(2), _err, _err, Err(2)), (Err(3), _sq, _sq, Err(3)), ), ) def test_and_then( self, start: Result[int, int], first: t.Callable[[int], Result[int, int]], second: t.Callable[[int], Result[int, int]], exp: Result[int, int], ) -> None: """Test that and_then chains result-generating functions.""" assert start.and_then(first).and_then(second) == exp def test_and_then_covariance(self) -> None: """Covariant errors are acceptable for flatmapping.""" class MyInt(int): """A subclass of int.""" def process_int(i: int) -> Result[MyInt, RuntimeError]: return Err(RuntimeError(f"it broke {i}")) start: Result[int, Exception] = Ok(5) # We can flatmap w/a function that takes any covariant type of # int or Exception. The result remains the original exception type, # since we cannot guarantee narrowing to the covariant type. flatmapped: Result[int, Exception] = start.and_then(process_int) assert flatmapped def test_flatmap(self) -> None: """Flatmap is an alias for and_then""" ok: Result[int, int] = Ok(2) err: Result[int, int] = Err(2) assert ok.flatmap(_sq) == Ok(4) assert err.flatmap(_sq) == Err(2) @pytest.mark.parametrize( "start, first, second, exp", ( (Ok(2), _sq, _sq, Ok(2)), (Ok(2), _err, _sq, Ok(2)), (Err(3), _sq, _err, Ok(9)), (Err(3), _err, _err, Err(3)), ), ) def test_or_else( self, start: Result[int, int], first: t.Callable[[int], Result[int, int]], second: t.Callable[[int], Result[int, int]], exp: Result[int, int], ) -> None: """Test that and_then chains result-generating functions.""" assert start.or_else(first).or_else(second) == exp @pytest.mark.parametrize("start, exp", ((Ok(2), Nothing()), (Err("err"), Some("err")))) def test_err(self, start: Result[int, str], exp: Option[str]) -> None: """Test converting a result to an option.""" assert start.err() == exp @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None: """Test expecting a value to be Ok().""" exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} input_val = 2 msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Err(input_val).expect(msg, **kwargs) assert msg in str(exc_info.value) assert str(input_val) in str(exc_info.value) @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_raise_if_err_raising(self, exc_cls: t.Type[Exception]) -> None: """Test raise_if_err for Err() values.""" exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} input_val = 2 msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Err(input_val).raise_if_err(msg, **kwargs) assert msg in str(exc_info.value) assert str(input_val) in str(exc_info.value) def test_expect_ok(self) -> None: """Expecting an Ok() value returns the value.""" assert Ok(2).expect("err") == 2 def test_raise_if_err_ok(self) -> None: """Raise_if_err returns the value when given an Ok() value.""" assert Ok(2).raise_if_err("err") == 2 @pytest.mark.parametrize("exc_cls", (None, IOError)) def test_expect_err_raising(self, exc_cls: t.Type[Exception]) -> None: """Test expecting a value to be Ok().""" exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError kwargs = {"exc_cls": exc_cls} if exc_cls else {} msg = "not what I expected" with pytest.raises(exp_exc) as exc_info: Ok(2).expect_err(msg, **kwargs) assert msg in str(exc_info.value) def test_expect_err_err(self) -> None: """Expecting an Ok() value returns the value.""" assert Err(2).expect_err("err") == 2 def test_is_err(self) -> None: """Err() returns true for is_err().""" assert Err(1).is_err() assert not Ok(1).is_err() def test_is_ok(self) -> None: """Ok() returns true for is_ok().""" assert Ok(1).is_ok() assert not Err(1).is_ok() @pytest.mark.parametrize("start, exp", ((Ok(1), (1, )), (Err(1), ()))) def test_iter(self, start: Result[int, int], exp: t.Tuple[int, ...]) -> None: """iter() returns a 1-member iterator on Ok(), 0-member for Err().""" assert tuple(start.iter()) == exp @pytest.mark.parametrize("start, exp", ((Ok(2), Ok(4)), (Err("foo"), Err("foo")))) def test_map(self, start: Result[int, str], exp: Result[int, str]) -> None: """.map() will map onto Ok() and ignore Err().""" assert start.map(lambda x: int(x**2)) == exp def test_map_covariance(self) -> None: """The input type to the map fn is covariant.""" class MyStr(str): """Subclass of str.""" def to_mystr(string: str) -> MyStr: return MyStr(string) if not isinstance(string, MyStr) else string start: Result[str, str] = Ok("foo") # We can assign the result to [str, str] even though we know it's # actually a MyStr, since MyStr is covariant with str end: Result[str, str] = start.map(to_mystr) assert end == Ok(MyStr("foo")) @pytest.mark.parametrize("start, exp", ((Ok("foo"), Ok("foo")), (Err(2), Err("2")))) def test_map_err(self, start: Result[str, int], exp: Result[str, str]) -> None: """.map_err() will map onto Err() and ignore Ok().""" assert start.map_err(str) == exp @pytest.mark.parametrize("start, exp", ((Ok(1), Some(1)), (Err(1), Nothing()))) def test_ok(self, start: Result[int, int], exp: Option[int]) -> None: """.ok() converts a result to an Option.""" assert start.ok() == exp @pytest.mark.parametrize( "left, right, exp", ( (Ok(5), Ok(6), Ok(5)), (Ok(5), Err(6), Ok(5)), (Err(5), Ok(6), Ok(6)), (Err(5), Err(6), Err(6)), ), ) def test_or( self, left: Result[int, str], right: Result[int, str], exp: Result[int, str], ) -> None: """.or_() returns the first available non-err value.""" assert left.or_(right) == exp def test_or_multichain(self) -> None: """.or_() calls can be chained indefinitely.""" err: Result[int, int] = Err(5) assert err.or_(Err(6)).or_(Err(7)).or_(Ok(8)) == Ok(8) def test_unwrap_is_ok(self) -> None: """.unwrap() returns an ok() value.""" assert Ok(5).unwrap() == 5 def test_unwrap_is_err(self) -> None: """.unwrap() raises for an error value.""" with pytest.raises(RuntimeError): Err(5).unwrap() def test_unwrap_err_is_ok(self) -> None: """.unwrap_err() raises for an ok value.""" with pytest.raises(RuntimeError): Ok(5).unwrap_err() def test_unwrap_err_is_err(self) -> None: """.unwrap_err() returns an error value.""" assert Err(5).unwrap_err() == 5 @pytest.mark.parametrize("start, alt, exp", ((Ok(5), 6, 5), (Err(5), 6, 6))) def test_unwrap_or(self, start: Result[int, int], alt: int, exp: int) -> None: """.unwrap_or() provides the default if the result is Err().""" assert start.unwrap_or(alt) == exp @pytest.mark.parametrize( "start, fn, exp", ((Ok(5), lambda i: i + 2, 5), (Err(5), lambda i: i + 2, 7)), ) def test_unwrap_or_else(self, start: Result[int, int], fn: t.Callable[[int], int], exp: int) -> None: """Calculates a result from Err() value if present.""" assert start.unwrap_or_else(fn) == exp @pytest.mark.parametrize( "inst, other, eq", ( (Ok(1), Ok(1), True), (Ok(1), Ok(2), False), (Ok(1), Err(1), False), (Ok(1), 1, False), (Err(1), Err(1), True), (Err(1), Err(2), False), (Err(1), Ok(1), False), (Err(1), 1, False), ), ) def test_equality_inequality(self, inst: t.Any, other: t.Any, eq: bool) -> None: """Test equality and inequality of results.""" assert (inst == other) is eq assert (inst != other) is not eq def test_stringify(self) -> None: """Repr and str representations are equivalent.""" assert repr(Ok(1)) == str(Ok(1)) == "Ok(1)" assert repr(Err(1)) == str(Err(1)) == "Err(1)"