Coverage for src / pyfplib / option.py: 94%
56 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-04 23:48 +0300
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-04 23:48 +0300
1"""This module provides Result[T] type
2and two constructors of Option: Ok[T] and Err[T].
3"""
5from typing import Callable, Generic, Optional, TypeVar, cast
7from pyfplib.errors import ExpectedError, UnwrapError
9T = TypeVar("T")
10U = TypeVar("U")
13class Option(Generic[T]):
14 """
15 Rust-inspired Option<T> monad for Python.
17 Represents a value that is either Some(T) containing data,
18 or Nothing() representing absence of value.
20 Provides safe null-handling through method chaining
21 instead of explicit None checks.
22 """
24 # Support for structural pattern matching (Python 3.10+)
25 __match_args__ = ("value",)
27 def __init__(self, value: Optional[T]):
28 """
29 Internal constructor - use Some(value) or Nothing() instead.
31 Args:
32 value: The contained value or None for Nothing
33 """
34 self.__value = value
36 @property
37 def value(self) -> Optional[T]:
38 """Returns the raw contained value (use with caution - may be None)."""
39 return self.__value
41 def is_some(self) -> bool:
42 """Returns True if this Option contains a value (Some[T])."""
43 return self.__value is not None
45 def is_none(self) -> bool:
46 """Returns True if this Option contains no value (Nothing)."""
47 return self.__value is None
49 def if_some(self, fn: Callable[[T], None]) -> "Option[T]":
50 """
51 Executes callback if Some[T], otherwise does nothing.
52 Returns self for method chaining.
54 Args:
55 fn: callback to execute with contained value
56 """
57 if self.__value is not None:
58 fn(self.__value)
59 return self
61 def if_none(self, fn: Callable[[], None]) -> "Option[T]":
62 """
63 Executes callback if Nothing, otherwise does nothing.
64 Returns self for method chaining.
66 Args:
67 fn: callback to execute when no value present
68 """
69 if self.__value is None:
70 fn()
71 return self
73 def unwrap(self) -> T:
74 """
75 Extracts the contained value, raising UnwrapError if Nothing.
77 Raises:
78 UnwrapError: When attempting to unwrap Nothing
79 """
80 if self.__value is None:
81 msg = "called `Option.unwrap()` on a `Nothing` value"
82 raise UnwrapError(msg)
83 return self.__value
85 def expect(self, message: str) -> T:
86 """Returns contained value or raises ExpectedError"""
87 if self.__value is None:
88 raise ExpectedError(message)
89 return self.__value
91 def unwrap_or(self, value: T) -> T:
92 """Returns contained value or provided default if Nothing."""
93 return value if self.__value is None else self.__value
95 def map(self, fn: Callable[[T], U]) -> "Option[U]":
96 """
97 Maps contained value T -> U, returns Nothing() if no value.
98 Preserves Option structure for method chaining.
100 Args:
101 fn: a pure function mapping T to U
102 """
103 return Some(fn(self.__value)) if self.__value is not None else Nothing()
105 def map_from(self, fn: Callable[[T], "Option[U]"]) -> "Option[U]":
106 """
107 Maps contained value through a function returning Option[U].
108 FlatMaps Option[T] -> Option[U], returns Nothing() if no value.
110 Args:
111 fn: the function returning Option[U]
112 """
113 return fn(self.__value) if self.__value is not None else Nothing()
115 def __eq__(self, other: object) -> bool:
116 """Equality comparison based on contained values."""
117 ret: bool = False
118 if isinstance(other, Option): 118 ↛ 120line 118 didn't jump to line 120 because the condition on line 118 was always true
119 ret = self.__value == other.value
120 return ret
122 def __hash__(self):
123 return hash(self.__value)
125 def __bool__(self) -> bool:
126 """Boolean conversion - True if Some[T], False if Nothing."""
127 return self.is_some()
129 @staticmethod
130 def from_optional(value: Optional[T] = None) -> "Option[T]":
131 """Creates Option[T] from Optional[T]."""
132 return Nothing() if value is None else Some(cast(T, value))
135class Some(Option[T]):
136 """
137 Some extends Option[T].
138 It is used to create Option[T] containing a value
140 Usage:
141 Some(123)
142 Some("hello")
143 """
145 def __init__(self, value: T):
146 super().__init__(value)
149class Nothing(Option[T]):
150 """
151 Nothing extends Option[T].
152 It is used to create empty Option[T]
154 Usage:
155 Nothing() - no value contained.
156 """
158 def __init__(self):
159 super().__init__(None)