pythonic-fp-gadgets 4.3.1__py3-none-any.whl → 4.5.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.
@@ -27,7 +27,6 @@ Gadgets
27
27
  """
28
28
 
29
29
  from collections.abc import Iterator
30
- from inspect import getmro
31
30
 
32
31
  __all__ = ['first_common_ancestor', 'iterate_over_arguments']
33
32
 
@@ -38,27 +37,33 @@ __license__ = 'Apache License 2.0'
38
37
 
39
38
  def first_common_ancestor(cls1: type, cls2: type) -> type:
40
39
  """
41
- .. admonition:: Least upper bound
40
+ .. admonition:: First common ancestor
42
41
 
43
- Find the least upper bound in the inheritance graph
44
- of two classes.
42
+ Best effort to find the least upper bound in the inheritance
43
+ graph of two classes.
45
44
 
46
45
  :param cls1: A class in the inheritance hierarchy.
47
46
  :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``.
47
+ :returns: First common ancestor in ``cls1.__mro__`` order.
48
+ :raises TypeError: Defensively raised when no common ancestor
49
+ is found.
51
50
 
52
- .. warning::
51
+ .. note::
52
+
53
+ The function is not symmetric in its arguments. In genuine
54
+ multiple inheritance graphs, swapping ``cls1`` and ``cls2``
55
+ can yield a different ancestor. Also, a virtual ancestor
56
+ registered with an ABC may be a tighter bound than anything
57
+ in the actual MRO.
53
58
 
54
- This function can fail with a TypeError. Some error messages
55
- seen are
59
+ .. note::
56
60
 
57
- - multiple bases have instance lay-out conflict
58
- - type 'bool' is not an acceptable base type
61
+ Since ``object`` terminates all actual MRO's, ``TypeError``
62
+ is unlikely to ever be thrown, except in the case of some
63
+ exotic metaclass breaking this assumption. This exception
64
+ is here mainly to let typing tools know that object ``None``
65
+ is not a possible return type.
59
66
 
60
- This happens frequently when the function is given
61
- Python builtin types or in multiple inheritance situations.
62
67
 
63
68
  """
64
69
  if issubclass(cls1, cls2):
@@ -66,10 +71,10 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
66
71
  if issubclass(cls2, cls1):
67
72
  return cls1
68
73
 
69
- for common_ancestor in getmro(type('LcaDiamondClass', (cls1, cls2), {})):
70
- if issubclass(cls1, common_ancestor) and issubclass(cls2, common_ancestor):
71
- return common_ancestor
72
- raise TypeError("latest_common_ancestor: no common ancestor found!!!")
74
+ for ancestor in cls1.__mro__:
75
+ if issubclass(cls2, ancestor):
76
+ return ancestor
77
+ raise TypeError("first_common_ancestor: no common ancestor found!")
73
78
 
74
79
 
75
80
  def iterate_over_arguments[A](*args: A) -> Iterator[A]:
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from collections.abc import Callable, Iterator
16
- from typing import cast, Final, overload
16
+ from typing import Final, cast, overload
17
17
 
18
18
  __all__ = ['Box']
19
19
 
@@ -105,9 +105,7 @@ class Box[T]:
105
105
 
106
106
  if self._item is other._item:
107
107
  return True
108
- if self._item == other._item:
109
- return True
110
- return False
108
+ return self._item == other._item
111
109
 
112
110
  def __repr__(self) -> str:
113
111
  """
@@ -199,7 +197,6 @@ class Box[T]:
199
197
  else:
200
198
  msg = 'Box: Trying to push an item in a non-empty Box'
201
199
  raise ValueError(msg)
202
- return None
203
200
 
204
201
  def put(self, item: T) -> None:
205
202
  """
@@ -228,7 +225,7 @@ class Box[T]:
228
225
  self._item = new_item
229
226
  return popped
230
227
 
231
- def map[U](self, f: Callable[[T], U]) -> 'Box[U]':
228
+ def map[U](self, f: Callable[[T], U]) -> Box[U]:
232
229
  """
233
230
  .. admonition:: Map
234
231
 
@@ -243,7 +240,7 @@ class Box[T]:
243
240
  return Box()
244
241
  return Box(f(cast(T, self._item)))
245
242
 
246
- def bind[U](self, f: Callable[[T], 'Box[U]']) -> 'Box[U]':
243
+ def bind[U](self, f: Callable[[T], Box[U]]) -> Box[U]:
247
244
  """
248
245
  .. admonition:: Bind
249
246
 
@@ -12,12 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __all__ = ['Wrap', 'HWrap']
16
-
17
- from collections.abc import Callable, Iterator, Hashable
15
+ from collections.abc import Callable, Hashable, Iterator
18
16
 
17
+ __all__ = ['Wrap', 'HWrap']
19
18
 
20
- class Wrap[T]():
19
+ class Wrap[T]:
21
20
  """
22
21
  .. admonition:: Wrap
23
22
 
@@ -115,7 +114,7 @@ class Wrap[T]():
115
114
  """
116
115
  return 'Wrap(' + str(self._item) + ')'
117
116
 
118
- def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
117
+ def map[U](self, f: Callable[[T], U]) -> Wrap[U]:
119
118
  """
120
119
  .. admonition:: map
121
120
 
@@ -127,7 +126,7 @@ class Wrap[T]():
127
126
  """
128
127
  return Wrap(f(self._item))
129
128
 
130
- def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
129
+ def bind[U](self, f: Callable[[T], Wrap[U]]) -> Wrap[U]:
131
130
  """
132
131
  .. admonition:: bind
133
132
 
@@ -148,11 +147,11 @@ class HWrap[T: Hashable](Hashable):
148
147
 
149
148
  .. tip::
150
149
 
151
- ``HWrap`` objects are hashable and matchable.
150
+ ``HWrap`` objects are matchable and hashable.
152
151
 
153
152
  """
154
153
 
155
- __slots__ = ('_item', '_hash')
154
+ __slots__ = ('_hash', '_item')
156
155
  __match_args__ = ('_item',)
157
156
 
158
157
  def __init__(self, item: T) -> None:
@@ -164,7 +163,7 @@ class HWrap[T: Hashable](Hashable):
164
163
  :param item: Item to be wrapped.
165
164
 
166
165
  """
167
- self._item, self._hash = item, hash(item)
166
+ self._item, self._hash = item, hash((item, '_hwrap'))
168
167
 
169
168
  def __hash__(self) -> int:
170
169
  return self._hash
@@ -244,7 +243,7 @@ class HWrap[T: Hashable](Hashable):
244
243
  """
245
244
  return 'Wrap(' + str(self._item) + ')'
246
245
 
247
- def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
246
+ def map[U](self, f: Callable[[T], U]) -> HWrap[U]:
248
247
  """
249
248
  .. admonition:: map
250
249
 
@@ -257,7 +256,7 @@ class HWrap[T: Hashable](Hashable):
257
256
  """
258
257
  return HWrap(f(self._item))
259
258
 
260
- def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
259
+ def bind[U](self, f: Callable[[T], HWrap[U]]) -> HWrap[U]:
261
260
  """
262
261
  .. admonition:: bind
263
262
 
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-gadgets
3
- Version: 4.3.1
3
+ Version: 4.5.0
4
4
  Summary: Gadgets without external Pythonic FP dependencies.
5
- Keywords: gadgets
5
+ Keywords: gadgets,no-external-dependencies
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
7
  Requires-Python: >=3.14
8
8
  Description-Content-Type: text/markdown
@@ -31,18 +31,19 @@ Part of the
31
31
  [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
32
32
  PyPI projects.
33
33
 
34
- ## Documentation
34
+ ## Description
35
35
 
36
+ Project Pythonic FP Gadgets contains
37
+
38
+ - single item box
39
+ - immutable wrapper classes, matchable, hashable and non-hashable versions
40
+ - sentinel values
41
+ - function returning iterator of its arguments
42
+ - function to find the latest common ancestor of two classes
43
+
36
44
  Documentation and other links for this project are hosted on
37
45
  [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/gadgets.html).
38
46
 
39
- - Gadgets
40
- - single item box
41
- - immutable wrapped (hashable) references
42
- - function returning iterator of its arguments
43
- - function to find the latest common ancestor of two classes
44
- - sentinel values
45
-
46
47
  ## Copyright and License
47
48
 
48
49
  Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
@@ -0,0 +1,14 @@
1
+ pythonic_fp/gadgets/__init__.py,sha256=XcjEjYz5Wi8VcSJ3BEqCWDyZ_6w_Q1m1XyUpFngBRUk,2973
2
+ pythonic_fp/gadgets/box.py,sha256=9BPmCp_pYjNr6si444p0C2n8Tfb1mtQn6IwAi_1oO54,7047
3
+ pythonic_fp/gadgets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ pythonic_fp/gadgets/wrap.py,sha256=QUN8ILCsqOqukDHBs9dZJmfwIkfp1E5jAQGLS9qwhi0,6462
5
+ pythonic_fp/gadgets/sentinels/__init__.py,sha256=Xh63-GTwiqqcBqs5OEYBSiLXuWtq_CVHfUz0mKO_2H0,775
6
+ pythonic_fp/gadgets/sentinels/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ pythonic_fp/gadgets/sentinels/flavored.py,sha256=LPmA-XOadcN5gJgxxo2prSnwRvq8bJ7o0Ae2_Y70Au0,2774
8
+ pythonic_fp/gadgets/sentinels/flavored.pyi,sha256=K3YSyXLyylPII2ahRP0olhzLJruOeR73TFJDC6rCUwk,212
9
+ pythonic_fp/gadgets/sentinels/novalue.py,sha256=gaQNm1TRtphbmWgDV8O0sblH7eU2M-Zymu_B67mwsJ8,6097
10
+ pythonic_fp/gadgets/sentinels/novalue.pyi,sha256=1_iUI6FEwYma8P4XXVCb6CKhSYmfq53S8dLmiwNtJw4,244
11
+ pythonic_fp_gadgets-4.5.0.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
12
+ pythonic_fp_gadgets-4.5.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
13
+ pythonic_fp_gadgets-4.5.0.dist-info/METADATA,sha256=vAT0Ldrr9Qyy2Uh4hkyidkSZ446UerMAOLBdnerSulo,1873
14
+ pythonic_fp_gadgets-4.5.0.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- from collections.abc import Iterator
2
-
3
- __all__ = ['first_common_ancestor', 'iterate_over_arguments']
4
-
5
- def first_common_ancestor(cls1: type, cls2: type) -> type: ...
6
- def iterate_over_arguments[A](*args: A) -> Iterator[A]: ...
@@ -1,27 +0,0 @@
1
- from _typeshed import Incomplete
2
- from collections.abc import Callable, Iterator
3
- from typing import overload
4
-
5
- __all__ = ['Box']
6
-
7
- type _Sentinel = object
8
- class Box[T]:
9
- __match_args__: Incomplete
10
- @overload
11
- def __init__(self, item: T) -> None: ...
12
- @overload
13
- def __init__(self) -> None: ...
14
- def __bool__(self) -> bool: ...
15
- def __iter__(self) -> Iterator[T]: ...
16
- def __len__(self) -> int: ...
17
- def __eq__(self, other: object) -> bool: ...
18
- @overload
19
- def get(self) -> T: ...
20
- @overload
21
- def get(self, alt: T) -> T: ...
22
- def pop(self) -> T: ...
23
- def push(self, item: T) -> None: ...
24
- def put(self, item: T) -> None: ...
25
- def exchange(self, new_item: T) -> T: ...
26
- def map[U](self, f: Callable[[T], U]) -> Box[U]: ...
27
- def bind[U](self, f: Callable[[T], 'Box[U]']) -> Box[U]: ...
@@ -1,23 +0,0 @@
1
- from _typeshed import Incomplete
2
- from collections.abc import Callable, Hashable, Iterator
3
-
4
- __all__ = ['Wrap', 'HWrap']
5
-
6
- class Wrap[T]:
7
- __match_args__: Incomplete
8
- def __init__(self, item: T) -> None: ...
9
- def __bool__(self) -> bool: ...
10
- def __iter__(self) -> Iterator[T]: ...
11
- def __eq__(self, other: object) -> bool: ...
12
- def map[U](self, f: Callable[[T], U]) -> Wrap[U]: ...
13
- def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> Wrap[U]: ...
14
-
15
- class HWrap[T: Hashable](Hashable):
16
- __match_args__: Incomplete
17
- def __init__(self, item: T) -> None: ...
18
- def __hash__(self) -> int: ...
19
- def __bool__(self) -> bool: ...
20
- def __iter__(self) -> Iterator[T]: ...
21
- def __eq__(self, other: object) -> bool: ...
22
- def map[U](self, f: Callable[[T], U]) -> HWrap[U]: ...
23
- def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> HWrap[U]: ...
@@ -1,17 +0,0 @@
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.3.1.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
15
- pythonic_fp_gadgets-4.3.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
16
- pythonic_fp_gadgets-4.3.1.dist-info/METADATA,sha256=IVjIF3Na-O4SOI3GOpr7jPtVShlsWa6Mlb-CMBupWgc,1798
17
- pythonic_fp_gadgets-4.3.1.dist-info/RECORD,,