errorz 0.2.0__tar.gz → 0.2.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.
- {errorz-0.2.0 → errorz-0.2.1}/PKG-INFO +1 -1
- {errorz-0.2.0 → errorz-0.2.1}/pyproject.toml +1 -1
- {errorz-0.2.0 → errorz-0.2.1}/src/errorz/__init__.py +343 -14
- errorz-0.2.1/src/errorz/__main__.py +10 -0
- errorz-0.2.0/src/errorz/__main__.py +0 -6
- {errorz-0.2.0 → errorz-0.2.1}/README.md +0 -0
- {errorz-0.2.0 → errorz-0.2.1}/src/errorz/hypothesis.py +0 -0
- {errorz-0.2.0 → errorz-0.2.1}/src/errorz/mypy.py +0 -0
- {errorz-0.2.0 → errorz-0.2.1}/src/errorz/py.typed +0 -0
|
@@ -28,6 +28,7 @@ from __future__ import annotations
|
|
|
28
28
|
|
|
29
29
|
import builtins
|
|
30
30
|
import functools
|
|
31
|
+
from collections import deque
|
|
31
32
|
from types import NotImplementedType
|
|
32
33
|
from typing import (
|
|
33
34
|
Any,
|
|
@@ -35,8 +36,10 @@ from typing import (
|
|
|
35
36
|
Generic,
|
|
36
37
|
Hashable,
|
|
37
38
|
Iterable,
|
|
39
|
+
Iterator,
|
|
38
40
|
Literal,
|
|
39
41
|
Mapping,
|
|
42
|
+
NamedTuple,
|
|
40
43
|
Optional,
|
|
41
44
|
Protocol,
|
|
42
45
|
Self,
|
|
@@ -85,6 +88,7 @@ class Err(Generic[E_co]):
|
|
|
85
88
|
"""
|
|
86
89
|
|
|
87
90
|
__is_errorz_error__ = True
|
|
91
|
+
__match_args__ = ("error",)
|
|
88
92
|
|
|
89
93
|
@property
|
|
90
94
|
def error(self) -> E_co:
|
|
@@ -215,6 +219,15 @@ class Err(Generic[E_co]):
|
|
|
215
219
|
return UnwrapError(e)
|
|
216
220
|
|
|
217
221
|
|
|
222
|
+
class Tagged[T, B: bool](NamedTuple):
|
|
223
|
+
value: T
|
|
224
|
+
is_error: B
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
type IsErr[E] = Tagged[E, Literal[True]]
|
|
228
|
+
type IsOk[T] = Tagged[T, Literal[False]]
|
|
229
|
+
|
|
230
|
+
|
|
218
231
|
def _any(x: object) -> Any:
|
|
219
232
|
return x
|
|
220
233
|
|
|
@@ -325,6 +338,27 @@ def check[T](value: Result[T], /) -> TypeIs[T]:
|
|
|
325
338
|
return True
|
|
326
339
|
|
|
327
340
|
|
|
341
|
+
def tagged[T, E](value: Result[T, E], /) -> IsOk[T] | IsErr[E]:
|
|
342
|
+
"""
|
|
343
|
+
Check if the value is an error and return False if it is, True otherwise.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
value: The result value to check.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
True if the value is not an error, False otherwise.
|
|
350
|
+
|
|
351
|
+
Examples:
|
|
352
|
+
>>> rz.tagged(42)
|
|
353
|
+
Tagged(value=42, is_error=False)
|
|
354
|
+
>>> rz.tagged(rz.err('error'))
|
|
355
|
+
Tagged(value='error', is_error=True)
|
|
356
|
+
"""
|
|
357
|
+
if isinstance(value, Err):
|
|
358
|
+
return cast("IsErr[E]", Tagged(value=value.error, is_error=True))
|
|
359
|
+
return cast("IsOk[T]", Tagged(value=value, is_error=False))
|
|
360
|
+
|
|
361
|
+
|
|
328
362
|
#
|
|
329
363
|
# Unwrappers
|
|
330
364
|
#
|
|
@@ -413,6 +447,62 @@ def expect[T](value: Result[T], /, error: str | BaseException) -> T:
|
|
|
413
447
|
raise UnwrapError(error)
|
|
414
448
|
|
|
415
449
|
|
|
450
|
+
def extract[T, E](fn: Callable[[E], T], value: Result[T, E], /) -> T:
|
|
451
|
+
"""
|
|
452
|
+
Extract the value from the result.
|
|
453
|
+
|
|
454
|
+
If it is an error, transform it with the given function.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
fn: The function to apply in case of errors.
|
|
458
|
+
value: The result value to extract the error from.
|
|
459
|
+
|
|
460
|
+
Examples:
|
|
461
|
+
>>> rz.extract(lambda e: f"error: {e}", rz.err(42))
|
|
462
|
+
'error: 42'
|
|
463
|
+
>>> rz.extract(lambda e: str(e), "ok")
|
|
464
|
+
'ok'
|
|
465
|
+
"""
|
|
466
|
+
return fn(value.error) if isinstance(value, Err) else value
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def unpack[T, E, R](
|
|
470
|
+
value: Result[T, E], /, ok: Callable[[T], R], err: Callable[[E], R]
|
|
471
|
+
) -> R:
|
|
472
|
+
"""
|
|
473
|
+
Unpack a result value using either the ok or err functions.
|
|
474
|
+
|
|
475
|
+
This is useful for chaining operations that may return errors without having to
|
|
476
|
+
check for errors at each step.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
value:
|
|
480
|
+
The result value to unpack.
|
|
481
|
+
ok:
|
|
482
|
+
The function to apply if the value is an ok value.
|
|
483
|
+
err:
|
|
484
|
+
The function to apply if the value is an error.
|
|
485
|
+
|
|
486
|
+
Examples:
|
|
487
|
+
>>> rz.unpack(rz.err("error"), ok=str, err=str.upper)
|
|
488
|
+
'ERROR'
|
|
489
|
+
>>> rz.unpack(42, ok=str, err=str.upper)
|
|
490
|
+
'42'
|
|
491
|
+
|
|
492
|
+
Notes:
|
|
493
|
+
This can be used as a poor-man's match statement. Real match statements
|
|
494
|
+
are supported, but there is no explicit Ok() case:
|
|
495
|
+
|
|
496
|
+
>>> result = rz.err("error")
|
|
497
|
+
>>> match result:
|
|
498
|
+
... case rz.Err(error):
|
|
499
|
+
... pass # code in the error case
|
|
500
|
+
... case value:
|
|
501
|
+
... pass # code in the ok case
|
|
502
|
+
"""
|
|
503
|
+
return err(value.error) if isinstance(value, Err) else ok(value)
|
|
504
|
+
|
|
505
|
+
|
|
416
506
|
def to_optional[T](value: Result[T, Any]) -> Optional[T]:
|
|
417
507
|
"""
|
|
418
508
|
Convert a Result to an Optional by converting any error to None.
|
|
@@ -429,6 +519,40 @@ def to_optional[T](value: Result[T, Any]) -> Optional[T]:
|
|
|
429
519
|
return value
|
|
430
520
|
|
|
431
521
|
|
|
522
|
+
def error[Any, E](value: Result[Any, E], /) -> Optional[E]:
|
|
523
|
+
"""
|
|
524
|
+
Get the error value if the result is an error, None otherwise.
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
value: The result value to check.
|
|
528
|
+
Examples:
|
|
529
|
+
>>> rz.error(42) is None
|
|
530
|
+
True
|
|
531
|
+
>>> rz.error(rz.err('error'))
|
|
532
|
+
'error'
|
|
533
|
+
"""
|
|
534
|
+
if isinstance(value, Err):
|
|
535
|
+
return value.error
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def swap[T, E](value: Result[T, E], /) -> Result[E, T]:
|
|
540
|
+
"""
|
|
541
|
+
Swap the ok and error values of a result.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
value: The result value to swap.
|
|
545
|
+
Examples:
|
|
546
|
+
>>> rz.swap(42)
|
|
547
|
+
Err(42)
|
|
548
|
+
>>> rz.swap(rz.err('error'))
|
|
549
|
+
'error'
|
|
550
|
+
"""
|
|
551
|
+
if isinstance(value, Err):
|
|
552
|
+
return value.error
|
|
553
|
+
return Err(value)
|
|
554
|
+
|
|
555
|
+
|
|
432
556
|
@overload
|
|
433
557
|
def map[T, E, R](fn: Callable[[T], R], value: Result[T, E], /) -> Result[R, E]: ...
|
|
434
558
|
|
|
@@ -543,6 +667,28 @@ def map(fn: Any, *values: Any) -> Any:
|
|
|
543
667
|
return fn(*values)
|
|
544
668
|
|
|
545
669
|
|
|
670
|
+
def map_err[T, E, R](fn: Callable[[E], R], value: Result[T, E], /) -> Result[T, R]:
|
|
671
|
+
"""
|
|
672
|
+
Apply the function to the error value if it is an error, otherwise return
|
|
673
|
+
the ok value.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
fn:
|
|
677
|
+
The function to apply to the error case.
|
|
678
|
+
value:
|
|
679
|
+
The result value to check.
|
|
680
|
+
|
|
681
|
+
Examples:
|
|
682
|
+
>>> rz.map_err(lambda e: f"error: {e}", rz.err(42))
|
|
683
|
+
Err('error: 42')
|
|
684
|
+
>>> rz.map_err(lambda e: f"error: {e}", 42)
|
|
685
|
+
42
|
|
686
|
+
"""
|
|
687
|
+
if isinstance(value, Err):
|
|
688
|
+
return Err(fn(value.error))
|
|
689
|
+
return value
|
|
690
|
+
|
|
691
|
+
|
|
546
692
|
@overload
|
|
547
693
|
def coalesce[T, E](values: Iterable[Result[T, E]], /) -> Result[T, E]: ...
|
|
548
694
|
|
|
@@ -617,32 +763,143 @@ def coalesce[T1, T2, T3, T4, T5, T6, T7, T8, E](
|
|
|
617
763
|
|
|
618
764
|
def coalesce(*values: Any) -> Any:
|
|
619
765
|
"""
|
|
620
|
-
Return the first value that is not an error, or the
|
|
766
|
+
Return the first value that is not an error, or the last error if all values
|
|
767
|
+
are errors.
|
|
768
|
+
|
|
769
|
+
This function works as short-circuiting "or" as if Ok values are thruthy
|
|
770
|
+
and Err values are falsy.
|
|
621
771
|
|
|
622
772
|
Examples:
|
|
623
773
|
>>> rz.coalesce(rz.err("e1"), rz.err("e2"), 42, 43, ...)
|
|
624
774
|
42
|
|
625
775
|
>>> rz.coalesce(rz.err("e1"), rz.err("e2"))
|
|
626
|
-
Err('
|
|
776
|
+
Err('e2')
|
|
627
777
|
>>> rz.coalesce([rz.err("e1"), rz.err("e2"), 42, 43, ...])
|
|
628
778
|
42
|
|
629
779
|
|
|
630
780
|
See also:
|
|
631
781
|
- :func:`rz.zip`
|
|
632
|
-
- :func:`rz.
|
|
782
|
+
- :func:`rz.all`
|
|
633
783
|
"""
|
|
634
784
|
if len(values) == 1:
|
|
635
785
|
values = values[0]
|
|
636
786
|
|
|
637
|
-
|
|
787
|
+
value: Any = None
|
|
638
788
|
for value in values:
|
|
639
789
|
if not isinstance(value, Err):
|
|
640
790
|
return value
|
|
641
|
-
|
|
642
|
-
err = value
|
|
643
|
-
if err is None:
|
|
791
|
+
if value is None:
|
|
644
792
|
raise ValueError("coalesce() expected at least one value")
|
|
645
|
-
return
|
|
793
|
+
return value
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@overload
|
|
797
|
+
def all[T, E](values: Iterable[Result[T, E]], /) -> Result[T, E]: ...
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
@overload
|
|
801
|
+
def all[T1, T2, E1, E2](
|
|
802
|
+
x1: Result[T1, E1], x2: Result[T2, E2], /
|
|
803
|
+
) -> Result[T1 | T2, E1 | E2]: ...
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
@overload
|
|
807
|
+
def all[T1, T2, T3, E](
|
|
808
|
+
x1: Result[T1, E], x2: Result[T2, E], x3: Result[T3, E], /
|
|
809
|
+
) -> Result[T1 | T2 | T3, E]: ...
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
@overload
|
|
813
|
+
def all[T1, T2, T3, T4, E](
|
|
814
|
+
x1: Result[T1, E], x2: Result[T2, E], x3: Result[T3, E], x4: Result[T4, E], /
|
|
815
|
+
) -> Result[T1 | T2 | T3 | T4, E]: ...
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
@overload
|
|
819
|
+
def all[T1, T2, T3, T4, T5, E](
|
|
820
|
+
x1: Result[T1, E],
|
|
821
|
+
x2: Result[T2, E],
|
|
822
|
+
x3: Result[T3, E],
|
|
823
|
+
x4: Result[T4, E],
|
|
824
|
+
x5: Result[T5, E],
|
|
825
|
+
/,
|
|
826
|
+
) -> Result[T1 | T2 | T3 | T4 | T5, E]: ...
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
@overload
|
|
830
|
+
def all[T1, T2, T3, T4, T5, T6, E](
|
|
831
|
+
x1: Result[T1, E],
|
|
832
|
+
x2: Result[T2, E],
|
|
833
|
+
x3: Result[T3, E],
|
|
834
|
+
x4: Result[T4, E],
|
|
835
|
+
x5: Result[T5, E],
|
|
836
|
+
x6: Result[T6, E],
|
|
837
|
+
/,
|
|
838
|
+
) -> Result[T1 | T2 | T3 | T4 | T5 | T6, E]: ...
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
@overload
|
|
842
|
+
def all[T1, T2, T3, T4, T5, T6, T7, E](
|
|
843
|
+
x1: Result[T1, E],
|
|
844
|
+
x2: Result[T2, E],
|
|
845
|
+
x3: Result[T3, E],
|
|
846
|
+
x4: Result[T4, E],
|
|
847
|
+
x5: Result[T5, E],
|
|
848
|
+
x6: Result[T6, E],
|
|
849
|
+
x7: Result[T7, E],
|
|
850
|
+
/,
|
|
851
|
+
) -> Result[T1 | T2 | T3 | T4 | T5 | T6 | T7, E]: ...
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
@overload
|
|
855
|
+
def all[T1, T2, T3, T4, T5, T6, T7, T8, E](
|
|
856
|
+
x1: Result[T1, E],
|
|
857
|
+
x2: Result[T2, E],
|
|
858
|
+
x3: Result[T3, E],
|
|
859
|
+
x4: Result[T4, E],
|
|
860
|
+
x5: Result[T5, E],
|
|
861
|
+
x6: Result[T6, E],
|
|
862
|
+
x7: Result[T7, E],
|
|
863
|
+
x8: Result[T8, E],
|
|
864
|
+
/,
|
|
865
|
+
) -> Result[T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8, E]: ...
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def all(*values: Any) -> Any:
|
|
869
|
+
"""
|
|
870
|
+
Return the first error in the arguments. If no argument is an error, return
|
|
871
|
+
the last ok value.
|
|
872
|
+
|
|
873
|
+
This function works as short-circuiting "and" as if Ok values are thruthy
|
|
874
|
+
and Err values are falsy.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
x1, x2, ...:
|
|
878
|
+
The result values to check.
|
|
879
|
+
It accepts any number of arguments (but only type checks up to 8).
|
|
880
|
+
|
|
881
|
+
Examples:
|
|
882
|
+
>>> rz.all(1, 2, 3)
|
|
883
|
+
3
|
|
884
|
+
>>> rz.all(1, rz.err("e1"), rz.err("e2"))
|
|
885
|
+
Err('e1')
|
|
886
|
+
|
|
887
|
+
See also:
|
|
888
|
+
- :func:`rz.coalesce`
|
|
889
|
+
"""
|
|
890
|
+
if len(values) == 1:
|
|
891
|
+
values = values[0]
|
|
892
|
+
|
|
893
|
+
if len(values) == 1:
|
|
894
|
+
values = values[0]
|
|
895
|
+
|
|
896
|
+
for value in values:
|
|
897
|
+
if isinstance(value, Err):
|
|
898
|
+
return value
|
|
899
|
+
try:
|
|
900
|
+
return value # pyright: ignore[reportPossiblyUnboundVariable]
|
|
901
|
+
except NameError:
|
|
902
|
+
raise ValueError("all() expected at least one value")
|
|
646
903
|
|
|
647
904
|
|
|
648
905
|
@overload
|
|
@@ -962,7 +1219,7 @@ def some[T, E](value: Result[T, E]) -> Iterable[T]:
|
|
|
962
1219
|
|
|
963
1220
|
Examples:
|
|
964
1221
|
>>> list(rz.some(42))
|
|
965
|
-
[42]
|
|
1222
|
+
[42]
|
|
966
1223
|
>>> list(rz.some(rz.err('error')))
|
|
967
1224
|
[]
|
|
968
1225
|
"""
|
|
@@ -995,14 +1252,86 @@ def filter[T, E](seq: Iterable[Result[T, E]], /) -> Iterable[T]:
|
|
|
995
1252
|
seq: The sequence of fallible values to filter.
|
|
996
1253
|
|
|
997
1254
|
Examples:
|
|
998
|
-
>>> list(rz.
|
|
1255
|
+
>>> list(rz.filter([1, 2, rz.err('error'), 3]))
|
|
999
1256
|
[1, 2, 3]
|
|
1000
|
-
>>> list(rz.
|
|
1257
|
+
>>> list(rz.filter([rz.err("error 1"), rz.err("error 2")]))
|
|
1001
1258
|
[]
|
|
1002
1259
|
"""
|
|
1003
1260
|
return (x for x in seq if not isinstance(x, Err))
|
|
1004
1261
|
|
|
1005
1262
|
|
|
1263
|
+
def partition[T, E](seq: Iterable[Result[T, E]], /) -> tuple[Iterable[T], Iterable[E]]:
|
|
1264
|
+
"""
|
|
1265
|
+
Partition the given sequence into two sequence: one of non-error values and one of error values.
|
|
1266
|
+
|
|
1267
|
+
Args:
|
|
1268
|
+
seq: The sequence of fallible values to partition.
|
|
1269
|
+
|
|
1270
|
+
Examples:
|
|
1271
|
+
>>> oks, errs = rz.partition([1, 2, rz.err('error'), 3])
|
|
1272
|
+
>>> list(oks)
|
|
1273
|
+
[1, 2, 3]
|
|
1274
|
+
>>> list(errs)
|
|
1275
|
+
['error']
|
|
1276
|
+
"""
|
|
1277
|
+
next = _iter(seq).__next__
|
|
1278
|
+
seen_values = deque["T"]()
|
|
1279
|
+
seen_errors = deque["E"]()
|
|
1280
|
+
|
|
1281
|
+
def oks() -> Iterator[T]:
|
|
1282
|
+
while True:
|
|
1283
|
+
try:
|
|
1284
|
+
if seen_values:
|
|
1285
|
+
yield seen_values.popleft()
|
|
1286
|
+
continue
|
|
1287
|
+
if isinstance(value := next(), Err):
|
|
1288
|
+
seen_errors.append(value.error)
|
|
1289
|
+
continue
|
|
1290
|
+
yield value
|
|
1291
|
+
except StopIteration:
|
|
1292
|
+
return
|
|
1293
|
+
|
|
1294
|
+
def errors() -> Iterator[E]:
|
|
1295
|
+
while True:
|
|
1296
|
+
try:
|
|
1297
|
+
if seen_errors:
|
|
1298
|
+
yield seen_errors.popleft()
|
|
1299
|
+
continue
|
|
1300
|
+
if isinstance(value := next(), Err):
|
|
1301
|
+
seen_errors.append(value.error)
|
|
1302
|
+
continue
|
|
1303
|
+
seen_values.append(value)
|
|
1304
|
+
except StopIteration:
|
|
1305
|
+
return
|
|
1306
|
+
|
|
1307
|
+
# Is it faster to use itertools.tee?
|
|
1308
|
+
return oks(), errors()
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
def combine[T, E](seq: Iterable[Result[T, E]], /) -> Result[list[T], E]:
|
|
1312
|
+
"""
|
|
1313
|
+
Combine the given sequence of fallible values into a single list if all
|
|
1314
|
+
values are non-errors.
|
|
1315
|
+
|
|
1316
|
+
Return the first error found otherwise.
|
|
1317
|
+
|
|
1318
|
+
Args:
|
|
1319
|
+
seq: The sequence of fallible values to combine.
|
|
1320
|
+
|
|
1321
|
+
Examples:
|
|
1322
|
+
>>> rz.combine([1, 2, 3])
|
|
1323
|
+
[1, 2, 3]
|
|
1324
|
+
>>> rz.combine([1, 2, rz.err('error')])
|
|
1325
|
+
Err('error')
|
|
1326
|
+
"""
|
|
1327
|
+
values = []
|
|
1328
|
+
for value in seq:
|
|
1329
|
+
if isinstance(value, Err):
|
|
1330
|
+
return value
|
|
1331
|
+
values.append(value)
|
|
1332
|
+
return values
|
|
1333
|
+
|
|
1334
|
+
|
|
1006
1335
|
def filter_values[K, V, E](seq: Mapping[K, Result[V, E]], /) -> dict[K, V]:
|
|
1007
1336
|
"""
|
|
1008
1337
|
Remove all Err values from the given mapping.
|
|
@@ -1039,7 +1368,7 @@ def non_empty[T](seq: Iterable[T], /, error: Any = MISSING) -> Result[list[T], A
|
|
|
1039
1368
|
>>> rz.non_empty([1, 2, 3])
|
|
1040
1369
|
[1, 2, 3]
|
|
1041
1370
|
>>> rz.non_empty([])
|
|
1042
|
-
Err(IndexError())
|
|
1371
|
+
Err(IndexError(...))
|
|
1043
1372
|
>>> rz.non_empty([], error="Sequence is empty")
|
|
1044
1373
|
Err('Sequence is empty')
|
|
1045
1374
|
"""
|
|
@@ -1071,9 +1400,9 @@ def single[T](seq: Iterable[T], /, error: Any = MISSING) -> Result[T, Any]:
|
|
|
1071
1400
|
>>> rz.single([42])
|
|
1072
1401
|
42
|
|
1073
1402
|
>>> rz.single([])
|
|
1074
|
-
Err(
|
|
1403
|
+
Err(IndexError(...))
|
|
1075
1404
|
>>> rz.single([1, 2])
|
|
1076
|
-
Err(
|
|
1405
|
+
Err(IndexError(...))
|
|
1077
1406
|
>>> rz.single([], error="No single value")
|
|
1078
1407
|
Err('No single value')
|
|
1079
1408
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|