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

ilotoki0804 / fieldenum / 11199500718

06 Oct 2024 06:09AM UTC coverage: 95.955% (+0.05%) from 95.905%
11199500718

push

github

ilotoki0804
Revise docs

14 of 14 new or added lines in 1 file covered. (100.0%)

16 existing lines in 1 file now uncovered.

1186 of 1236 relevant lines covered (95.95%)

0.96 hits per line

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

82.63
/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
import sys
1✔
11
from typing import TYPE_CHECKING, Any, Callable, NoReturn, final, overload
1✔
12

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

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

18
_MISSING = object()
1✔
19

20

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

109
            case other:
110
                unreachable(other)
111

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

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

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

123
            case other:
124
                unreachable(other)
125

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

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

134
            case other:
135
                unreachable(other)
136

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

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

145
            case other:
146
                unreachable(other)
147

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

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

162

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

215
    @overload
216
    def unwrap(self) -> R: ...
217

218
    @overload
219
    def unwrap(self, default: R) -> R: ...
220

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

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

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

232
            case BoundResult.Failed(error, _):
1✔
233
                return default
1✔
234

235
            case other:
236
                unreachable(other)
237

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

243
            case BoundResult.Failed(_, _):
1✔
244
                return Option.Nothing
1✔
245

246
            case other:
247
                unreachable(other)
248

249
    def exit(self, error_code: str | int | None = 1) -> NoReturn:
1✔
250
        sys.exit(0 if self else error_code)
1✔
251

252
    def rebound[NewBound: BaseException](self, bound: type[NewBound], /) -> BoundResult[R, NewBound]:
1✔
253
        match self:
1✔
254
            case BoundResult.Success(value, _):
1✔
255
                return BoundResult.Success(value, bound)
1✔
256

257
            case BoundResult.Failed(error, _):
1✔
258
                return BoundResult.Failed(error, bound)
1✔
259

260
            case other:
261
                unreachable(other)
262

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

274
            case BoundResult.Failed(error, bound) as failed:
1✔
275
                if TYPE_CHECKING:
276
                    return BoundResult.Failed[NewReturn, E](error, bound)
277
                else:
278
                    return failed
1✔
279

280
            case other:
281
                unreachable(other)
282

283
        match result:
1✔
284
            case BoundResult.Success(ok, _):
1✔
285
                return BoundResult.Success(ok, bound)
1✔
286

287
            case BoundResult.Failed(error, _):
1✔
288
                return BoundResult.Failed(error, bound)
1✔
289

290
            case other:
291
                return BoundResult.Success(other, bound)
292

293
    def map_as_is[NewReturn](self, func: Callable[[R], NewReturn], /) -> BoundResult[NewReturn, E]:
1✔
294
        match self:
1✔
295
            case BoundResult.Success(ok, bound):
1✔
296
                try:
1✔
297
                    return BoundResult.Success(func(ok), bound)
1✔
298

299
                except BaseException as error:
×
300
                    if isinstance(error, bound):
×
UNCOV
301
                        return BoundResult.Failed(error, bound)
×
302
                    else:
UNCOV
303
                        raise
×
304

305
            case BoundResult.Failed(_, _) as failed:
1✔
306
                return failed
1✔
307

308
            case other:
309
                unreachable(other)
310

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

317
    @overload
318
    @classmethod
319
    def wrap[**Params, Return, Bound: BaseException](
320
        cls, func: Callable[Params, BoundResult[Return, Any] | Return], bound: type[Bound], /
321
    ) -> Callable[Params, BoundResult[Return, Bound]]: ...
322

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

338
                        match result:
1✔
339
                            case BoundResult.Failed(error, _):
1✔
340
                                # basically same as rebound operation
UNCOV
341
                                return BoundResult.Failed(error, bound)
×
342

343
                            case BoundResult.Success(value, _):
1✔
UNCOV
344
                                return BoundResult.Success(value, bound)
×
345

346
                            case value:
1✔
347
                                return BoundResult.Success(value, bound)
1✔
348

349
                    return inner
1✔
350

351
                return decorator
1✔
352

353
            case func, bound:
1✔
354
                @functools.wraps(func)
1✔
355
                def inner(*args, **kwargs):
1✔
356
                    try:
1✔
357
                        result = BoundResult.Success(func(*args, **kwargs), bound)
1✔
358
                    except BaseException as error:
1✔
359
                        if isinstance(error, bound):
1✔
360
                            return BoundResult.Failed(error, bound)
1✔
361
                        else:
362
                            raise
1✔
363

364
                    match result:
1✔
365
                        case BoundResult.Failed(error, _):
1✔
366
                            # basically same as rebound operation
UNCOV
367
                            return BoundResult.Failed(error, bound)
×
368

369
                        case BoundResult.Success(value, _):
1✔
370
                            return BoundResult.Success(value, bound)
1✔
371

372
                        case value:
×
UNCOV
373
                            return BoundResult.Success(value, bound)
×
374

375
                return inner
1✔
376

377
            case _, _, *params:
1✔
378
                raise TypeError(f"Received unexpected parameter(s): {params}")
1✔
379

380
            case other:
381
                unreachable(other)
382

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

389
    @overload
390
    @classmethod
391
    def wrap_as_is[**Params, Return, Bound: BaseException](
392
        cls, func: Callable[Params, Return], bound: type[Bound], /
393
    ) -> Callable[Params, BoundResult[Return, Bound]]: ...
394

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

UNCOV
410
                    return inner
×
411

UNCOV
412
                return decorator
×
413

414
            case func, bound:
×
415
                @functools.wraps(func)
×
416
                def inner(*args, **kwargs):
×
417
                    try:
×
418
                        return BoundResult.Success(func(*args, **kwargs), bound)
×
419
                    except BaseException as error:
×
420
                        if isinstance(error, bound):
×
UNCOV
421
                            return BoundResult.Failed(error, bound)
×
422
                        else:
UNCOV
423
                            raise
×
424

UNCOV
425
                return inner
×
426

427
            case _, _, *params:
×
UNCOV
428
                raise TypeError(f"Received unexpected parameter(s): {params}")
×
429

430
            case other:
431
                unreachable(other)
432

433

434
@final  # A redundant decorator for type checkers.
1✔
435
@fieldenum
1✔
436
class Message:
1✔
437
    """Test fieldenum to play with."""
1✔
438
    if TYPE_CHECKING:
439
        Quit = Unit
440

441
        class Move(Message):
442
            __match_args__ = ("x", "y")
443
            __fields__ = ("x", "y")
444

445
            @property
446
            def x(self) -> int: ...
447

448
            @property
449
            def y(self) -> int: ...
450

451
            def __init__(self, x: int, y: int): ...
452

453
            def dump(self) -> dict[str, int]: ...
454

455
        class Write(Message):
456
            __match_args__ = ("_0",)
457
            __fields__ = (0,)
458

459
            @property
460
            def _0(self) -> str: ...
461

462
            def __init__(self, message: str, /): ...
463

464
            def dump(self) -> tuple[int]: ...
465

466
        class ChangeColor(Message):
467
            __match_args__ = ("_0", "_1", "_2")
468
            __fields__ = (0, 1, 2)
469

470
            @property
471
            def _0(self) -> int: ...
472

473
            @property
474
            def _1(self) -> int: ...
475

476
            @property
477
            def _2(self) -> int: ...
478

479
            def __init__(self, red: int, green: int, blue: int, /): ...
480

481
            def dump(self) -> tuple[int, int, int]: ...
482

483
        class Pause(Message):
484
            __match_args__ = ()
485
            __fields__ = ()
486

487
            @property
488
            def _0(self) -> int: ...
489

490
            @property
491
            def _1(self) -> int: ...
492

493
            @property
494
            def _2(self) -> int: ...
495

496
            def __init__(self): ...
497

498
            def dump(self) -> tuple[()]: ...
499

500
    else:
501
        Quit = Unit
1✔
502
        Move = Variant(x=int, y=int)
1✔
503
        Write = Variant(str)
1✔
504
        ChangeColor = Variant(int, int, int)
1✔
505
        Pause = Variant()
1✔
506

507

508
Some = Option.Some
1✔
509
Success = BoundResult.Success
1✔
510
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