pythonic-fp-gadgets 4.0.3__py3-none-any.whl → 4.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
- - No pythonic_fp dependencies at all.
24
+
25
+ - No pythonic_fp dependencies at all.
25
26
 
26
27
  """
27
28
 
@@ -42,22 +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
- .. warning::
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
- This function can fail with a TypeError. Some error messages
48
- seen are
52
+ .. warning::
49
53
 
50
- - multiple bases have instance lay-out conflict
51
- - type 'bool' is not an acceptable base type
54
+ This function can fail with a TypeError. Some error messages
55
+ seen are
52
56
 
53
- This happens frequently when the function is given
54
- Python builtin types or in multiple inheritance situations.
57
+ - multiple bases have instance lay-out conflict
58
+ - type 'bool' is not an acceptable base type
55
59
 
56
- :param cls1: A class in the inheritance hierarchy.
57
- :param cls2: A class in the inheritance hierarchy.
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``.
60
+ This happens frequently when the function is given
61
+ Python builtin types or in multiple inheritance situations.
61
62
 
62
63
  """
63
64
  if issubclass(cls1, cls2):
@@ -77,12 +78,12 @@ def iterate_over_arguments[A](*args: A) -> Iterator[A]:
77
78
 
78
79
  Function returning an iterator over its arguments.
79
80
 
80
- .. note::
81
+ :param args: Objects to iterate over.
82
+ :returns: An iterator of the function's arguments.
81
83
 
82
- Does not create a Python object to iterate over.
84
+ .. note::
83
85
 
84
- :param args: Objects to iterate over.
85
- :returns: An iterator of the functions arguments.
86
+ Does not create a Python object to iterate over.
86
87
 
87
88
  """
88
89
  yield from args
@@ -1,6 +1,6 @@
1
1
  from collections.abc import Iterator
2
2
 
3
- __all__ = ['iterate_over_arguments', 'first_common_ancestor']
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]: ...
@@ -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
  """
57
- :param item: An optional initial ``item`` for the ``Box``.
51
+ .. admonition:: Initializer
52
+
53
+ Initialize ``Box`` with 0 or 1 items.
54
+
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
- if self:
67
- yield cast(T, self._item)
70
+ """
71
+ .. admonition:: iter
68
72
 
69
- def __repr__(self) -> str:
73
+ :yields: Boxed item.
74
+
75
+ """
70
76
  if self:
71
- return 'Box(' + repr(self._item) + ')'
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,10 +94,10 @@ class Box[T]:
80
94
 
81
95
  Efficiently compare ``Box`` to another object.
82
96
 
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``.
97
+ :param other: The object to be compared.
98
+ :returns: ``True`` if ``other`` is another ``Box`` containing
99
+ an object which compares as equal to the item
100
+ contained in the ``Box``, otherwise ``False``.
87
101
 
88
102
  """
89
103
  if not isinstance(other, type(self)):
@@ -95,6 +109,37 @@ class Box[T]:
95
109
  return True
96
110
  return False
97
111
 
112
+ def __repr__(self) -> str:
113
+ """
114
+ .. admonition:: repr 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
+
127
+ def __str__(self) -> str:
128
+ """
129
+ .. admonition:: user string
130
+
131
+ Construct string 'Box()' if empty, otherwise 'Box(item_str)'
132
+ where ``item_str = str(item)`` for the currently contained
133
+ item.
134
+
135
+ :returns: A user meaningful string to represent the current
136
+ state of the ``Box``.
137
+
138
+ """
139
+ if self:
140
+ return 'Box(' + str(self._item) + ')'
141
+ return 'Box()'
142
+
98
143
  @overload
99
144
  def get(self) -> T: ...
100
145
  @overload
@@ -107,12 +152,12 @@ class Box[T]:
107
152
  Return the boxed item, if it exists, otherwise
108
153
  an alternate item, if given.
109
154
 
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.
155
+ :param alt: An optional item of type ``T`` to return
156
+ if the ``Box`` is empty.
157
+ :returns: Contents of ``Box`` or an alternate item, if given,
158
+ when the ``Box`` is empty.
159
+ :raises ValueError: When the ``alt`` item is not provided
160
+ but needed.
116
161
 
117
162
  """
118
163
  if self._item is not _sentinel:
@@ -128,8 +173,8 @@ class Box[T]:
128
173
 
129
174
  Pop item from ``Box`` if not empty.
130
175
 
131
- :returns: The item contained in the ``Box``.
132
- :raises ValueError: If Box is empty.
176
+ :returns: The item contained in the ``Box``.
177
+ :raises ValueError: If Box is empty.
133
178
 
134
179
  """
135
180
  if self._item is _sentinel:
@@ -145,8 +190,8 @@ class Box[T]:
145
190
 
146
191
  Push an item into ``Box`` if empty.
147
192
 
148
- :param item: Item to push into the empty ``Box``.
149
- :raises ValueError: If ``Box`` is not empty.
193
+ :param item: Item to push into the empty ``Box``.
194
+ :raises ValueError: If ``Box`` is not empty.
150
195
 
151
196
  """
152
197
  if self._item is _sentinel:
@@ -171,9 +216,9 @@ class Box[T]:
171
216
 
172
217
  Exchange an item with what is in the Box.
173
218
 
174
- :param ``new_item``: New item to exchange for current item.
175
- :returns: Original contents of the ``Box``.
176
- :raises ValueError: If Box is empty.
219
+ :param ``new_item``: New item to exchange for current item.
220
+ :returns: Original contents of the ``Box``.
221
+ :raises ValueError: If Box is empty.
177
222
 
178
223
  """
179
224
  if self._item is _sentinel:
@@ -190,8 +235,8 @@ class Box[T]:
190
235
  Map function ``f`` over contents. We need to return a new
191
236
  instance since the type of Box can change.
192
237
 
193
- :param f: Mapping function.
194
- :returns: New instance.
238
+ :param f: Mapping function.
239
+ :returns: New instance.
195
240
 
196
241
  """
197
242
  if self._item is _sentinel:
@@ -204,8 +249,8 @@ class Box[T]:
204
249
 
205
250
  Flatmap ``Box`` with function ``f``.
206
251
 
207
- :param f: Binding function.
208
- :returns: New instance.
252
+ :param f: Binding function.
253
+ :returns: New instance.
209
254
 
210
255
  """
211
256
  if self._item is _sentinel:
@@ -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
- __author__ = 'Geoffrey R. Scheller'
16
- __copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
17
- __license__ = 'Apache License 2.0'
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
- :param flavor: Some Hashable value of generic type ``H``.
62
- :returns: The ``Sentinel`` singleton instance with flavor ``flavor``.
63
- :rtype: ``Sentinel[H]`` where ``H`` is a subtype of Hashable.
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 flavor(self) -> H:
80
+ def __str__(self) -> str:
73
81
  """
74
- .. admonition:: Get flavor
82
+ .. admonition:: user string
83
+
84
+ Construct string 'Sentinel(flavor)' where the flavor
85
+ is displayed with ``str()``.
75
86
 
76
- Get the sentinel's flavor, a hashable
77
- value of type ``H``.
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
- :returns: The sentinel's flavor.
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
- .. admonition:: Missing value.
15
+ import threading
16
+ from typing import ClassVar, final
17
17
 
18
- Singleton class representing an actually,
19
- not potentially, missing value.
18
+ __all__ = ['NoValue']
20
19
 
21
- ``NoValue()`` is a singleton object representing a missing value.
22
20
 
23
- While ``None`` and ``()`` are frequently used as sentinel values,
24
- I prefer to think of them as
21
+ @final
22
+ class NoValue:
23
+ """
24
+ .. admonition:: missing value
25
25
 
26
- - ``None``: Returns, or returned, no values.
27
- - ``()``: An empty, possibly typed, iterable collection.
26
+ Singleton class representing an actual, not
27
+ potential, missing value.
28
28
 
29
- .. important::
29
+ While ``None`` and ``()`` are frequently used as sentinel values,
30
+ I prefer to think of them as
30
31
 
31
- Given variables
32
+ - ``None`` as returns, or returned, no values.
33
+ - ``()`` as an empty, possibly typed, iterable collection.
32
34
 
33
- .. code:: python
35
+ .. important::
34
36
 
35
- x: int | NoValue
36
- y: int | NoValue
37
+ Given variables
37
38
 
38
- Equality between ``x`` and ``y`` means both values exist and compare
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
- .. table:: ``x == y``
41
+ x: int | NoValue
42
+ y: int | NoValue
43
43
 
44
- +-----------+-----------+--------+--------+
45
- | x∖y | NoValue() | 42 | 57 |
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
- Similarly for not equals.
47
+ .. table:: ``x == y``
55
48
 
56
- .. table:: ``x != y``:wq
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
- .. warning::
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
- Only use ``==`` or ``!=`` in value comparisons. To directly
71
- identity the ``NoValue`` singleton, use ``is`` and ``is not``
72
- instead.
75
+ .. warning::
73
76
 
74
- .. note::
77
+ - use ``==`` or ``!=`` only in value comparisons
78
+ - use ``is`` and ``is not`` to identity the ``NoValue()``
79
+ singleton itself
75
80
 
76
- Threadsafe.
81
+ .. tip::
77
82
 
78
- .. tip::
83
+ Use in a union type when creating "optional" arguments
84
+ to functions and methods.
79
85
 
80
- Use as a hidden implementation detail when creating "optional"
81
- arguments to functions and methods.
86
+ To help ensure the abstraction stays a hidden implementation
87
+ detail and does not leak out into user code,
82
88
 
83
- To help ensure the abstraction does not leak,
89
+ - Do not export the sentinel value.
84
90
 
85
- - Do not export the sentinel value.
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
- __all__ = ['NoValue']
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
- :returns: The ``NoValue`` singleton instance.
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
- :returns: ``False``
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
- :returns: ``False``
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
@@ -2,5 +2,7 @@ __all__ = ['NoValue']
2
2
 
3
3
  class NoValue:
4
4
  def __new__(cls) -> NoValue: ...
5
+ def __hash__(self) -> int: ...
6
+ def __bool__(self) -> bool: ...
5
7
  def __eq__(self, other: object) -> bool: ...
6
8
  def __ne__(self, other: object) -> bool: ...
@@ -11,15 +11,6 @@
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
- """
23
14
 
24
15
  __all__ = ['Wrap', 'HWrap']
25
16
 
@@ -28,9 +19,13 @@ from collections.abc import Callable, Iterator, Hashable
28
19
 
29
20
  class Wrap[T]():
30
21
  """
31
- .. admonition:: Wrap object
22
+ .. admonition:: Wrap
23
+
24
+ Immutablely wrap exactly one item.
32
25
 
33
- Immutablely wrap exactly one value of a given type.
26
+ .. tip::
27
+
28
+ ``Wrap`` objects are matchable.
34
29
 
35
30
  """
36
31
  __slots__ = ('_item',)
@@ -38,43 +33,55 @@ class Wrap[T]():
38
33
 
39
34
  def __init__(self, item: T) -> None:
40
35
  """
41
- :param item: Item to be wrapped.
36
+ .. admonition:: init
37
+
38
+ Initialize ``Wrap`` with 1 required item.
39
+
40
+ :param item: Item to be wrapped.
42
41
 
43
42
  """
44
43
  self._item = item
45
44
 
46
45
  def __bool__(self) -> bool:
47
46
  """
48
- .. admonition:: Bool
47
+ .. admonition:: bool
49
48
 
50
49
  Truthiness same as wrapped object.
51
50
 
52
51
  """
53
52
  return bool(self._item)
54
53
 
54
+ def __len__(self) -> int:
55
+ """
56
+ .. admonition:: len
57
+
58
+ Wrapped items always contain just one item.
59
+
60
+ :returns: 1
61
+
62
+ """
63
+ return 1
64
+
55
65
  def __iter__(self) -> Iterator[T]:
56
66
  """
57
- .. admonition:: Iter
67
+ .. admonition:: iter
58
68
 
59
- Iterable, iterates wrapped item.
69
+ :yields: A reference to the wrapped item.
60
70
 
61
71
  """
62
72
  if self:
63
73
  yield self._item
64
74
 
65
- def __repr__(self) -> str:
66
- return 'Wrap(' + repr(self._item) + ')'
67
-
68
75
  def __eq__(self, other: object) -> bool:
69
76
  """
70
- .. admonition:: Equality comparison
77
+ .. admonition:: equality comparison
71
78
 
72
- Efficiently compare ``Wrap`` to another object.
79
+ Compare ``Wrap`` to another object.
73
80
 
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``.
81
+ :param other: Object to be compared.
82
+ :returns: ``True`` if ``other`` is of type Wrap and wraps
83
+ an item which compares as equal to the wrapped
84
+ item, otherwise ``False``.
78
85
 
79
86
  """
80
87
  if not isinstance(other, type(self)):
@@ -84,28 +91,50 @@ class Wrap[T]():
84
91
  return True
85
92
  return self._item == other._item
86
93
 
94
+ def __repr__(self) -> str:
95
+ """
96
+ .. admonition:: repr string
97
+
98
+ Construct string 'Wrap(item_str)'
99
+ where ``item_str = repr(item)`` for the contained item.
100
+
101
+ :returns: A string to reproduce of the wrapped item.
102
+
103
+ """
104
+ return 'Wrap(' + repr(self._item) + ')'
105
+
106
+ def __str__(self) -> str:
107
+ """
108
+ .. admonition:: user string
109
+
110
+ Construct string 'Wrap(item_str)'
111
+ where ``item_str = str(item)`` for the contained item.
112
+
113
+ :returns: A string meaningful to an end user.
114
+
115
+ """
116
+ return 'Wrap(' + str(self._item) + ')'
117
+
87
118
  def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
88
119
  """
89
- .. admonition:: Map
120
+ .. admonition:: map
90
121
 
91
122
  Map function ``f`` over contents.
92
123
 
93
- Map function ``f`` over contents.
94
-
95
- :param f: Mapping function.
96
- :returns: New instance.
124
+ :param f: Mapping function.
125
+ :returns: New instance.
97
126
 
98
127
  """
99
128
  return Wrap(f(self._item))
100
129
 
101
130
  def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
102
131
  """
103
- .. admonition:: Bind
132
+ .. admonition:: bind
104
133
 
105
134
  Flatmap wrapped object with function ``f``.
106
135
 
107
- :param f: Binding function.
108
- :returns: New instance.
136
+ :param f: Binding function.
137
+ :returns: New instance.
109
138
 
110
139
  """
111
140
  return f(self._item)
@@ -113,23 +142,28 @@ class Wrap[T]():
113
142
 
114
143
  class HWrap[T: Hashable](Hashable):
115
144
  """
116
- .. admonition:: Wrap hashable object
145
+ .. admonition:: HWrap
117
146
 
118
- Immutablely wrap exactly one value of a given hashable type.
147
+ Immutablely wrap exactly one hashable item.
119
148
 
120
- .. tip::
149
+ .. tip::
121
150
 
122
- ``HWrap`` objects can be used in Python match statements.
123
-
124
- .. tip::
125
-
126
- ``HWrap`` objects are hashable.
151
+ ``HWrap`` objects are hashable and matchable.
127
152
 
128
153
  """
154
+
129
155
  __slots__ = ('_item', '_hash')
130
156
  __match_args__ = ('_item',)
131
157
 
132
158
  def __init__(self, item: T) -> None:
159
+ """
160
+ .. admonition:: init
161
+
162
+ Initialize ``HWrap`` with 1 required item.
163
+
164
+ :param item: Item to be wrapped.
165
+
166
+ """
133
167
  self._item, self._hash = item, hash(item)
134
168
 
135
169
  def __hash__(self) -> int:
@@ -137,36 +171,44 @@ class HWrap[T: Hashable](Hashable):
137
171
 
138
172
  def __bool__(self) -> bool:
139
173
  """
140
- .. admonition:: Bool
174
+ .. admonition:: bool
141
175
 
142
176
  Truthiness same as wrapped object.
143
177
 
144
178
  """
145
179
  return bool(self._item)
146
180
 
181
+ def __len__(self) -> int:
182
+ """
183
+ .. admonition:: len
184
+
185
+ HWrapped items always contain just one item.
186
+
187
+ :returns: 1
188
+
189
+ """
190
+ return 1
191
+
147
192
  def __iter__(self) -> Iterator[T]:
148
193
  """
149
- .. admonition:: Iter
194
+ .. admonition:: iter
150
195
 
151
- Iterable, iterates wrapped item.
196
+ :yields: A reference to the wrapped item.
152
197
 
153
198
  """
154
199
  if self:
155
200
  yield self._item
156
201
 
157
- def __repr__(self) -> str:
158
- return 'Wrap(' + repr(self._item) + ')'
159
-
160
202
  def __eq__(self, other: object) -> bool:
161
203
  """
162
- .. admonition:: Equality comparison
204
+ .. admonition:: equality comparison
163
205
 
164
- Efficiently compare to another object.
206
+ Compare ``HWrap`` to another object.
165
207
 
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``.
208
+ :param other: Object to be compared.
209
+ :returns: ``True`` if ``other`` is of type HWrap and wraps
210
+ an item which compares as equal to the wrapped
211
+ item, otherwise ``False``.
170
212
 
171
213
  """
172
214
  if not isinstance(other, type(self)):
@@ -178,27 +220,51 @@ class HWrap[T: Hashable](Hashable):
178
220
  return True
179
221
  return self._item == other._item
180
222
 
223
+ def __repr__(self) -> str:
224
+ """
225
+ .. admonition:: repr string
226
+
227
+ Construct string 'HWrap(item_str)'
228
+ where ``item_str = repr(item)`` for the contained item.
229
+
230
+ :returns: A string to reproduce of the wrapped object.
231
+
232
+ """
233
+ return 'Wrap(' + repr(self._item) + ')'
234
+
235
+ def __str__(self) -> str:
236
+ """
237
+ .. admonition:: user string
238
+
239
+ Construct string 'HWrap(item_str)'
240
+ where ``item_str = str(item)`` for the contained item.
241
+
242
+ :returns: A string meaningful to an end user.
243
+
244
+ """
245
+ return 'Wrap(' + str(self._item) + ')'
246
+
181
247
  def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
182
248
  """
183
- .. admonition:: Map
249
+ .. admonition:: map
184
250
 
185
251
  Map function ``f`` over wrapped the wrapped object
186
252
  returning a new ``HWrap`` instance.
187
253
 
188
- :param f: Mapping function.
189
- :returns: New instance.
254
+ :param f: Mapping function.
255
+ :returns: New instance.
190
256
 
191
257
  """
192
258
  return HWrap(f(self._item))
193
259
 
194
260
  def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
195
261
  """
196
- .. admonition:: Bind
262
+ .. admonition:: bind
197
263
 
198
264
  Flatmap ``Box`` with function ``f``.
199
265
 
200
- :param f: Binding function.
201
- :returns: New instance.
266
+ :param f: Binding function.
267
+ :returns: New instance.
202
268
 
203
269
  """
204
270
  return f(self._item)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-gadgets
3
- Version: 4.0.3
3
+ Version: 4.1.0
4
4
  Summary: Gadgets
5
5
  Keywords: gadgets
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
@@ -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-gadgets/
21
- Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/html/
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/homepage/html/>`_
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 is hosted on
51
+ Documentation and other links for this project are hosted on
52
52
  `GitHub Pages
53
- <https://grscheller.github.io/pythonic-fp-gadgets>`_.
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=zwRgxkaQYUbqjR-kX8pQeuPqipCB1bllVOEVBtGXl6I,2807
2
+ pythonic_fp/gadgets/__init__.pyi,sha256=-O921ioE28UPnDm_EL928vl6dDxhrfXHBQenusBo1_o,224
3
+ pythonic_fp/gadgets/box.py,sha256=gcW9mg-QCjc2lLTcOeqYJK0NAkHAbFs1JxhFjVTW1uQ,7115
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=Bdi-0rg7x37NWd0ciRi_IeDrsmlKQHJOMY9HcCnKt9M,6465
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.1.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
15
+ pythonic_fp_gadgets-4.1.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
16
+ pythonic_fp_gadgets-4.1.0.dist-info/METADATA,sha256=eI5gy-KQSFKzOW1E18RF8lzF7EQcEcF-WWWFEeX63Ko,1894
17
+ pythonic_fp_gadgets-4.1.0.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,,