StructResult 0.9.0__tar.gz → 0.9.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StructResult
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
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
@@ -9,7 +9,7 @@ where = ["src"]
9
9
 
10
10
  [project]
11
11
  name = "StructResult"
12
- version = "0.9.0"
12
+ version = "0.9.2"
13
13
  requires-python = ">= 3.12"
14
14
  authors = [
15
15
  {name="Serj Kotilevski", email="youserj@outlook.com"}
@@ -1,6 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import Optional, Self, Protocol, Iterator, Any, Never
3
-
2
+ from typing import Optional, Self, Protocol, Iterator, Any, Never, ClassVar, Final, TypeVar, overload
4
3
  """
5
4
  Functional error handling system with:
6
5
  - Result composition
@@ -15,6 +14,9 @@ Core concepts:
15
14
 
16
15
 
17
16
  class Result(Protocol):
17
+ value: Any
18
+ err: Optional[ExceptionGroup]
19
+
18
20
  """Protocol for operation results"""
19
21
  def is_ok(self) -> bool:
20
22
  """Returns True if successful (no errors)"""
@@ -24,6 +26,7 @@ class Result(Protocol):
24
26
 
25
27
 
26
28
  class Ok(Result):
29
+
27
30
  """Singleton success marker without value"""
28
31
  def is_ok(self) -> bool:
29
32
  return True
@@ -39,12 +42,18 @@ class Ok(Result):
39
42
  """Always return OK"""
40
43
  return OK
41
44
 
45
+ @property
46
+ def err(self) -> None: # type: ignore[override]
47
+ return None
48
+
42
49
 
43
50
  OK = Ok()
44
51
 
45
52
 
46
53
  class Null:
47
54
  """Non value result marker"""
55
+ def __str__(self) -> str:
56
+ return "NULL"
48
57
 
49
58
 
50
59
  NULL = Null()
@@ -81,7 +90,7 @@ class ErrorPropagator(Result, Protocol):
81
90
  self.err = ExceptionGroup(err.message, (*self.err.exceptions, err))
82
91
  return self
83
92
 
84
- def propagate_err[T](self, res: "Collector[T] | ErrorAccumulator") -> T | Null:
93
+ def propagate_err[T](self, res: "Collector[T] | ErrorAccumulator | Ok") -> T | Null | Ok:
85
94
  """Merges errors from another result and returns its value:
86
95
  1. If res has errors - merges them into current
87
96
  2. Returns res's value (if exists)
@@ -110,6 +119,10 @@ class Error(ErrorPropagator):
110
119
  """Always raises exception"""
111
120
  raise self.err
112
121
 
122
+ @property
123
+ def value(self) -> Null:
124
+ return NULL
125
+
113
126
 
114
127
  @dataclass(slots=True)
115
128
  class StrictOk(ErrorPropagator):
@@ -207,8 +220,8 @@ class Collector[T](ErrorPropagator, Protocol):
207
220
  """Protocol for value containers with error handling"""
208
221
  value: T
209
222
 
210
- def __iter__(self) -> Iterator[Any]:
211
- return iter((self.value, self.err))
223
+ def unpack(self) -> tuple[T, Optional[ExceptionGroup]]:
224
+ return self.value, self.err
212
225
 
213
226
  def unwrap(self) -> T:
214
227
  """Returns value or raises exception if errors exist"""
@@ -245,26 +258,20 @@ class Option[T](Simple[Optional[T]], Result):
245
258
 
246
259
 
247
260
  @dataclass(slots=True)
248
- class List[T](Collector[list[Optional[T | Ok]]], Result):
261
+ class List[T](Collector[list[Optional[T | Ok | Null]]], Result):
249
262
  """List collector with error accumulation"""
250
- value: list[Optional[T] | Ok] = field(init=False, default_factory=list)
263
+ value: list[Optional[T] | Ok | Null] = field(init=False, default_factory=list)
251
264
  err: Optional[ExceptionGroup] = field(init=False, default=None)
252
265
 
253
- def append(self, res: Option[T] | Simple[T] | Error | Ok) -> None:
266
+ def append(self, res: Option[T] | Simple[T] | Error | Ok) -> None:
254
267
  """Appends result with rules:
255
268
  - For OK: adds OK marker
256
269
  - For Error: adds None and merges errors
257
270
  - For Collector: adds value and merges errors
258
271
  """
259
- if isinstance(res, Ok):
260
- self.value.append(OK)
261
- return
262
272
  if res.err is not None:
263
273
  self.append_err(res.err)
264
- if isinstance(res, Error):
265
- self.value.append(None)
266
- else:
267
- self.value.append(res.value)
274
+ self.value.append(res.value)
268
275
 
269
276
  def __add__(self, other: Option[T] | Simple[T] | Error | Ok) -> Self:
270
277
  self.append(other)
@@ -274,6 +281,9 @@ class List[T](Collector[list[Optional[T | Ok]]], Result):
274
281
  type SimpleOrError[T: Any] = Simple[T] | Error
275
282
 
276
283
 
284
+ T1 = TypeVar('T1')
285
+
286
+
277
287
  class Sequence[*Ts](Collector[tuple[*Ts]], Result):
278
288
  """
279
289
  A strictly-typed heterogeneous sequence container with error handling capabilities.
@@ -299,6 +309,26 @@ class Sequence[*Ts](Collector[tuple[*Ts]], Result):
299
309
  self.value = values
300
310
  self.err = err
301
311
 
312
+ @overload
313
+ def add(self, res: Collector[T1]) -> "Sequence[*Ts, T1]": ...
314
+
315
+ @overload
316
+ def add(self, res: Error) -> "Sequence[*Ts, Null]": ...
317
+
318
+ @overload
319
+ def add(self, res: Ok) -> "Sequence[*Ts, Ok]": ...
320
+
321
+ def add(self, res: Collector[T1] | Error | Ok) -> "Sequence[*Ts, Any]":
322
+ """Basic adding method"""
323
+ new_value = self.value + (res.value,)
324
+ new = Sequence(*new_value, err=self.err)
325
+ if res.err is not None:
326
+ new.append_err(res.err)
327
+ return new
328
+
329
+ def __str__(self) -> str:
330
+ return f"({", ".join(map(str, self.value))}){"" if not self.err else str(self.err)}"
331
+
302
332
 
303
333
  __all__ = [
304
334
  "SimpleOrError"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: StructResult
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
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
@@ -0,0 +1,486 @@
1
+ import unittest
2
+ from src.StructResult.result import Sequence, Option, Simple, OK, Error, NULL
3
+
4
+
5
+ class TestSequence(unittest.TestCase):
6
+ """Tests for Sequence class"""
7
+
8
+ def test_basic_creation(self) -> None:
9
+ """Test basic sequence creation"""
10
+ seq = Sequence(1, "hello", True)
11
+ self.assertEqual(seq.value, (1, "hello", True))
12
+ self.assertIsNone(seq.err)
13
+ self.assertTrue(seq.is_ok())
14
+
15
+ def test_type_annotations(self) -> None:
16
+ """Test strict type annotations"""
17
+ seq_int_str: Sequence[int, str] = Sequence(1, "test")
18
+ seq_mixed: Sequence[int, str, bool] = Sequence(1, "hello", True)
19
+
20
+ # Type checking should work
21
+ first: int = seq_int_str.value[0]
22
+ second: str = seq_int_str.value[1]
23
+
24
+ self.assertEqual(first, 1)
25
+ self.assertEqual(second, "test")
26
+
27
+ def test_empty_sequence(self) -> None:
28
+ """Test empty sequence creation"""
29
+ seq = Sequence()
30
+ self.assertEqual(seq.value, ())
31
+ self.assertIsNone(seq.err)
32
+ self.assertTrue(seq.is_ok())
33
+
34
+ def test_with_errors(self) -> None:
35
+ """Test sequence with errors"""
36
+ error = ExceptionGroup("test", [ValueError("error1")])
37
+ seq = Sequence(1, "test", err=error)
38
+
39
+ self.assertEqual(seq.value, (1, "test"))
40
+ self.assertIs(seq.err, error)
41
+ self.assertFalse(seq.is_ok())
42
+
43
+ def test_unwrap_success(self) -> None:
44
+ """Test successful unwrap"""
45
+ seq = Sequence(1, "hello", 3.14)
46
+ result = seq.unwrap()
47
+
48
+ self.assertEqual(result, (1, "hello", 3.14))
49
+ self.assertIsInstance(result, tuple)
50
+
51
+ def test_unwrap_failure(self) -> None:
52
+ """Test unwrap with errors"""
53
+ error = ExceptionGroup("test", [ValueError("error1")])
54
+ seq = Sequence(1, "test", err=error)
55
+
56
+ with self.assertRaises(ExceptionGroup) as context:
57
+ seq.unwrap()
58
+
59
+ self.assertIs(context.exception, error)
60
+
61
+ def test_err_propagation(self) -> None:
62
+ """Test error propagation compatibility"""
63
+ seq = Sequence(1, "test")
64
+
65
+ # Should work with ErrorPropagator protocol
66
+ self.assertTrue(hasattr(seq, 'append_e'))
67
+ self.assertTrue(hasattr(seq, 'append_err'))
68
+ self.assertTrue(hasattr(seq, 'propagate_err'))
69
+
70
+ def test_collector_protocol(self) -> None:
71
+ """Test Collector protocol compliance"""
72
+ seq = Sequence(1, "hello")
73
+
74
+ # Should support iteration
75
+ values, err = seq.unpack()
76
+ self.assertEqual(values, (1, "hello"))
77
+ self.assertIsNone(err)
78
+
79
+ # Should have value attribute
80
+ self.assertTrue(hasattr(seq, 'value'))
81
+ self.assertTrue(hasattr(seq, 'err'))
82
+
83
+ def test_result_protocol(self) -> None:
84
+ """Test Result protocol compliance"""
85
+ seq = Sequence(1, "test")
86
+
87
+ self.assertTrue(hasattr(seq, 'is_ok'))
88
+ self.assertTrue(hasattr(seq, 'unwrap'))
89
+ self.assertTrue(seq.is_ok())
90
+
91
+ def test_complex_types(self) -> None:
92
+ """Test sequences with complex types"""
93
+ # Nested sequences
94
+ inner_seq = Sequence("nested")
95
+ outer_seq = Sequence(1, inner_seq, True)
96
+
97
+ self.assertEqual(outer_seq.value[0], 1)
98
+ self.assertEqual(outer_seq.value[1].value, ("nested",))
99
+ self.assertEqual(outer_seq.value[2], True)
100
+
101
+ def test_equality(self) -> None:
102
+ """Test sequence equality"""
103
+ seq1 = Sequence(1, "test")
104
+ seq2 = Sequence(1, "test")
105
+ seq3 = Sequence(1, "different")
106
+
107
+ # Different instances with same values
108
+ self.assertEqual(seq1.value, seq2.value)
109
+ self.assertNotEqual(seq1.value, seq3.value)
110
+
111
+ def test_len_behavior(self) -> None:
112
+ """Test sequence length behavior"""
113
+ seq_single = Sequence(1)
114
+ seq_multi = Sequence(1, "two", 3.0)
115
+
116
+ self.assertEqual(len(seq_single.value), 1)
117
+ self.assertEqual(len(seq_multi.value), 3)
118
+
119
+ def test_pattern_matching(self) -> None:
120
+ """Test sequence pattern matching support"""
121
+ seq = Sequence(1, "hello", True)
122
+
123
+ # Basic unpacking
124
+ a, b, c = seq.value
125
+ self.assertEqual(a, 1)
126
+ self.assertEqual(b, "hello")
127
+ self.assertEqual(c, True)
128
+
129
+ def test_error_accumulation(self) -> None:
130
+ """Test error accumulation functionality"""
131
+ seq = Sequence(1, "test")
132
+
133
+ # Add first error
134
+ seq.append_e(ValueError("first error"), "context1")
135
+ self.assertIsNotNone(seq.err)
136
+ if seq.err is not None:
137
+ self.assertEqual(len(seq.err.exceptions), 1)
138
+
139
+ # Add second error with same message (should merge)
140
+ seq.append_e(TypeError("second error"), "context1")
141
+ if seq.err is not None:
142
+ self.assertEqual(len(seq.err.exceptions), 2)
143
+
144
+ # Add error with different message (should nest)
145
+ seq.append_e(RuntimeError("third error"), "context2")
146
+ if seq.err is not None:
147
+ self.assertEqual(seq.err.message, "context2")
148
+
149
+
150
+ class TestSequenceIntegration(unittest.TestCase):
151
+ """Integration tests with other system components"""
152
+
153
+ def test_with_simple_collector(self) -> None:
154
+ """Test integration with Simple collector"""
155
+ simple = Simple(42)
156
+ seq = Sequence(simple.value, "additional")
157
+
158
+ self.assertEqual(seq.value, (42, "additional"))
159
+ self.assertTrue(seq.is_ok())
160
+
161
+ def test_with_option_collector(self) -> None:
162
+ """Test integration with Option collector"""
163
+ option = Option(100) # Assuming Option can be created with value
164
+ seq = Sequence(option.value, "test")
165
+
166
+ self.assertEqual(seq.value[0], 100)
167
+ self.assertEqual(seq.value[1], "test")
168
+
169
+ def test_error_propagation_from_other_collectors(self) -> None:
170
+ """Test error propagation from other collectors"""
171
+ # Create a collector with error
172
+ error_collector = Simple(100)
173
+ error_collector.append_e(ValueError("source error"))
174
+
175
+ seq = Sequence("start")
176
+ result = seq.propagate_err(error_collector)
177
+
178
+ self.assertEqual(result, 100) # Should return the value
179
+ self.assertIsNotNone(seq.err) # But errors should be propagated
180
+ if seq.err is not None:
181
+ self.assertEqual(len(seq.err.exceptions), 1)
182
+
183
+ def test_with_ok_singleton(self) -> None:
184
+ """Test integration with OK singleton"""
185
+ seq = Sequence(OK, "regular value")
186
+
187
+ self.assertIs(seq.value[0], OK)
188
+ self.assertEqual(seq.value[1], "regular value")
189
+ self.assertTrue(seq.is_ok())
190
+
191
+
192
+ class TestSequenceEdgeCases(unittest.TestCase):
193
+ """Edge case tests for Sequence"""
194
+
195
+ def test_none_values(self) -> None:
196
+ """Test sequences with None values"""
197
+ seq = Sequence(None, "test", None)
198
+ self.assertEqual(seq.value, (None, "test", None))
199
+ self.assertEqual(seq.unwrap(), (None, "test", None))
200
+
201
+ def test_large_sequences(self) -> None:
202
+ """Test sequences with many elements"""
203
+ large_tuple = tuple(range(100))
204
+ seq = Sequence(*large_tuple)
205
+ self.assertEqual(seq.value, large_tuple)
206
+ self.assertEqual(len(seq.value), 100)
207
+
208
+ def test_nested_tuples(self) -> None:
209
+ """Test sequences containing nested tuples"""
210
+ nested = (1, 2, 3)
211
+ seq = Sequence(nested, "test")
212
+ self.assertEqual(seq.value[0], nested)
213
+ self.assertEqual(seq.value[1], "test")
214
+
215
+ def test_serialization(self) -> None:
216
+ """Test basic serialization properties"""
217
+ seq = Sequence(1, "test")
218
+
219
+ # Should be picklable
220
+ import pickle
221
+ pickled = pickle.dumps(seq)
222
+ unpickled = pickle.loads(pickled)
223
+
224
+ self.assertEqual(unpickled.value, seq.value)
225
+ self.assertEqual(unpickled.err, seq.err)
226
+
227
+
228
+ class TestSequenceAdd(unittest.TestCase):
229
+ """Tests for Sequence.add method"""
230
+
231
+ def test_add_simple_value(self) -> None:
232
+ """Test adding Simple value to sequence"""
233
+ seq1: Sequence[int, str] = Sequence(1, "hello")
234
+ simple_val = Simple(3.14)
235
+
236
+ seq2 = seq1.add(simple_val)
237
+
238
+ self.assertEqual(seq2.value, (1, "hello", 3.14))
239
+ self.assertIsNone(seq2.err)
240
+ self.assertTrue(seq2.is_ok())
241
+
242
+ def test_add_ok_singleton(self) -> None:
243
+ """Test adding OK singleton to sequence"""
244
+ seq1: Sequence[int, str] = Sequence(1, "hello")
245
+
246
+ seq2 = seq1.add(OK)
247
+
248
+ self.assertEqual(seq2.value, (1, "hello", OK))
249
+ self.assertIsNone(seq2.err)
250
+ self.assertTrue(seq2.is_ok())
251
+
252
+ def test_add_error(self) -> None:
253
+ """Test adding Error to sequence"""
254
+ seq1: Sequence[int, str] = Sequence(1, "hello")
255
+ error = Error.from_e(ValueError("test error"))
256
+
257
+ seq2 = seq1.add(error)
258
+
259
+ # Value should include Null for error
260
+ self.assertEqual(seq2.value, (1, "hello", NULL))
261
+ self.assertIsNotNone(seq2.err)
262
+ self.assertFalse(seq2.is_ok())
263
+ if seq2.err:
264
+ self.assertEqual(len(seq2.err.exceptions), 1)
265
+
266
+ def test_add_preserves_existing_errors(self) -> None:
267
+ """Test that add preserves errors from original sequence"""
268
+ seq1: Sequence[int, str] = Sequence(1, "hello",
269
+ err=ExceptionGroup("original", [ValueError("original error")]))
270
+ simple_val = Simple(3.14)
271
+
272
+ seq2 = seq1.add(simple_val)
273
+
274
+ self.assertEqual(seq2.value, (1, "hello", 3.14))
275
+ self.assertIsNotNone(seq2.err)
276
+ if seq2.err:
277
+ self.assertEqual(seq2.err.message, "original")
278
+ self.assertEqual(len(seq2.err.exceptions), 1)
279
+
280
+ def test_add_merges_errors_from_both_sides(self) -> None:
281
+ """Test that errors from both original and added result are merged"""
282
+ seq1: Sequence[int, str] = Sequence(1, "hello",
283
+ err=ExceptionGroup("original", [ValueError("error1")]))
284
+ error_val = Error.from_e(TypeError("error2"), "added")
285
+
286
+ seq2 = seq1.add(error_val)
287
+
288
+ self.assertEqual(seq2.value, (1, "hello", NULL))
289
+ self.assertIsNotNone(seq2.err)
290
+ # Errors should be merged with proper nesting
291
+ if seq2.err:
292
+ self.assertIsNotNone(seq2.err.subgroup(TypeError))
293
+
294
+ def test_add_to_empty_sequence(self) -> None:
295
+ """Test adding to empty sequence"""
296
+ seq1 = Sequence()
297
+ simple_val = Simple("test")
298
+
299
+ seq2 = seq1.add(simple_val)
300
+
301
+ self.assertEqual(seq2.value, ("test",))
302
+ self.assertIsNone(seq2.err)
303
+ self.assertTrue(seq2.is_ok())
304
+
305
+ def test_add_multiple_times(self) -> None:
306
+ """Test chaining multiple add operations"""
307
+ seq = Sequence(1)
308
+ seq = seq.add(Simple("hello"))
309
+ seq = seq.add(Simple(3.14))
310
+ seq = seq.add(OK)
311
+
312
+ self.assertEqual(seq.value, (1, "hello", 3.14, OK))
313
+ self.assertIsNone(seq.err)
314
+ self.assertTrue(seq.is_ok())
315
+
316
+ def test_add_with_option_type(self) -> None:
317
+ """Test adding Option values"""
318
+ seq1: Sequence[int] = Sequence(1)
319
+ option_val = Option("test") # Assuming Option works like Simple for value
320
+
321
+ seq2 = seq1.add(option_val)
322
+
323
+ self.assertEqual(seq2.value, (1, "test"))
324
+ self.assertIsNone(seq2.err)
325
+
326
+ def test_type_annotations_after_add(self) -> None:
327
+ """Test that type annotations are correct after add"""
328
+ seq1: Sequence[int, str] = Sequence(1, "hello")
329
+
330
+ # Adding Simple should preserve type info
331
+ seq2 = seq1.add(Simple(3.14))
332
+ self.assertEqual(seq2.value[0], 1) # int
333
+ self.assertEqual(seq2.value[1], "hello") # str
334
+ self.assertEqual(seq2.value[2], 3.14) # float
335
+
336
+ # Adding OK should work
337
+ seq3 = seq2.add(OK)
338
+ self.assertIs(seq3.value[3], OK)
339
+
340
+ # Adding Error should include Null
341
+ seq4 = seq3.add(Error.from_e(ValueError("test")))
342
+ self.assertIs(seq4.value[4], NULL)
343
+
344
+ def test_add_error_propagation_behavior(self) -> None:
345
+ """Test that error propagation works correctly in add"""
346
+ # Create a result with error
347
+ error_result = Simple("will error")
348
+ error_result.append_e(ValueError("source error"), "context")
349
+
350
+ seq1: Sequence[int] = Sequence(1)
351
+ seq2 = seq1.add(error_result)
352
+
353
+ # Should have the error from the added result
354
+ self.assertIsNotNone(seq2.err)
355
+ self.assertEqual(seq2.err.message, "context")
356
+ self.assertEqual(len(seq2.err.exceptions), 1)
357
+
358
+ def test_add_preserves_sequence_immutability(self) -> None:
359
+ """Test that original sequence is not modified by add"""
360
+ seq1: Sequence[int, str] = Sequence(1, "hello")
361
+ original_value = seq1.value
362
+ original_err = seq1.err
363
+
364
+ seq2 = seq1.add(Simple(3.14))
365
+
366
+ # Original should be unchanged
367
+ self.assertEqual(seq1.value, original_value)
368
+ self.assertEqual(seq1.err, original_err)
369
+
370
+ # New sequence should be different
371
+ self.assertIsNot(seq1, seq2)
372
+ self.assertEqual(seq2.value, (1, "hello", 3.14))
373
+
374
+ def test_add_with_none_values(self) -> None:
375
+ """Test adding results with None values"""
376
+ seq1: Sequence[int] = Sequence(1)
377
+ option_with_none = Option(None) # Optional value
378
+
379
+ seq2 = seq1.add(option_with_none)
380
+
381
+ self.assertEqual(seq2.value, (1, None))
382
+ self.assertIsNone(seq2.err)
383
+
384
+
385
+ class TestSequenceAddEdgeCases(unittest.TestCase):
386
+ """Edge case tests for Sequence.add"""
387
+
388
+ def test_add_after_error_accumulation(self) -> None:
389
+ """Test adding to sequence that already has multiple errors"""
390
+ seq1 = Sequence(1, "test")
391
+ seq1.append_e(ValueError("error1"), "validation")
392
+ seq1.append_e(TypeError("error2"), "validation")
393
+
394
+ seq2 = seq1.add(Simple(3.14))
395
+
396
+ self.assertEqual(seq2.value, (1, "test", 3.14))
397
+ self.assertIsNotNone(seq2.err)
398
+ self.assertEqual(len(seq2.err.exceptions), 2)
399
+
400
+ def test_add_complex_error_structure(self) -> None:
401
+ """Test adding with complex ExceptionGroup structures"""
402
+ complex_error = ExceptionGroup("complex", [
403
+ ValueError("err1"),
404
+ ExceptionGroup("nested", [TypeError("err2")])
405
+ ])
406
+
407
+ seq1 = Sequence(1)
408
+ error_result = Error(complex_error)
409
+
410
+ seq2 = seq1.add(error_result)
411
+
412
+ self.assertEqual(seq2.value, (1, NULL))
413
+ self.assertIsNotNone(seq2.err)
414
+ # Complex structure should be preserved
415
+ self.assertEqual(seq2.err.message, "complex")
416
+
417
+ def test_add_with_custom_collector(self) -> None:
418
+ """Test adding custom collector types"""
419
+
420
+ class CustomCollector(Simple[str]):
421
+ def custom_method(self) -> str:
422
+ return self.value.upper()
423
+
424
+ custom = CustomCollector("test")
425
+ seq1 = Sequence(1)
426
+
427
+ seq2 = seq1.add(custom)
428
+
429
+ self.assertEqual(seq2.value, (1, "test"))
430
+ self.assertIsNone(seq2.err)
431
+
432
+
433
+ class TestSequenceAddTypeSafety(unittest.TestCase):
434
+ """Type safety tests for Sequence.add"""
435
+
436
+ def test_type_inference_chain(self) -> None:
437
+ """Test that type inference works through add chain"""
438
+ # Start with specific types
439
+ seq: Sequence[int] = Sequence(1)
440
+
441
+ # Each add should properly infer new type
442
+ seq = seq.add(Simple("hello")) # Should be Sequence[int, str]
443
+ self.assertEqual(seq.value[0], 1)
444
+ self.assertEqual(seq.value[1], "hello")
445
+
446
+ seq = seq.add(Simple(3.14)) # Should be Sequence[int, str, float]
447
+ self.assertEqual(seq.value[2], 3.14)
448
+
449
+ seq = seq.add(OK) # Should be Sequence[int, str, float, Ok]
450
+ self.assertIs(seq.value[3], OK)
451
+
452
+ def test_unwrap_after_add_operations(self) -> None:
453
+ """Test that unwrap works correctly after multiple adds"""
454
+ seq = Sequence(1)
455
+ seq: Sequence[int, str] = seq.add(Simple("test"))
456
+ seq = seq.add(Simple(True))
457
+
458
+ result = seq.unwrap()
459
+ self.assertEqual(result, (1, "test", True))
460
+ self.assertIsInstance(result, tuple)
461
+
462
+
463
+ class TestSequenceTyping(unittest.TestCase):
464
+ """Test type annotations are correct"""
465
+
466
+ def test_type_annotations(self) -> None:
467
+ """Test that type annotations work correctly"""
468
+ # These should not cause type errors when checked with mypy/pyright
469
+ seq1: Sequence[int, str] = Sequence(1, "hello")
470
+ seq2: Sequence[int, str, bool] = Sequence(1, "test", True)
471
+ seq3: Sequence[()] = Sequence() # Empty tuple type
472
+
473
+ # Type narrowing should work
474
+ if seq1.is_ok():
475
+ values: tuple[int, str] = seq1.unwrap()
476
+ self.assertEqual(values, (1, "hello"))
477
+
478
+
479
+ def suite() -> unittest.TestSuite:
480
+ """Create test suite"""
481
+ test_suite = unittest.TestSuite()
482
+ test_suite.addTest(unittest.makeSuite(TestSequence))
483
+ test_suite.addTest(unittest.makeSuite(TestSequenceIntegration))
484
+ test_suite.addTest(unittest.makeSuite(TestSequenceEdgeCases))
485
+ test_suite.addTest(unittest.makeSuite(TestSequenceTyping))
486
+ return test_suite
@@ -1,7 +1,7 @@
1
1
  import unittest
2
2
  from unittest.mock import patch
3
3
  from typing import Any
4
- from src.StructResult.result import Option, Bool, Ok, OK, Error, List, SimpleOrError, Simple, Sequence
4
+ from src.StructResult.result import Option, Bool, Ok, OK, Error, List, SimpleOrError, Simple, Sequence, Null
5
5
 
6
6
 
7
7
  class TestResultSystem(unittest.TestCase):
@@ -141,7 +141,7 @@ class TestResultSystem(unittest.TestCase):
141
141
  self.assertEqual(len(lst.value), 3)
142
142
  self.assertEqual(lst.value[0], 42)
143
143
  self.assertEqual(lst.value[1], "hello")
144
- self.assertIsInstance(lst.value[2], type(None))
144
+ self.assertIsInstance(lst.value[2], Null)
145
145
 
146
146
  def test_propagate_none(self) -> None:
147
147
  res: Option[int] = Option(42)
@@ -192,7 +192,7 @@ class TestResultSystem(unittest.TestCase):
192
192
 
193
193
  def test_iterator_protocol(self) -> None:
194
194
  res: Option[str] = Option("test")
195
- values = list(res)
195
+ values = res.unpack()
196
196
  self.assertEqual(len(values), 2)
197
197
  self.assertEqual(values[0], "test")
198
198
  self.assertIsNone(values[1])
@@ -1,251 +0,0 @@
1
- import unittest
2
- from src.StructResult.result import Sequence, Option, Simple, OK
3
-
4
-
5
- class TestSequence(unittest.TestCase):
6
- """Tests for Sequence class"""
7
-
8
- def test_basic_creation(self) -> None:
9
- """Test basic sequence creation"""
10
- seq = Sequence(1, "hello", True)
11
- self.assertEqual(seq.value, (1, "hello", True))
12
- self.assertIsNone(seq.err)
13
- self.assertTrue(seq.is_ok())
14
-
15
- def test_type_annotations(self) -> None:
16
- """Test strict type annotations"""
17
- seq_int_str: Sequence[int, str] = Sequence(1, "test")
18
- seq_mixed: Sequence[int, str, bool] = Sequence(1, "hello", True)
19
-
20
- # Type checking should work
21
- first: int = seq_int_str.value[0]
22
- second: str = seq_int_str.value[1]
23
-
24
- self.assertEqual(first, 1)
25
- self.assertEqual(second, "test")
26
-
27
- def test_empty_sequence(self) -> None:
28
- """Test empty sequence creation"""
29
- seq = Sequence()
30
- self.assertEqual(seq.value, ())
31
- self.assertIsNone(seq.err)
32
- self.assertTrue(seq.is_ok())
33
-
34
- def test_with_errors(self) -> None:
35
- """Test sequence with errors"""
36
- error = ExceptionGroup("test", [ValueError("error1")])
37
- seq = Sequence(1, "test", err=error)
38
-
39
- self.assertEqual(seq.value, (1, "test"))
40
- self.assertIs(seq.err, error)
41
- self.assertFalse(seq.is_ok())
42
-
43
- def test_unwrap_success(self) -> None:
44
- """Test successful unwrap"""
45
- seq = Sequence(1, "hello", 3.14)
46
- result = seq.unwrap()
47
-
48
- self.assertEqual(result, (1, "hello", 3.14))
49
- self.assertIsInstance(result, tuple)
50
-
51
- def test_unwrap_failure(self) -> None:
52
- """Test unwrap with errors"""
53
- error = ExceptionGroup("test", [ValueError("error1")])
54
- seq = Sequence(1, "test", err=error)
55
-
56
- with self.assertRaises(ExceptionGroup) as context:
57
- seq.unwrap()
58
-
59
- self.assertIs(context.exception, error)
60
-
61
- def test_err_propagation(self) -> None:
62
- """Test error propagation compatibility"""
63
- seq = Sequence(1, "test")
64
-
65
- # Should work with ErrorPropagator protocol
66
- self.assertTrue(hasattr(seq, 'append_e'))
67
- self.assertTrue(hasattr(seq, 'append_err'))
68
- self.assertTrue(hasattr(seq, 'propagate_err'))
69
-
70
- def test_collector_protocol(self) -> None:
71
- """Test Collector protocol compliance"""
72
- seq = Sequence(1, "hello")
73
-
74
- # Should support iteration
75
- values, err = tuple(seq)
76
- self.assertEqual(values, (1, "hello"))
77
- self.assertIsNone(err)
78
-
79
- # Should have value attribute
80
- self.assertTrue(hasattr(seq, 'value'))
81
- self.assertTrue(hasattr(seq, 'err'))
82
-
83
- def test_result_protocol(self) -> None:
84
- """Test Result protocol compliance"""
85
- seq = Sequence(1, "test")
86
-
87
- self.assertTrue(hasattr(seq, 'is_ok'))
88
- self.assertTrue(hasattr(seq, 'unwrap'))
89
- self.assertTrue(seq.is_ok())
90
-
91
- def test_complex_types(self) -> None:
92
- """Test sequences with complex types"""
93
- # Nested sequences
94
- inner_seq = Sequence("nested")
95
- outer_seq = Sequence(1, inner_seq, True)
96
-
97
- self.assertEqual(outer_seq.value[0], 1)
98
- self.assertEqual(outer_seq.value[1].value, ("nested",))
99
- self.assertEqual(outer_seq.value[2], True)
100
-
101
- def test_equality(self) -> None:
102
- """Test sequence equality"""
103
- seq1 = Sequence(1, "test")
104
- seq2 = Sequence(1, "test")
105
- seq3 = Sequence(1, "different")
106
-
107
- # Different instances with same values
108
- self.assertEqual(seq1.value, seq2.value)
109
- self.assertNotEqual(seq1.value, seq3.value)
110
-
111
- def test_len_behavior(self) -> None:
112
- """Test sequence length behavior"""
113
- seq_single = Sequence(1)
114
- seq_multi = Sequence(1, "two", 3.0)
115
-
116
- self.assertEqual(len(seq_single.value), 1)
117
- self.assertEqual(len(seq_multi.value), 3)
118
-
119
- def test_pattern_matching(self) -> None:
120
- """Test sequence pattern matching support"""
121
- seq = Sequence(1, "hello", True)
122
-
123
- # Basic unpacking
124
- a, b, c = seq.value
125
- self.assertEqual(a, 1)
126
- self.assertEqual(b, "hello")
127
- self.assertEqual(c, True)
128
-
129
- def test_error_accumulation(self) -> None:
130
- """Test error accumulation functionality"""
131
- seq = Sequence(1, "test")
132
-
133
- # Add first error
134
- seq.append_e(ValueError("first error"), "context1")
135
- self.assertIsNotNone(seq.err)
136
- if seq.err is not None:
137
- self.assertEqual(len(seq.err.exceptions), 1)
138
-
139
- # Add second error with same message (should merge)
140
- seq.append_e(TypeError("second error"), "context1")
141
- if seq.err is not None:
142
- self.assertEqual(len(seq.err.exceptions), 2)
143
-
144
- # Add error with different message (should nest)
145
- seq.append_e(RuntimeError("third error"), "context2")
146
- if seq.err is not None:
147
- self.assertEqual(seq.err.message, "context2")
148
-
149
-
150
- class TestSequenceIntegration(unittest.TestCase):
151
- """Integration tests with other system components"""
152
-
153
- def test_with_simple_collector(self) -> None:
154
- """Test integration with Simple collector"""
155
- simple = Simple(42)
156
- seq = Sequence(simple.value, "additional")
157
-
158
- self.assertEqual(seq.value, (42, "additional"))
159
- self.assertTrue(seq.is_ok())
160
-
161
- def test_with_option_collector(self) -> None:
162
- """Test integration with Option collector"""
163
- option = Option(100) # Assuming Option can be created with value
164
- seq = Sequence(option.value, "test")
165
-
166
- self.assertEqual(seq.value[0], 100)
167
- self.assertEqual(seq.value[1], "test")
168
-
169
- def test_error_propagation_from_other_collectors(self) -> None:
170
- """Test error propagation from other collectors"""
171
- # Create a collector with error
172
- error_collector = Simple(100)
173
- error_collector.append_e(ValueError("source error"))
174
-
175
- seq = Sequence("start")
176
- result = seq.propagate_err(error_collector)
177
-
178
- self.assertEqual(result, 100) # Should return the value
179
- self.assertIsNotNone(seq.err) # But errors should be propagated
180
- if seq.err is not None:
181
- self.assertEqual(len(seq.err.exceptions), 1)
182
-
183
- def test_with_ok_singleton(self) -> None:
184
- """Test integration with OK singleton"""
185
- seq = Sequence(OK, "regular value")
186
-
187
- self.assertIs(seq.value[0], OK)
188
- self.assertEqual(seq.value[1], "regular value")
189
- self.assertTrue(seq.is_ok())
190
-
191
-
192
- class TestSequenceEdgeCases(unittest.TestCase):
193
- """Edge case tests for Sequence"""
194
-
195
- def test_none_values(self) -> None:
196
- """Test sequences with None values"""
197
- seq = Sequence(None, "test", None)
198
- self.assertEqual(seq.value, (None, "test", None))
199
- self.assertEqual(seq.unwrap(), (None, "test", None))
200
-
201
- def test_large_sequences(self) -> None:
202
- """Test sequences with many elements"""
203
- large_tuple = tuple(range(100))
204
- seq = Sequence(*large_tuple)
205
- self.assertEqual(seq.value, large_tuple)
206
- self.assertEqual(len(seq.value), 100)
207
-
208
- def test_nested_tuples(self) -> None:
209
- """Test sequences containing nested tuples"""
210
- nested = (1, 2, 3)
211
- seq = Sequence(nested, "test")
212
- self.assertEqual(seq.value[0], nested)
213
- self.assertEqual(seq.value[1], "test")
214
-
215
- def test_serialization(self) -> None:
216
- """Test basic serialization properties"""
217
- seq = Sequence(1, "test")
218
-
219
- # Should be picklable
220
- import pickle
221
- pickled = pickle.dumps(seq)
222
- unpickled = pickle.loads(pickled)
223
-
224
- self.assertEqual(unpickled.value, seq.value)
225
- self.assertEqual(unpickled.err, seq.err)
226
-
227
-
228
- class TestSequenceTyping(unittest.TestCase):
229
- """Test type annotations are correct"""
230
-
231
- def test_type_annotations(self) -> None:
232
- """Test that type annotations work correctly"""
233
- # These should not cause type errors when checked with mypy/pyright
234
- seq1: Sequence[int, str] = Sequence(1, "hello")
235
- seq2: Sequence[int, str, bool] = Sequence(1, "test", True)
236
- seq3: Sequence[()] = Sequence() # Empty tuple type
237
-
238
- # Type narrowing should work
239
- if seq1.is_ok():
240
- values: tuple[int, str] = seq1.unwrap()
241
- self.assertEqual(values, (1, "hello"))
242
-
243
-
244
- def suite():
245
- """Create test suite"""
246
- test_suite = unittest.TestSuite()
247
- test_suite.addTest(unittest.makeSuite(TestSequence))
248
- test_suite.addTest(unittest.makeSuite(TestSequenceIntegration))
249
- test_suite.addTest(unittest.makeSuite(TestSequenceEdgeCases))
250
- test_suite.addTest(unittest.makeSuite(TestSequenceTyping))
251
- return test_suite
File without changes
File without changes