StructResult 0.8.0__tar.gz → 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {structresult-0.8.0 → structresult-0.8.2}/PKG-INFO +1 -1
- {structresult-0.8.0 → structresult-0.8.2}/pyproject.toml +1 -1
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult/result.py +54 -40
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult.egg-info/PKG-INFO +1 -1
- {structresult-0.8.0 → structresult-0.8.2}/test/benchmark.py +14 -6
- {structresult-0.8.0 → structresult-0.8.2}/test/test_formatter.py +14 -14
- {structresult-0.8.0 → structresult-0.8.2}/test/test_result.py +50 -52
- {structresult-0.8.0 → structresult-0.8.2}/README.md +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/setup.cfg +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult/__init__.py +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult/formatter.py +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult/py.typed +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult.egg-info/SOURCES.txt +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult.egg-info/dependency_links.txt +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult.egg-info/requires.txt +0 -0
- {structresult-0.8.0 → structresult-0.8.2}/src/StructResult.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from typing import Optional, Self, Protocol, Iterator, Any
|
|
3
|
+
|
|
3
4
|
"""
|
|
4
5
|
Functional error handling system with:
|
|
5
6
|
- Result composition
|
|
6
|
-
- Error accumulation
|
|
7
|
+
- Error accumulation
|
|
7
8
|
- Type-safe operations
|
|
8
9
|
|
|
9
10
|
Core concepts:
|
|
@@ -19,6 +20,18 @@ class Result(Protocol):
|
|
|
19
20
|
"""Returns True if successful (no errors)"""
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
class Ok(Result):
|
|
24
|
+
"""Singleton success marker without value"""
|
|
25
|
+
def is_ok(self) -> bool:
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
return "OK"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
OK = Ok()
|
|
33
|
+
|
|
34
|
+
|
|
22
35
|
class ErrorPropagator(Result, Protocol):
|
|
23
36
|
"""Protocol for error-accumulating types"""
|
|
24
37
|
err: Optional[ExceptionGroup]
|
|
@@ -53,7 +66,7 @@ class ErrorPropagator(Result, Protocol):
|
|
|
53
66
|
self.err = ExceptionGroup(self.msg, (e, self.err))
|
|
54
67
|
return self
|
|
55
68
|
|
|
56
|
-
def propagate_err[T](self, res: "Collector[T]") -> Optional[T]:
|
|
69
|
+
def propagate_err[T](self, res: "Collector[T] | ErrorAccumulator") -> Optional[T]:
|
|
57
70
|
"""Merges errors from another result and returns its value:
|
|
58
71
|
1. If res has errors - merges them into current
|
|
59
72
|
2. Returns res's value (if exists)
|
|
@@ -63,6 +76,30 @@ class ErrorPropagator(Result, Protocol):
|
|
|
63
76
|
return res.value if hasattr(res, "value") else None
|
|
64
77
|
|
|
65
78
|
|
|
79
|
+
@dataclass(slots=True)
|
|
80
|
+
class Error(ErrorPropagator):
|
|
81
|
+
"""Error-only result container"""
|
|
82
|
+
err: ExceptionGroup
|
|
83
|
+
msg: str = ""
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_e(cls, e: Exception, msg: str = "") -> Self:
|
|
87
|
+
return cls(ExceptionGroup(msg, (e,)), msg)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(slots=True)
|
|
91
|
+
class ErrorAccumulator(ErrorPropagator):
|
|
92
|
+
"""Base container for error propagation with status conversion"""
|
|
93
|
+
err: Optional[ExceptionGroup] = field(init=False, default=None)
|
|
94
|
+
msg: str = ""
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def result(self) -> Ok | Error:
|
|
98
|
+
if self.err is None:
|
|
99
|
+
return OK
|
|
100
|
+
return Error(self.err)
|
|
101
|
+
|
|
102
|
+
|
|
66
103
|
class Collector[T](ErrorPropagator, Protocol):
|
|
67
104
|
"""Protocol for value containers with error handling"""
|
|
68
105
|
value: T
|
|
@@ -78,64 +115,41 @@ class Collector[T](ErrorPropagator, Protocol):
|
|
|
78
115
|
|
|
79
116
|
|
|
80
117
|
@dataclass(slots=True)
|
|
81
|
-
class Simple[T](Collector[
|
|
82
|
-
"""Basic collector for
|
|
118
|
+
class Simple[T](Collector[T], Result):
|
|
119
|
+
"""Basic collector for values"""
|
|
120
|
+
value: T
|
|
83
121
|
msg: str = ""
|
|
84
|
-
value: Optional[T] = field(default=None)
|
|
85
122
|
err: Optional[ExceptionGroup] = field(init=False, default=None)
|
|
86
123
|
|
|
87
|
-
def set(self, res: "Simple[T]") ->
|
|
124
|
+
def set(self, res: "Simple[T]") -> T:
|
|
88
125
|
"""set value and append errors"""
|
|
89
126
|
self.value = res.value
|
|
90
|
-
|
|
127
|
+
if res.err is not None:
|
|
128
|
+
self.append_err(res.err)
|
|
129
|
+
return res.value
|
|
91
130
|
|
|
92
131
|
|
|
93
132
|
@dataclass(slots=True)
|
|
94
|
-
class Bool(
|
|
133
|
+
class Bool(Simple[bool], Result):
|
|
95
134
|
"""Specialized collector for boolean results"""
|
|
96
|
-
msg: str = ""
|
|
97
135
|
value: bool = field(default=False)
|
|
98
136
|
err: Optional[ExceptionGroup] = field(init=False, default=None)
|
|
99
137
|
|
|
100
|
-
def set(self, res: "Bool") -> bool:
|
|
101
|
-
"""set value and append errors"""
|
|
102
|
-
self.value = res.value
|
|
103
|
-
self.propagate_err(res)
|
|
104
|
-
return res.value
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
class Ok(Result):
|
|
108
|
-
"""Singleton success marker without value"""
|
|
109
|
-
__slots__ = ()
|
|
110
|
-
|
|
111
|
-
def is_ok(self) -> bool:
|
|
112
|
-
return True
|
|
113
|
-
|
|
114
|
-
def __str__(self) -> str:
|
|
115
|
-
return "OK"
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
OK = Ok()
|
|
119
|
-
|
|
120
138
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def __init__(self, e: Exception, msg: str = "") -> None:
|
|
127
|
-
self.msg = msg
|
|
128
|
-
self.err = ExceptionGroup(self.msg, (e,))
|
|
139
|
+
@dataclass(slots=True)
|
|
140
|
+
class Option[T](Simple[Optional[T]], Result):
|
|
141
|
+
"""Basic collector for optional values"""
|
|
142
|
+
value: Optional[T] = field(default=None)
|
|
129
143
|
|
|
130
144
|
|
|
131
145
|
@dataclass(slots=True)
|
|
132
146
|
class List[T](Collector[list[Optional[T | Ok]]], Result):
|
|
133
147
|
"""List collector with error accumulation"""
|
|
134
|
-
msg: str = ""
|
|
135
148
|
value: list[Optional[T] | Ok] = field(init=False, default_factory=list)
|
|
149
|
+
msg: str = ""
|
|
136
150
|
err: Optional[ExceptionGroup] = field(init=False, default=None)
|
|
137
151
|
|
|
138
|
-
def append(self, res:
|
|
152
|
+
def append(self, res: Option[T] | Simple[T] | Error | Ok) -> None:
|
|
139
153
|
"""Appends result with rules:
|
|
140
154
|
- For OK: adds OK marker
|
|
141
155
|
- For Error: adds None and merges errors
|
|
@@ -151,6 +165,6 @@ class List[T](Collector[list[Optional[T | Ok]]], Result):
|
|
|
151
165
|
else:
|
|
152
166
|
self.value.append(res.value)
|
|
153
167
|
|
|
154
|
-
def __add__(self, other:
|
|
168
|
+
def __add__(self, other: Option[T] | Simple[T] | Error | Ok) -> Self:
|
|
155
169
|
self.append(other)
|
|
156
170
|
return self
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import timeit
|
|
2
|
-
from
|
|
2
|
+
from typing import Any, TypeGuard
|
|
3
|
+
from src.StructResult import result
|
|
4
|
+
from src.StructResult.result import Ok, Error, Simple, Result, Collector
|
|
3
5
|
|
|
4
6
|
# test data
|
|
5
7
|
ok_result = Simple(value=1)
|
|
6
|
-
error_result = Error(ValueError("fail"))
|
|
8
|
+
error_result = Error.from_e(ValueError("fail"))
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
# var 1: if not result.is_ok()
|
|
10
|
-
def check_is_ok(
|
|
11
|
-
return not
|
|
12
|
+
def check_is_ok[T](res: Collector[T] | Error | Ok) -> TypeGuard[Collector[T] | Ok]:
|
|
13
|
+
return not res.is_ok()
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
# var 2: if not isinstance(result, Error)
|
|
15
|
-
def check_isinstance(
|
|
16
|
-
return not isinstance(
|
|
17
|
+
def check_isinstance(res: Result) -> bool:
|
|
18
|
+
return not isinstance(res, Error)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def foo(res: Simple[Any] | Error) -> None:
|
|
22
|
+
if not check_is_ok(res):
|
|
23
|
+
raise ValueError()
|
|
24
|
+
print(res.value)
|
|
17
25
|
|
|
18
26
|
|
|
19
27
|
# OK_result speed
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import unittest
|
|
2
|
-
from src.StructResult.result import
|
|
2
|
+
from src.StructResult.result import Option, Error, List
|
|
3
3
|
from src.StructResult.formatter import format_eg
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class TestFormatEG(unittest.TestCase):
|
|
7
7
|
def setUp(self) -> None:
|
|
8
|
-
self.simple_error = ValueError("
|
|
8
|
+
self.simple_error = ValueError("Option error")
|
|
9
9
|
self.nested_group = ExceptionGroup("Nested", [TypeError("Type error")])
|
|
10
10
|
|
|
11
11
|
# Create a complex group with at least one exception in each subgroup
|
|
@@ -16,16 +16,16 @@ class TestFormatEG(unittest.TestCase):
|
|
|
16
16
|
])
|
|
17
17
|
|
|
18
18
|
# Create test Result objects
|
|
19
|
-
self.simple_result =
|
|
20
|
-
self.error_result = Error(self.simple_error, msg="error occurred")
|
|
19
|
+
self.simple_result = Option[str](value="test")
|
|
20
|
+
self.error_result = Error.from_e(self.simple_error, msg="error occurred")
|
|
21
21
|
self.list_result = List[int]()
|
|
22
|
-
self.list_result.append(
|
|
23
|
-
self.list_result.append(
|
|
22
|
+
self.list_result.append(Option[int](value=42))
|
|
23
|
+
self.list_result.append(Option[int](value=None))
|
|
24
24
|
|
|
25
25
|
def test_basic_exception_group(self) -> None:
|
|
26
26
|
eg = ExceptionGroup("Test", [self.simple_error])
|
|
27
27
|
result = format_eg(eg)
|
|
28
|
-
expected = "Test (1 sub-exception):\n - ValueError('
|
|
28
|
+
expected = "Test (1 sub-exception):\n - ValueError('Option error')"
|
|
29
29
|
self.assertEqual(result, expected)
|
|
30
30
|
|
|
31
31
|
def test_nested_exception_group(self) -> None:
|
|
@@ -37,7 +37,7 @@ class TestFormatEG(unittest.TestCase):
|
|
|
37
37
|
result = format_eg(self.complex_group)
|
|
38
38
|
expected = (
|
|
39
39
|
"Complex (3 sub-exceptions):\n"
|
|
40
|
-
" - ValueError('
|
|
40
|
+
" - ValueError('Option error')\n"
|
|
41
41
|
" Nested (1 sub-exception):\n"
|
|
42
42
|
" - TypeError('Type error')\n"
|
|
43
43
|
" With content (1 sub-exception):\n"
|
|
@@ -46,13 +46,13 @@ class TestFormatEG(unittest.TestCase):
|
|
|
46
46
|
self.assertEqual(result, expected)
|
|
47
47
|
|
|
48
48
|
def test_with_result_protocol(self) -> None:
|
|
49
|
-
error_result =
|
|
49
|
+
error_result = Option[str]()
|
|
50
50
|
error_result.append_err(self.complex_group)
|
|
51
51
|
if error_result.err is not None:
|
|
52
52
|
result = format_eg(error_result.err)
|
|
53
53
|
expected = (
|
|
54
54
|
"Complex (3 sub-exceptions):\n"
|
|
55
|
-
" - ValueError('
|
|
55
|
+
" - ValueError('Option error')\n"
|
|
56
56
|
" Nested (1 sub-exception):\n"
|
|
57
57
|
" - TypeError('Type error')\n"
|
|
58
58
|
" With content (1 sub-exception):\n"
|
|
@@ -69,19 +69,19 @@ class TestFormatEG(unittest.TestCase):
|
|
|
69
69
|
show_count=False,
|
|
70
70
|
repr_fn=lambda e: f"{type(e).__name__}: {str(e)}"
|
|
71
71
|
)
|
|
72
|
-
expected = "Custom:\n * ValueError:
|
|
72
|
+
expected = "Custom:\n * ValueError: Option error"
|
|
73
73
|
self.assertEqual(result, expected)
|
|
74
74
|
|
|
75
75
|
def test_with_list_result_errors(self) -> None:
|
|
76
76
|
list_result = List[int]()
|
|
77
|
-
list_result.append(
|
|
78
|
-
error_result =
|
|
77
|
+
list_result.append(Option[int](value=1))
|
|
78
|
+
error_result = Option[int](value=2)
|
|
79
79
|
error_result.append_err(self.simple_error)
|
|
80
80
|
list_result.append(error_result)
|
|
81
81
|
|
|
82
82
|
if list_result.err:
|
|
83
83
|
result = format_eg(list_result.err)
|
|
84
|
-
expected = " (1 sub-exception):\n - ValueError('
|
|
84
|
+
expected = " (1 sub-exception):\n - ValueError('Option error')"
|
|
85
85
|
self.assertTrue(result.endswith(expected))
|
|
86
86
|
|
|
87
87
|
def test_protocol_compatibility(self) -> None:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from unittest.mock import patch
|
|
3
|
-
from typing import
|
|
4
|
-
from src.StructResult.result import
|
|
3
|
+
from typing import Any
|
|
4
|
+
from src.StructResult.result import Option, Bool, Ok, OK, Error, List
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class TestResultSystem(unittest.TestCase):
|
|
@@ -11,7 +11,7 @@ class TestResultSystem(unittest.TestCase):
|
|
|
11
11
|
|
|
12
12
|
def test_error_creation(self) -> None:
|
|
13
13
|
exc = ValueError("test error")
|
|
14
|
-
err = Error(exc, "context")
|
|
14
|
+
err = Error.from_e(exc, "context")
|
|
15
15
|
self.assertFalse(err.is_ok())
|
|
16
16
|
self.assertIsNotNone(err.err)
|
|
17
17
|
self.assertEqual(err.msg, "context")
|
|
@@ -19,30 +19,30 @@ class TestResultSystem(unittest.TestCase):
|
|
|
19
19
|
self.assertIsInstance(err.err.exceptions[0], ValueError)
|
|
20
20
|
|
|
21
21
|
def test_simple_success(self) -> None:
|
|
22
|
-
res =
|
|
22
|
+
res = Option[str]("success", "test")
|
|
23
23
|
self.assertTrue(res.is_ok())
|
|
24
24
|
self.assertEqual(res.unwrap(), "success")
|
|
25
25
|
self.assertIsNone(res.err)
|
|
26
26
|
|
|
27
27
|
def test_simple_failure(self) -> None:
|
|
28
28
|
exc = TypeError("type error")
|
|
29
|
-
res =
|
|
29
|
+
res = Option[str]("test").append_err(exc)
|
|
30
30
|
self.assertFalse(res.is_ok())
|
|
31
31
|
self.assertIsNotNone(res.err)
|
|
32
32
|
with self.assertRaises(ExceptionGroup):
|
|
33
33
|
res.unwrap()
|
|
34
34
|
|
|
35
35
|
def test_bool_type(self) -> None:
|
|
36
|
-
true_res = Bool("test"
|
|
37
|
-
false_res = Bool("test"
|
|
36
|
+
true_res = Bool(value=True, msg="test")
|
|
37
|
+
false_res = Bool(value=False, msg="test")
|
|
38
38
|
self.assertTrue(true_res.unwrap())
|
|
39
39
|
self.assertFalse(false_res.unwrap())
|
|
40
40
|
|
|
41
41
|
def test_error_propagation(self) -> None:
|
|
42
42
|
exc1 = RuntimeError("error 1")
|
|
43
43
|
exc2 = KeyError("error 2")
|
|
44
|
-
res1 =
|
|
45
|
-
res2 =
|
|
44
|
+
res1 = Option[int](msg="op1").append_err(exc1)
|
|
45
|
+
res2 = Option[int](msg="op2").append_err(exc2)
|
|
46
46
|
res1.propagate_err(res2)
|
|
47
47
|
self.assertFalse(res1.is_ok())
|
|
48
48
|
if res1.err is not None:
|
|
@@ -52,10 +52,10 @@ class TestResultSystem(unittest.TestCase):
|
|
|
52
52
|
|
|
53
53
|
def test_list_collector(self) -> None:
|
|
54
54
|
lst = List[int]("collection")
|
|
55
|
-
lst.append(
|
|
56
|
-
lst.append(Error(ValueError("bad value"), "item2"))
|
|
55
|
+
lst.append(Option[int](42, "item1"))
|
|
56
|
+
lst.append(Error.from_e(ValueError("bad value"), "item2"))
|
|
57
57
|
lst.append(OK)
|
|
58
|
-
lst.append(
|
|
58
|
+
lst.append(Option[int](100, "item3"))
|
|
59
59
|
self.assertEqual(len(lst.value), 4)
|
|
60
60
|
self.assertEqual(lst.value[0], 42)
|
|
61
61
|
self.assertIsInstance(lst.value[2], Ok)
|
|
@@ -65,8 +65,8 @@ class TestResultSystem(unittest.TestCase):
|
|
|
65
65
|
self.assertIsInstance(lst.err.exceptions[0].exceptions[0], ValueError)
|
|
66
66
|
|
|
67
67
|
def test_list_operator_overload(self) -> None:
|
|
68
|
-
lst = List[str]("test") +
|
|
69
|
-
lst += Error(TypeError("type error"), "second")
|
|
68
|
+
lst = List[str]("test") + Option[str]("hello", "first")
|
|
69
|
+
lst += Error.from_e(TypeError("type error"), "second")
|
|
70
70
|
self.assertEqual(len(lst.value), 2)
|
|
71
71
|
self.assertEqual(lst.value[0], "hello")
|
|
72
72
|
self.assertFalse(lst.is_ok())
|
|
@@ -75,8 +75,8 @@ class TestResultSystem(unittest.TestCase):
|
|
|
75
75
|
exc1 = ValueError("val1")
|
|
76
76
|
exc2 = TypeError("type1")
|
|
77
77
|
exc3 = KeyError("key1")
|
|
78
|
-
res1 =
|
|
79
|
-
res2 =
|
|
78
|
+
res1: Option[object] = Option(msg="group1").append_err(exc1).append_err(exc2)
|
|
79
|
+
res2: Option[object] = Option(msg="group1").append_err(exc3)
|
|
80
80
|
res1.propagate_err(res2)
|
|
81
81
|
self.assertEqual(len(res1.err.exceptions), 3)
|
|
82
82
|
self.assertEqual(res1.err.message, "group1")
|
|
@@ -84,34 +84,34 @@ class TestResultSystem(unittest.TestCase):
|
|
|
84
84
|
def test_different_error_groups(self) -> None:
|
|
85
85
|
exc1 = ValueError("val1")
|
|
86
86
|
exc2 = TypeError("type1")
|
|
87
|
-
res1 =
|
|
88
|
-
res2 =
|
|
87
|
+
res1: Option[object] = Option(msg="group1").append_err(exc1)
|
|
88
|
+
res2: Option[object] = Option(msg="group2").append_err(exc2)
|
|
89
89
|
res1.propagate_err(res2)
|
|
90
90
|
self.assertEqual(len(res1.err.exceptions), 2)
|
|
91
91
|
self.assertIsInstance(res1.err.exceptions[1], ExceptionGroup)
|
|
92
92
|
|
|
93
93
|
def test_set_operation(self) -> None:
|
|
94
|
-
main
|
|
95
|
-
other
|
|
94
|
+
main: Option[str] = Option(msg="main")
|
|
95
|
+
other: Option[str] = Option("data", "other")
|
|
96
96
|
result = main.set(other)
|
|
97
97
|
self.assertEqual(main.value, "data")
|
|
98
98
|
self.assertEqual(result, "data")
|
|
99
99
|
self.assertTrue(main.is_ok())
|
|
100
100
|
|
|
101
101
|
def test_bool_set_operation(self) -> None:
|
|
102
|
-
main = Bool("main")
|
|
103
|
-
other = Bool("other"
|
|
102
|
+
main = Bool(msg="main")
|
|
103
|
+
other = Bool(value=True, msg="other")
|
|
104
104
|
result = main.set(other)
|
|
105
105
|
self.assertTrue(main.value)
|
|
106
106
|
self.assertTrue(result)
|
|
107
107
|
|
|
108
108
|
def test_simple_none_value(self) -> None:
|
|
109
|
-
res
|
|
109
|
+
res: Option[int] = Option(msg="test")
|
|
110
110
|
self.assertTrue(res.is_ok())
|
|
111
111
|
self.assertIsNone(res.unwrap())
|
|
112
112
|
|
|
113
113
|
def test_bool_false_with_error(self) -> None:
|
|
114
|
-
res = Bool("test"
|
|
114
|
+
res = Bool(value=False, msg="test")
|
|
115
115
|
res.append_err(ValueError("bool error"))
|
|
116
116
|
self.assertFalse(res.value)
|
|
117
117
|
self.assertFalse(res.is_ok())
|
|
@@ -121,7 +121,7 @@ class TestResultSystem(unittest.TestCase):
|
|
|
121
121
|
def test_nested_exception_groups(self) -> None:
|
|
122
122
|
inner_group = ExceptionGroup("inner", [ValueError("v1"), TypeError("t1")])
|
|
123
123
|
outer_group = ExceptionGroup("outer", [inner_group, KeyError("k1")])
|
|
124
|
-
res =
|
|
124
|
+
res = Option[int](msg="test").append_err(outer_group)
|
|
125
125
|
if res.err is not None:
|
|
126
126
|
self.assertEqual(len(res.err.exceptions), 1)
|
|
127
127
|
self.assertIsInstance(res.err.exceptions[0], ExceptionGroup)
|
|
@@ -135,35 +135,35 @@ class TestResultSystem(unittest.TestCase):
|
|
|
135
135
|
self.assertIsNone(lst.err)
|
|
136
136
|
|
|
137
137
|
def test_list_mixed_types(self) -> None:
|
|
138
|
-
lst = List
|
|
139
|
-
lst.append(
|
|
140
|
-
lst.append(
|
|
141
|
-
lst.append(Error(ValueError("error"), "error"))
|
|
138
|
+
lst: List[str | int] = List("mixed")
|
|
139
|
+
lst.append(Option(42, "int"))
|
|
140
|
+
lst.append(Option("hello", "str"))
|
|
141
|
+
lst.append(Error.from_e(ValueError("error"), "error"))
|
|
142
142
|
self.assertEqual(len(lst.value), 3)
|
|
143
143
|
self.assertEqual(lst.value[0], 42)
|
|
144
144
|
self.assertEqual(lst.value[1], "hello")
|
|
145
145
|
self.assertIsInstance(lst.value[2], type(None))
|
|
146
146
|
|
|
147
147
|
def test_propagate_none(self) -> None:
|
|
148
|
-
res =
|
|
149
|
-
res.propagate_err(
|
|
148
|
+
res = Option[int](42, "main")
|
|
149
|
+
res.propagate_err(Option[int](msg="other"))
|
|
150
150
|
self.assertTrue(res.is_ok())
|
|
151
151
|
self.assertEqual(res.unwrap(), 42)
|
|
152
152
|
|
|
153
153
|
def test_type_hints(self) -> None:
|
|
154
|
-
def processor() ->
|
|
155
|
-
return
|
|
154
|
+
def processor() -> Option[str]:
|
|
155
|
+
return Option[str]("result", "processor")
|
|
156
156
|
|
|
157
157
|
result = processor()
|
|
158
|
-
value: str = result.unwrap()
|
|
158
|
+
value: str | None = result.unwrap()
|
|
159
159
|
self.assertEqual(value, "result")
|
|
160
160
|
|
|
161
161
|
def test_combined_workflow(self) -> None:
|
|
162
162
|
main = List[int]("combined workflow")
|
|
163
|
-
main +=
|
|
164
|
-
main +=
|
|
165
|
-
main += Error(ValueError("invalid value"), "op3")
|
|
166
|
-
main +=
|
|
163
|
+
main += Option[int](10, "op1")
|
|
164
|
+
main += Option[int](20, "op2")
|
|
165
|
+
main += Error.from_e(ValueError("invalid value"), "op3")
|
|
166
|
+
main += Option[int](30, "op4")
|
|
167
167
|
self.assertEqual(len(main.value), 4)
|
|
168
168
|
self.assertEqual(main.value[0], 10)
|
|
169
169
|
self.assertEqual(main.value[1], 20)
|
|
@@ -174,42 +174,40 @@ class TestResultSystem(unittest.TestCase):
|
|
|
174
174
|
main.unwrap()
|
|
175
175
|
|
|
176
176
|
def test_multiple_propagations(self) -> None:
|
|
177
|
-
res1
|
|
178
|
-
res2
|
|
179
|
-
res3
|
|
180
|
-
|
|
181
|
-
main = Simple[int]("main")
|
|
177
|
+
res1: Option[int] = Option(msg="first").append_err(ValueError("v1"))
|
|
178
|
+
res2: Option[int] = Option(msg="second").append_err(TypeError("t1"))
|
|
179
|
+
res3: Option[int] = Option(msg="third").append_err(KeyError("k1"))
|
|
180
|
+
main: Option[int] = Option(msg="main")
|
|
182
181
|
main.propagate_err(res1)
|
|
183
182
|
main.propagate_err(res2)
|
|
184
183
|
main.propagate_err(res3)
|
|
185
|
-
|
|
186
184
|
self.assertEqual(len(main.err.exceptions), 3)
|
|
187
185
|
|
|
188
|
-
@patch.object(
|
|
186
|
+
@patch.object(Option, "append_err")
|
|
189
187
|
def test_propagate_err_calls(self, mock_append: Any) -> None:
|
|
190
|
-
err_res =
|
|
188
|
+
err_res = Option[int](msg="error")
|
|
191
189
|
err_res.err = ExceptionGroup("error", [ValueError("test")])
|
|
192
|
-
main =
|
|
190
|
+
main = Option[int](msg="main")
|
|
193
191
|
main.propagate_err(err_res)
|
|
194
192
|
mock_append.assert_called_once_with(err_res.err)
|
|
195
193
|
|
|
196
194
|
def test_iterator_protocol(self) -> None:
|
|
197
|
-
res =
|
|
195
|
+
res = Option[str]("test", "iter")
|
|
198
196
|
values = list(res)
|
|
199
197
|
self.assertEqual(len(values), 2)
|
|
200
198
|
self.assertEqual(values[0], "test")
|
|
201
199
|
self.assertIsNone(values[1])
|
|
202
200
|
|
|
203
201
|
def test_bool_truthiness(self) -> None:
|
|
204
|
-
true_res = Bool("true"
|
|
205
|
-
false_res = Bool("false"
|
|
202
|
+
true_res = Bool(value=True, msg="true")
|
|
203
|
+
false_res = Bool(value=False, msg="false")
|
|
206
204
|
self.assertTrue(true_res.value)
|
|
207
205
|
self.assertFalse(false_res.value)
|
|
208
206
|
self.assertTrue(bool(true_res.unwrap()))
|
|
209
207
|
self.assertFalse(bool(false_res.unwrap()))
|
|
210
208
|
|
|
211
|
-
def test_equal(self):
|
|
212
|
-
res =
|
|
209
|
+
def test_equal(self) -> None:
|
|
210
|
+
res: Option[str] = Option(msg="1")
|
|
213
211
|
group = ExceptionGroup("1", (ValueError("1"),))
|
|
214
212
|
res.append_err(group)
|
|
215
213
|
self.assertEqual(res.err, group)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|