StructResult 0.4.2__tar.gz → 0.5.1__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.4.2 → structresult-0.5.1}/PKG-INFO +3 -3
- {structresult-0.4.2 → structresult-0.5.1}/pyproject.toml +8 -3
- structresult-0.5.1/src/StructResult/result.py +129 -0
- {structresult-0.4.2 → structresult-0.5.1}/src/StructResult.egg-info/PKG-INFO +3 -3
- structresult-0.5.1/test/test_Result.py +124 -0
- structresult-0.4.2/src/StructResult/result.py +0 -105
- structresult-0.4.2/test/test_Result.py +0 -36
- {structresult-0.4.2 → structresult-0.5.1}/README.md +0 -0
- {structresult-0.4.2 → structresult-0.5.1}/setup.cfg +0 -0
- {structresult-0.4.2 → structresult-0.5.1}/src/StructResult/__init__.py +0 -0
- {structresult-0.4.2 → structresult-0.5.1}/src/StructResult.egg-info/SOURCES.txt +0 -0
- {structresult-0.4.2 → structresult-0.5.1}/src/StructResult.egg-info/dependency_links.txt +0 -0
- {structresult-0.4.2 → structresult-0.5.1}/src/StructResult.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: StructResult
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: structural result with
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: structural result with ExceptionGroup
|
|
5
5
|
Author-email: Serj Kotilevski <youserj@outlook.com>
|
|
6
6
|
Project-URL: Source, https://github.com/youserj/Result_prj
|
|
7
|
-
Keywords: result,
|
|
7
|
+
Keywords: result,Exception,ExceptionGroup,AggregateExceptions
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -9,16 +9,21 @@ where = ["src"]
|
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = "StructResult"
|
|
12
|
-
version = "0.
|
|
12
|
+
version = "0.5.1"
|
|
13
13
|
requires-python = ">= 3.12"
|
|
14
14
|
authors = [
|
|
15
15
|
{name="Serj Kotilevski", email="youserj@outlook.com"}
|
|
16
16
|
]
|
|
17
17
|
dependencies = [
|
|
18
18
|
]
|
|
19
|
-
description="structural result with
|
|
19
|
+
description="structural result with ExceptionGroup"
|
|
20
20
|
readme = "README.md"
|
|
21
|
-
keywords=[
|
|
21
|
+
keywords=[
|
|
22
|
+
'result',
|
|
23
|
+
'Exception',
|
|
24
|
+
'ExceptionGroup',
|
|
25
|
+
'AggregateExceptions'
|
|
26
|
+
]
|
|
22
27
|
classifiers = [
|
|
23
28
|
"Programming Language :: Python :: 3",
|
|
24
29
|
"License :: OSI Approved :: MIT License",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from typing import Optional, TypeVar, Generic, Self
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
empty = tuple()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Result(Generic[T], ABC):
|
|
10
|
+
value: Optional[T]
|
|
11
|
+
err: Optional[ExceptionGroup]
|
|
12
|
+
msg: str = ""
|
|
13
|
+
__slots__ = empty
|
|
14
|
+
|
|
15
|
+
def __getitem__(self, item):
|
|
16
|
+
if item == 0:
|
|
17
|
+
return self.value
|
|
18
|
+
elif item == 1:
|
|
19
|
+
return self.err
|
|
20
|
+
else:
|
|
21
|
+
raise StopIteration
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def append(self, res: Self) -> T:
|
|
25
|
+
""""""
|
|
26
|
+
|
|
27
|
+
def append_err(self, e: Exception | ExceptionGroup):
|
|
28
|
+
if isinstance(e, ExceptionGroup):
|
|
29
|
+
if self.err is None:
|
|
30
|
+
self.err = e
|
|
31
|
+
elif self.msg == e.message:
|
|
32
|
+
self.err = ExceptionGroup(self.msg, (*self.err.exceptions, *e.exceptions))
|
|
33
|
+
else:
|
|
34
|
+
self.err = ExceptionGroup(self.msg, (*self.err.exceptions, e))
|
|
35
|
+
else: # for Exception
|
|
36
|
+
if self.err is None:
|
|
37
|
+
self.err = ExceptionGroup(self.msg, (e,))
|
|
38
|
+
elif self.msg == self.err.message:
|
|
39
|
+
self.err = ExceptionGroup(self.msg, (*self.err.exceptions, e))
|
|
40
|
+
else:
|
|
41
|
+
self.err = ExceptionGroup(self.msg, (e, self.err))
|
|
42
|
+
|
|
43
|
+
def unwrap(self) -> T:
|
|
44
|
+
if self.err:
|
|
45
|
+
raise self.err
|
|
46
|
+
return self.value
|
|
47
|
+
|
|
48
|
+
def is_ok(self) -> bool:
|
|
49
|
+
return self.err is None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Simple(Result, Generic[T]):
|
|
53
|
+
__slots__ = ("value", "err", "msg")
|
|
54
|
+
|
|
55
|
+
def __init__(self, value: Optional[T] = None, e: Exception = None, msg: str = ""):
|
|
56
|
+
self.value = value
|
|
57
|
+
if e is not None:
|
|
58
|
+
self.err = ExceptionGroup(msg, (e,))
|
|
59
|
+
else:
|
|
60
|
+
self.err = None
|
|
61
|
+
self.msg = msg
|
|
62
|
+
|
|
63
|
+
def append(self, res: Result) -> T:
|
|
64
|
+
"""set value and append errors"""
|
|
65
|
+
self.value = res.value
|
|
66
|
+
if res.err is not None:
|
|
67
|
+
self.append_err(res.err)
|
|
68
|
+
return res.value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Null(Result):
|
|
72
|
+
"""can't append value or errors"""
|
|
73
|
+
__slots__ = empty
|
|
74
|
+
|
|
75
|
+
def append(self, res: Self):
|
|
76
|
+
raise RuntimeError(F"can't append for {self.__class__.__name__}")
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def value(self):
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def err(self):
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
NONE = Null()
|
|
88
|
+
"""None result"""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Error(Result):
|
|
92
|
+
__slots__ = ("err", "msg")
|
|
93
|
+
|
|
94
|
+
def __init__(self, e: Exception = None, msg: str = ""):
|
|
95
|
+
if e is not None:
|
|
96
|
+
self.err = ExceptionGroup(msg, (e,))
|
|
97
|
+
else:
|
|
98
|
+
self.err = None
|
|
99
|
+
self.msg = msg
|
|
100
|
+
|
|
101
|
+
def append(self, res: Result) -> None:
|
|
102
|
+
if res.err is not None:
|
|
103
|
+
self.append_err(res.err)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def value(self):
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class List(Result, Generic[T]):
|
|
112
|
+
value: list[T]
|
|
113
|
+
__slots__ = ("value", "err", "msg")
|
|
114
|
+
|
|
115
|
+
def __init__(self, msg: str = ""):
|
|
116
|
+
self.value = list()
|
|
117
|
+
self.err = None
|
|
118
|
+
self.msg = msg
|
|
119
|
+
|
|
120
|
+
def append(self, res: Result[T]) -> T:
|
|
121
|
+
"""append value and errors"""
|
|
122
|
+
self.value.append(res.value)
|
|
123
|
+
if res.err is not None:
|
|
124
|
+
self.append_err(res.err)
|
|
125
|
+
return res.value[-1]
|
|
126
|
+
|
|
127
|
+
def __add__(self, other: Result[T]) -> Self:
|
|
128
|
+
self.append(other)
|
|
129
|
+
return self
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: StructResult
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: structural result with
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: structural result with ExceptionGroup
|
|
5
5
|
Author-email: Serj Kotilevski <youserj@outlook.com>
|
|
6
6
|
Project-URL: Source, https://github.com/youserj/Result_prj
|
|
7
|
-
Keywords: result,
|
|
7
|
+
Keywords: result,Exception,ExceptionGroup,AggregateExceptions
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
import unittest
|
|
3
|
+
from StructResult import result
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestType(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_init(self):
|
|
9
|
+
res = result.Simple[int](1, msg="handle")
|
|
10
|
+
res.append_err(ExceptionGroup("handle1", [ValueError(2)]))
|
|
11
|
+
res.append_err(ValueError(1))
|
|
12
|
+
self.assertEqual(res.value, 1)
|
|
13
|
+
traceback.print_exception(res.err)
|
|
14
|
+
|
|
15
|
+
def test_Optional(self):
|
|
16
|
+
|
|
17
|
+
def foo() -> result.Simple[int]:
|
|
18
|
+
return result.Simple(None)
|
|
19
|
+
|
|
20
|
+
def test_resultList(self):
|
|
21
|
+
res = result.List[int]("list")
|
|
22
|
+
res.append(result.Simple(1, ValueError(1)))
|
|
23
|
+
res.append_err(ValueError(2))
|
|
24
|
+
res.append(result.Simple(2))
|
|
25
|
+
res.append(result.Simple(None, ZeroDivisionError(3), msg="devide"))
|
|
26
|
+
res.append(result.Simple("1"))
|
|
27
|
+
res += result.Simple(3, TypeError(13), msg="type")
|
|
28
|
+
traceback.print_exception(res.err)
|
|
29
|
+
|
|
30
|
+
def test_Null(self):
|
|
31
|
+
res = result.NONE
|
|
32
|
+
self.assertRaises(RuntimeError, res.append, result.Simple(1))
|
|
33
|
+
a, b = res
|
|
34
|
+
print(a)
|
|
35
|
+
|
|
36
|
+
def test_Error(self):
|
|
37
|
+
res = result.Error()
|
|
38
|
+
res.append(result.Simple(1, ZeroDivisionError()))
|
|
39
|
+
a, b = res
|
|
40
|
+
print(a)
|
|
41
|
+
|
|
42
|
+
def test_simple_append_value(self):
|
|
43
|
+
# Test appending a successful Simple result
|
|
44
|
+
res1 = result.Simple(10)
|
|
45
|
+
res2 = result.Simple(20)
|
|
46
|
+
res1.append(res2)
|
|
47
|
+
self.assertEqual(res1.value, 20)
|
|
48
|
+
self.assertIsNone(res1.err)
|
|
49
|
+
|
|
50
|
+
def test_simple_append_error(self):
|
|
51
|
+
# Test appending an error Simple result
|
|
52
|
+
res1 = result.Simple(10)
|
|
53
|
+
res2 = result.Simple(e=ValueError("test error"), msg="test")
|
|
54
|
+
res1.append(res2)
|
|
55
|
+
self.assertEqual(res1.value, None)
|
|
56
|
+
self.assertIsNotNone(res1.err)
|
|
57
|
+
self.assertEqual(len(res1.err.exceptions), 1)
|
|
58
|
+
|
|
59
|
+
def test_simple_append_multiple_errors(self):
|
|
60
|
+
# Test error aggregation
|
|
61
|
+
res1 = result.Simple(e=TypeError("type error"), msg="test")
|
|
62
|
+
res2 = result.Simple(e=ValueError("value error"), msg="test")
|
|
63
|
+
res1.append(res2)
|
|
64
|
+
self.assertEqual(len(res1.err.exceptions), 2)
|
|
65
|
+
|
|
66
|
+
def test_simple_append_different_messages(self):
|
|
67
|
+
# Test error aggregation with different messages
|
|
68
|
+
res1 = result.Simple(e=TypeError("type error"), msg="test1")
|
|
69
|
+
res2 = result.Simple(e=ValueError("value error"), msg="test2")
|
|
70
|
+
res1.append(res2)
|
|
71
|
+
# Should nest the exception groups
|
|
72
|
+
self.assertEqual(len(res1.err.exceptions), 2)
|
|
73
|
+
self.assertIsInstance(res1.err.exceptions[1], ExceptionGroup)
|
|
74
|
+
|
|
75
|
+
def test_null_append(self):
|
|
76
|
+
# Test that Null can't be appended to
|
|
77
|
+
with self.assertRaises(RuntimeError):
|
|
78
|
+
result.NONE.append(result.Simple(10))
|
|
79
|
+
|
|
80
|
+
def test_error_append_value(self):
|
|
81
|
+
# Test that Error ignores values but collects errors
|
|
82
|
+
err1 = result.Error(e=ValueError("error1"), msg="test")
|
|
83
|
+
simple = result.Simple(10)
|
|
84
|
+
err1.append(simple)
|
|
85
|
+
self.assertIsNone(err1.value)
|
|
86
|
+
|
|
87
|
+
def test_error_append_error(self):
|
|
88
|
+
# Test error aggregation in Error class
|
|
89
|
+
err1 = result.Error(e=ValueError("error1"), msg="test")
|
|
90
|
+
err2 = result.Error(e=TypeError("error2"), msg="test")
|
|
91
|
+
err1.append(err2)
|
|
92
|
+
self.assertEqual(len(err1.err.exceptions), 2)
|
|
93
|
+
|
|
94
|
+
def test_list_append_values(self):
|
|
95
|
+
# Test value collection in List
|
|
96
|
+
lst = result.List[int]()
|
|
97
|
+
lst.append(result.Simple(1))
|
|
98
|
+
lst.append(result.Simple(2))
|
|
99
|
+
lst.append(result.Simple(3))
|
|
100
|
+
self.assertEqual(lst.value, [1, 2, 3])
|
|
101
|
+
self.assertIsNone(lst.err)
|
|
102
|
+
|
|
103
|
+
def test_list_append_mixed(self):
|
|
104
|
+
# Test mixed success and error cases
|
|
105
|
+
lst = result.List[int](msg="test")
|
|
106
|
+
lst.append(result.Simple(1))
|
|
107
|
+
lst.append(result.Simple(e=ValueError("error1")))
|
|
108
|
+
lst.append(result.Simple(2))
|
|
109
|
+
lst.append(result.Simple(e=TypeError("error2")))
|
|
110
|
+
self.assertEqual(lst.value, [1, None, 2, None])
|
|
111
|
+
self.assertEqual(len(lst.err.exceptions), 2)
|
|
112
|
+
|
|
113
|
+
def test_list_operator_overload(self):
|
|
114
|
+
# Test the __add__ operator
|
|
115
|
+
lst = result.List[int]()
|
|
116
|
+
lst += result.Simple(1)
|
|
117
|
+
lst = lst + result.Simple(2)
|
|
118
|
+
self.assertEqual(lst.value, [1, 2])
|
|
119
|
+
|
|
120
|
+
def test_type_safety(self):
|
|
121
|
+
# Test type safety (should ideally fail but currently doesn't)
|
|
122
|
+
lst = result.List[int]()
|
|
123
|
+
lst.append(result.Simple("string")) # This should ideally raise TypeError
|
|
124
|
+
self.assertEqual(lst.value, ["string"])
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
from typing import Optional, TypeVar, Generic, Self
|
|
2
|
-
from abc import ABC, abstractmethod
|
|
3
|
-
|
|
4
|
-
T = TypeVar("T")
|
|
5
|
-
|
|
6
|
-
empty = tuple()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Result(Generic[T], ABC):
|
|
10
|
-
value: Optional[T]
|
|
11
|
-
err: Optional[list[Exception]]
|
|
12
|
-
__slots__ = empty
|
|
13
|
-
|
|
14
|
-
def __getitem__(self, item):
|
|
15
|
-
if item == 0:
|
|
16
|
-
return self.value
|
|
17
|
-
elif item == 1:
|
|
18
|
-
return self.err
|
|
19
|
-
else:
|
|
20
|
-
raise StopIteration
|
|
21
|
-
|
|
22
|
-
@abstractmethod
|
|
23
|
-
def append(self, res: Self):
|
|
24
|
-
""""""
|
|
25
|
-
|
|
26
|
-
def append_err(self, e: Exception):
|
|
27
|
-
if self.err is None:
|
|
28
|
-
self.err = list()
|
|
29
|
-
self.err.append(e)
|
|
30
|
-
|
|
31
|
-
def extend_err(self, e: list[Exception]):
|
|
32
|
-
if self.err is None:
|
|
33
|
-
self.err = list()
|
|
34
|
-
self.err.extend(e)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class Simple(Result, Generic[T]):
|
|
38
|
-
value: Optional[T]
|
|
39
|
-
err: Optional[list[Exception]]
|
|
40
|
-
__slots__ = ("value", "err")
|
|
41
|
-
|
|
42
|
-
def __init__(self, value: Optional[T] = None, err: list[Exception] = None):
|
|
43
|
-
self.value = value
|
|
44
|
-
self.err = err
|
|
45
|
-
|
|
46
|
-
def append(self, res: Result):
|
|
47
|
-
"""set value and append errors"""
|
|
48
|
-
self.value = res.value
|
|
49
|
-
if res.err is not None:
|
|
50
|
-
self.extend_err(res.err)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Null(Result):
|
|
54
|
-
"""can't append value or errors"""
|
|
55
|
-
__slots__ = empty
|
|
56
|
-
|
|
57
|
-
def append(self, res: Self):
|
|
58
|
-
raise RuntimeError(F"can't append for {self.__class__.__name__}")
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def value(self):
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def err(self):
|
|
66
|
-
return None
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
NONE = Null()
|
|
70
|
-
"""None result"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class Error(Result):
|
|
74
|
-
err: Optional[list[Exception]]
|
|
75
|
-
__slots__ = ("err",)
|
|
76
|
-
|
|
77
|
-
def __init__(self, err: list[Exception] = None):
|
|
78
|
-
self.err = err
|
|
79
|
-
|
|
80
|
-
def append(self, res: Result):
|
|
81
|
-
if res.err is not None:
|
|
82
|
-
self.extend_err(res.err)
|
|
83
|
-
|
|
84
|
-
@property
|
|
85
|
-
def value(self):
|
|
86
|
-
return None
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class List(Result, Generic[T]):
|
|
90
|
-
value: list[T]
|
|
91
|
-
__slots__ = ("value", "err")
|
|
92
|
-
|
|
93
|
-
def __init__(self, err: list[Exception] = None):
|
|
94
|
-
self.value = list()
|
|
95
|
-
self.err = err
|
|
96
|
-
|
|
97
|
-
def append(self, res: Result[T]):
|
|
98
|
-
"""append value and errors"""
|
|
99
|
-
self.value.append(res.value)
|
|
100
|
-
if res.err is not None:
|
|
101
|
-
self.extend_err(res.err)
|
|
102
|
-
|
|
103
|
-
def __add__(self, other: Result[T]) -> Self:
|
|
104
|
-
self.append(other)
|
|
105
|
-
return self
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
from StructResult import result
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class TestType(unittest.TestCase):
|
|
6
|
-
|
|
7
|
-
def test_init(self):
|
|
8
|
-
res = result.Simple[int](1)
|
|
9
|
-
self.assertEqual(res.value, 1)
|
|
10
|
-
self.assertEqual(list(res), [1, None])
|
|
11
|
-
|
|
12
|
-
def test_Optional(self):
|
|
13
|
-
|
|
14
|
-
def foo() -> result.Simple[int]:
|
|
15
|
-
return result.Simple(None)
|
|
16
|
-
|
|
17
|
-
def test_resultList(self):
|
|
18
|
-
res = result.List[int]()
|
|
19
|
-
res.append(result.Simple(1, [ValueError("1")]))
|
|
20
|
-
res.append(result.Simple(2))
|
|
21
|
-
res.append(result.Simple(None, [ZeroDivisionError()]))
|
|
22
|
-
res.append(result.Simple("1"))
|
|
23
|
-
res += result.Simple(3)
|
|
24
|
-
print(res)
|
|
25
|
-
|
|
26
|
-
def test_Null(self):
|
|
27
|
-
res = result.NONE
|
|
28
|
-
self.assertRaises(RuntimeError, res.append, result.Simple(1))
|
|
29
|
-
a, b = res
|
|
30
|
-
print(a)
|
|
31
|
-
|
|
32
|
-
def test_Error(self):
|
|
33
|
-
res = result.Error()
|
|
34
|
-
res.append(result.Simple(1, [ZeroDivisionError()]))
|
|
35
|
-
a, b = res
|
|
36
|
-
print(a)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|