Coverage for src / lexigram / contracts / core / result.py: 5%
164 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-19 05:41 +0800
1"""Result type — concrete Ok/Err implementation.
3Canonical location for Result[T, E], Ok, and Err.
4All Lexigram packages import from here.
5"""
7from __future__ import annotations
9from collections.abc import Awaitable, Callable
10from typing import Any, Generic, TypeVar, cast
12T = TypeVar("T")
13E = TypeVar("E")
14U = TypeVar("U")
15F = TypeVar("F")
18class UnwrapError(Exception):
19 """Raised when unwrap() or unwrap_err() is called on the wrong variant.
21 Provides a clearer error type than the generic ``ValueError`` so callers
22 can catch it explicitly when needed.
23 """
25 _code: str = "LEX_ERR_RESULT_002"
27 def __init__(self, message: str, result: Any | None = None) -> None:
28 """Initialise the error.
30 Args:
31 message: Human-readable description of what was attempted.
32 result: The ``Result`` instance that caused the error (optional).
33 """
34 super().__init__(message)
35 self.result = result
38class Result(Generic[T, E]):
39 """Base Result type. Not abstract — Ok and Err are the only variants."""
41 __slots__ = ()
43 def is_ok(self) -> bool:
44 raise NotImplementedError
46 def is_err(self) -> bool:
47 raise NotImplementedError
49 def unwrap(self) -> T:
50 raise NotImplementedError
52 def unwrap_err(self) -> E:
53 raise NotImplementedError
55 def unwrap_or(self, default: T) -> T:
56 raise NotImplementedError
58 def unwrap_or_else(self, op: Callable[[E], T]) -> T:
59 raise NotImplementedError
61 def map_sync(self, op: Callable[[T], U]) -> Result[U, E]:
62 raise NotImplementedError
64 def map_err(self, op: Callable[[E], F]) -> Result[T, F]:
65 raise NotImplementedError
67 def and_then_sync(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
68 raise NotImplementedError
70 def or_else_sync(self, op: Callable[[E], Result[T, F]]) -> Result[T, F]:
71 raise NotImplementedError
73 def expect(self, message: str) -> T:
74 raise NotImplementedError
76 def match(self, ok: Callable[[T], U], err: Callable[[E], U]) -> U:
77 raise NotImplementedError
79 async def map(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
80 raise NotImplementedError
82 async def and_then(
83 self, op: Callable[[T], Awaitable[Result[U, E]]]
84 ) -> Result[U, E]:
85 raise NotImplementedError
87 async def or_else(self, op: Callable[[E], Awaitable[Result[T, F]]]) -> Result[T, F]:
88 raise NotImplementedError
90 def flatten(self) -> Result[Any, E]:
91 raise NotImplementedError
93 def filter(self, predicate: Callable[[T], bool], error: E) -> Result[T, E]:
94 raise NotImplementedError
96 def ok_or(self, default: U) -> T | U:
97 raise NotImplementedError
99 @classmethod
100 def from_exception(
101 cls,
102 exc: Exception,
103 ok_type: type[T] = type(None), # type: ignore[assignment]
104 ) -> Result[T, Exception]:
105 """Wrap a caught exception into an Err result."""
106 return Err(exc)
108 def to_optional(self) -> T | None:
109 return self.unwrap() if self.is_ok() else None
111 def inspect(self, op: Callable[[T], None]) -> Result[T, E]:
112 if self.is_ok():
113 op(self.unwrap())
114 return self
116 def inspect_err(self, op: Callable[[E], None]) -> Result[T, E]:
117 if self.is_err():
118 op(self.unwrap_err())
119 return self
122class Ok(Result[T, E]):
123 __slots__ = ("_value",)
124 __match_args__ = ("_value",)
126 def __init__(self, value: T) -> None:
127 self._value = value
129 def is_ok(self) -> bool:
130 return True
132 def is_err(self) -> bool:
133 return False
135 def unwrap(self) -> T:
136 return self._value
138 def unwrap_err(self) -> E:
139 raise UnwrapError(f"Called unwrap_err on Ok({self._value!r})")
141 def unwrap_or(self, default: T) -> T:
142 return self._value
144 def unwrap_or_else(self, op: Callable[[E], T]) -> T:
145 return self._value
147 def map_sync(self, op: Callable[[T], U]) -> Result[U, E]:
148 return Ok(op(self._value))
150 def map_err(self, op: Callable[[E], F]) -> Result[T, F]:
151 return cast("Result[T, F]", self)
153 def and_then_sync(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
154 return op(self._value)
156 def or_else_sync(self, op: Callable[[E], Result[T, F]]) -> Result[T, F]:
157 return cast("Result[T, F]", self)
159 def expect(self, message: str) -> T:
160 return self._value
162 def match(self, ok: Callable[[T], U], err: Callable[[E], U]) -> U:
163 return ok(self._value)
165 async def map(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
166 return Ok(await op(self._value))
168 async def async_map(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
169 """Alias for ``map`` — exists for backward compatibility."""
170 return Ok(await op(self._value))
172 async def and_then(
173 self, op: Callable[[T], Awaitable[Result[U, E]]]
174 ) -> Result[U, E]:
175 return await op(self._value)
177 async def or_else(self, op: Callable[[E], Awaitable[Result[T, F]]]) -> Result[T, F]:
178 return cast("Result[T, F]", self)
180 def flatten(self) -> Result[Any, E]:
181 if isinstance(self._value, Result):
182 return self._value
183 return cast("Result[Any, E]", self)
185 def filter(self, predicate: Callable[[T], bool], error: E) -> Result[T, E]:
186 return self if predicate(self._value) else Err(error)
188 def ok_or(self, default: U) -> T:
189 return self._value
191 def __repr__(self) -> str:
192 return f"Ok({self._value!r})"
194 def __eq__(self, other: object) -> bool:
195 return isinstance(other, Ok) and self._value == other._value
197 def __hash__(self) -> int:
198 return hash(("Ok", self._value))
201class Err(Result[T, E]):
202 __slots__ = ("_error",)
203 __match_args__ = ("_error",)
205 def __init__(self, error: E) -> None:
206 self._error = error
208 def is_ok(self) -> bool:
209 return False
211 def is_err(self) -> bool:
212 return True
214 def unwrap(self) -> T:
215 raise UnwrapError(f"Called unwrap on Err({self._error!r})", self)
217 def unwrap_err(self) -> E:
218 return self._error
220 def unwrap_or(self, default: T) -> T:
221 return default
223 def unwrap_or_else(self, op: Callable[[E], T]) -> T:
224 return op(self._error)
226 def map_sync(self, op: Callable[[T], U]) -> Result[U, E]:
227 return cast("Result[U, E]", self)
229 def map_err(self, op: Callable[[E], F]) -> Result[T, F]:
230 return Err(op(self._error))
232 def and_then_sync(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
233 return cast("Result[U, E]", self)
235 def or_else_sync(self, op: Callable[[E], Result[T, F]]) -> Result[T, F]:
236 return op(self._error)
238 def expect(self, message: str) -> T:
239 raise UnwrapError(f"{message}: {self._error!r}", self)
241 def match(self, ok: Callable[[T], U], err: Callable[[E], U]) -> U:
242 return err(self._error)
244 async def map(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
245 return cast("Result[U, E]", self)
247 async def async_map(self, op: Callable[[T], Awaitable[U]]) -> Result[U, E]:
248 """Alias for ``map`` — exists for backward compatibility."""
249 return cast("Result[U, E]", self)
251 async def and_then(
252 self, op: Callable[[T], Awaitable[Result[U, E]]]
253 ) -> Result[U, E]:
254 return cast("Result[U, E]", self)
256 async def or_else(self, op: Callable[[E], Awaitable[Result[T, F]]]) -> Result[T, F]:
257 return await op(self._error)
259 def flatten(self) -> Result[Any, E]:
260 return cast("Result[Any, E]", self)
262 def filter(self, predicate: Callable[[T], bool], error: E) -> Result[T, E]:
263 return self
265 def ok_or(self, default: U) -> U:
266 return default
268 def __repr__(self) -> str:
269 return f"Err({self._error!r})"
271 def __eq__(self, other: object) -> bool:
272 return isinstance(other, Err) and self._error == other._error
274 def __hash__(self) -> int:
275 return hash(("Err", self._error))
278__all__ = ["Err", "Ok", "Result", "UnwrapError"]