• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ilotoki0804 / fieldenum / 10643944867

31 Aug 2024 08:41AM UTC coverage: 94.912% (-0.3%) from 95.187%
10643944867

push

github

ilotoki0804
Add validation to BoundResult

4 of 6 new or added lines in 2 files covered. (66.67%)

10 existing lines in 2 files now uncovered.

914 of 963 relevant lines covered (94.91%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.3
/src/fieldenum/enums.py
1
# type: ignore
2
"""Collection of useful fieldenums.
1✔
3

4
WARNING: This submodule can only be imported on Python 3.12 or later.
5
"""
6

7
from __future__ import annotations
1✔
8

9
import functools
1✔
10
from typing import TYPE_CHECKING, Any, Callable, final, overload
1✔
11

12
from . import Unit, Variant, fieldenum, unreachable
13
from .exceptions import IncompatibleBoundError
1✔
14

15
__all__ = ["Option", "BoundResult", "Message", "Some", "Success", "Failed"]
1✔
16

17
_MISSING = object()
1✔
18

19

20
@final  # A redundant decorator for type checkers.
1✔
21
@fieldenum
1✔
22
class Option[T]:
1✔
23
    if TYPE_CHECKING:
24
        Nothing = Unit
25
        class Some[T](Option[T]):
26
            __match_args__ = ("_0",)
27
            __fields__ = ("_0",)
28

29
            @property
30
            def _0(self) -> T: ...
31

32
            def __init__(self, value: T, /): ...
33

34
            def dump(self) -> tuple[T]: ...
35

36
    else:
37
        Nothing = Unit
1✔
38
        Some = Variant(T)
1✔
39

40
    def __bool__(self):
1✔
41
        return self is not Option.Nothing
1✔
42

43
    @overload
44
    @classmethod
45
    def new(cls, value: None, /) -> Option[T]: ...
46

47
    @overload
48
    @classmethod
49
    def new[U](cls, value: Option[U] | U | None, /) -> Option[U]: ...
50

51
    @overload
52
    @classmethod
53
    def new(cls, value: Option[T] | T | None, /) -> Option[T]: ...
54

55
    @classmethod
1✔
56
    def new(cls, value, /):
1✔
57
        if isinstance(value, Option):
1✔
58
            return value
1✔
59

60
        match value:
1✔
61
            case None:
1✔
62
                return Option.Nothing
1✔
63

64
            case value:
1✔
65
                return Option.Some(value)
1✔
66

67
    @overload
68
    @classmethod
69
    def new_as_is(cls, value: None, /) -> Option[T]: ...
70

71
    @overload
72
    @classmethod
73
    def new_as_is[U](cls, value: U | None, /) -> Option[U]: ...
74

75
    @overload
76
    @classmethod
77
    def new_as_is(cls, value: T | None, /) -> Option[T]: ...
78

79
    @classmethod
1✔
80
    def new_as_is(cls, value: T | None, /) -> Option[T]:
1✔
81
        match value:
1✔
82
            case None:
1✔
83
                return Option.Nothing
1✔
84

85
            case value:
1✔
86
                return Option.Some(value)
1✔
87

88
    @overload
89
    def unwrap(self) -> T: ...
90

91
    @overload
92
    def unwrap(self, default: T) -> T: ...
93

94
    @overload
95
    def unwrap[U](self, default: U) -> T | U: ...
96

97
    def unwrap(self, default=_MISSING):
1✔
98
        match self:
1✔
99
            case Option.Nothing if default is _MISSING:
1✔
100
                raise ValueError("Unwrap failed.")
1✔
101

102
            case Option.Nothing:
1✔
103
                return default
1✔
104

105
            case Option.Some(value):
1✔
106
                return value
1✔
107

108
            case other:
109
                unreachable(other)
110

111
    def expect(self, message_or_exception: str | BaseException, /) -> T:
1✔
112
        match self:
1✔
113
            case Option.Nothing if isinstance(message_or_exception, BaseException):
1✔
114
                raise message_or_exception
1✔
115

116
            case Option.Nothing:
1✔
117
                raise ValueError(message_or_exception)
1✔
118

119
            case Option.Some(value):
1✔
120
                return value
1✔
121

122
            case other:
123
                unreachable(other)
124

125
    def map[U](self, func: Callable[[T], Option[U] | None | U], /) -> Option[U]:
1✔
126
        match self:
1✔
127
            case Option.Nothing:
1✔
128
                return Option.Nothing
1✔
129

130
            case Option.Some(value):
1✔
131
                return Option.new(func(value))
1✔
132

133
            case other:
134
                unreachable(other)
135

136
    def map_as_is[U](self, func: Callable[[T], U | None], /) -> Option[U]:
1✔
137
        match self:
1✔
138
            case Option.Nothing:
1✔
139
                return Option.Nothing
×
140

141
            case Option.Some(value):
1✔
142
                return Option.new_as_is(func(value))
1✔
143

144
            case other:
145
                unreachable(other)
146

147
    @classmethod
1✔
148
    def wrap[**Params, Return](cls, func: Callable[Params, Option[Return] | Return | None], /) -> Callable[Params, Option[Return]]:
1✔
149
        @functools.wraps(func)
1✔
150
        def decorator(*args: Params.args, **kwargs: Params.kwargs):
1✔
151
            return Option.new(func(*args, **kwargs))
1✔
152
        return decorator
1✔
153

154
    @classmethod
1✔
155
    def wrap_as_is[**Params, Return](cls, func: Callable[Params, Return | None], /) -> Callable[Params, Option[Return]]:
1✔
156
        @functools.wraps(func)
1✔
157
        def decorator(*args: Params.args, **kwargs: Params.kwargs):
1✔
158
            return Option.new_as_is(func(*args, **kwargs))
1✔
159
        return decorator
1✔
160

161

162
@final  # A redundant decorator for type checkers.
1✔
163
@fieldenum
1✔
164
class BoundResult[R, E: BaseException]:
1✔
165
    if TYPE_CHECKING:
166
        class Success[R, E](BoundResult[R, E]):
167
            __match_args__ = ("value", "bound")
168
            __fields__ = ("value", "bound")
169

170
            @property
171
            def value(self) -> R: ...
172

173
            @property
174
            def bound(self) -> type[E]: ...
175

176
            def __init__(self, value: R, bound: type[E]): ...
177

178
            def dump(self) -> tuple[R, E]: ...
179

180
        class Failed[R, E](BoundResult[R, E]):
181
            __match_args__ = ("error", "bound")
182
            __fields__ = ("error", "bound")
183

184
            @property
185
            def error(self) -> E: ...
186

187
            @property
188
            def bound(self) -> type[E]: ...
189

190
            def __init__(self, error: E, bound: type[E]): ...
191

192
            def dump(self) -> tuple[E, type[E]]: ...
193

194
        @property
195
        def bound(self) -> type[E]: ...
196

197
    else:
198
        Success = Variant(value=R, bound=type[E])
1✔
199
        Failed = Variant(error=E, bound=type[E])
1✔
200

201
    def __bool__(self) -> bool:
1✔
202
        return isinstance(self, BoundResult.Success)
1✔
203

204
    def __post_init__(self):
1✔
205
        if not issubclass(self.bound, BaseException):
1✔
NEW
206
            raise IncompatibleBoundError(f"{self.bound} is not an exception.")
×
207

208
        if isinstance(self, Failed) and not isinstance(self.error, self.bound):
1✔
NEW
209
            raise IncompatibleBoundError(
×
210
                f"Bound {self.bound.__qualname__!r} is not compatible with existing error: {type(self.error).__qualname__}."
211
            )
212

213
    @overload
214
    def unwrap(self) -> R: ...
215

216
    @overload
217
    def unwrap(self, default: R) -> R: ...
218

219
    @overload
220
    def unwrap[T](self, default: T) -> R | T: ...
221

222
    def unwrap(self, default=_MISSING):
1✔
223
        match self:
1✔
224
            case BoundResult.Success(value, _):
1✔
225
                return value
1✔
226

227
            case BoundResult.Failed(error, _) if default is _MISSING:
1✔
228
                raise error
1✔
229

230
            case BoundResult.Failed(error, _):
1✔
231
                return default
1✔
232

233
            case other:
234
                unreachable(other)
235

236
    def as_option(self) -> Option[R]:
1✔
237
        match self:
1✔
238
            case BoundResult.Success(value, _):
1✔
239
                return Option.Some(value)
1✔
240

241
            case BoundResult.Failed(_, _):
1✔
242
                return Option.Nothing
1✔
243

244
            case other:
245
                unreachable(other)
246

247
    def rebound[NewBound: BaseException](self, bound: type[NewBound], /) -> BoundResult[R, NewBound]:
1✔
248
        match self:
1✔
249
            case BoundResult.Success(value, _):
1✔
250
                return BoundResult.Success(value, bound)
1✔
251

252
            case BoundResult.Failed(error, _):
1✔
253
                return BoundResult.Failed(error, bound)
1✔
254

255
            case other:
256
                unreachable(other)
257

258
    def map[NewReturn](self, func: Callable[[R], BoundResult[NewReturn, Any] | NewReturn], /) -> BoundResult[NewReturn, E]:
1✔
259
        match self:
1✔
260
            case BoundResult.Success(ok, bound):
1✔
261
                try:
1✔
262
                    result = func(ok)
1✔
263
                except BaseException as error:
1✔
264
                    if isinstance(error, bound):
1✔
265
                        return BoundResult.Failed(error, bound)
1✔
266
                    else:
267
                        raise
1✔
268

269
            case BoundResult.Failed(error, bound) as failed:
1✔
270
                if TYPE_CHECKING:
271
                    return BoundResult.Failed[NewReturn, E](error, bound)
272
                else:
273
                    return failed
1✔
274

275
            case other:
276
                unreachable(other)
277

278
        match result:
1✔
279
            case BoundResult.Success(ok, _):
1✔
280
                return BoundResult.Success(ok, bound)
1✔
281

282
            case BoundResult.Failed(error, _):
1✔
283
                return BoundResult.Failed(error, bound)
1✔
284

285
            case other:
286
                return BoundResult.Success(other, bound)
287

288
    def map_as_is[NewReturn](self, func: Callable[[R], NewReturn], /) -> BoundResult[NewReturn, E]:
1✔
289
        match self:
1✔
290
            case BoundResult.Success(ok, bound):
1✔
291
                try:
1✔
292
                    return BoundResult.Success(func(ok), bound)
1✔
293

294
                except BaseException as error:
×
295
                    if isinstance(error, bound):
×
296
                        return BoundResult.Failed(error, bound)
×
297
                    else:
298
                        raise
×
299

300
            case BoundResult.Failed(_, _) as failed:
1✔
301
                return failed
1✔
302

303
            case other:
304
                unreachable(other)
305

306
    @overload
307
    @classmethod
308
    def wrap[**Params, Return, Bound: BaseException](
309
        cls, bound: type[Bound], /
310
    ) -> Callable[[Callable[Params, BoundResult[Return, Any] | Return]], Callable[Params, BoundResult[Return, Bound]]]: ...
311

312
    @overload
313
    @classmethod
314
    def wrap[**Params, Return, Bound: BaseException](
315
        cls, func: Callable[Params, BoundResult[Return, Any] | Return], bound: type[Bound], /
316
    ) -> Callable[Params, BoundResult[Return, Bound]]: ...
317

318
    @classmethod
1✔
319
    def wrap(cls, *args):
1✔
320
        match args:
1✔
321
            case (bound,):
1✔
322
                def decorator(func):
1✔
323
                    @functools.wraps(func)
1✔
324
                    def inner(*args, **kwargs):
1✔
325
                        try:
1✔
326
                            result = func(*args, **kwargs)
1✔
327
                        except BaseException as error:
1✔
328
                            if isinstance(error, bound):
1✔
329
                                return BoundResult.Failed(error, bound)
1✔
330
                            else:
331
                                raise
1✔
332

333
                        match result:
1✔
334
                            case BoundResult.Failed(error, _):
1✔
335
                                # basically same as rebound operation
336
                                return BoundResult.Failed(error, bound)
×
337

338
                            case BoundResult.Success(value, _):
1✔
339
                                return BoundResult.Success(value, bound)
×
340

341
                            case value:
1✔
342
                                return BoundResult.Success(value, bound)
1✔
343

344
                    return inner
1✔
345

346
                return decorator
1✔
347

348
            case func, bound:
1✔
349
                @functools.wraps(func)
1✔
350
                def inner(*args, **kwargs):
1✔
351
                    try:
1✔
352
                        result = BoundResult.Success(func(*args, **kwargs), bound)
1✔
353
                    except BaseException as error:
1✔
354
                        if isinstance(error, bound):
1✔
355
                            return BoundResult.Failed(error, bound)
1✔
356
                        else:
357
                            raise
1✔
358

359
                    match result:
1✔
360
                        case BoundResult.Failed(error, _):
1✔
361
                            # basically same as rebound operation
362
                            return BoundResult.Failed(error, bound)
×
363

364
                        case BoundResult.Success(value, _):
1✔
365
                            return BoundResult.Success(value, bound)
1✔
366

367
                        case value:
×
368
                            return BoundResult.Success(value, bound)
×
369

370
                return inner
1✔
371

372
            case _, _, *params:
1✔
373
                raise TypeError(f"Received unexpected parameter(s): {params}")
1✔
374

375
            case other:
376
                unreachable(other)
377

378
    @overload
379
    @classmethod
380
    def wrap_as_is[**Params, Return, Bound: BaseException](
381
        cls, bound: type[Bound], /
382
    ) -> Callable[[Callable[Params, Return]], Callable[Params, BoundResult[Return, Bound]]]: ...
383

384
    @overload
385
    @classmethod
386
    def wrap_as_is[**Params, Return, Bound: BaseException](
387
        cls, func: Callable[Params, Return], bound: type[Bound], /
388
    ) -> Callable[Params, BoundResult[Return, Bound]]: ...
389

390
    @classmethod
1✔
391
    def wrap_as_is(cls, *args):
1✔
392
        match args:
×
393
            case (bound,):
×
394
                def decorator(func):
×
395
                    @functools.wraps(func)
×
396
                    def inner(*args, **kwargs):
×
397
                        try:
×
398
                            return BoundResult.Success(func(*args, **kwargs), bound)
×
399
                        except BaseException as error:
×
400
                            if isinstance(error, bound):
×
401
                                return BoundResult.Failed(error, bound)
×
402
                            else:
403
                                raise
×
404

405
                    return inner
×
406

407
                return decorator
×
408

409
            case func, bound:
×
410
                @functools.wraps(func)
×
411
                def inner(*args, **kwargs):
×
412
                    try:
×
413
                        return BoundResult.Success(func(*args, **kwargs), bound)
×
414
                    except BaseException as error:
×
415
                        if isinstance(error, bound):
×
416
                            return BoundResult.Failed(error, bound)
×
417
                        else:
418
                            raise
×
419

420
                return inner
×
421

422
            case _, _, *params:
×
423
                raise TypeError(f"Received unexpected parameter(s): {params}")
×
424

425
            case other:
426
                unreachable(other)
427

428

429
@final  # A redundant decorator for type checkers.
1✔
430
@fieldenum
1✔
431
class Message:
1✔
432
    """Test fieldenum to play with."""
1✔
433
    if TYPE_CHECKING:
434
        Quit = Unit
435

436
        class Move(Message):
437
            __match_args__ = ("x", "y")
438
            __fields__ = ("x", "y")
439

440
            @property
441
            def x(self) -> int: ...
442

443
            @property
444
            def y(self) -> int: ...
445

446
            def __init__(self, x: int, y: int): ...
447

448
            def dump(self) -> dict[str, int]: ...
449

450
        class Write(Message):
451
            __match_args__ = ("_0",)
452
            __fields__ = ("_0",)
453

454
            @property
455
            def _0(self) -> str: ...
456

457
            def __init__(self, message: str, /): ...
458

459
            def dump(self) -> tuple[int]: ...
460

461
        class ChangeColor(Message):
462
            __match_args__ = ("_0", "_1", "_2")
463
            __fields__ = ("_0", "_1", "_2")
464

465
            @property
466
            def _0(self) -> int: ...
467

468
            @property
469
            def _1(self) -> int: ...
470

471
            @property
472
            def _2(self) -> int: ...
473

474
            def __init__(self, red: int, green: int, blue: int, /): ...
475

476
            def dump(self) -> tuple[int, int, int]: ...
477

478
        class Pause(Message):
479
            __match_args__ = ()
480
            __fields__ = ()
481

482
            @property
483
            def _0(self) -> int: ...
484

485
            @property
486
            def _1(self) -> int: ...
487

488
            @property
489
            def _2(self) -> int: ...
490

491
            def __init__(self): ...
492

493
            def dump(self) -> tuple[()]: ...
494

495
    else:
496
        Quit = Unit
1✔
497
        Move = Variant(x=int, y=int)
1✔
498
        Write = Variant(str)
1✔
499
        ChangeColor = Variant(int, int, int)
1✔
500
        Pause = Variant()
1✔
501

502

503
Some = Option.Some
1✔
504
Success = BoundResult.Success
1✔
505
Failed = BoundResult.Failed
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc