pythonic-fp-gadgets 4.0.2__py3-none-any.whl → 4.0.4__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.
- pythonic_fp/gadgets/__init__.py +28 -25
- pythonic_fp/gadgets/__init__.pyi +2 -2
- pythonic_fp/gadgets/box.py +97 -34
- pythonic_fp/gadgets/sentinels/__init__.py +15 -4
- pythonic_fp/gadgets/sentinels/flavored.py +53 -28
- pythonic_fp/gadgets/sentinels/novalue.py +124 -60
- pythonic_fp/gadgets/sentinels/novalue.pyi +2 -0
- pythonic_fp/gadgets/wrap.py +99 -24
- {pythonic_fp_gadgets-4.0.2.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/METADATA +13 -11
- pythonic_fp_gadgets-4.0.4.dist-info/RECORD +17 -0
- pythonic_fp_gadgets-4.0.2.dist-info/RECORD +0 -17
- {pythonic_fp_gadgets-4.0.2.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/WHEEL +0 -0
- {pythonic_fp_gadgets-4.0.2.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/licenses/LICENSE +0 -0
pythonic_fp/gadgets/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
16
|
Gadgets
|
|
17
|
-
|
|
17
|
+
=======
|
|
18
18
|
|
|
19
19
|
.. admonition:: Collection of mostly self-contained functions and classes
|
|
20
20
|
|
|
21
21
|
- Functions and classes which could go multiple places or have
|
|
22
22
|
no good place to go.
|
|
23
23
|
- Self-contained with minimal dependencies.
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
- No pythonic_fp dependencies at all.
|
|
25
26
|
|
|
26
27
|
"""
|
|
27
28
|
|
|
@@ -31,31 +32,33 @@ from inspect import getmro
|
|
|
31
32
|
__all__ = ['first_common_ancestor', 'iterate_over_arguments']
|
|
32
33
|
|
|
33
34
|
__author__ = 'Geoffrey R. Scheller'
|
|
34
|
-
__copyright__ = 'Copyright (c) 2023-
|
|
35
|
+
__copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
|
|
35
36
|
__license__ = 'Apache License 2.0'
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
39
|
-
"""
|
|
40
|
-
|
|
40
|
+
"""
|
|
41
|
+
.. admonition:: Least upper bound
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
Find the least upper bound in the inheritance graph
|
|
44
|
+
of two classes.
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
:param cls1: A class in the inheritance hierarchy.
|
|
47
|
+
:param cls2: A class in the inheritance hierarchy.
|
|
48
|
+
:returns: First common ancestor based on ``getmro`` order.
|
|
49
|
+
:raises TypeError: Raised when no common ancestor exists, or when
|
|
50
|
+
not caught when raised by ``inspect.getmro``.
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
- type 'bool' is not an acceptable base type
|
|
52
|
+
.. warning::
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
This function can fail with a TypeError. Some error messages
|
|
55
|
+
seen are
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
:returns: First common ancestor based on getmro order.
|
|
56
|
-
:raises TypeError: Raised when no common ancestor or not
|
|
57
|
-
caught when raised by ``inspect.getmro``.
|
|
57
|
+
- multiple bases have instance lay-out conflict
|
|
58
|
+
- type 'bool' is not an acceptable base type
|
|
58
59
|
|
|
60
|
+
This happens frequently when the function is given
|
|
61
|
+
Python builtin types or in multiple inheritance situations.
|
|
59
62
|
"""
|
|
60
63
|
if issubclass(cls1, cls2):
|
|
61
64
|
return cls2
|
|
@@ -69,17 +72,17 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
def iterate_over_arguments[A](*args: A) -> Iterator[A]:
|
|
72
|
-
"""
|
|
75
|
+
"""
|
|
76
|
+
.. admonition:: Iterate over arguments.
|
|
73
77
|
|
|
74
|
-
|
|
78
|
+
Function returning an iterator over its arguments.
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
:param args: Objects to iterate over.
|
|
81
|
+
:returns: An iterator of the function's arguments.
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
- maybe in the C world
|
|
83
|
+
.. note::
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
:returns: An iterator of the functions arguments.
|
|
85
|
+
Does not create a Python object to iterate over.
|
|
83
86
|
|
|
84
87
|
"""
|
|
85
88
|
yield from args
|
pythonic_fp/gadgets/__init__.pyi
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from collections.abc import Iterator
|
|
2
2
|
|
|
3
|
-
__all__ = ['
|
|
3
|
+
__all__ = ['first_common_ancestor', 'iterate_over_arguments']
|
|
4
4
|
|
|
5
|
-
def iterate_over_arguments[A](*args: A) -> Iterator[A]: ...
|
|
6
5
|
def first_common_ancestor(cls1: type, cls2: type) -> type: ...
|
|
6
|
+
def iterate_over_arguments[A](*args: A) -> Iterator[A]: ...
|
pythonic_fp/gadgets/box.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,29 +12,30 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
__all__ = ['Box']
|
|
16
|
-
|
|
17
15
|
from collections.abc import Callable, Iterator
|
|
18
16
|
from typing import cast, Final, overload
|
|
19
17
|
|
|
18
|
+
__all__ = ['Box']
|
|
19
|
+
|
|
20
20
|
type _Sentinel = object
|
|
21
21
|
_sentinel: Final[_Sentinel] = object()
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class Box[T]:
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
.. note::
|
|
25
|
+
"""
|
|
26
|
+
.. admonition:: Box
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
- ``Box[T]()``: creates empty container
|
|
28
|
+
Container holding at most one item of a given type.
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
could be ``None`` or even ``Never``.
|
|
30
|
+
.. tip::
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
Objects of the ``Box`` type
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
- Are truthy if not empty.
|
|
35
|
+
- Can be combined with other iterators before being filled.
|
|
36
|
+
- Can be used like a promise.
|
|
37
|
+
- Can be used in Python match statements.
|
|
38
|
+
- Threadsafe
|
|
38
39
|
|
|
39
40
|
"""
|
|
40
41
|
__slots__ = ('_item',)
|
|
@@ -47,34 +48,57 @@ class Box[T]:
|
|
|
47
48
|
|
|
48
49
|
def __init__(self, item: T | _Sentinel = _sentinel) -> None:
|
|
49
50
|
"""
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
.. admonition:: Initializer
|
|
52
|
+
|
|
53
|
+
Initialize ``Box`` with 0 or 1 items.
|
|
54
|
+
|
|
55
|
+
:param item: An optional initial ``item`` for the ``Box``.
|
|
56
|
+
|
|
52
57
|
"""
|
|
53
58
|
self._item = item
|
|
54
59
|
|
|
55
60
|
def __bool__(self) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
.. admonition:: Bool
|
|
63
|
+
|
|
64
|
+
Truthy if not empty.
|
|
65
|
+
|
|
66
|
+
"""
|
|
56
67
|
return self._item is not _sentinel
|
|
57
68
|
|
|
58
69
|
def __iter__(self) -> Iterator[T]:
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
"""
|
|
71
|
+
.. admonition:: Iterability
|
|
61
72
|
|
|
62
|
-
|
|
73
|
+
Iterates boxed item.
|
|
74
|
+
|
|
75
|
+
"""
|
|
63
76
|
if self:
|
|
64
|
-
|
|
65
|
-
return 'Box()'
|
|
77
|
+
yield cast(T, self._item)
|
|
66
78
|
|
|
67
79
|
def __len__(self) -> int:
|
|
80
|
+
"""
|
|
81
|
+
.. admonition:: Length
|
|
82
|
+
|
|
83
|
+
- 1 if ``Box`` contains an item
|
|
84
|
+
- 0 if ``Box`` is empty
|
|
85
|
+
|
|
86
|
+
:returns: The number of items currently in the ``Box``.
|
|
87
|
+
|
|
88
|
+
"""
|
|
68
89
|
return 1 if self else 0
|
|
69
90
|
|
|
70
91
|
def __eq__(self, other: object) -> bool:
|
|
71
92
|
"""
|
|
72
|
-
|
|
93
|
+
.. admonition:: Equality comparison
|
|
94
|
+
|
|
95
|
+
Efficiently compare ``Box`` to another object.
|
|
73
96
|
|
|
74
|
-
:param other: The object to be compared
|
|
75
|
-
:returns: ``True`` if ``other`` is
|
|
97
|
+
:param other: The object to be compared.
|
|
98
|
+
:returns: ``True`` if ``other`` is another ``Box`` and contains
|
|
76
99
|
an object which compares as equal to the object
|
|
77
100
|
contained in the ``Box``, otherwise ``False``.
|
|
101
|
+
|
|
78
102
|
"""
|
|
79
103
|
if not isinstance(other, type(self)):
|
|
80
104
|
return False
|
|
@@ -85,16 +109,34 @@ class Box[T]:
|
|
|
85
109
|
return True
|
|
86
110
|
return False
|
|
87
111
|
|
|
112
|
+
def __repr__(self) -> str:
|
|
113
|
+
"""
|
|
114
|
+
.. admonition:: Representation string
|
|
115
|
+
|
|
116
|
+
Construct string 'Box()' if empty, otherwise 'Box(item_repr)'
|
|
117
|
+
where ``item_repr = repr(item)`` for the currently contained
|
|
118
|
+
item.
|
|
119
|
+
|
|
120
|
+
:returns: A string to reproduce the current state of the ``Box``.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
if self:
|
|
124
|
+
return 'Box(' + repr(self._item) + ')'
|
|
125
|
+
return 'Box()'
|
|
126
|
+
|
|
88
127
|
@overload
|
|
89
128
|
def get(self) -> T: ...
|
|
90
129
|
@overload
|
|
91
130
|
def get(self, alt: T) -> T: ...
|
|
92
131
|
|
|
93
132
|
def get(self, alt: T | _Sentinel = _sentinel) -> T:
|
|
94
|
-
"""
|
|
95
|
-
|
|
133
|
+
"""
|
|
134
|
+
.. admonition:: Get
|
|
135
|
+
|
|
136
|
+
Return the boxed item, if it exists, otherwise
|
|
137
|
+
an alternate item, if given.
|
|
96
138
|
|
|
97
|
-
:param alt: An optional item of type ``T`` to return
|
|
139
|
+
:param alt: An optional item of type ``T`` to return
|
|
98
140
|
if the ``Box`` is empty.
|
|
99
141
|
:returns: Contents of ``Box`` or an alternate item, if given,
|
|
100
142
|
when the ``Box`` is empty.
|
|
@@ -110,7 +152,10 @@ class Box[T]:
|
|
|
110
152
|
return cast(T, alt)
|
|
111
153
|
|
|
112
154
|
def pop(self) -> T:
|
|
113
|
-
"""
|
|
155
|
+
"""
|
|
156
|
+
.. admonition:: Pop
|
|
157
|
+
|
|
158
|
+
Pop item from ``Box`` if not empty.
|
|
114
159
|
|
|
115
160
|
:returns: The item contained in the ``Box``.
|
|
116
161
|
:raises ValueError: If Box is empty.
|
|
@@ -124,7 +169,10 @@ class Box[T]:
|
|
|
124
169
|
return popped
|
|
125
170
|
|
|
126
171
|
def push(self, item: T) -> None:
|
|
127
|
-
"""
|
|
172
|
+
"""
|
|
173
|
+
.. admonition:: Push
|
|
174
|
+
|
|
175
|
+
Push an item into ``Box`` if empty.
|
|
128
176
|
|
|
129
177
|
:param item: Item to push into the empty ``Box``.
|
|
130
178
|
:raises ValueError: If ``Box`` is not empty.
|
|
@@ -138,15 +186,24 @@ class Box[T]:
|
|
|
138
186
|
return None
|
|
139
187
|
|
|
140
188
|
def put(self, item: T) -> None:
|
|
141
|
-
"""
|
|
189
|
+
"""
|
|
190
|
+
.. admonition:: Put
|
|
191
|
+
|
|
192
|
+
Put an item in the Box. Discard any previous contents.
|
|
193
|
+
|
|
194
|
+
"""
|
|
142
195
|
self._item = item
|
|
143
196
|
|
|
144
197
|
def exchange(self, new_item: T) -> T:
|
|
145
|
-
"""
|
|
198
|
+
"""
|
|
199
|
+
.. admonition:: Exchange
|
|
200
|
+
|
|
201
|
+
Exchange an item with what is in the Box.
|
|
146
202
|
|
|
147
203
|
:param ``new_item``: New item to exchange for current item.
|
|
148
204
|
:returns: Original contents of the ``Box``.
|
|
149
205
|
:raises ValueError: If Box is empty.
|
|
206
|
+
|
|
150
207
|
"""
|
|
151
208
|
if self._item is _sentinel:
|
|
152
209
|
msg = 'Box: Trying to exchange items from an empty Box'
|
|
@@ -156,11 +213,14 @@ class Box[T]:
|
|
|
156
213
|
return popped
|
|
157
214
|
|
|
158
215
|
def map[U](self, f: Callable[[T], U]) -> 'Box[U]':
|
|
159
|
-
"""
|
|
160
|
-
|
|
216
|
+
"""
|
|
217
|
+
.. admonition:: Map
|
|
218
|
+
|
|
219
|
+
Map function ``f`` over contents. We need to return a new
|
|
220
|
+
instance since the type of Box can change.
|
|
161
221
|
|
|
162
222
|
:param f: Mapping function.
|
|
163
|
-
:returns:
|
|
223
|
+
:returns: New instance.
|
|
164
224
|
|
|
165
225
|
"""
|
|
166
226
|
if self._item is _sentinel:
|
|
@@ -168,10 +228,13 @@ class Box[T]:
|
|
|
168
228
|
return Box(f(cast(T, self._item)))
|
|
169
229
|
|
|
170
230
|
def bind[U](self, f: Callable[[T], 'Box[U]']) -> 'Box[U]':
|
|
171
|
-
"""
|
|
231
|
+
"""
|
|
232
|
+
.. admonition:: Bind
|
|
233
|
+
|
|
234
|
+
Flatmap ``Box`` with function ``f``.
|
|
172
235
|
|
|
173
236
|
:param f: Binding function.
|
|
174
|
-
:returns:
|
|
237
|
+
:returns: New instance.
|
|
175
238
|
|
|
176
239
|
"""
|
|
177
240
|
if self._item is _sentinel:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
"""
|
|
16
|
+
sentinels
|
|
17
|
+
---------
|
|
18
|
+
|
|
19
|
+
.. admonition:: Sentinel values
|
|
20
|
+
|
|
21
|
+
- Singletons
|
|
22
|
+
- Threadsafe
|
|
23
|
+
- Useful for both
|
|
24
|
+
|
|
25
|
+
- referenced objects
|
|
26
|
+
- hidden implementation details
|
|
27
|
+
|
|
28
|
+
"""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,29 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""
|
|
16
|
-
.. admonition:: Sentinel values labeled by different (hashable) flavors.
|
|
17
|
-
|
|
18
|
-
When different flavors of the truth are needed.
|
|
19
|
-
|
|
20
|
-
.. note::
|
|
21
|
-
|
|
22
|
-
Can be compared using ``==`` and ``!=``. A flavored sentinel
|
|
23
|
-
value always equals itself and never equals anything else,
|
|
24
|
-
especially other flavored sentinel values.
|
|
25
|
-
|
|
26
|
-
Useful for union types where ``Sentinel[H]`` is one of the
|
|
27
|
-
types making up the union.
|
|
28
|
-
|
|
29
|
-
To ensure that reference equality is used, put the known
|
|
30
|
-
sentinel value first in the comparison.
|
|
31
|
-
|
|
32
|
-
.. note::
|
|
33
|
-
|
|
34
|
-
Threadsafe.
|
|
35
|
-
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
15
|
import threading
|
|
39
16
|
from typing import ClassVar, final, Hashable
|
|
40
17
|
|
|
@@ -43,12 +20,34 @@ __all__ = ['Sentinel']
|
|
|
43
20
|
|
|
44
21
|
@final
|
|
45
22
|
class Sentinel[H: Hashable]:
|
|
23
|
+
"""
|
|
24
|
+
.. admonition:: Sentinel
|
|
25
|
+
|
|
26
|
+
Sentinel values labeled by different (hashable) flavors.
|
|
27
|
+
|
|
28
|
+
.. note::
|
|
29
|
+
|
|
30
|
+
- Useful for union types.
|
|
31
|
+
- A flavored ``Sentinel`` value always equals itself
|
|
32
|
+
and never equals anything else, especially other
|
|
33
|
+
flavored sentinel values.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
46
37
|
__slots__ = ('_flavor',)
|
|
47
38
|
|
|
48
39
|
_flavors: 'dict[H, Sentinel[H]]' = {}
|
|
49
40
|
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
50
41
|
|
|
51
42
|
def __new__(cls, flavor: H) -> 'Sentinel[H]':
|
|
43
|
+
"""
|
|
44
|
+
.. admonition:: new
|
|
45
|
+
|
|
46
|
+
:param flavor: Hashable value determining which
|
|
47
|
+
flavored ``Sentinel`` to return.
|
|
48
|
+
:returns: The ``Sentinel(flavor)`` singleton instance.
|
|
49
|
+
|
|
50
|
+
"""
|
|
52
51
|
if flavor not in cls._flavors:
|
|
53
52
|
with cls._lock:
|
|
54
53
|
if flavor not in cls._flavors:
|
|
@@ -57,18 +56,44 @@ class Sentinel[H: Hashable]:
|
|
|
57
56
|
|
|
58
57
|
def __init__(self, flavor: H) -> None:
|
|
59
58
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
.. admonition:: init
|
|
60
|
+
|
|
61
|
+
:param flavor: Hashable value to initially cache the flavor
|
|
62
|
+
:type flavor: ``H: Hashable``
|
|
63
|
+
|
|
63
64
|
"""
|
|
64
65
|
if not hasattr(self, '_flavor'):
|
|
65
66
|
self._flavor = flavor
|
|
66
67
|
|
|
67
68
|
def __repr__(self) -> str:
|
|
69
|
+
"""
|
|
70
|
+
.. admonition:: repr string
|
|
71
|
+
|
|
72
|
+
Construct string 'Sentinel(flavor)' where the flavor
|
|
73
|
+
is displayed with ``repr()``.
|
|
74
|
+
|
|
75
|
+
:returns: A string to reproduce the flavored sentinel.
|
|
76
|
+
|
|
77
|
+
"""
|
|
68
78
|
return "Sentinel('" + repr(self._flavor) + "')"
|
|
69
79
|
|
|
80
|
+
def __str__(self) -> str:
|
|
81
|
+
"""
|
|
82
|
+
.. admonition:: user string
|
|
83
|
+
|
|
84
|
+
Construct string 'Sentinel(flavor)' where the flavor
|
|
85
|
+
is displayed with ``str()``.
|
|
86
|
+
|
|
87
|
+
:returns: A string meaningful to an end user.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
return "Sentinel('" + str(self._flavor) + "')"
|
|
91
|
+
|
|
70
92
|
def flavor(self) -> H:
|
|
71
93
|
"""
|
|
72
|
-
|
|
94
|
+
.. admonition:: get flavor
|
|
95
|
+
|
|
96
|
+
:returns: The sentinel's flavor.
|
|
97
|
+
|
|
73
98
|
"""
|
|
74
99
|
return self._flavor
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,112 +12,176 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
not potentially, missing value.
|
|
15
|
+
import threading
|
|
16
|
+
from typing import ClassVar, final
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
__all__ = ['NoValue']
|
|
20
19
|
|
|
21
|
-
While ``None`` and ``()`` are frequently used as sentinel values,
|
|
22
|
-
I prefer to think of them as
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
@final
|
|
22
|
+
class NoValue:
|
|
23
|
+
"""
|
|
24
|
+
.. admonition:: missing value
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
Singleton class representing an actual, not
|
|
27
|
+
potential, missing value.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
While ``None`` and ``()`` are frequently used as sentinel values,
|
|
30
|
+
I prefer to think of them as
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
- ``None`` as returns, or returned, no values.
|
|
33
|
+
- ``()`` as an empty, possibly typed, iterable collection.
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
y: int | NoValue
|
|
35
|
+
.. important::
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
as equal. If one or both of theses values are missing, then what is
|
|
38
|
-
there to compare?
|
|
37
|
+
Given variables
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
.. code:: python
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
+===========+===========+========+========+
|
|
45
|
-
| NoValue() | false | false | false |
|
|
46
|
-
+-----------+-----------+--------+--------+
|
|
47
|
-
| 42 | false | true | false |
|
|
48
|
-
+-----------+-----------+--------+--------+
|
|
49
|
-
| 57 | false | false | true |
|
|
50
|
-
+-----------+-----------+--------+--------+
|
|
41
|
+
x: int | NoValue
|
|
42
|
+
y: int | NoValue
|
|
51
43
|
|
|
52
|
-
|
|
44
|
+
Equality between ``x`` and ``y`` means both values exist
|
|
45
|
+
and compare as equal.
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
.. table:: ``x == y``
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
+-------------------+-------------------+------------+------------+
|
|
50
|
+
| | | | |
|
|
51
|
+
+===================+===================+============+============+
|
|
52
|
+
| | ``y = NoValue()`` | ``y = 42`` | ``y = 57`` |
|
|
53
|
+
+-------------------+-------------------+------------+------------+
|
|
54
|
+
| ``x = NoValue()`` | ``False`` | ``False`` | ``False`` |
|
|
55
|
+
+-------------------+-------------------+------------+------------+
|
|
56
|
+
| ``x = 42`` | ``False`` | ``True`` | ``False`` |
|
|
57
|
+
+-------------------+-------------------+------------+------------+
|
|
58
|
+
| ``x = 57`` | ``False`` | ``False`` | ``True`` |
|
|
59
|
+
+-------------------+-------------------+------------+------------+
|
|
65
60
|
|
|
66
|
-
..
|
|
61
|
+
.. table:: ``x != y``
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
+-------------------+-------------------+------------+------------+
|
|
64
|
+
| | | | |
|
|
65
|
+
+===================+===================+============+============+
|
|
66
|
+
| | ``y = NoValue()`` | ``y = 42`` | ``y = 57`` |
|
|
67
|
+
+-------------------+-------------------+------------+------------+
|
|
68
|
+
| ``x = NoValue()`` | ``False`` | ``False`` | ``False`` |
|
|
69
|
+
+-------------------+-------------------+------------+------------+
|
|
70
|
+
| ``x = 42`` | ``False`` | ``False`` | ``True`` |
|
|
71
|
+
+-------------------+-------------------+------------+------------+
|
|
72
|
+
| ``x = 57`` | ``False`` | ``True`` | ``False`` |
|
|
73
|
+
+-------------------+-------------------+------------+------------+
|
|
71
74
|
|
|
72
|
-
..
|
|
75
|
+
.. warning::
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
- use ``==`` or ``!=`` only in value comparisons
|
|
78
|
+
- use ``is`` and ``is not`` to identity the ``NoValue()``
|
|
79
|
+
singleton itself
|
|
75
80
|
|
|
76
|
-
.. tip::
|
|
81
|
+
.. tip::
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
Use in a union type when creating "optional" arguments
|
|
84
|
+
to functions and methods.
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
To help ensure the abstraction stays a hidden implementation
|
|
87
|
+
detail and does not leak out into user code,
|
|
82
88
|
|
|
83
|
-
|
|
84
|
-
- Use ``@overload`` to keep the NoValue type out of documentation and IDEs.
|
|
85
|
-
|
|
86
|
-
"""
|
|
87
|
-
import threading
|
|
88
|
-
from typing import ClassVar, final
|
|
89
|
+
- Do not export the sentinel value.
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
- A new reference can always be generated via ``NoValue()``.
|
|
91
92
|
|
|
93
|
+
- Use ``@overload`` to keep the ``NoValue`` type out of
|
|
94
|
+
documentation and IDEs.
|
|
95
|
+
|
|
96
|
+
"""
|
|
92
97
|
|
|
93
|
-
@final
|
|
94
|
-
class NoValue():
|
|
95
98
|
__slots__ = ()
|
|
96
99
|
|
|
97
100
|
_instance: 'ClassVar[NoValue | None]' = None
|
|
98
101
|
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
102
|
+
_hash: ClassVar[int] = 0
|
|
99
103
|
|
|
100
104
|
def __new__(cls) -> 'NoValue':
|
|
101
105
|
"""
|
|
102
|
-
|
|
106
|
+
.. admonition:: new
|
|
107
|
+
|
|
108
|
+
:returns: The ``NoValue()`` singleton instance.
|
|
109
|
+
|
|
103
110
|
"""
|
|
104
111
|
if cls._instance is None:
|
|
105
112
|
with cls._lock:
|
|
106
113
|
if cls._instance is None:
|
|
114
|
+
cls._hash = id(cls)
|
|
107
115
|
cls._instance = super().__new__(cls)
|
|
108
116
|
return cls._instance
|
|
109
117
|
|
|
118
|
+
def __hash__(self) -> int:
|
|
119
|
+
"""
|
|
120
|
+
.. admonition:: hash
|
|
121
|
+
|
|
122
|
+
:returns: The singleton's unique integer hash value.
|
|
123
|
+
"""
|
|
124
|
+
return type(self)._hash
|
|
125
|
+
|
|
110
126
|
def __repr__(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
.. admonition:: repr string
|
|
129
|
+
|
|
130
|
+
:returns: The string 'NoValue()'.
|
|
131
|
+
|
|
132
|
+
"""
|
|
111
133
|
return 'NoValue()'
|
|
112
134
|
|
|
135
|
+
def __bool__(self) -> bool:
|
|
136
|
+
"""
|
|
137
|
+
.. admonition:: bool
|
|
138
|
+
|
|
139
|
+
Always falsy.
|
|
140
|
+
|
|
141
|
+
:returns: False
|
|
142
|
+
|
|
143
|
+
.. tip
|
|
144
|
+
|
|
145
|
+
Can be used to provide a fallback value when used
|
|
146
|
+
with Python shortcut logic.
|
|
147
|
+
|
|
148
|
+
.. code:: python
|
|
149
|
+
|
|
150
|
+
result: str | NoValue = NoValue()
|
|
151
|
+
if predicate(x):
|
|
152
|
+
result = 'some non-empty string'
|
|
153
|
+
value = result or 'fallback string'
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
return False
|
|
157
|
+
|
|
113
158
|
def __eq__(self, other: object) -> bool:
|
|
114
159
|
"""
|
|
115
|
-
|
|
160
|
+
.. admonition:: Equality comparison
|
|
161
|
+
|
|
162
|
+
:param other: The object to be compared.
|
|
163
|
+
:returns: ``False`` even if compared to itself.
|
|
164
|
+
|
|
165
|
+
.. warning::
|
|
166
|
+
|
|
167
|
+
- non-standard comparison semantics
|
|
168
|
+
- always returns ``False``
|
|
169
|
+
- if one or both values are missing,
|
|
170
|
+
then what is there to compare?
|
|
171
|
+
|
|
116
172
|
"""
|
|
117
173
|
return False
|
|
118
174
|
|
|
119
175
|
def __ne__(self, other: object) -> bool:
|
|
120
176
|
"""
|
|
121
|
-
|
|
177
|
+
.. admonition:: not equal
|
|
178
|
+
|
|
179
|
+
:returns: ``False``
|
|
180
|
+
|
|
181
|
+
.. warning::
|
|
182
|
+
|
|
183
|
+
- non-standard comparison semantics
|
|
184
|
+
- always returns ``False``
|
|
185
|
+
|
|
122
186
|
"""
|
|
123
187
|
return False
|
pythonic_fp/gadgets/wrap.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,6 +11,15 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
.. admonition:: Module wrap
|
|
16
|
+
|
|
17
|
+
Wrap objects in ways to make them "immutable."
|
|
18
|
+
|
|
19
|
+
- Class ``Wrap``: wrap an object
|
|
20
|
+
- Class ``HWrap``: wrap a hashable object
|
|
21
|
+
|
|
22
|
+
"""
|
|
14
23
|
|
|
15
24
|
__all__ = ['Wrap', 'HWrap']
|
|
16
25
|
|
|
@@ -18,37 +27,71 @@ from collections.abc import Callable, Iterator, Hashable
|
|
|
18
27
|
|
|
19
28
|
|
|
20
29
|
class Wrap[T]():
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
.. tip::
|
|
30
|
+
"""
|
|
31
|
+
.. admonition:: Wrap object
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
Immutablely wrap exactly one value of a
|
|
34
|
+
given type. ``Wrap`` objects can be used
|
|
35
|
+
in Python match statements.
|
|
26
36
|
|
|
27
37
|
"""
|
|
28
38
|
__slots__ = ('_item',)
|
|
29
39
|
__match_args__ = ('_item',)
|
|
30
40
|
|
|
31
41
|
def __init__(self, item: T) -> None:
|
|
42
|
+
"""
|
|
43
|
+
.. admonition:: Initializer
|
|
44
|
+
|
|
45
|
+
Initialize ``Wrap`` with 1 required item.
|
|
46
|
+
|
|
47
|
+
:param item: Item to be wrapped.
|
|
48
|
+
|
|
49
|
+
"""
|
|
32
50
|
self._item = item
|
|
33
51
|
|
|
34
52
|
def __bool__(self) -> bool:
|
|
53
|
+
"""
|
|
54
|
+
.. admonition:: Bool
|
|
55
|
+
|
|
56
|
+
Truthiness same as wrapped object.
|
|
57
|
+
|
|
58
|
+
"""
|
|
35
59
|
return bool(self._item)
|
|
36
60
|
|
|
37
61
|
def __iter__(self) -> Iterator[T]:
|
|
62
|
+
"""
|
|
63
|
+
.. admonition:: Iter
|
|
64
|
+
|
|
65
|
+
Iterable, iterates wrapped item.
|
|
66
|
+
|
|
67
|
+
"""
|
|
38
68
|
if self:
|
|
39
69
|
yield self._item
|
|
40
70
|
|
|
41
|
-
def
|
|
42
|
-
|
|
71
|
+
def __str__(self) -> str:
|
|
72
|
+
"""
|
|
73
|
+
.. admonition:: User string
|
|
74
|
+
|
|
75
|
+
Construct string 'Box(item_str)'
|
|
76
|
+
where ``item_str = str(item)`` for the currently contained
|
|
77
|
+
item.
|
|
78
|
+
|
|
79
|
+
:returns: A string to reproduce the current state of the ``Box``.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
return 'Wrap(' + str(self._item) + ')'
|
|
43
83
|
|
|
44
84
|
def __eq__(self, other: object) -> bool:
|
|
45
85
|
"""
|
|
46
|
-
|
|
86
|
+
.. admonition:: Equality comparison
|
|
87
|
+
|
|
88
|
+
Efficiently compare ``Wrap`` to another object.
|
|
47
89
|
|
|
48
90
|
:param other: The object to be compared with,
|
|
49
91
|
:returns: ``True`` if ``other`` is of type Wrap and wraps
|
|
50
92
|
an object which compares as equal to the wrapped
|
|
51
93
|
object, otherwise ``False``.
|
|
94
|
+
|
|
52
95
|
"""
|
|
53
96
|
if not isinstance(other, type(self)):
|
|
54
97
|
return False
|
|
@@ -58,36 +101,46 @@ class Wrap[T]():
|
|
|
58
101
|
return self._item == other._item
|
|
59
102
|
|
|
60
103
|
def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
|
|
61
|
-
"""
|
|
104
|
+
"""
|
|
105
|
+
.. admonition:: Map
|
|
106
|
+
|
|
107
|
+
Map function ``f`` over contents.
|
|
108
|
+
|
|
109
|
+
Map function ``f`` over contents.
|
|
62
110
|
|
|
63
111
|
:param f: Mapping function.
|
|
64
|
-
:returns:
|
|
112
|
+
:returns: New instance.
|
|
65
113
|
|
|
66
114
|
"""
|
|
67
115
|
return Wrap(f(self._item))
|
|
68
116
|
|
|
69
117
|
def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
|
|
70
|
-
"""
|
|
118
|
+
"""
|
|
119
|
+
.. admonition:: Bind
|
|
120
|
+
|
|
121
|
+
Flatmap wrapped object with function ``f``.
|
|
71
122
|
|
|
72
123
|
:param f: Binding function.
|
|
73
|
-
:returns:
|
|
124
|
+
:returns: New instance.
|
|
74
125
|
|
|
75
126
|
"""
|
|
76
127
|
return f(self._item)
|
|
77
128
|
|
|
78
129
|
|
|
79
130
|
class HWrap[T: Hashable](Hashable):
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
.. tip::
|
|
131
|
+
"""
|
|
132
|
+
.. admonition:: Wrap hashable object
|
|
83
133
|
|
|
84
|
-
|
|
134
|
+
Immutablely wrap exactly one value of a
|
|
135
|
+
given hashable type. ``HWrap`` objects can
|
|
136
|
+
be used in Python match statements.
|
|
85
137
|
|
|
86
|
-
|
|
138
|
+
.. tip::
|
|
87
139
|
|
|
88
|
-
|
|
140
|
+
``HWrap`` objects are hashable.
|
|
89
141
|
|
|
90
142
|
"""
|
|
143
|
+
|
|
91
144
|
__slots__ = ('_item', '_hash')
|
|
92
145
|
__match_args__ = ('_item',)
|
|
93
146
|
|
|
@@ -98,9 +151,21 @@ class HWrap[T: Hashable](Hashable):
|
|
|
98
151
|
return self._hash
|
|
99
152
|
|
|
100
153
|
def __bool__(self) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
.. admonition:: Bool
|
|
156
|
+
|
|
157
|
+
Truthiness same as wrapped object.
|
|
158
|
+
|
|
159
|
+
"""
|
|
101
160
|
return bool(self._item)
|
|
102
161
|
|
|
103
162
|
def __iter__(self) -> Iterator[T]:
|
|
163
|
+
"""
|
|
164
|
+
.. admonition:: Iter
|
|
165
|
+
|
|
166
|
+
Iterable, iterates wrapped item.
|
|
167
|
+
|
|
168
|
+
"""
|
|
104
169
|
if self:
|
|
105
170
|
yield self._item
|
|
106
171
|
|
|
@@ -109,12 +174,15 @@ class HWrap[T: Hashable](Hashable):
|
|
|
109
174
|
|
|
110
175
|
def __eq__(self, other: object) -> bool:
|
|
111
176
|
"""
|
|
112
|
-
|
|
177
|
+
.. admonition:: Equality comparison
|
|
113
178
|
|
|
114
|
-
|
|
179
|
+
Efficiently compare to another object.
|
|
180
|
+
|
|
181
|
+
:param other: Object to be compared
|
|
115
182
|
:returns: ``True`` if ``other`` is of type HWrap and wraps
|
|
116
183
|
an object which compares as equal to the wrapped
|
|
117
184
|
object, otherwise ``False``.
|
|
185
|
+
|
|
118
186
|
"""
|
|
119
187
|
if not isinstance(other, type(self)):
|
|
120
188
|
return False
|
|
@@ -126,19 +194,26 @@ class HWrap[T: Hashable](Hashable):
|
|
|
126
194
|
return self._item == other._item
|
|
127
195
|
|
|
128
196
|
def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
|
|
129
|
-
"""
|
|
197
|
+
"""
|
|
198
|
+
.. admonition:: Map
|
|
199
|
+
|
|
200
|
+
Map function ``f`` over wrapped the wrapped object
|
|
201
|
+
returning a new ``HWrap`` instance.
|
|
130
202
|
|
|
131
203
|
:param f: Mapping function.
|
|
132
|
-
:returns:
|
|
204
|
+
:returns: New instance.
|
|
133
205
|
|
|
134
206
|
"""
|
|
135
207
|
return HWrap(f(self._item))
|
|
136
208
|
|
|
137
209
|
def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
|
|
138
|
-
"""
|
|
210
|
+
"""
|
|
211
|
+
.. admonition:: Bind
|
|
212
|
+
|
|
213
|
+
Flatmap ``Box`` with function ``f``.
|
|
139
214
|
|
|
140
215
|
:param f: Binding function.
|
|
141
|
-
:returns:
|
|
216
|
+
:returns: New instance.
|
|
142
217
|
|
|
143
218
|
"""
|
|
144
219
|
return f(self._item)
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonic-fp-gadgets
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.4
|
|
4
4
|
Summary: Gadgets
|
|
5
5
|
Keywords: gadgets
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.13
|
|
8
8
|
Description-Content-Type: text/x-rst
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Framework :: Pytest
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: pytest>=8.4.1 ; extra == "test"
|
|
18
|
-
Requires-Dist: pythonic-fp-circulararray>=6.0.
|
|
18
|
+
Requires-Dist: pythonic-fp-circulararray>=6.0.4 ; extra == "test"
|
|
19
19
|
Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-gadgets/blob/main/CHANGELOG.rst
|
|
20
|
-
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/gadgets
|
|
21
|
-
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
|
|
20
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/projects/gadgets.html
|
|
21
|
+
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
|
|
22
22
|
Project-URL: Source, https://github.com/grscheller/pythonic-fp-gadgets
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
|
|
@@ -27,7 +27,7 @@ Pythonic FP - Gadgets
|
|
|
27
27
|
|
|
28
28
|
PyPI project
|
|
29
29
|
`pythonic-fp-gadgets
|
|
30
|
-
<https://pypi.org/project/pythonic-fp>`_.
|
|
30
|
+
<https://pypi.org/project/pythonic-fp-gadgets>`_.
|
|
31
31
|
|
|
32
32
|
Library of simple, but useful, classes and functions with no dependencies
|
|
33
33
|
outside the Python Standard Library.
|
|
@@ -35,24 +35,26 @@ outside the Python Standard Library.
|
|
|
35
35
|
- Gadgets
|
|
36
36
|
|
|
37
37
|
- single item box
|
|
38
|
+
- immutable wrapped (hashable) references
|
|
38
39
|
- function returning iterator of its arguments
|
|
39
40
|
- function to find the latest common ancestor of two classes
|
|
41
|
+
- sentinel values
|
|
40
42
|
|
|
41
43
|
Part of the
|
|
42
44
|
`pythonic-fp
|
|
43
|
-
<https://grscheller.github.io/pythonic-fp
|
|
45
|
+
<https://grscheller.github.io/pythonic-fp/>`_
|
|
44
46
|
PyPI projects.
|
|
45
47
|
|
|
46
48
|
Documentation
|
|
47
49
|
-------------
|
|
48
50
|
|
|
49
|
-
Documentation for this project
|
|
51
|
+
Documentation and other links for this project are hosted on
|
|
50
52
|
`GitHub Pages
|
|
51
|
-
<https://grscheller.github.io/pythonic-fp/gadgets>`_.
|
|
53
|
+
<https://grscheller.github.io/pythonic-fp/projects/gadgets.html>`_.
|
|
52
54
|
|
|
53
55
|
Copyright and License
|
|
54
56
|
---------------------
|
|
55
57
|
|
|
56
|
-
Copyright (c) 2025 Geoffrey R. Scheller. Licensed under the Apache
|
|
58
|
+
Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
|
|
57
59
|
License, Version 2.0. See the LICENSE file for details.
|
|
58
60
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pythonic_fp/gadgets/__init__.py,sha256=-pgzuWq-DTORXvy8JDQpnNPhQou85oiySH2-gArNeWc,2806
|
|
2
|
+
pythonic_fp/gadgets/__init__.pyi,sha256=-O921ioE28UPnDm_EL928vl6dDxhrfXHBQenusBo1_o,224
|
|
3
|
+
pythonic_fp/gadgets/box.py,sha256=Dx5wF3x9LyUs5MHjvHP6yTM05f4ojHPqqzfjuXJOo8I,6580
|
|
4
|
+
pythonic_fp/gadgets/box.pyi,sha256=UkJBY8Xiv4oe70Q3bhJL1jGHwYmFWwkTIdxvf2zR3v4,837
|
|
5
|
+
pythonic_fp/gadgets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pythonic_fp/gadgets/wrap.py,sha256=tAnG7P3EYl_1-CH2cs7IfY-0h4LET1LEIjmMo26OzCg,5388
|
|
7
|
+
pythonic_fp/gadgets/wrap.pyi,sha256=TRbBph_517VNCKchwU-pdtPCn73c09gq8EchDFM7Uyo,868
|
|
8
|
+
pythonic_fp/gadgets/sentinels/__init__.py,sha256=Xh63-GTwiqqcBqs5OEYBSiLXuWtq_CVHfUz0mKO_2H0,775
|
|
9
|
+
pythonic_fp/gadgets/sentinels/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
pythonic_fp/gadgets/sentinels/flavored.py,sha256=LPmA-XOadcN5gJgxxo2prSnwRvq8bJ7o0Ae2_Y70Au0,2774
|
|
11
|
+
pythonic_fp/gadgets/sentinels/flavored.pyi,sha256=K3YSyXLyylPII2ahRP0olhzLJruOeR73TFJDC6rCUwk,212
|
|
12
|
+
pythonic_fp/gadgets/sentinels/novalue.py,sha256=gaQNm1TRtphbmWgDV8O0sblH7eU2M-Zymu_B67mwsJ8,6097
|
|
13
|
+
pythonic_fp/gadgets/sentinels/novalue.pyi,sha256=1_iUI6FEwYma8P4XXVCb6CKhSYmfq53S8dLmiwNtJw4,244
|
|
14
|
+
pythonic_fp_gadgets-4.0.4.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
15
|
+
pythonic_fp_gadgets-4.0.4.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
16
|
+
pythonic_fp_gadgets-4.0.4.dist-info/METADATA,sha256=SbxLXeCZA7LOtg3Q_mpBPlux1FR6-shkI9S2tutdVgk,1895
|
|
17
|
+
pythonic_fp_gadgets-4.0.4.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pythonic_fp/gadgets/__init__.py,sha256=iEm1j8DIchgQvH4Tom6IyMeYz8i3LSAXBBBtz_gPykE,2673
|
|
2
|
-
pythonic_fp/gadgets/__init__.pyi,sha256=cAf10WX2ZHMs-Tf93mImmDdZw7lNEmwjorkiVF04s_E,224
|
|
3
|
-
pythonic_fp/gadgets/box.py,sha256=cmHe2kZhEMNtH51xlpdyd68SV-bBXxC6gRH8N0W6TqE,5443
|
|
4
|
-
pythonic_fp/gadgets/box.pyi,sha256=UkJBY8Xiv4oe70Q3bhJL1jGHwYmFWwkTIdxvf2zR3v4,837
|
|
5
|
-
pythonic_fp/gadgets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
pythonic_fp/gadgets/wrap.py,sha256=x3mvawiRh9G3vMq3ub5Va6qBJgKcvkCKvWeR6pWrJBw,3929
|
|
7
|
-
pythonic_fp/gadgets/wrap.pyi,sha256=TRbBph_517VNCKchwU-pdtPCn73c09gq8EchDFM7Uyo,868
|
|
8
|
-
pythonic_fp/gadgets/sentinels/__init__.py,sha256=81T-HGQ9BgXj57PO_pduo8MpSQpRgZwT8LIXwODhHkI,724
|
|
9
|
-
pythonic_fp/gadgets/sentinels/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
pythonic_fp/gadgets/sentinels/flavored.py,sha256=sXgjhBPHEulH9C3Yc09PwAPDscfRH2vKj5JldgS6Sak,2260
|
|
11
|
-
pythonic_fp/gadgets/sentinels/flavored.pyi,sha256=K3YSyXLyylPII2ahRP0olhzLJruOeR73TFJDC6rCUwk,212
|
|
12
|
-
pythonic_fp/gadgets/sentinels/novalue.py,sha256=UbyQ4AVTdM2flLmF2WE0sh7_dErPQiTeM8U6GdWaHN4,3602
|
|
13
|
-
pythonic_fp/gadgets/sentinels/novalue.pyi,sha256=zhZdePpWoQ3ZXOq7xQWGR0A4W_XzasdBQKcoywXZEZI,173
|
|
14
|
-
pythonic_fp_gadgets-4.0.2.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
15
|
-
pythonic_fp_gadgets-4.0.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
16
|
-
pythonic_fp_gadgets-4.0.2.dist-info/METADATA,sha256=GsyqIcxiofODEI34GKxzySI-FBv-pkwxaEhnTfZzcOs,1815
|
|
17
|
-
pythonic_fp_gadgets-4.0.2.dist-info/RECORD,,
|
|
File without changes
|
{pythonic_fp_gadgets-4.0.2.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|