def ask(self, prompt: str, default: Literal['y', 'n'], do: ZIOMonad[System, NoReturn]) -> ZIO[System, NoReturn, bool]: default_str = 'Y/n' if default == 'y' else 'y/N' choice = do << self.get_input_from_console( prompt=f"{prompt} [{default_str}]: ", parse_value=(ZIO.from_callable(str).map(str.lower).require( lambda s: s in {'y', 'n'}, lambda s: s).either().to_callable()), default_value=default) return ZIO.succeed(choice == 'y')
def prog( do: ZIOMonad[ConsoleSystemEnvironment, NoReturn] ) -> ZIO[ConsoleSystemEnvironment, NoReturn, int]: age = do << console.get_input_from_console( prompt="How old are you?\n", parse_value=ZIO.from_callable(str).map(int).catch( ValueError).either().to_callable(), default_value=21) do << console.print(f"You are {age} years old.") return ZIO.succeed(age)
def input( self, prompt: Optional[str] = None ) -> ZIO[object, Union[EOFError, KeyboardInterrupt], str]: user_input = self._user_input.pop(0) self._effects.append(console_effect.Input(prompt, user_input)) if isinstance(user_input, str): return ZIO.succeed(user_input) else: result: Union[EOFError, KeyboardInterrupt] = user_input return ZIO.fail(result)
def test_zio_flatten_1() -> None: count = 0 def _impure_function(x: int) -> ZIO[object, NoReturn, int]: nonlocal count count += 1 return ZIO.succeed(x + 1) assert (ZIO.succeed(100).flat_map(_impure_function).flat_map( _impure_function).flat_map(_impure_function).map( lambda x: ZIO.succeed(f"The result is: {x}")).flatten()._run( ())) == Right("The result is: 103") assert count == 3
def test_from_callable() -> None: def _f(arg_1: int, arg_2: str, *, arg_3: float) -> str: return f"{arg_1} {arg_2} {arg_3}" program = ZIO.from_callable(_f) assert unsafe_run(program.provide(FunctionArguments( 42, "foo", arg_3=3.14))) == "42 foo 3.14"
def test_zio_flatten_2() -> None: count = 0 def _impure_function(x: int) -> ZIO[object, NoReturn, int]: nonlocal count count += 1 return ZIO.succeed(x + 1) def _kaboom(x: int) -> ZIO[object, NoReturn, int]: raise Bippy assert (ZIO.succeed(100).flat_map(_impure_function).flat_map( _kaboom).flat_map(_impure_function).flat_map(_impure_function).map( lambda x: ZIO.succeed(f"The result is: {x}")).catch( Bippy).flatten()._run(())) == Left(Bippy()) assert count == 1
def test_zio_catch_3() -> None: def _impure_function() -> int: raise Bippy program = ZIO.effect_total(_impure_function).catch(NotBippy) with pytest.raises(Bippy): program._run(())
def test_zio_effect_catch_3() -> None: def _impure_function() -> None: raise Bippy program = ZIO.effect_catch(_impure_function, NotBippy) with pytest.raises(Bippy): program._run(())
def test_get_input_from_console_eof_error() -> None: program = console.get_input_from_console( prompt="How much wood would a woodchuck chuck?", parse_value=ZIO.from_callable(str).map(int).catch( ValueError).either().to_callable(), default_value=None) exception = EOFError() mock_console = MockConsole(["bad", "input", exception]) mock_system = MockSystem() with pytest.raises(SystemExit): unsafe_run( program.provide( ConsoleSystemEnvironment(console=mock_console, system=mock_system))) assert mock_console.effects == [ console_effect.Input("How much wood would a woodchuck chuck?", "bad"), console_effect.Input("How much wood would a woodchuck chuck?", "input"), console_effect.Input("How much wood would a woodchuck chuck?", exception), console_effect.Print("") ] assert mock_console.user_input == [] assert mock_system.effects == [system_effect.Exit(None)]
def input( self, prompt: Optional[str] = None ) -> ZIO[object, Union[EOFError, KeyboardInterrupt], str]: return (ZIO.effect_catch( lambda: builtins.input(prompt) if prompt is not None else builtins.input(), EOFError).catch(KeyboardInterrupt))
def program( do: ZIOMonad[Console, Union[EOFError, KeyboardInterrupt]] ) -> ZIO[Console, Union[EOFError, KeyboardInterrupt], str]: con = do << Environment() do << con.print("Hello, what is your name?") name = do << con.input() do << con.print(f"Your name is: {name}") x = do << ZIO.succeed(1) while x < 20: x = do << ( ZIO.succeed(x).map(lambda p: p + 1).flat_map(lambda q: ZIO.succeed( q - 1)).flat_map(lambda r: ZIO.succeed(r + 1))) do << con.print(f"The value of x is: {x}") return ZIO.succeed(f"Hello, {name}!")
def get_input_from_console( self, prompt: str, parse_value: Callable[[str], Either[E, A]], default_value: Optional[A], do: ZIOMonad[System, NoReturn]) -> ZIO[System, NoReturn, A]: while True: keyboard_input = do << ( self.input(prompt).either().map(lambda e: e.to_union())) if isinstance(keyboard_input, (EOFError, KeyboardInterrupt)): do << self.print("") system = do << Environment() return system.exit() if keyboard_input == '' and default_value is not None: return ZIO.succeed(default_value) parse_result = parse_value(keyboard_input) if isinstance(parse_result, Right): return ZIO.succeed(parse_result.value)
def test_zio_effect_catch_1() -> None: x: Optional[int] = None def _impure_function() -> int: nonlocal x x = 100 return 42 program = ZIO.effect_catch(_impure_function, Bippy) assert x is None assert program._run(()) == Right(42) assert x == 100
def test_zio_map_1() -> None: count = 0 def _impure_function(x: int) -> int: nonlocal count count += 1 return x + 1 assert (ZIO.succeed(100).map(_impure_function).map(_impure_function).map( _impure_function).map(lambda x: f"The result is: {x}")._run( ())) == Right("The result is: 103") assert count == 3
def test_from_to_callable_1() -> None: class Cat: def meow(self, *, volume: int) -> str: if volume < 10: return "meow" else: return "MEOW!" meow_to_callable = (Environment[Cat]().map(lambda cat: ZIO.from_callable( cat.meow)).swap_environments().to_callable()) assert unsafe_run(meow_to_callable(volume=11).provide(Cat())) == "MEOW!"
def test_zio_effect_success() -> None: x: Optional[int] = None def _impure_function() -> None: nonlocal x x = 42 program = ZIO.effect(_impure_function) assert x is None program._run(()) assert x == 42
def test_zio_map_error_1() -> None: count = 0 def _impure_function(x: int) -> int: nonlocal count count += 1 return x + 1 assert (ZIO.fail(100).map_error(_impure_function).map_error( _impure_function).map_error(_impure_function).map_error( lambda x: f"The result is: {x}")._run( ())) == Left("The result is: 103") assert count == 3
def test_zio_lshift_2() -> None: def _kaboom(x: object) -> Either[NoReturn, int]: raise Bippy assert ((ZIO.succeed(100) << ZIO.succeed(1) << ZIO.succeed(2) << ZIO(_kaboom) << ZIO.succeed(3)).flat_map(lambda x: ZIO.succeed( f"The result is: {x}")).catch(Bippy))._run( ()) == Left(Bippy())
def test_from_to_callable_2() -> None: @dataclass(frozen=True) class SomeException(Exception): message: str class SomeAPI: def thing_that_may_fail(self, *, bippy: str) -> int: raise SomeException("Murphy's Law") safer_thing = (Environment[SomeAPI]().map( lambda api: (ZIO.from_callable(api.thing_that_may_fail).catch( SomeException).either())).swap_environments().to_callable()) assert unsafe_run(safer_thing(bippy="bippy").provide(SomeAPI())) == Left( SomeException("Murphy's Law"))
def test_zio_map_error_2() -> None: count = 0 def _impure_function(x: int) -> int: nonlocal count count += 1 return x + 1 def _kaboom(x: int) -> int: raise Bippy assert (ZIO.fail(100).map_error(_impure_function).map_error(_kaboom). map_error(_impure_function).map_error(_impure_function).map_error( lambda x: f"The result is: {x}").catch(Bippy)._run( ())) == Left(Bippy()) assert count == 1
def test_get_input_from_console_1() -> None: program = console.get_input_from_console( prompt="How much wood would a woodchuck chuck?", parse_value=ZIO.from_callable(str).map(int).catch( ValueError).either().to_callable(), default_value=None) mock_console = MockConsole(["42"]) mock_system = MockSystem() output = unsafe_run( program.provide( ConsoleSystemEnvironment(console=mock_console, system=mock_system))) assert output == 42 assert mock_console.effects == [ console_effect.Input("How much wood would a woodchuck chuck?", "42") ] assert mock_console.user_input == []
def exit( self, exit_code: Optional[int] = None ) -> ZIO[object, NoReturn, NoReturn]: self._effects.append(system_effect.Exit(exit_code)) return ZIO.fail(SystemExit(exit_code)).or_die()
def exit( self, exit_code: Optional[int] = None ) -> ZIO[object, NoReturn, NoReturn]: return ZIO.effect_total(lambda: sys.exit(exit_code)) # type: ignore
def print(self, line: str) -> ZIO[object, NoReturn, None]: self._effects.append(console_effect.Print(line)) return ZIO.succeed(None)
def test_zio_fail() -> None: assert ZIO.fail(42)._run(()) == Left(42)
def print(self, line: str) -> ZIO[object, NoReturn, None]: return ZIO.effect_total(lambda: builtins.print(line))
def test_to_callable() -> None: def _f(arg_1: int, arg_2: str, *, arg_3: float) -> str: return f"{arg_1} {arg_2} {arg_3}" g = ZIO.from_callable(_f).to_callable() assert g(42, "foo", arg_3=3.14) == "42 foo 3.14"
def test_zio_effect_failure() -> None: def _impure_function() -> None: raise Bippy program = ZIO.effect(_impure_function) assert program._run(()) == Left(Bippy())
def effects( self) -> List[Union[console_effect.Print, console_effect.Input]]: return self._effects @property def user_input(self) -> List[Union[EOFError, KeyboardInterrupt, str]]: return self._user_input class HasConsole(Protocol): @property def console(self) -> Console: pass # pragma: nocover print = (Environment[HasConsole]().map(lambda env: ZIO.from_callable( env.console.print).flatten()).swap_environments().to_callable()) input = (Environment[HasConsole]().map(lambda env: ZIO.from_callable( env.console.input).flatten()).swap_environments().to_callable()) class HasConsoleSystem(HasConsole, HasSystem, Protocol): pass def get_input_from_console( prompt: str, parse_value: Callable[[str], Either[E, A]], default_value: Optional[A]) -> ZIO[HasConsoleSystem, NoReturn, A]: return (Environment[HasConsoleSystem] ().flat_map(lambda env: env.console.get_input_from_console( prompt, parse_value, default_value).provide(env.system)))
def test_zio_effect_catch_2() -> None: def _impure_function() -> None: raise Bippy program = ZIO.effect_catch(_impure_function, Bippy) assert program._run(()) == Left(Bippy())