pythonic-fp-gadgets 4.0.3__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 +19 -19
- pythonic_fp/gadgets/__init__.pyi +2 -2
- pythonic_fp/gadgets/box.py +54 -25
- pythonic_fp/gadgets/sentinels/__init__.py +14 -3
- pythonic_fp/gadgets/sentinels/flavored.py +49 -32
- pythonic_fp/gadgets/sentinels/novalue.py +119 -60
- pythonic_fp/gadgets/sentinels/novalue.pyi +2 -0
- pythonic_fp/gadgets/wrap.py +25 -10
- {pythonic_fp_gadgets-4.0.3.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/METADATA +8 -8
- pythonic_fp_gadgets-4.0.4.dist-info/RECORD +17 -0
- pythonic_fp_gadgets-4.0.3.dist-info/RECORD +0 -17
- {pythonic_fp_gadgets-4.0.3.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/WHEEL +0 -0
- {pythonic_fp_gadgets-4.0.3.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/licenses/LICENSE +0 -0
pythonic_fp/gadgets/__init__.py
CHANGED
|
@@ -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
|
|
|
@@ -42,23 +43,22 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
|
42
43
|
Find the least upper bound in the inheritance graph
|
|
43
44
|
of two classes.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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``.
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
- type 'bool' is not an acceptable base type
|
|
52
|
+
.. warning::
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
This function can fail with a TypeError. Some error messages
|
|
55
|
+
seen are
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
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``.
|
|
57
|
+
- multiple bases have instance lay-out conflict
|
|
58
|
+
- type 'bool' is not an acceptable base type
|
|
61
59
|
|
|
60
|
+
This happens frequently when the function is given
|
|
61
|
+
Python builtin types or in multiple inheritance situations.
|
|
62
62
|
"""
|
|
63
63
|
if issubclass(cls1, cls2):
|
|
64
64
|
return cls2
|
|
@@ -77,12 +77,12 @@ def iterate_over_arguments[A](*args: A) -> Iterator[A]:
|
|
|
77
77
|
|
|
78
78
|
Function returning an iterator over its arguments.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
:param args: Objects to iterate over.
|
|
81
|
+
:returns: An iterator of the function's arguments.
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
.. note::
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
:returns: An iterator of the functions arguments.
|
|
85
|
+
Does not create a Python object to iterate over.
|
|
86
86
|
|
|
87
87
|
"""
|
|
88
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
|
@@ -11,28 +11,12 @@
|
|
|
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
|
-
"""
|
|
30
|
-
|
|
31
|
-
__all__ = ['Box']
|
|
32
14
|
|
|
33
15
|
from collections.abc import Callable, Iterator
|
|
34
16
|
from typing import cast, Final, overload
|
|
35
17
|
|
|
18
|
+
__all__ = ['Box']
|
|
19
|
+
|
|
36
20
|
type _Sentinel = object
|
|
37
21
|
_sentinel: Final[_Sentinel] = object()
|
|
38
22
|
|
|
@@ -43,6 +27,16 @@ class Box[T]:
|
|
|
43
27
|
|
|
44
28
|
Container holding at most one item of a given type.
|
|
45
29
|
|
|
30
|
+
.. tip::
|
|
31
|
+
|
|
32
|
+
Objects of the ``Box`` type
|
|
33
|
+
|
|
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
|
|
39
|
+
|
|
46
40
|
"""
|
|
47
41
|
__slots__ = ('_item',)
|
|
48
42
|
__match_args__ = ('_item',)
|
|
@@ -54,24 +48,44 @@ class Box[T]:
|
|
|
54
48
|
|
|
55
49
|
def __init__(self, item: T | _Sentinel = _sentinel) -> None:
|
|
56
50
|
"""
|
|
51
|
+
.. admonition:: Initializer
|
|
52
|
+
|
|
53
|
+
Initialize ``Box`` with 0 or 1 items.
|
|
54
|
+
|
|
57
55
|
:param item: An optional initial ``item`` for the ``Box``.
|
|
58
56
|
|
|
59
57
|
"""
|
|
60
58
|
self._item = item
|
|
61
59
|
|
|
62
60
|
def __bool__(self) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
.. admonition:: Bool
|
|
63
|
+
|
|
64
|
+
Truthy if not empty.
|
|
65
|
+
|
|
66
|
+
"""
|
|
63
67
|
return self._item is not _sentinel
|
|
64
68
|
|
|
65
69
|
def __iter__(self) -> Iterator[T]:
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
"""
|
|
71
|
+
.. admonition:: Iterability
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
Iterates boxed item.
|
|
74
|
+
|
|
75
|
+
"""
|
|
70
76
|
if self:
|
|
71
|
-
|
|
72
|
-
return 'Box()'
|
|
77
|
+
yield cast(T, self._item)
|
|
73
78
|
|
|
74
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
|
+
"""
|
|
75
89
|
return 1 if self else 0
|
|
76
90
|
|
|
77
91
|
def __eq__(self, other: object) -> bool:
|
|
@@ -80,8 +94,8 @@ class Box[T]:
|
|
|
80
94
|
|
|
81
95
|
Efficiently compare ``Box`` to another object.
|
|
82
96
|
|
|
83
|
-
:param other: The object to be compared
|
|
84
|
-
:returns: ``True`` if ``other`` is
|
|
97
|
+
:param other: The object to be compared.
|
|
98
|
+
:returns: ``True`` if ``other`` is another ``Box`` and contains
|
|
85
99
|
an object which compares as equal to the object
|
|
86
100
|
contained in the ``Box``, otherwise ``False``.
|
|
87
101
|
|
|
@@ -95,6 +109,21 @@ class Box[T]:
|
|
|
95
109
|
return True
|
|
96
110
|
return False
|
|
97
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
|
+
|
|
98
127
|
@overload
|
|
99
128
|
def get(self) -> T: ...
|
|
100
129
|
@overload
|
|
@@ -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
|
+
"""
|
|
@@ -12,30 +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:: When different flavors of the truth are needed.
|
|
17
|
-
|
|
18
|
-
Sentinel values labeled by different (hashable) flavors.
|
|
19
|
-
Threadsafe.
|
|
20
|
-
|
|
21
|
-
.. tip::
|
|
22
|
-
|
|
23
|
-
Can be compared using ``==`` and ``!=``. A flavored sentinel
|
|
24
|
-
value always equals itself and never equals anything else,
|
|
25
|
-
especially other flavored sentinel values.
|
|
26
|
-
|
|
27
|
-
.. tip::
|
|
28
|
-
|
|
29
|
-
Useful for union types where ``Sentinel[H]`` is one of the
|
|
30
|
-
types making up the union.
|
|
31
|
-
|
|
32
|
-
.. tip::
|
|
33
|
-
|
|
34
|
-
To ensure that reference equality is used, put the known
|
|
35
|
-
sentinel value first in the comparison.
|
|
36
|
-
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
15
|
import threading
|
|
40
16
|
from typing import ClassVar, final, Hashable
|
|
41
17
|
|
|
@@ -44,12 +20,34 @@ __all__ = ['Sentinel']
|
|
|
44
20
|
|
|
45
21
|
@final
|
|
46
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
|
+
|
|
47
37
|
__slots__ = ('_flavor',)
|
|
48
38
|
|
|
49
39
|
_flavors: 'dict[H, Sentinel[H]]' = {}
|
|
50
40
|
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
51
41
|
|
|
52
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
|
+
"""
|
|
53
51
|
if flavor not in cls._flavors:
|
|
54
52
|
with cls._lock:
|
|
55
53
|
if flavor not in cls._flavors:
|
|
@@ -58,25 +56,44 @@ class Sentinel[H: Hashable]:
|
|
|
58
56
|
|
|
59
57
|
def __init__(self, flavor: H) -> None:
|
|
60
58
|
"""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
.. admonition:: init
|
|
60
|
+
|
|
61
|
+
:param flavor: Hashable value to initially cache the flavor
|
|
62
|
+
:type flavor: ``H: Hashable``
|
|
64
63
|
|
|
65
64
|
"""
|
|
66
65
|
if not hasattr(self, '_flavor'):
|
|
67
66
|
self._flavor = flavor
|
|
68
67
|
|
|
69
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
|
+
"""
|
|
70
78
|
return "Sentinel('" + repr(self._flavor) + "')"
|
|
71
79
|
|
|
72
|
-
def
|
|
80
|
+
def __str__(self) -> str:
|
|
73
81
|
"""
|
|
74
|
-
.. admonition::
|
|
82
|
+
.. admonition:: user string
|
|
83
|
+
|
|
84
|
+
Construct string 'Sentinel(flavor)' where the flavor
|
|
85
|
+
is displayed with ``str()``.
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
:returns: A string meaningful to an end user.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
return "Sentinel('" + str(self._flavor) + "')"
|
|
91
|
+
|
|
92
|
+
def flavor(self) -> H:
|
|
93
|
+
"""
|
|
94
|
+
.. admonition:: get flavor
|
|
78
95
|
|
|
79
|
-
|
|
96
|
+
:returns: The sentinel's flavor.
|
|
80
97
|
|
|
81
98
|
"""
|
|
82
99
|
return self._flavor
|
|
@@ -12,117 +12,176 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import threading
|
|
16
|
+
from typing import ClassVar, final
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
not potentially, missing value.
|
|
18
|
+
__all__ = ['NoValue']
|
|
20
19
|
|
|
21
|
-
``NoValue()`` is a singleton object representing a missing value.
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
@final
|
|
22
|
+
class NoValue:
|
|
23
|
+
"""
|
|
24
|
+
.. admonition:: missing value
|
|
25
25
|
|
|
26
|
-
|
|
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
|
-
|
|
35
|
+
.. important::
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
y: int | NoValue
|
|
37
|
+
Given variables
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
as equal. If one or both of theses values are missing, then what is
|
|
40
|
-
there to compare?
|
|
39
|
+
.. code:: python
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
x: int | NoValue
|
|
42
|
+
y: int | NoValue
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
+===========+===========+========+========+
|
|
47
|
-
| NoValue() | false | false | false |
|
|
48
|
-
+-----------+-----------+--------+--------+
|
|
49
|
-
| 42 | false | true | false |
|
|
50
|
-
+-----------+-----------+--------+--------+
|
|
51
|
-
| 57 | false | false | true |
|
|
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
|
-
|
|
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
|
+
+-------------------+-------------------+------------+------------+
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
| x∖y | NoValue() | 42 | 57 |
|
|
60
|
-
+===========+===========+========+========+
|
|
61
|
-
| NoValue() | false | false | false |
|
|
62
|
-
+-----------+-----------+--------+--------+
|
|
63
|
-
| 42 | false | false | true |
|
|
64
|
-
+-----------+-----------+--------+--------+
|
|
65
|
-
| 57 | false | true | false |
|
|
66
|
-
+-----------+-----------+--------+--------+
|
|
61
|
+
.. table:: ``x != y``
|
|
67
62
|
|
|
68
|
-
|
|
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
|
+
+-------------------+-------------------+------------+------------+
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
identity the ``NoValue`` singleton, use ``is`` and ``is not``
|
|
72
|
-
instead.
|
|
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
|
-
|
|
81
|
+
.. tip::
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
Use in a union type when creating "optional" arguments
|
|
84
|
+
to functions and methods.
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
To help ensure the abstraction stays a hidden implementation
|
|
87
|
+
detail and does not leak out into user code,
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
- Do not export the sentinel value.
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
- Use ``@overload`` to keep the NoValue type out of documentation and IDEs.
|
|
87
|
-
|
|
88
|
-
"""
|
|
89
|
-
import threading
|
|
90
|
-
from typing import ClassVar, final
|
|
91
|
+
- A new reference can always be generated via ``NoValue()``.
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
- Use ``@overload`` to keep the ``NoValue`` type out of
|
|
94
|
+
documentation and IDEs.
|
|
93
95
|
|
|
96
|
+
"""
|
|
94
97
|
|
|
95
|
-
@final
|
|
96
|
-
class NoValue():
|
|
97
98
|
__slots__ = ()
|
|
98
99
|
|
|
99
100
|
_instance: 'ClassVar[NoValue | None]' = None
|
|
100
101
|
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
102
|
+
_hash: ClassVar[int] = 0
|
|
101
103
|
|
|
102
104
|
def __new__(cls) -> 'NoValue':
|
|
103
105
|
"""
|
|
104
|
-
|
|
106
|
+
.. admonition:: new
|
|
107
|
+
|
|
108
|
+
:returns: The ``NoValue()`` singleton instance.
|
|
105
109
|
|
|
106
110
|
"""
|
|
107
111
|
if cls._instance is None:
|
|
108
112
|
with cls._lock:
|
|
109
113
|
if cls._instance is None:
|
|
114
|
+
cls._hash = id(cls)
|
|
110
115
|
cls._instance = super().__new__(cls)
|
|
111
116
|
return cls._instance
|
|
112
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
|
+
|
|
113
126
|
def __repr__(self) -> str:
|
|
127
|
+
"""
|
|
128
|
+
.. admonition:: repr string
|
|
129
|
+
|
|
130
|
+
:returns: The string 'NoValue()'.
|
|
131
|
+
|
|
132
|
+
"""
|
|
114
133
|
return 'NoValue()'
|
|
115
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
|
+
|
|
116
158
|
def __eq__(self, other: object) -> bool:
|
|
117
159
|
"""
|
|
118
|
-
|
|
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?
|
|
119
171
|
|
|
120
172
|
"""
|
|
121
173
|
return False
|
|
122
174
|
|
|
123
175
|
def __ne__(self, other: object) -> bool:
|
|
124
176
|
"""
|
|
125
|
-
|
|
177
|
+
.. admonition:: not equal
|
|
178
|
+
|
|
179
|
+
:returns: ``False``
|
|
180
|
+
|
|
181
|
+
.. warning::
|
|
182
|
+
|
|
183
|
+
- non-standard comparison semantics
|
|
184
|
+
- always returns ``False``
|
|
126
185
|
|
|
127
186
|
"""
|
|
128
187
|
return False
|
pythonic_fp/gadgets/wrap.py
CHANGED
|
@@ -30,7 +30,9 @@ class Wrap[T]():
|
|
|
30
30
|
"""
|
|
31
31
|
.. admonition:: Wrap object
|
|
32
32
|
|
|
33
|
-
Immutablely wrap exactly one value of a
|
|
33
|
+
Immutablely wrap exactly one value of a
|
|
34
|
+
given type. ``Wrap`` objects can be used
|
|
35
|
+
in Python match statements.
|
|
34
36
|
|
|
35
37
|
"""
|
|
36
38
|
__slots__ = ('_item',)
|
|
@@ -38,6 +40,10 @@ class Wrap[T]():
|
|
|
38
40
|
|
|
39
41
|
def __init__(self, item: T) -> None:
|
|
40
42
|
"""
|
|
43
|
+
.. admonition:: Initializer
|
|
44
|
+
|
|
45
|
+
Initialize ``Wrap`` with 1 required item.
|
|
46
|
+
|
|
41
47
|
:param item: Item to be wrapped.
|
|
42
48
|
|
|
43
49
|
"""
|
|
@@ -62,8 +68,18 @@ class Wrap[T]():
|
|
|
62
68
|
if self:
|
|
63
69
|
yield self._item
|
|
64
70
|
|
|
65
|
-
def
|
|
66
|
-
|
|
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) + ')'
|
|
67
83
|
|
|
68
84
|
def __eq__(self, other: object) -> bool:
|
|
69
85
|
"""
|
|
@@ -115,17 +131,16 @@ class HWrap[T: Hashable](Hashable):
|
|
|
115
131
|
"""
|
|
116
132
|
.. admonition:: Wrap hashable object
|
|
117
133
|
|
|
118
|
-
Immutablely wrap exactly one value of a
|
|
134
|
+
Immutablely wrap exactly one value of a
|
|
135
|
+
given hashable type. ``HWrap`` objects can
|
|
136
|
+
be used in Python match statements.
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
.. tip::
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.. tip::
|
|
125
|
-
|
|
126
|
-
``HWrap`` objects are hashable.
|
|
140
|
+
``HWrap`` objects are hashable.
|
|
127
141
|
|
|
128
142
|
"""
|
|
143
|
+
|
|
129
144
|
__slots__ = ('_item', '_hash')
|
|
130
145
|
__match_args__ = ('_item',)
|
|
131
146
|
|
|
@@ -1,12 +1,12 @@
|
|
|
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
|
|
@@ -17,8 +17,8 @@ License-File: LICENSE
|
|
|
17
17
|
Requires-Dist: pytest>=8.4.1 ; extra == "test"
|
|
18
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/
|
|
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
|
|
|
@@ -35,22 +35,22 @@ outside the Python Standard Library.
|
|
|
35
35
|
- Gadgets
|
|
36
36
|
|
|
37
37
|
- single item box
|
|
38
|
-
- wrapped (hashable) references
|
|
38
|
+
- immutable wrapped (hashable) references
|
|
39
39
|
- function returning iterator of its arguments
|
|
40
40
|
- function to find the latest common ancestor of two classes
|
|
41
41
|
- sentinel values
|
|
42
42
|
|
|
43
43
|
Part of the
|
|
44
44
|
`pythonic-fp
|
|
45
|
-
<https://grscheller.github.io/pythonic-fp
|
|
45
|
+
<https://grscheller.github.io/pythonic-fp/>`_
|
|
46
46
|
PyPI projects.
|
|
47
47
|
|
|
48
48
|
Documentation
|
|
49
49
|
-------------
|
|
50
50
|
|
|
51
|
-
Documentation for this project
|
|
51
|
+
Documentation and other links for this project are hosted on
|
|
52
52
|
`GitHub Pages
|
|
53
|
-
<https://grscheller.github.io/pythonic-fp
|
|
53
|
+
<https://grscheller.github.io/pythonic-fp/projects/gadgets.html>`_.
|
|
54
54
|
|
|
55
55
|
Copyright and License
|
|
56
56
|
---------------------
|
|
@@ -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=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,,
|
|
File without changes
|
{pythonic_fp_gadgets-4.0.3.dist-info → pythonic_fp_gadgets-4.0.4.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|