pythonic-fp-gadgets 4.0.1__py3-none-any.whl → 4.0.3__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 +35 -30
- pythonic_fp/gadgets/box.py +76 -29
- pythonic_fp/gadgets/sentinels/__init__.py +2 -2
- pythonic_fp/gadgets/sentinels/flavored.py +18 -8
- pythonic_fp/gadgets/sentinels/novalue.py +10 -6
- pythonic_fp/gadgets/wrap.py +95 -15
- {pythonic_fp_gadgets-4.0.1.dist-info → pythonic_fp_gadgets-4.0.3.dist-info}/METADATA +13 -11
- pythonic_fp_gadgets-4.0.3.dist-info/RECORD +17 -0
- pythonic_fp_gadgets-4.0.1.dist-info/RECORD +0 -17
- {pythonic_fp_gadgets-4.0.1.dist-info → pythonic_fp_gadgets-4.0.3.dist-info}/WHEEL +0 -0
- {pythonic_fp_gadgets-4.0.1.dist-info → pythonic_fp_gadgets-4.0.3.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.
|
|
@@ -13,47 +13,34 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
16
|
+
Gadgets
|
|
17
|
+
-------
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
.. admonition:: Collection of mostly self-contained functions and classes
|
|
20
|
+
|
|
21
|
+
- Functions and classes which could go multiple places or have
|
|
22
|
+
no good place to go.
|
|
23
|
+
- Self-contained with minimal dependencies.
|
|
24
|
+
- No pythonic_fp dependencies at all.
|
|
24
25
|
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
28
|
from collections.abc import Iterator
|
|
28
29
|
from inspect import getmro
|
|
29
30
|
|
|
30
|
-
__all__ = ['
|
|
31
|
+
__all__ = ['first_common_ancestor', 'iterate_over_arguments']
|
|
31
32
|
|
|
32
33
|
__author__ = 'Geoffrey R. Scheller'
|
|
33
|
-
__copyright__ = 'Copyright (c) 2023-
|
|
34
|
+
__copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
|
|
34
35
|
__license__ = 'Apache License 2.0'
|
|
35
36
|
|
|
36
37
|
|
|
37
|
-
def
|
|
38
|
-
"""Function returning an iterator of its arguments.
|
|
39
|
-
|
|
40
|
-
.. note::
|
|
41
|
-
|
|
42
|
-
Does not create an object to iterate over.
|
|
43
|
-
|
|
44
|
-
- well, not in the Python world
|
|
45
|
-
- maybe in the C world
|
|
46
|
-
|
|
47
|
-
:param args: Objects to iterate over.
|
|
48
|
-
:returns: An iterator of the arguments.
|
|
49
|
-
|
|
38
|
+
def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
50
39
|
"""
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
.. admonition:: Least upper bound
|
|
53
41
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
of two classes.
|
|
42
|
+
Find the least upper bound in the inheritance graph
|
|
43
|
+
of two classes.
|
|
57
44
|
|
|
58
45
|
.. warning::
|
|
59
46
|
|
|
@@ -68,8 +55,9 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
|
68
55
|
|
|
69
56
|
:param cls1: A class in the inheritance hierarchy.
|
|
70
57
|
:param cls2: A class in the inheritance hierarchy.
|
|
71
|
-
:returns: First common ancestor based on getmro order.
|
|
72
|
-
:raises TypeError: Raised
|
|
58
|
+
:returns: First common ancestor based on ``getmro`` order.
|
|
59
|
+
:raises TypeError: Raised when no common ancestor exists, or when
|
|
60
|
+
not caught when raised by ``inspect.getmro``.
|
|
73
61
|
|
|
74
62
|
"""
|
|
75
63
|
if issubclass(cls1, cls2):
|
|
@@ -81,3 +69,20 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
|
81
69
|
if issubclass(cls1, common_ancestor) and issubclass(cls2, common_ancestor):
|
|
82
70
|
return common_ancestor
|
|
83
71
|
raise TypeError("latest_common_ancestor: no common ancestor found!!!")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def iterate_over_arguments[A](*args: A) -> Iterator[A]:
|
|
75
|
+
"""
|
|
76
|
+
.. admonition:: Iterate over arguments.
|
|
77
|
+
|
|
78
|
+
Function returning an iterator over its arguments.
|
|
79
|
+
|
|
80
|
+
.. note::
|
|
81
|
+
|
|
82
|
+
Does not create a Python object to iterate over.
|
|
83
|
+
|
|
84
|
+
:param args: Objects to iterate over.
|
|
85
|
+
:returns: An iterator of the functions arguments.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
yield from args
|
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.
|
|
@@ -11,6 +11,22 @@
|
|
|
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 box
|
|
16
|
+
|
|
17
|
+
Containers holding at most one item of a given type.
|
|
18
|
+
|
|
19
|
+
.. tip::
|
|
20
|
+
|
|
21
|
+
Objects of the ``Box`` type
|
|
22
|
+
|
|
23
|
+
- Are truthy if not empty.
|
|
24
|
+
- Can be combined with other iterators before being filled.
|
|
25
|
+
- Can be used like a promise.
|
|
26
|
+
- Threadsafe
|
|
27
|
+
- Can be used in Python match statements.
|
|
28
|
+
|
|
29
|
+
"""
|
|
14
30
|
|
|
15
31
|
__all__ = ['Box']
|
|
16
32
|
|
|
@@ -22,19 +38,10 @@ _sentinel: Final[_Sentinel] = object()
|
|
|
22
38
|
|
|
23
39
|
|
|
24
40
|
class Box[T]:
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
.. note::
|
|
28
|
-
|
|
29
|
-
- ``Box(item: T)``: contains at one item of type ``T``
|
|
30
|
-
- ``Box[T]()``: creates empty container
|
|
31
|
-
|
|
32
|
-
Where type ``T`` is some definite type, which
|
|
33
|
-
could be ``None`` or even ``Never``.
|
|
34
|
-
|
|
35
|
-
.. tip ::
|
|
41
|
+
"""
|
|
42
|
+
.. admonition:: Box
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
Container holding at most one item of a given type.
|
|
38
45
|
|
|
39
46
|
"""
|
|
40
47
|
__slots__ = ('_item',)
|
|
@@ -47,7 +54,8 @@ class Box[T]:
|
|
|
47
54
|
|
|
48
55
|
def __init__(self, item: T | _Sentinel = _sentinel) -> None:
|
|
49
56
|
"""
|
|
50
|
-
:param item: An
|
|
57
|
+
:param item: An optional initial ``item`` for the ``Box``.
|
|
58
|
+
|
|
51
59
|
"""
|
|
52
60
|
self._item = item
|
|
53
61
|
|
|
@@ -67,6 +75,17 @@ class Box[T]:
|
|
|
67
75
|
return 1 if self else 0
|
|
68
76
|
|
|
69
77
|
def __eq__(self, other: object) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
.. admonition:: Equality comparison
|
|
80
|
+
|
|
81
|
+
Efficiently compare ``Box`` to another object.
|
|
82
|
+
|
|
83
|
+
:param other: The object to be compared with,
|
|
84
|
+
:returns: ``True`` if ``other`` is of type Box and contains
|
|
85
|
+
an object which compares as equal to the object
|
|
86
|
+
contained in the ``Box``, otherwise ``False``.
|
|
87
|
+
|
|
88
|
+
"""
|
|
70
89
|
if not isinstance(other, type(self)):
|
|
71
90
|
return False
|
|
72
91
|
|
|
@@ -82,11 +101,18 @@ class Box[T]:
|
|
|
82
101
|
def get(self, alt: T) -> T: ...
|
|
83
102
|
|
|
84
103
|
def get(self, alt: T | _Sentinel = _sentinel) -> T:
|
|
85
|
-
"""
|
|
104
|
+
"""
|
|
105
|
+
.. admonition:: Get
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
Return the boxed item, if it exists, otherwise
|
|
108
|
+
an alternate item, if given.
|
|
109
|
+
|
|
110
|
+
:param alt: An optional item of type ``T`` to return
|
|
111
|
+
if the ``Box`` is empty.
|
|
112
|
+
:returns: Contents of ``Box`` or an alternate item, if given,
|
|
113
|
+
when the ``Box`` is empty.
|
|
114
|
+
:raises ValueError: When the ``alt`` item is not provided
|
|
115
|
+
but needed.
|
|
90
116
|
|
|
91
117
|
"""
|
|
92
118
|
if self._item is not _sentinel:
|
|
@@ -97,7 +123,10 @@ class Box[T]:
|
|
|
97
123
|
return cast(T, alt)
|
|
98
124
|
|
|
99
125
|
def pop(self) -> T:
|
|
100
|
-
"""
|
|
126
|
+
"""
|
|
127
|
+
.. admonition:: Pop
|
|
128
|
+
|
|
129
|
+
Pop item from ``Box`` if not empty.
|
|
101
130
|
|
|
102
131
|
:returns: The item contained in the ``Box``.
|
|
103
132
|
:raises ValueError: If Box is empty.
|
|
@@ -111,7 +140,10 @@ class Box[T]:
|
|
|
111
140
|
return popped
|
|
112
141
|
|
|
113
142
|
def push(self, item: T) -> None:
|
|
114
|
-
"""
|
|
143
|
+
"""
|
|
144
|
+
.. admonition:: Push
|
|
145
|
+
|
|
146
|
+
Push an item into ``Box`` if empty.
|
|
115
147
|
|
|
116
148
|
:param item: Item to push into the empty ``Box``.
|
|
117
149
|
:raises ValueError: If ``Box`` is not empty.
|
|
@@ -125,15 +157,24 @@ class Box[T]:
|
|
|
125
157
|
return None
|
|
126
158
|
|
|
127
159
|
def put(self, item: T) -> None:
|
|
128
|
-
"""
|
|
160
|
+
"""
|
|
161
|
+
.. admonition:: Put
|
|
162
|
+
|
|
163
|
+
Put an item in the Box. Discard any previous contents.
|
|
164
|
+
|
|
165
|
+
"""
|
|
129
166
|
self._item = item
|
|
130
167
|
|
|
131
168
|
def exchange(self, new_item: T) -> T:
|
|
132
|
-
"""
|
|
169
|
+
"""
|
|
170
|
+
.. admonition:: Exchange
|
|
171
|
+
|
|
172
|
+
Exchange an item with what is in the Box.
|
|
133
173
|
|
|
134
174
|
:param ``new_item``: New item to exchange for current item.
|
|
135
175
|
:returns: Original contents of the ``Box``.
|
|
136
176
|
:raises ValueError: If Box is empty.
|
|
177
|
+
|
|
137
178
|
"""
|
|
138
179
|
if self._item is _sentinel:
|
|
139
180
|
msg = 'Box: Trying to exchange items from an empty Box'
|
|
@@ -143,11 +184,14 @@ class Box[T]:
|
|
|
143
184
|
return popped
|
|
144
185
|
|
|
145
186
|
def map[U](self, f: Callable[[T], U]) -> 'Box[U]':
|
|
146
|
-
"""
|
|
147
|
-
|
|
187
|
+
"""
|
|
188
|
+
.. admonition:: Map
|
|
189
|
+
|
|
190
|
+
Map function ``f`` over contents. We need to return a new
|
|
191
|
+
instance since the type of Box can change.
|
|
148
192
|
|
|
149
|
-
:param f:
|
|
150
|
-
:returns:
|
|
193
|
+
:param f: Mapping function.
|
|
194
|
+
:returns: New instance.
|
|
151
195
|
|
|
152
196
|
"""
|
|
153
197
|
if self._item is _sentinel:
|
|
@@ -155,10 +199,13 @@ class Box[T]:
|
|
|
155
199
|
return Box(f(cast(T, self._item)))
|
|
156
200
|
|
|
157
201
|
def bind[U](self, f: Callable[[T], 'Box[U]']) -> 'Box[U]':
|
|
158
|
-
"""
|
|
202
|
+
"""
|
|
203
|
+
.. admonition:: Bind
|
|
204
|
+
|
|
205
|
+
Flatmap ``Box`` with function ``f``.
|
|
159
206
|
|
|
160
|
-
:param f:
|
|
161
|
-
:returns:
|
|
207
|
+
:param f: Binding function.
|
|
208
|
+
:returns: New instance.
|
|
162
209
|
|
|
163
210
|
"""
|
|
164
211
|
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.
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
__author__ = 'Geoffrey R. Scheller'
|
|
16
|
-
__copyright__ = 'Copyright (c) 2023-
|
|
16
|
+
__copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
|
|
17
17
|
__license__ = 'Apache License 2.0'
|
|
@@ -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.
|
|
@@ -13,24 +13,27 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
16
|
+
.. admonition:: When different flavors of the truth are needed.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Sentinel values labeled by different (hashable) flavors.
|
|
19
|
+
Threadsafe.
|
|
20
|
+
|
|
21
|
+
.. tip::
|
|
19
22
|
|
|
20
23
|
Can be compared using ``==`` and ``!=``. A flavored sentinel
|
|
21
24
|
value always equals itself and never equals anything else,
|
|
22
25
|
especially other flavored sentinel values.
|
|
23
26
|
|
|
27
|
+
.. tip::
|
|
28
|
+
|
|
24
29
|
Useful for union types where ``Sentinel[H]`` is one of the
|
|
25
30
|
types making up the union.
|
|
26
31
|
|
|
32
|
+
.. tip::
|
|
33
|
+
|
|
27
34
|
To ensure that reference equality is used, put the known
|
|
28
35
|
sentinel value first in the comparison.
|
|
29
36
|
|
|
30
|
-
.. note::
|
|
31
|
-
|
|
32
|
-
Threadsafe.
|
|
33
|
-
|
|
34
37
|
"""
|
|
35
38
|
|
|
36
39
|
import threading
|
|
@@ -58,6 +61,7 @@ class Sentinel[H: Hashable]:
|
|
|
58
61
|
:param flavor: Some Hashable value of generic type ``H``.
|
|
59
62
|
:returns: The ``Sentinel`` singleton instance with flavor ``flavor``.
|
|
60
63
|
:rtype: ``Sentinel[H]`` where ``H`` is a subtype of Hashable.
|
|
64
|
+
|
|
61
65
|
"""
|
|
62
66
|
if not hasattr(self, '_flavor'):
|
|
63
67
|
self._flavor = flavor
|
|
@@ -67,6 +71,12 @@ class Sentinel[H: Hashable]:
|
|
|
67
71
|
|
|
68
72
|
def flavor(self) -> H:
|
|
69
73
|
"""
|
|
70
|
-
|
|
74
|
+
.. admonition:: Get flavor
|
|
75
|
+
|
|
76
|
+
Get the sentinel's flavor, a hashable
|
|
77
|
+
value of type ``H``.
|
|
78
|
+
|
|
79
|
+
:returns: The sentinel's flavor.
|
|
80
|
+
|
|
71
81
|
"""
|
|
72
82
|
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.
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
16
|
+
.. admonition:: Missing value.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Singleton class representing an actually,
|
|
19
|
+
not potentially, missing value.
|
|
19
20
|
|
|
20
21
|
``NoValue()`` is a singleton object representing a missing value.
|
|
21
22
|
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
|
|
53
54
|
Similarly for not equals.
|
|
54
55
|
|
|
55
|
-
.. table:: ``x != y
|
|
56
|
+
.. table:: ``x != y``:wq
|
|
56
57
|
|
|
57
58
|
+-----------+-----------+--------+--------+
|
|
58
59
|
| x∖y | NoValue() | 42 | 57 |
|
|
@@ -101,6 +102,7 @@ class NoValue():
|
|
|
101
102
|
def __new__(cls) -> 'NoValue':
|
|
102
103
|
"""
|
|
103
104
|
:returns: The ``NoValue`` singleton instance.
|
|
105
|
+
|
|
104
106
|
"""
|
|
105
107
|
if cls._instance is None:
|
|
106
108
|
with cls._lock:
|
|
@@ -113,12 +115,14 @@ class NoValue():
|
|
|
113
115
|
|
|
114
116
|
def __eq__(self, other: object) -> bool:
|
|
115
117
|
"""
|
|
116
|
-
:returns: False
|
|
118
|
+
:returns: ``False``
|
|
119
|
+
|
|
117
120
|
"""
|
|
118
121
|
return False
|
|
119
122
|
|
|
120
123
|
def __ne__(self, other: object) -> bool:
|
|
121
124
|
"""
|
|
122
|
-
:returns: False
|
|
125
|
+
:returns: ``False``
|
|
126
|
+
|
|
123
127
|
"""
|
|
124
128
|
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,23 +27,38 @@ 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 given type.
|
|
26
34
|
|
|
27
35
|
"""
|
|
28
36
|
__slots__ = ('_item',)
|
|
29
37
|
__match_args__ = ('_item',)
|
|
30
38
|
|
|
31
39
|
def __init__(self, item: T) -> None:
|
|
40
|
+
"""
|
|
41
|
+
:param item: Item to be wrapped.
|
|
42
|
+
|
|
43
|
+
"""
|
|
32
44
|
self._item = item
|
|
33
45
|
|
|
34
46
|
def __bool__(self) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
.. admonition:: Bool
|
|
49
|
+
|
|
50
|
+
Truthiness same as wrapped object.
|
|
51
|
+
|
|
52
|
+
"""
|
|
35
53
|
return bool(self._item)
|
|
36
54
|
|
|
37
55
|
def __iter__(self) -> Iterator[T]:
|
|
56
|
+
"""
|
|
57
|
+
.. admonition:: Iter
|
|
58
|
+
|
|
59
|
+
Iterable, iterates wrapped item.
|
|
60
|
+
|
|
61
|
+
"""
|
|
38
62
|
if self:
|
|
39
63
|
yield self._item
|
|
40
64
|
|
|
@@ -42,6 +66,17 @@ class Wrap[T]():
|
|
|
42
66
|
return 'Wrap(' + repr(self._item) + ')'
|
|
43
67
|
|
|
44
68
|
def __eq__(self, other: object) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
.. admonition:: Equality comparison
|
|
71
|
+
|
|
72
|
+
Efficiently compare ``Wrap`` to another object.
|
|
73
|
+
|
|
74
|
+
:param other: The object to be compared with,
|
|
75
|
+
:returns: ``True`` if ``other`` is of type Wrap and wraps
|
|
76
|
+
an object which compares as equal to the wrapped
|
|
77
|
+
object, otherwise ``False``.
|
|
78
|
+
|
|
79
|
+
"""
|
|
45
80
|
if not isinstance(other, type(self)):
|
|
46
81
|
return False
|
|
47
82
|
|
|
@@ -50,30 +85,45 @@ class Wrap[T]():
|
|
|
50
85
|
return self._item == other._item
|
|
51
86
|
|
|
52
87
|
def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
|
|
53
|
-
"""
|
|
88
|
+
"""
|
|
89
|
+
.. admonition:: Map
|
|
90
|
+
|
|
91
|
+
Map function ``f`` over contents.
|
|
92
|
+
|
|
93
|
+
Map function ``f`` over contents.
|
|
54
94
|
|
|
55
95
|
:param f: Mapping function.
|
|
56
|
-
:returns:
|
|
96
|
+
:returns: New instance.
|
|
57
97
|
|
|
58
98
|
"""
|
|
59
99
|
return Wrap(f(self._item))
|
|
60
100
|
|
|
61
101
|
def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
|
|
62
|
-
"""
|
|
102
|
+
"""
|
|
103
|
+
.. admonition:: Bind
|
|
104
|
+
|
|
105
|
+
Flatmap wrapped object with function ``f``.
|
|
63
106
|
|
|
64
107
|
:param f: Binding function.
|
|
65
|
-
:returns:
|
|
108
|
+
:returns: New instance.
|
|
66
109
|
|
|
67
110
|
"""
|
|
68
111
|
return f(self._item)
|
|
69
112
|
|
|
70
113
|
|
|
71
114
|
class HWrap[T: Hashable](Hashable):
|
|
72
|
-
"""
|
|
115
|
+
"""
|
|
116
|
+
.. admonition:: Wrap hashable object
|
|
117
|
+
|
|
118
|
+
Immutablely wrap exactly one value of a given hashable type.
|
|
119
|
+
|
|
120
|
+
.. tip::
|
|
121
|
+
|
|
122
|
+
``HWrap`` objects can be used in Python match statements.
|
|
73
123
|
|
|
74
124
|
.. tip::
|
|
75
125
|
|
|
76
|
-
``
|
|
126
|
+
``HWrap`` objects are hashable.
|
|
77
127
|
|
|
78
128
|
"""
|
|
79
129
|
__slots__ = ('_item', '_hash')
|
|
@@ -86,9 +136,21 @@ class HWrap[T: Hashable](Hashable):
|
|
|
86
136
|
return self._hash
|
|
87
137
|
|
|
88
138
|
def __bool__(self) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
.. admonition:: Bool
|
|
141
|
+
|
|
142
|
+
Truthiness same as wrapped object.
|
|
143
|
+
|
|
144
|
+
"""
|
|
89
145
|
return bool(self._item)
|
|
90
146
|
|
|
91
147
|
def __iter__(self) -> Iterator[T]:
|
|
148
|
+
"""
|
|
149
|
+
.. admonition:: Iter
|
|
150
|
+
|
|
151
|
+
Iterable, iterates wrapped item.
|
|
152
|
+
|
|
153
|
+
"""
|
|
92
154
|
if self:
|
|
93
155
|
yield self._item
|
|
94
156
|
|
|
@@ -96,6 +158,17 @@ class HWrap[T: Hashable](Hashable):
|
|
|
96
158
|
return 'Wrap(' + repr(self._item) + ')'
|
|
97
159
|
|
|
98
160
|
def __eq__(self, other: object) -> bool:
|
|
161
|
+
"""
|
|
162
|
+
.. admonition:: Equality comparison
|
|
163
|
+
|
|
164
|
+
Efficiently compare to another object.
|
|
165
|
+
|
|
166
|
+
:param other: Object to be compared
|
|
167
|
+
:returns: ``True`` if ``other`` is of type HWrap and wraps
|
|
168
|
+
an object which compares as equal to the wrapped
|
|
169
|
+
object, otherwise ``False``.
|
|
170
|
+
|
|
171
|
+
"""
|
|
99
172
|
if not isinstance(other, type(self)):
|
|
100
173
|
return False
|
|
101
174
|
|
|
@@ -106,19 +179,26 @@ class HWrap[T: Hashable](Hashable):
|
|
|
106
179
|
return self._item == other._item
|
|
107
180
|
|
|
108
181
|
def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
|
|
109
|
-
"""
|
|
182
|
+
"""
|
|
183
|
+
.. admonition:: Map
|
|
184
|
+
|
|
185
|
+
Map function ``f`` over wrapped the wrapped object
|
|
186
|
+
returning a new ``HWrap`` instance.
|
|
110
187
|
|
|
111
188
|
:param f: Mapping function.
|
|
112
|
-
:returns:
|
|
189
|
+
:returns: New instance.
|
|
113
190
|
|
|
114
191
|
"""
|
|
115
192
|
return HWrap(f(self._item))
|
|
116
193
|
|
|
117
194
|
def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
|
|
118
|
-
"""
|
|
195
|
+
"""
|
|
196
|
+
.. admonition:: Bind
|
|
197
|
+
|
|
198
|
+
Flatmap ``Box`` with function ``f``.
|
|
119
199
|
|
|
120
200
|
:param f: Binding function.
|
|
121
|
-
:returns:
|
|
201
|
+
:returns: New instance.
|
|
122
202
|
|
|
123
203
|
"""
|
|
124
204
|
return f(self._item)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonic-fp-gadgets
|
|
3
|
-
Version: 4.0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 4.0.3
|
|
4
|
+
Summary: Gadgets
|
|
5
5
|
Keywords: gadgets
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.13
|
|
@@ -11,14 +11,14 @@ 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
|
|
21
|
-
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/
|
|
20
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-gadgets/
|
|
21
|
+
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/html/
|
|
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,12 +35,14 @@ outside the Python Standard Library.
|
|
|
35
35
|
- Gadgets
|
|
36
36
|
|
|
37
37
|
- single item box
|
|
38
|
+
- wrapped (hashable) references
|
|
38
39
|
- function returning iterator of its arguments
|
|
39
|
-
- find the latest common ancestor of two classes
|
|
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/homepage/html/>`_
|
|
44
46
|
PyPI projects.
|
|
45
47
|
|
|
46
48
|
Documentation
|
|
@@ -48,11 +50,11 @@ Documentation
|
|
|
48
50
|
|
|
49
51
|
Documentation for this project is hosted on
|
|
50
52
|
`GitHub Pages
|
|
51
|
-
<https://grscheller.github.io/pythonic-fp
|
|
53
|
+
<https://grscheller.github.io/pythonic-fp-gadgets>`_.
|
|
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=29fNYn8LLhCUfvZ_vcofSoGhs9_Hw95cPF3sd0G1z9M,2739
|
|
2
|
+
pythonic_fp/gadgets/__init__.pyi,sha256=cAf10WX2ZHMs-Tf93mImmDdZw7lNEmwjorkiVF04s_E,224
|
|
3
|
+
pythonic_fp/gadgets/box.py,sha256=2PL0wkimRwgsL15aTkB2QTo4B40IY6G0lCo0Esa0CTU,5860
|
|
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=EVhp115DcokaBa-0xN3CN3Qlr4vkHvEZQIPuMYYxHas,4950
|
|
7
|
+
pythonic_fp/gadgets/wrap.pyi,sha256=TRbBph_517VNCKchwU-pdtPCn73c09gq8EchDFM7Uyo,868
|
|
8
|
+
pythonic_fp/gadgets/sentinels/__init__.py,sha256=WsGXds578j1y7FsLEGer59P1c8EItUBTp60cuA0T56E,724
|
|
9
|
+
pythonic_fp/gadgets/sentinels/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
pythonic_fp/gadgets/sentinels/flavored.py,sha256=W33020r3U8jxIDdJDaa-xE7o4cI1NY0bLZi8puIUBqA,2353
|
|
11
|
+
pythonic_fp/gadgets/sentinels/flavored.pyi,sha256=K3YSyXLyylPII2ahRP0olhzLJruOeR73TFJDC6rCUwk,212
|
|
12
|
+
pythonic_fp/gadgets/sentinels/novalue.py,sha256=9wya9NBDQzfr2rxwrCd7uh74Trm4iVsK53UAUZo5Iyk,3613
|
|
13
|
+
pythonic_fp/gadgets/sentinels/novalue.pyi,sha256=zhZdePpWoQ3ZXOq7xQWGR0A4W_XzasdBQKcoywXZEZI,173
|
|
14
|
+
pythonic_fp_gadgets-4.0.3.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
15
|
+
pythonic_fp_gadgets-4.0.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
16
|
+
pythonic_fp_gadgets-4.0.3.dist-info/METADATA,sha256=z6LP-dUh7AgGHq-TTbn3qS0QyIDc-5GNohGM23QXMzU,1868
|
|
17
|
+
pythonic_fp_gadgets-4.0.3.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pythonic_fp/gadgets/__init__.py,sha256=PXZJRwsDkGrI2_B3nbwU4n3tzeSGONquV-_inWroxsg,2811
|
|
2
|
-
pythonic_fp/gadgets/__init__.pyi,sha256=cAf10WX2ZHMs-Tf93mImmDdZw7lNEmwjorkiVF04s_E,224
|
|
3
|
-
pythonic_fp/gadgets/box.py,sha256=aSR9V_PuAhjqQ8Vs7fj80zgirb8BWzkUFr1pWvSbBDc,4994
|
|
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=5FonuzJ6E3SeUoneUoUsjM6x9COZUnyzWQHPKN45cRo,3259
|
|
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=RMqDRmN_ImbRlf_Yaqj7jwnOc6YBeEdAfi8xGCyw1xs,2195
|
|
11
|
-
pythonic_fp/gadgets/sentinels/flavored.pyi,sha256=K3YSyXLyylPII2ahRP0olhzLJruOeR73TFJDC6rCUwk,212
|
|
12
|
-
pythonic_fp/gadgets/sentinels/novalue.py,sha256=RA5foyWFgSHCpaJPJ0N0Td7cldLEbwX_bzkTgs_ZW-o,3573
|
|
13
|
-
pythonic_fp/gadgets/sentinels/novalue.pyi,sha256=zhZdePpWoQ3ZXOq7xQWGR0A4W_XzasdBQKcoywXZEZI,173
|
|
14
|
-
pythonic_fp_gadgets-4.0.1.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
15
|
-
pythonic_fp_gadgets-4.0.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
16
|
-
pythonic_fp_gadgets-4.0.1.dist-info/METADATA,sha256=lpGqtuqWWxiuh__avGe4ZUyXnRTf532iLL0UFLeaLPM,1828
|
|
17
|
-
pythonic_fp_gadgets-4.0.1.dist-info/RECORD,,
|
|
File without changes
|
{pythonic_fp_gadgets-4.0.1.dist-info → pythonic_fp_gadgets-4.0.3.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|