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.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StructResult
3
- Version: 0.4.2
4
- Summary: structural result with exception
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,exception
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.4.2"
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 exception"
19
+ description="structural result with ExceptionGroup"
20
20
  readme = "README.md"
21
- keywords=['result', 'exception']
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.2
4
- Summary: structural result with exception
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,exception
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