optionz 0.1.0__py3-none-any.whl
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.
- opt.py +582 -0
- optionz-0.1.0.dist-info/METADATA +83 -0
- optionz-0.1.0.dist-info/RECORD +5 -0
- optionz-0.1.0.dist-info/WHEEL +4 -0
- optionz-0.1.0.dist-info/licenses/LICENSE +21 -0
opt.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
from typing import Any, Callable, Iterable, Optional, overload
|
|
3
|
+
|
|
4
|
+
# Raised when trying to unwrap a None value. It is an alias for ValueError to allow
|
|
5
|
+
# mocking it in tests (or global mokey-patching if you are feelling adventurous).
|
|
6
|
+
UnwrapError = ValueError
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
"UnwrapError",
|
|
10
|
+
"unwrap",
|
|
11
|
+
"unwrap_or",
|
|
12
|
+
"unwrap_or_else",
|
|
13
|
+
"expect",
|
|
14
|
+
"map",
|
|
15
|
+
"coalesce",
|
|
16
|
+
"values",
|
|
17
|
+
"together",
|
|
18
|
+
"zip",
|
|
19
|
+
"elements",
|
|
20
|
+
"getattr",
|
|
21
|
+
"call",
|
|
22
|
+
"iter",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _any(x: object) -> Any:
|
|
27
|
+
return x
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Unwrappers
|
|
32
|
+
#
|
|
33
|
+
def unwrap[T](value: Optional[T], /) -> T:
|
|
34
|
+
"""
|
|
35
|
+
Unwrap a value that may be None. If the value is None, raise a ValueError.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
value: The optional value to unwrap.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
>>> opt.unwrap(42)
|
|
42
|
+
42
|
|
43
|
+
>>> opt.unwrap(None)
|
|
44
|
+
Traceback (most recent call last):
|
|
45
|
+
...
|
|
46
|
+
ValueError: Value is None
|
|
47
|
+
|
|
48
|
+
See also:
|
|
49
|
+
- :func:`opt.expect`
|
|
50
|
+
- :func:`opt.unwrap_or`
|
|
51
|
+
- :func:`opt.unwrap_or_else`
|
|
52
|
+
"""
|
|
53
|
+
if value is None:
|
|
54
|
+
raise UnwrapError("Value is None")
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def unwrap_or[T](value: Optional[T], /, default: T) -> T:
|
|
59
|
+
"""
|
|
60
|
+
Unwrap a value that may be None. If the value is None, return the default
|
|
61
|
+
value.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: The optional value to unwrap.
|
|
65
|
+
default: The value to return if the optional value is None.
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
>>> opt.unwrap_or(0, 42)
|
|
69
|
+
0
|
|
70
|
+
>>> opt.unwrap_or(None, 42)
|
|
71
|
+
42
|
|
72
|
+
|
|
73
|
+
See also:
|
|
74
|
+
- :func:`opt.unwrap`
|
|
75
|
+
- :func:`opt.unwrap_or_else`
|
|
76
|
+
"""
|
|
77
|
+
return value if value is not None else default
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def unwrap_or_else[T](value: Optional[T], /, default_fn: Callable[[], T]) -> T:
|
|
81
|
+
"""
|
|
82
|
+
Unwrap a value that may be None. If the value is None, return the result of
|
|
83
|
+
calling the default function.
|
|
84
|
+
|
|
85
|
+
This may be used instead of :func:`opt.unwrap_or` when the default value is
|
|
86
|
+
expensive to compute or produces side effects.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
value: The optional value to unwrap.
|
|
90
|
+
default_fn: The function to call if the optional value is None.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
>>> opt.unwrap_or_else(0, lambda: 42)
|
|
94
|
+
0
|
|
95
|
+
>>> opt.unwrap_or_else(None, lambda: 42)
|
|
96
|
+
42
|
|
97
|
+
|
|
98
|
+
See also:
|
|
99
|
+
- :func:`opt.unwrap`
|
|
100
|
+
- :func:`opt.unwrap_or`
|
|
101
|
+
"""
|
|
102
|
+
return value if value is not None else default_fn()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def expect[T](value: Optional[T], /, message: str) -> T:
|
|
106
|
+
"""
|
|
107
|
+
Unwrap a value that may be None. If the value is None, raise a ValueError
|
|
108
|
+
with the given message.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
value: The optional value to unwrap.
|
|
112
|
+
message: The message to include in the ValueError if the value is None.
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
>>> opt.expect(42, "Value is None")
|
|
116
|
+
42
|
|
117
|
+
>>> opt.expect(None, "Value is None")
|
|
118
|
+
Traceback (most recent call last):
|
|
119
|
+
...
|
|
120
|
+
ValueError: Value is None
|
|
121
|
+
|
|
122
|
+
See also:
|
|
123
|
+
- :func:`opt.unwrap`
|
|
124
|
+
"""
|
|
125
|
+
if value is None:
|
|
126
|
+
raise UnwrapError(message)
|
|
127
|
+
return value
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@overload
|
|
131
|
+
def map[T, R](fn: Callable[[T], R], value: Optional[T], /) -> Optional[R]: ...
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@overload
|
|
135
|
+
def map[T1, T2, R](
|
|
136
|
+
fn: Callable[[T1, T2], R], value: Optional[T1], value2: Optional[T2], /
|
|
137
|
+
) -> Optional[R]: ...
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@overload
|
|
141
|
+
def map[T1, T2, T3, R](
|
|
142
|
+
fn: Callable[[T1, T2, T3], R],
|
|
143
|
+
value: Optional[T1],
|
|
144
|
+
value2: Optional[T2],
|
|
145
|
+
value3: Optional[T3],
|
|
146
|
+
/,
|
|
147
|
+
) -> Optional[R]: ...
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@overload
|
|
151
|
+
def map[T1, T2, T3, T4, R](
|
|
152
|
+
fn: Callable[[T1, T2, T3, T4], R],
|
|
153
|
+
x1: Optional[T1],
|
|
154
|
+
x2: Optional[T2],
|
|
155
|
+
x3: Optional[T3],
|
|
156
|
+
x4: Optional[T4],
|
|
157
|
+
/,
|
|
158
|
+
) -> Optional[R]: ...
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@overload
|
|
162
|
+
def map[T1, T2, T3, T4, T5, R](
|
|
163
|
+
fn: Callable[[T1, T2, T3, T4, T5], R],
|
|
164
|
+
x1: Optional[T1],
|
|
165
|
+
x2: Optional[T2],
|
|
166
|
+
x3: Optional[T3],
|
|
167
|
+
x4: Optional[T4],
|
|
168
|
+
x5: Optional[T5],
|
|
169
|
+
/,
|
|
170
|
+
) -> Optional[R]: ...
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@overload
|
|
174
|
+
def map[T1, T2, T3, T4, T5, T6, R](
|
|
175
|
+
fn: Callable[[T1, T2, T3, T4, T5, T6], R],
|
|
176
|
+
x1: Optional[T1],
|
|
177
|
+
x2: Optional[T2],
|
|
178
|
+
x3: Optional[T3],
|
|
179
|
+
x4: Optional[T4],
|
|
180
|
+
x5: Optional[T5],
|
|
181
|
+
x6: Optional[T6],
|
|
182
|
+
/,
|
|
183
|
+
) -> Optional[R]: ...
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@overload
|
|
187
|
+
def map[T1, T2, T3, T4, T5, T6, T7, R](
|
|
188
|
+
fn: Callable[[T1, T2, T3, T4, T5, T6, T7], R],
|
|
189
|
+
x1: Optional[T1],
|
|
190
|
+
x2: Optional[T2],
|
|
191
|
+
x3: Optional[T3],
|
|
192
|
+
x4: Optional[T4],
|
|
193
|
+
x5: Optional[T5],
|
|
194
|
+
x6: Optional[T6],
|
|
195
|
+
x7: Optional[T7],
|
|
196
|
+
/,
|
|
197
|
+
) -> Optional[R]: ...
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@overload
|
|
201
|
+
def map[T1, T2, T3, T4, T5, T6, T7, T8, R](
|
|
202
|
+
fn: Callable[[T1, T2, T3, T4, T5, T6, T7, T8], R],
|
|
203
|
+
x1: Optional[T1],
|
|
204
|
+
x2: Optional[T2],
|
|
205
|
+
x3: Optional[T3],
|
|
206
|
+
x4: Optional[T4],
|
|
207
|
+
x5: Optional[T5],
|
|
208
|
+
x6: Optional[T6],
|
|
209
|
+
x7: Optional[T7],
|
|
210
|
+
x8: Optional[T8],
|
|
211
|
+
/,
|
|
212
|
+
) -> Optional[R]: ...
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def map(fn: Any, *values: Any) -> Any:
|
|
216
|
+
"""
|
|
217
|
+
Apply the function to the values if none of them are None, otherwise return None.
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
fn:
|
|
222
|
+
The function to apply to the values if none of them are None.
|
|
223
|
+
x1, x2, ...:
|
|
224
|
+
The optional values to apply the function to.
|
|
225
|
+
It accepts any number of arguments (but only type checks up to 8).
|
|
226
|
+
|
|
227
|
+
Examples:
|
|
228
|
+
>>> opt.map(lambda x: x + 1, 41)
|
|
229
|
+
42
|
|
230
|
+
>>> opt.map(lambda x, y: x + y, 40, 2)
|
|
231
|
+
42
|
|
232
|
+
>>> opt.map(lambda x, y: x + y, 42, None)
|
|
233
|
+
None
|
|
234
|
+
"""
|
|
235
|
+
if any(value is None for value in values):
|
|
236
|
+
return None
|
|
237
|
+
return fn(*values)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@overload
|
|
241
|
+
def coalesce[T](values: Iterable[Optional[T]], /) -> Optional[T]: ...
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@overload
|
|
245
|
+
def coalesce[T](x: Optional[T], /, *rest: Optional[T]) -> Optional[T]: ...
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def coalesce(*values: Any) -> Any:
|
|
249
|
+
"""
|
|
250
|
+
Return the first value that is not None, or None if all values are None.
|
|
251
|
+
|
|
252
|
+
Examples:
|
|
253
|
+
>>> opt.coalesce(None, None, 42, 43, ...)
|
|
254
|
+
42
|
|
255
|
+
>>> opt.coalesce(None, None)
|
|
256
|
+
None
|
|
257
|
+
>>> opt.coalesce([None, None, 42, 43, ...])
|
|
258
|
+
42
|
|
259
|
+
"""
|
|
260
|
+
if len(values) == 1:
|
|
261
|
+
values = values[0]
|
|
262
|
+
for value in values:
|
|
263
|
+
if value is not None:
|
|
264
|
+
return value
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def values[T](seq: Iterable[Optional[T]], /) -> Iterable[T]:
|
|
269
|
+
"""
|
|
270
|
+
Return an iterable of the non-None values in the given sequence.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
seq: The sequence of options to filter.
|
|
274
|
+
|
|
275
|
+
Examples:
|
|
276
|
+
>>> list(opt.values([1, None, 2, None, 3]))
|
|
277
|
+
[1, 2, 3]
|
|
278
|
+
>>> list(opt.values([None, None]))
|
|
279
|
+
[]
|
|
280
|
+
"""
|
|
281
|
+
return (value for value in seq if value is not None)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@overload
|
|
285
|
+
def together[T1, T2](
|
|
286
|
+
x1: Optional[T1], x2: Optional[T2], /
|
|
287
|
+
) -> tuple[Optional[T1], Optional[T2]]: ...
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@overload
|
|
291
|
+
def together[T1, T2, T3](
|
|
292
|
+
x1: Optional[T1],
|
|
293
|
+
x2: Optional[T2],
|
|
294
|
+
x3: Optional[T3],
|
|
295
|
+
/,
|
|
296
|
+
) -> tuple[Optional[T1], Optional[T2], Optional[T3]]: ...
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@overload
|
|
300
|
+
def together[T1, T2, T3, T4](
|
|
301
|
+
x1: Optional[T1],
|
|
302
|
+
x2: Optional[T2],
|
|
303
|
+
x3: Optional[T3],
|
|
304
|
+
x4: Optional[T4],
|
|
305
|
+
/,
|
|
306
|
+
) -> tuple[Optional[T1], Optional[T2], Optional[T3], Optional[T4]]: ...
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@overload
|
|
310
|
+
def together[T1, T2, T3, T4, T5](
|
|
311
|
+
x1: Optional[T1],
|
|
312
|
+
x2: Optional[T2],
|
|
313
|
+
x3: Optional[T3],
|
|
314
|
+
x4: Optional[T4],
|
|
315
|
+
x5: Optional[T5],
|
|
316
|
+
/,
|
|
317
|
+
) -> tuple[Optional[T1], Optional[T2], Optional[T3], Optional[T4], Optional[T5]]: ...
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@overload
|
|
321
|
+
def together[T1, T2, T3, T4, T5, T6](
|
|
322
|
+
x1: Optional[T1],
|
|
323
|
+
x2: Optional[T2],
|
|
324
|
+
x3: Optional[T3],
|
|
325
|
+
x4: Optional[T4],
|
|
326
|
+
x5: Optional[T5],
|
|
327
|
+
x6: Optional[T6],
|
|
328
|
+
/,
|
|
329
|
+
) -> tuple[
|
|
330
|
+
Optional[T1], Optional[T2], Optional[T3], Optional[T4], Optional[T5], Optional[T6]
|
|
331
|
+
]: ...
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@overload
|
|
335
|
+
def together[T1, T2, T3, T4, T5, T6, T7](
|
|
336
|
+
x1: Optional[T1],
|
|
337
|
+
x2: Optional[T2],
|
|
338
|
+
x3: Optional[T3],
|
|
339
|
+
x4: Optional[T4],
|
|
340
|
+
x5: Optional[T5],
|
|
341
|
+
x6: Optional[T6],
|
|
342
|
+
x7: Optional[T7],
|
|
343
|
+
/,
|
|
344
|
+
) -> tuple[
|
|
345
|
+
Optional[T1],
|
|
346
|
+
Optional[T2],
|
|
347
|
+
Optional[T3],
|
|
348
|
+
Optional[T4],
|
|
349
|
+
Optional[T5],
|
|
350
|
+
Optional[T6],
|
|
351
|
+
Optional[T7],
|
|
352
|
+
]: ...
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@overload
|
|
356
|
+
def together[T1, T2, T3, T4, T5, T6, T7, T8](
|
|
357
|
+
x1: Optional[T1],
|
|
358
|
+
x2: Optional[T2],
|
|
359
|
+
x3: Optional[T3],
|
|
360
|
+
x4: Optional[T4],
|
|
361
|
+
x5: Optional[T5],
|
|
362
|
+
x6: Optional[T6],
|
|
363
|
+
x7: Optional[T7],
|
|
364
|
+
x8: Optional[T8],
|
|
365
|
+
/,
|
|
366
|
+
) -> tuple[
|
|
367
|
+
Optional[T1],
|
|
368
|
+
Optional[T2],
|
|
369
|
+
Optional[T3],
|
|
370
|
+
Optional[T4],
|
|
371
|
+
Optional[T5],
|
|
372
|
+
Optional[T6],
|
|
373
|
+
Optional[T7],
|
|
374
|
+
Optional[T8],
|
|
375
|
+
]: ...
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def together(*args: Any) -> Any:
|
|
379
|
+
"""
|
|
380
|
+
Return a tuple of nones if any argument is None, otherwise return the
|
|
381
|
+
arguments as a tuple.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
x1, x2, ...:
|
|
385
|
+
The optional values to check.
|
|
386
|
+
It accepts any number of arguments (but only type checks up to 8).
|
|
387
|
+
|
|
388
|
+
Examples:
|
|
389
|
+
>>> opt.together(1, 2, 3)
|
|
390
|
+
(1, 2, 3)
|
|
391
|
+
>>> opt.together(1, 2, None)
|
|
392
|
+
(None, None, None)
|
|
393
|
+
|
|
394
|
+
See also:
|
|
395
|
+
- :func:`zip`, which returns None if any argument is None instead of a tuple of nones.
|
|
396
|
+
"""
|
|
397
|
+
if any(arg is None for arg in args):
|
|
398
|
+
return tuple(None for _ in args)
|
|
399
|
+
return args
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@overload
|
|
403
|
+
def zip[T1, T2](x1: T1 | None, x2: T2 | None, /) -> tuple[T1, T2] | None: ...
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@overload
|
|
407
|
+
def zip[T1, T2, T3](
|
|
408
|
+
x1: Optional[T1],
|
|
409
|
+
x2: Optional[T2],
|
|
410
|
+
x3: Optional[T3],
|
|
411
|
+
/,
|
|
412
|
+
) -> Optional[tuple[T1, T2, T3]]: ...
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@overload
|
|
416
|
+
def zip[T1, T2, T3, T4](
|
|
417
|
+
x1: Optional[T1],
|
|
418
|
+
x2: Optional[T2],
|
|
419
|
+
x3: Optional[T3],
|
|
420
|
+
x4: Optional[T4],
|
|
421
|
+
/,
|
|
422
|
+
) -> Optional[tuple[T1, T2, T3, T4]]: ...
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@overload
|
|
426
|
+
def zip[T1, T2, T3, T4, T5](
|
|
427
|
+
x1: Optional[T1],
|
|
428
|
+
x2: Optional[T2],
|
|
429
|
+
x3: Optional[T3],
|
|
430
|
+
x4: Optional[T4],
|
|
431
|
+
x5: Optional[T5],
|
|
432
|
+
/,
|
|
433
|
+
) -> Optional[tuple[T1, T2, T3, T4, T5]]: ...
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@overload
|
|
437
|
+
def zip[T1, T2, T3, T4, T5, T6](
|
|
438
|
+
x1: Optional[T1],
|
|
439
|
+
x2: Optional[T2],
|
|
440
|
+
x3: Optional[T3],
|
|
441
|
+
x4: Optional[T4],
|
|
442
|
+
x5: Optional[T5],
|
|
443
|
+
x6: Optional[T6],
|
|
444
|
+
/,
|
|
445
|
+
) -> Optional[tuple[T1, T2, T3, T4, T5, T6]]: ...
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@overload
|
|
449
|
+
def zip[T1, T2, T3, T4, T5, T6, T7](
|
|
450
|
+
x1: Optional[T1],
|
|
451
|
+
x2: Optional[T2],
|
|
452
|
+
x3: Optional[T3],
|
|
453
|
+
x4: Optional[T4],
|
|
454
|
+
x5: Optional[T5],
|
|
455
|
+
x6: Optional[T6],
|
|
456
|
+
x7: Optional[T7],
|
|
457
|
+
/,
|
|
458
|
+
) -> Optional[tuple[T1, T2, T3, T4, T5, T6, T7]]: ...
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@overload
|
|
462
|
+
def zip[T1, T2, T3, T4, T5, T6, T7, T8](
|
|
463
|
+
x1: Optional[T1],
|
|
464
|
+
x2: Optional[T2],
|
|
465
|
+
x3: Optional[T3],
|
|
466
|
+
x4: Optional[T4],
|
|
467
|
+
x5: Optional[T5],
|
|
468
|
+
x6: Optional[T6],
|
|
469
|
+
x7: Optional[T7],
|
|
470
|
+
x8: Optional[T8],
|
|
471
|
+
/,
|
|
472
|
+
) -> Optional[tuple[T1, T2, T3, T4, T5, T6, T7, T8]]: ...
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def zip(*args: Any) -> Any:
|
|
476
|
+
"""
|
|
477
|
+
Return a tuple if no argument is None and None otherwise.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
x1, x2, ...:
|
|
481
|
+
The optional values to check.
|
|
482
|
+
It accepts any number of arguments (but only type checks up to 8).
|
|
483
|
+
|
|
484
|
+
Examples:
|
|
485
|
+
>>> opt.zip(1, 2, 3)
|
|
486
|
+
(1, 2, 3)
|
|
487
|
+
>>> opt.zip(1, 2, None)
|
|
488
|
+
None
|
|
489
|
+
|
|
490
|
+
See also:
|
|
491
|
+
- :func:`together`, which returns a tuple of nones if any argument is None instead of None.
|
|
492
|
+
"""
|
|
493
|
+
if any(arg is None for arg in args):
|
|
494
|
+
return None
|
|
495
|
+
return args
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def elements[T](value: T | None) -> Iterable[T]:
|
|
499
|
+
"""
|
|
500
|
+
Yield nothing or the optional value, if it exists.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
value: The optional value to yield.
|
|
504
|
+
|
|
505
|
+
Examples:
|
|
506
|
+
>>> list(opt.elements(42))
|
|
507
|
+
[42]
|
|
508
|
+
>>> list(opt.elements(None))
|
|
509
|
+
[]
|
|
510
|
+
"""
|
|
511
|
+
if value is not None:
|
|
512
|
+
yield value
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def iter[T](value: Iterable[T] | None) -> Iterable[T]:
|
|
516
|
+
"""
|
|
517
|
+
Yield nothing or the elements of an iterable.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
value: The optional iterable to yield from.
|
|
521
|
+
|
|
522
|
+
Examples:
|
|
523
|
+
>>> list(opt.iter([42]))
|
|
524
|
+
[42]
|
|
525
|
+
>>> list(opt.iter(None))
|
|
526
|
+
[]
|
|
527
|
+
"""
|
|
528
|
+
return iter(()) if value is None else iter(value)
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def call[**P, R](
|
|
532
|
+
fn: Optional[Callable[P, R]], *args: P.args, **kwargs: P.kwargs
|
|
533
|
+
) -> Optional[R]:
|
|
534
|
+
"""
|
|
535
|
+
Call the optional function with the given arguments or return None.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
fn: The optional function to call.
|
|
539
|
+
*args: The positional arguments to pass to the function.
|
|
540
|
+
**kwargs: The keyword arguments to pass to the function.
|
|
541
|
+
|
|
542
|
+
Examples:
|
|
543
|
+
>>> opt.call(lambda x: x + 1, 41)
|
|
544
|
+
42
|
|
545
|
+
>>> opt.call(None, 40, 2)
|
|
546
|
+
None
|
|
547
|
+
"""
|
|
548
|
+
return fn(*args, **kwargs) if fn is not None else None
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def getattr[T, R: type](
|
|
552
|
+
obj: Optional[T], attr: str, *, type: type[R] = _any(None)
|
|
553
|
+
) -> Optional[R]:
|
|
554
|
+
"""
|
|
555
|
+
Get the attribute of an object if it is not None, otherwise return None.
|
|
556
|
+
|
|
557
|
+
It raises an AttributeError if the attribute do not exist.
|
|
558
|
+
|
|
559
|
+
You can optionally specify the expected result type to get better type checking.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
obj: The optional object to get the attribute from.
|
|
563
|
+
attr: The name of the attribute to get.
|
|
564
|
+
type: The expected type of the attribute (optional).
|
|
565
|
+
|
|
566
|
+
Examples:
|
|
567
|
+
>>> opt.getattr(42, "real")
|
|
568
|
+
42
|
|
569
|
+
>>> opt.getattr(None, "imag")
|
|
570
|
+
None
|
|
571
|
+
"""
|
|
572
|
+
if obj is None:
|
|
573
|
+
return None
|
|
574
|
+
|
|
575
|
+
value = builtins.getattr(obj, attr)
|
|
576
|
+
if type is not None and not isinstance(value, type):
|
|
577
|
+
msg = (
|
|
578
|
+
f"Expected attribute {attr} to be of type {type}, "
|
|
579
|
+
f"got {builtins.type(value)}"
|
|
580
|
+
)
|
|
581
|
+
raise TypeError(msg)
|
|
582
|
+
return value # type: ignore
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: optionz
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Some utility functions for Optional values.
|
|
5
|
+
Project-URL: Homepage, http://github.com/fabiomacedomendes/optionz
|
|
6
|
+
Project-URL: Repository, http://github.com/fabiomacedomendes/optionz
|
|
7
|
+
Author-email: Fábio Macêdo Mendes <fabiomacedomendes@gmail.com>
|
|
8
|
+
Maintainer-email: Fábio Macêdo Mendes <fabiomacedomendes@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.13
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Optionz
|
|
23
|
+
|
|
24
|
+
A nullable type is needed when there can be an absence of a value. Python uses
|
|
25
|
+
`None` to represent the empty value and `Optional[T]`, i.e., `T | None` to
|
|
26
|
+
represent a nullable `T`. The other option is to use exceptions, but they are
|
|
27
|
+
not expressed as values in the type system, which can be brittle and error-prone.
|
|
28
|
+
|
|
29
|
+
Python approach is a compromise between the "million dollar mistake",
|
|
30
|
+
in which `null` is a valid member of every type and the more structured
|
|
31
|
+
`Maybe[T]`, that models nullables as a tagged union of `Just[T]` and `Nothing`.
|
|
32
|
+
|
|
33
|
+
This library provides some utility functions for working with `Optional` values
|
|
34
|
+
in a more functional style, inspired by the `Maybe` monad in Haskell and Rust's
|
|
35
|
+
equivalent `Option` type.
|
|
36
|
+
|
|
37
|
+
The main philosophy is that we *do not* want to introduce a new type like
|
|
38
|
+
[returns](https://returns.readthedocs.io/) and other similar libraries.
|
|
39
|
+
Instead, we want to provide a similar functionality using plain `Optional` values
|
|
40
|
+
so that your code can adopt it incrementally without feeling like an alien in
|
|
41
|
+
the Python ecosystem.
|
|
42
|
+
|
|
43
|
+
As much as I like functional programming and the `Maybe` monad, I think Python's
|
|
44
|
+
approach is fine and offers most of the same static guarantees. The crucial
|
|
45
|
+
difference is that `Maybe` is a **tagged union** of two types and Python's
|
|
46
|
+
`Optional` is a **union of sets**. They behave mostly the same, but the latter do
|
|
47
|
+
not allow nesting: `Optional[Optional[T]]` flattens to `Optional[T]`, while
|
|
48
|
+
`Maybe[Maybe[T]]` is a whole new type. This is a difference that rarely
|
|
49
|
+
matters in practice, and I don't any anyone is clearly superior to the other.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
Install Optionz using pip/uv/poetry whatever you like. For example:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install optionz
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Optionz consists of a single file, so you can also just copy `opt.py` to your
|
|
61
|
+
project and import it from there. It do not define any new type so there is
|
|
62
|
+
no conflict with code that import vs ones that vendorize it.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
Import `opt` and use the functions as needed.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import opt
|
|
71
|
+
|
|
72
|
+
opt.unwrap(42) # 42
|
|
73
|
+
opt.unwrap(None) # raises ValueError
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Documentation
|
|
77
|
+
|
|
78
|
+
The documentation is available at https://optionz.rtfd.io/ and includes more
|
|
79
|
+
examples and explanations of the functions provided by the library.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
Optionz is licensed under the MIT License. See [LICENSE](LICENSE) for more details.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
opt.py,sha256=B86FCvpiW1jOE0yetUo3IAHYggTv8rrM7-RvwDA8cmY,13032
|
|
2
|
+
optionz-0.1.0.dist-info/METADATA,sha256=L-B-Mt1egTbfhTBhQqfr69fq2z-c6WtSXcxDJ25L_74,3214
|
|
3
|
+
optionz-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
4
|
+
optionz-0.1.0.dist-info/licenses/LICENSE,sha256=H_RNRyTRIzs8yhgeBUPYa1U1MyJZUZtoWBBSDVAhE-c,1079
|
|
5
|
+
optionz-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Fábio Macêdo Mendes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|