Coverage for src / ocarina / railway / result.py: 88.24%

17 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-06-04 16:22 +0200

1"""Result type for Railway Oriented Programming. 

2 

3Represents computations that can succeed (Ok) or fail (Fail), making error 

4handling explicit in the type system. 

5 

6Example: 

7 >>> def divide(a: int, b: int) -> Result[float]: 

8 ... if b == 0: 

9 ... return Fail(error=Exception("Division by zero")) 

10 ... return Ok(value=a / b) 

11 ... 

12 >>> result = divide(10, 2) 

13 >>> if is_ok(result): 

14 ... print(f"Result: {result.value}") 

15 >>> else: 

16 ... print(f"Error: {result.error}") 

17 

18""" 

19 

20from dataclasses import dataclass, field 

21from typing import TypeGuard, final 

22 

23 

24class _BaseResult: 

25 """Base class ensuring both Ok and Fail have error attribute for type narrowing.""" 

26 

27 error: Exception | None 

28 

29 

30@final 

31@dataclass(frozen=True) 

32class Ok[T](_BaseResult): 

33 """Success result containing a value. 

34 

35 Attributes: 

36 value: The successful result value of type T. 

37 error: Always None for Ok results. 

38 

39 """ 

40 

41 value: T 

42 error: None = None 

43 

44 

45@final 

46@dataclass(frozen=True) 

47class Fail(_BaseResult): 

48 """Failure result containing an error. 

49 

50 Attributes: 

51 error: The exception that caused the failure. 

52 

53 """ 

54 

55 error: Exception = field(default_factory=lambda: Exception("Unknown error")) 

56 

57 

58type Result[T] = Ok[T] | Fail 

59"""Discriminated union representing success (Ok[T]) or failure (Fail). 

60 

61Forces explicit error handling via type checker. 

62""" 

63 

64 

65def is_ok[T](result: Result[T]) -> TypeGuard[Ok[T]]: 

66 """Check if result is Ok, narrowing type to Ok[T].""" 

67 return isinstance(result, Ok) 

68 

69 

70def is_fail[T](result: Result[T]) -> TypeGuard[Fail]: 

71 """Check if result is Fail, narrowing type to Fail.""" 

72 return isinstance(result, Fail)