pythonic-fp-gadgets 3.1.0__tar.gz → 4.0.0__tar.gz
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-3.1.0 → pythonic_fp_gadgets-4.0.0}/CHANGELOG.rst +9 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/PKG-INFO +6 -6
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/README.rst +2 -2
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/pyproject.toml +4 -3
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/__init__.py +83 -0
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/__init__.pyi +6 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/src/pythonic_fp/gadgets/box.py +4 -6
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/box.pyi +27 -0
- pythonic_fp_gadgets-3.1.0/src/pythonic_fp/gadgets/iterate_arguments.py → pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/sentinels/__init__.py +3 -22
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/sentinels/__init__.pyi +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/src/pythonic_fp/gadgets/sentinels/flavored.py +3 -17
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/sentinels/flavored.pyi +8 -0
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/sentinels/novalue.py +124 -0
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/sentinels/novalue.pyi +6 -0
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/wrap.py +124 -0
- pythonic_fp_gadgets-4.0.0/src/pythonic_fp/gadgets/wrap.pyi +23 -0
- pythonic_fp_gadgets-4.0.0/tests/test_first_common_ancestor.py +115 -0
- pythonic_fp_gadgets-4.0.0/tests/test_hwrap.py +76 -0
- pythonic_fp_gadgets-3.1.0/tests/test_it.py → pythonic_fp_gadgets-4.0.0/tests/test_iterate_over_arguments.py +1 -1
- pythonic_fp_gadgets-4.0.0/tests/test_wrap.py +71 -0
- pythonic_fp_gadgets-3.1.0/src/pythonic_fp/gadgets/__init__.py +0 -39
- pythonic_fp_gadgets-3.1.0/src/pythonic_fp/gadgets/latest_common_ancestor.py +0 -51
- pythonic_fp_gadgets-3.1.0/src/pythonic_fp/gadgets/sentinels/__init__.py +0 -31
- pythonic_fp_gadgets-3.1.0/src/pythonic_fp/gadgets/sentinels/novalue.py +0 -106
- pythonic_fp_gadgets-3.1.0/tests/test_lca.py +0 -114
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/.gitignore +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/LICENSE +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/src/pythonic_fp/gadgets/py.typed +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/tests/sentinels/test_novalue.py +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/tests/sentinels/test_sentinel_hidden_implemetation_detail.py +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/tests/sentinels/test_sentinel_with_functions.py +0 -0
- {pythonic_fp_gadgets-3.1.0 → pythonic_fp_gadgets-4.0.0}/tests/test_box.py +0 -0
|
@@ -17,6 +17,15 @@ See `Semantic Versioning 2.0.0 <https://semver.org>`_.
|
|
|
17
17
|
Releases and Important Milestones
|
|
18
18
|
---------------------------------
|
|
19
19
|
|
|
20
|
+
PyPI v4.0.0 - TBD
|
|
21
|
+
~~~~~~~~~~~~~~~~~
|
|
22
|
+
|
|
23
|
+
Move functions out of their own packages to __init__.py (2025-10-13).
|
|
24
|
+
|
|
25
|
+
- latest_common_ancestor.lca -> first_common_ancestor
|
|
26
|
+
- iterate_arguments.ita -> iterate_over_arguments
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
PyPI v3.0.1 - 2025-09-09
|
|
21
30
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
22
31
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonic-fp-gadgets
|
|
3
|
-
Version:
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: **Module level function names.**
|
|
5
5
|
Keywords: gadgets
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.13
|
|
@@ -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.0 ; 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/development/build/html/
|
|
21
|
-
Project-URL: Homepage, https://github.
|
|
20
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/gadgets/development/build/html/
|
|
21
|
+
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/build/html/
|
|
22
22
|
Project-URL: Source, https://github.com/grscheller/pythonic-fp-gadgets
|
|
23
23
|
Provides-Extra: test
|
|
24
24
|
|
|
@@ -39,7 +39,7 @@ outside the Python Standard Library.
|
|
|
39
39
|
|
|
40
40
|
Part of the
|
|
41
41
|
`pythonic-fp
|
|
42
|
-
<https://grscheller.github.io/pythonic-fp
|
|
42
|
+
<https://grscheller.github.io/pythonic-fp>`_
|
|
43
43
|
PyPI projects.
|
|
44
44
|
|
|
45
45
|
Documentation
|
|
@@ -47,7 +47,7 @@ Documentation
|
|
|
47
47
|
|
|
48
48
|
Documentation for this project is hosted on
|
|
49
49
|
`GitHub Pages
|
|
50
|
-
<https://grscheller.github.io/pythonic-fp/gadgets
|
|
50
|
+
<https://grscheller.github.io/pythonic-fp/gadgets>`_.
|
|
51
51
|
|
|
52
52
|
Copyright and License
|
|
53
53
|
---------------------
|
|
@@ -15,7 +15,7 @@ outside the Python Standard Library.
|
|
|
15
15
|
|
|
16
16
|
Part of the
|
|
17
17
|
`pythonic-fp
|
|
18
|
-
<https://grscheller.github.io/pythonic-fp
|
|
18
|
+
<https://grscheller.github.io/pythonic-fp>`_
|
|
19
19
|
PyPI projects.
|
|
20
20
|
|
|
21
21
|
Documentation
|
|
@@ -23,7 +23,7 @@ Documentation
|
|
|
23
23
|
|
|
24
24
|
Documentation for this project is hosted on
|
|
25
25
|
`GitHub Pages
|
|
26
|
-
<https://grscheller.github.io/pythonic-fp/gadgets
|
|
26
|
+
<https://grscheller.github.io/pythonic-fp/gadgets>`_.
|
|
27
27
|
|
|
28
28
|
Copyright and License
|
|
29
29
|
---------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pythonic-fp-gadgets"
|
|
3
|
-
version = "
|
|
3
|
+
version = "4.0.0"
|
|
4
4
|
readme = "README.rst"
|
|
5
5
|
requires-python = ">=3.13"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -22,8 +22,8 @@ dynamic = ["description"]
|
|
|
22
22
|
|
|
23
23
|
[project.urls]
|
|
24
24
|
Changelog = "https://github.com/grscheller/pythonic-fp-gadgets/blob/main/CHANGELOG.rst"
|
|
25
|
-
Documentation = "https://grscheller.github.io/pythonic-fp/gadgets/development/build/html/
|
|
26
|
-
Homepage = "https://github.
|
|
25
|
+
Documentation = "https://grscheller.github.io/pythonic-fp/gadgets/development/build/html/"
|
|
26
|
+
Homepage = "https://grscheller.github.io/pythonic-fp/homepage/build/html/"
|
|
27
27
|
Source = "https://github.com/grscheller/pythonic-fp-gadgets"
|
|
28
28
|
|
|
29
29
|
[project.optional-dependencies]
|
|
@@ -69,6 +69,7 @@ docstring-quotes = "double"
|
|
|
69
69
|
|
|
70
70
|
[tool.ruff.lint.per-file-ignores]
|
|
71
71
|
"tests/*" = ["C901"]
|
|
72
|
+
"tests/**/*.py" = ['E731']
|
|
72
73
|
"**/*.py" = ["E741"]
|
|
73
74
|
|
|
74
75
|
[tool.ruff.format]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
**Module level function names.**
|
|
17
|
+
|
|
18
|
+
========================== ================================================
|
|
19
|
+
Function Description
|
|
20
|
+
========================== ================================================
|
|
21
|
+
``first_common_ancestor`` Find least common base class in a type hierarchy
|
|
22
|
+
``iterate_over_arguments`` Return an iterator over the function's arguments
|
|
23
|
+
========================== ================================================
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from collections.abc import Iterator
|
|
28
|
+
from inspect import getmro
|
|
29
|
+
|
|
30
|
+
__all__ = ['iterate_over_arguments', 'first_common_ancestor']
|
|
31
|
+
|
|
32
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
33
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
34
|
+
__license__ = 'Apache License 2.0'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def iterate_over_arguments[A](*args: A) -> Iterator[A]:
|
|
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
|
+
|
|
50
|
+
"""
|
|
51
|
+
yield from args
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def first_common_ancestor(cls1: type, cls2: type) -> type:
|
|
55
|
+
"""Find the least upper bound in the inheritance graph
|
|
56
|
+
of two classes.
|
|
57
|
+
|
|
58
|
+
.. warning::
|
|
59
|
+
|
|
60
|
+
This function can fail with a TypeError. Some error messages
|
|
61
|
+
seen are
|
|
62
|
+
|
|
63
|
+
- multiple bases have instance lay-out conflict
|
|
64
|
+
- type 'bool' is not an acceptable base type
|
|
65
|
+
|
|
66
|
+
This happens frequently when the function is given
|
|
67
|
+
Python builtin types or in multiple inheritance situations.
|
|
68
|
+
|
|
69
|
+
:param cls1: A class in the inheritance hierarchy.
|
|
70
|
+
:param cls2: A class in the inheritance hierarchy.
|
|
71
|
+
:returns: First common ancestor based on getmro order.
|
|
72
|
+
:raises TypeError: Raised by ``inspect.getmro``.
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
if issubclass(cls1, cls2):
|
|
76
|
+
return cls2
|
|
77
|
+
if issubclass(cls2, cls1):
|
|
78
|
+
return cls1
|
|
79
|
+
|
|
80
|
+
for common_ancestor in getmro(type('LcaDiamondClass', (cls1, cls2), {})):
|
|
81
|
+
if issubclass(cls1, common_ancestor) and issubclass(cls2, common_ancestor):
|
|
82
|
+
return common_ancestor
|
|
83
|
+
raise TypeError("latest_common_ancestor: no common ancestor found!!!")
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""Class for a stateful container that can hold at most one item."""
|
|
16
|
-
|
|
17
15
|
__all__ = ['Box']
|
|
18
16
|
|
|
19
17
|
from collections.abc import Callable, Iterator
|
|
@@ -28,8 +26,8 @@ class Box[T]:
|
|
|
28
26
|
|
|
29
27
|
.. note::
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
- ``Box(item: T)``: contains at one item of type ``T``
|
|
30
|
+
- ``Box[T]()``: creates empty container
|
|
33
31
|
|
|
34
32
|
Where type ``T`` is some definite type, which
|
|
35
33
|
could be ``None`` or even ``Never``.
|
|
@@ -42,10 +40,10 @@ class Box[T]:
|
|
|
42
40
|
__slots__ = ('_item',)
|
|
43
41
|
__match_args__ = ('_item',)
|
|
44
42
|
|
|
45
|
-
@overload
|
|
46
|
-
def __init__(self) -> None: ...
|
|
47
43
|
@overload
|
|
48
44
|
def __init__(self, item: T) -> None: ...
|
|
45
|
+
@overload
|
|
46
|
+
def __init__(self) -> None: ...
|
|
49
47
|
|
|
50
48
|
def __init__(self, item: T | _Sentinel = _sentinel) -> None:
|
|
51
49
|
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
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]: ...
|
|
@@ -12,25 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
__all__ = ['ita']
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def ita[A](*args: A) -> Iterator[A]:
|
|
23
|
-
"""Function returning an iterator of its arguments.
|
|
24
|
-
|
|
25
|
-
.. note::
|
|
26
|
-
|
|
27
|
-
Does not create an object to iterate over.
|
|
28
|
-
|
|
29
|
-
- well, not in the Python world
|
|
30
|
-
- maybe in the C world
|
|
31
|
-
|
|
32
|
-
:param args: Objects to iterate over.
|
|
33
|
-
:returns: An iterator of the arguments.
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
yield from args
|
|
15
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
16
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
17
|
+
__license__ = 'Apache License 2.0'
|
|
File without changes
|
|
@@ -13,15 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
"""
|
|
16
|
-
|
|
17
|
-
==================
|
|
18
|
-
|
|
19
|
-
Sentinel values labeled by different (hashable) flavors. Can be used
|
|
20
|
-
with functions or classes.
|
|
21
|
-
|
|
22
|
-
.. note::
|
|
23
|
-
|
|
24
|
-
Threadsafe.
|
|
16
|
+
**Sentinel values labeled by different (hashable) flavors.**
|
|
25
17
|
|
|
26
18
|
.. note::
|
|
27
19
|
|
|
@@ -35,15 +27,9 @@ with functions or classes.
|
|
|
35
27
|
To ensure that reference equality is used, put the known
|
|
36
28
|
sentinel value first in the comparison.
|
|
37
29
|
|
|
38
|
-
..
|
|
39
|
-
|
|
40
|
-
Use as a hidden implementation detail when creating "optional"
|
|
41
|
-
arguments to functions and methods. Do not export the sentinel
|
|
42
|
-
value.
|
|
43
|
-
|
|
44
|
-
- does not clash with end user code
|
|
30
|
+
.. note::
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
Threadsafe.
|
|
47
33
|
|
|
48
34
|
"""
|
|
49
35
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
**Singleton class representing an actually, not potentially, missing value.**
|
|
17
|
+
|
|
18
|
+
.. note:
|
|
19
|
+
|
|
20
|
+
``NoValue()`` is a singleton object representing a missing value.
|
|
21
|
+
|
|
22
|
+
While ``None`` and ``()`` are frequently used as sentinel values,
|
|
23
|
+
I prefer to think of them as
|
|
24
|
+
|
|
25
|
+
- ``None``: Returns, or returned, no values.
|
|
26
|
+
- ``()``: An empty, possibly typed, iterable collection.
|
|
27
|
+
|
|
28
|
+
.. important::
|
|
29
|
+
|
|
30
|
+
Given variables
|
|
31
|
+
|
|
32
|
+
.. code:: python
|
|
33
|
+
|
|
34
|
+
x: int | NoValue
|
|
35
|
+
y: int | NoValue
|
|
36
|
+
|
|
37
|
+
Equality between ``x`` and ``y`` means both values exist and compare
|
|
38
|
+
as equal. If one or both of theses values are missing, then what is
|
|
39
|
+
there to compare?
|
|
40
|
+
|
|
41
|
+
.. table:: ``x == y``
|
|
42
|
+
|
|
43
|
+
+-----------+-----------+--------+--------+
|
|
44
|
+
| x∖y | NoValue() | 42 | 57 |
|
|
45
|
+
+===========+===========+========+========+
|
|
46
|
+
| NoValue() | false | false | false |
|
|
47
|
+
+-----------+-----------+--------+--------+
|
|
48
|
+
| 42 | false | true | false |
|
|
49
|
+
+-----------+-----------+--------+--------+
|
|
50
|
+
| 57 | false | false | true |
|
|
51
|
+
+-----------+-----------+--------+--------+
|
|
52
|
+
|
|
53
|
+
Similarly for not equals.
|
|
54
|
+
|
|
55
|
+
.. table:: ``x != y``
|
|
56
|
+
|
|
57
|
+
+-----------+-----------+--------+--------+
|
|
58
|
+
| x∖y | NoValue() | 42 | 57 |
|
|
59
|
+
+===========+===========+========+========+
|
|
60
|
+
| NoValue() | false | false | false |
|
|
61
|
+
+-----------+-----------+--------+--------+
|
|
62
|
+
| 42 | false | false | true |
|
|
63
|
+
+-----------+-----------+--------+--------+
|
|
64
|
+
| 57 | false | true | false |
|
|
65
|
+
+-----------+-----------+--------+--------+
|
|
66
|
+
|
|
67
|
+
.. warning::
|
|
68
|
+
|
|
69
|
+
Only use ``==`` or ``!=`` in value comparisons. To directly
|
|
70
|
+
identity the ``NoValue`` singleton, use ``is`` and ``is not``
|
|
71
|
+
instead.
|
|
72
|
+
|
|
73
|
+
.. note::
|
|
74
|
+
|
|
75
|
+
Threadsafe.
|
|
76
|
+
|
|
77
|
+
.. tip::
|
|
78
|
+
|
|
79
|
+
Use as a hidden implementation detail when creating "optional"
|
|
80
|
+
arguments to functions and methods.
|
|
81
|
+
|
|
82
|
+
To help ensure the abstraction does not leak,
|
|
83
|
+
|
|
84
|
+
- Do not export the sentinel value.
|
|
85
|
+
- Use ``@overload`` to keep the NoValue type out of documentation and IDEs.
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
import threading
|
|
89
|
+
from typing import ClassVar, final
|
|
90
|
+
|
|
91
|
+
__all__ = ['NoValue']
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@final
|
|
95
|
+
class NoValue():
|
|
96
|
+
__slots__ = ()
|
|
97
|
+
|
|
98
|
+
_instance: 'ClassVar[NoValue | None]' = None
|
|
99
|
+
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
100
|
+
|
|
101
|
+
def __new__(cls) -> 'NoValue':
|
|
102
|
+
"""
|
|
103
|
+
:returns: The ``NoValue`` singleton instance.
|
|
104
|
+
"""
|
|
105
|
+
if cls._instance is None:
|
|
106
|
+
with cls._lock:
|
|
107
|
+
if cls._instance is None:
|
|
108
|
+
cls._instance = super().__new__(cls)
|
|
109
|
+
return cls._instance
|
|
110
|
+
|
|
111
|
+
def __repr__(self) -> str:
|
|
112
|
+
return 'NoValue()'
|
|
113
|
+
|
|
114
|
+
def __eq__(self, other: object) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
:returns: False
|
|
117
|
+
"""
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
def __ne__(self, other: object) -> bool:
|
|
121
|
+
"""
|
|
122
|
+
:returns: False
|
|
123
|
+
"""
|
|
124
|
+
return False
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
__all__ = ['Wrap', 'HWrap']
|
|
16
|
+
|
|
17
|
+
from collections.abc import Callable, Iterator, Hashable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Wrap[T]():
|
|
21
|
+
"""Immutablely wrap exactly one value of a given type.
|
|
22
|
+
|
|
23
|
+
.. tip::
|
|
24
|
+
|
|
25
|
+
``Wrap`` objects can be used in Python match statements.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
__slots__ = ('_item',)
|
|
29
|
+
__match_args__ = ('_item',)
|
|
30
|
+
|
|
31
|
+
def __init__(self, item: T) -> None:
|
|
32
|
+
self._item = item
|
|
33
|
+
|
|
34
|
+
def __bool__(self) -> bool:
|
|
35
|
+
return bool(self._item)
|
|
36
|
+
|
|
37
|
+
def __iter__(self) -> Iterator[T]:
|
|
38
|
+
if self:
|
|
39
|
+
yield self._item
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return 'Wrap(' + repr(self._item) + ')'
|
|
43
|
+
|
|
44
|
+
def __eq__(self, other: object) -> bool:
|
|
45
|
+
if not isinstance(other, type(self)):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
if self._item is other._item:
|
|
49
|
+
return True
|
|
50
|
+
return self._item == other._item
|
|
51
|
+
|
|
52
|
+
def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
|
|
53
|
+
"""Map function ``f`` over contents.
|
|
54
|
+
|
|
55
|
+
:param f: Mapping function.
|
|
56
|
+
:returns: A new instance.
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
return Wrap(f(self._item))
|
|
60
|
+
|
|
61
|
+
def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
|
|
62
|
+
"""Flatmap the ``Wrap`` with function ``f``.
|
|
63
|
+
|
|
64
|
+
:param f: Binding function.
|
|
65
|
+
:returns: A new instance.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
return f(self._item)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class HWrap[T: Hashable](Hashable):
|
|
72
|
+
"""Immutablely wrap exactly one hashable value of a given type.
|
|
73
|
+
|
|
74
|
+
.. tip::
|
|
75
|
+
|
|
76
|
+
``Wrap`` objects can be used in Python match statements.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
__slots__ = ('_item', '_hash')
|
|
80
|
+
__match_args__ = ('_item',)
|
|
81
|
+
|
|
82
|
+
def __init__(self, item: T) -> None:
|
|
83
|
+
self._item, self._hash = item, hash(item)
|
|
84
|
+
|
|
85
|
+
def __hash__(self) -> int:
|
|
86
|
+
return self._hash
|
|
87
|
+
|
|
88
|
+
def __bool__(self) -> bool:
|
|
89
|
+
return bool(self._item)
|
|
90
|
+
|
|
91
|
+
def __iter__(self) -> Iterator[T]:
|
|
92
|
+
if self:
|
|
93
|
+
yield self._item
|
|
94
|
+
|
|
95
|
+
def __repr__(self) -> str:
|
|
96
|
+
return 'Wrap(' + repr(self._item) + ')'
|
|
97
|
+
|
|
98
|
+
def __eq__(self, other: object) -> bool:
|
|
99
|
+
if not isinstance(other, type(self)):
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
if self._hash != other._hash:
|
|
103
|
+
return False
|
|
104
|
+
if self._item is other._item:
|
|
105
|
+
return True
|
|
106
|
+
return self._item == other._item
|
|
107
|
+
|
|
108
|
+
def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
|
|
109
|
+
"""Map function ``f`` over contents.
|
|
110
|
+
|
|
111
|
+
:param f: Mapping function.
|
|
112
|
+
:returns: A new instance.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
return HWrap(f(self._item))
|
|
116
|
+
|
|
117
|
+
def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
|
|
118
|
+
"""Flatmap the ``Wrap`` with function ``f``.
|
|
119
|
+
|
|
120
|
+
:param f: Binding function.
|
|
121
|
+
:returns: A new instance.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
return f(self._item)
|
|
@@ -0,0 +1,23 @@
|
|
|
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]: ...
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pythonic_fp.gadgets import first_common_ancestor as fca
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class A1():
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class A1B1(A1):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
class A1B1C1(A1B1):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
class A1B1C2(A1B1):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class A1B2(A1):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class A1B2C1(A1B2):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
class A1B2C2(A1B2):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
class A2():
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
class A2B1(A2):
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
class A2B1C1(A2B1):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
class A2B1C2(A2B1):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
class A2B2(A2):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
class A2B2C1(A2B2):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
class A2B2C1D1(A2B2C1):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
class A2B2C1D2(A2B2C1):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
class A2B2C2(A2B2):
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
class A2B2C2D1(A2B2C2):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
class A2B2C2D2(A2B2C2):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
class TestLatestCommonAncestor:
|
|
73
|
+
"""Functionality testing"""
|
|
74
|
+
|
|
75
|
+
def test_self(self) -> None:
|
|
76
|
+
assert fca(A1, A1) is A1
|
|
77
|
+
assert fca(A1B1, A1B1) is A1B1
|
|
78
|
+
assert fca(A1B1C1, A1B1C1) is A1B1C1
|
|
79
|
+
assert fca(int, int) is int
|
|
80
|
+
assert fca(bool, bool) is bool
|
|
81
|
+
|
|
82
|
+
def test_linear_descent(self) -> None:
|
|
83
|
+
assert fca(A1, A1B1) is A1
|
|
84
|
+
assert fca(A1B1, A1) is A1
|
|
85
|
+
assert fca(A1B1C1, A1) is A1
|
|
86
|
+
assert fca(A1, A1B1C1) is A1
|
|
87
|
+
assert fca(A1B1C1, A1B1) is A1B1
|
|
88
|
+
assert fca(A1B1, A1B1C1) is A1B1
|
|
89
|
+
assert fca(A1B1C1, A1B1) is A1B1
|
|
90
|
+
assert fca(A1B1, A1B1C1) is A1B1
|
|
91
|
+
assert fca(A1B1C1, A1) is A1
|
|
92
|
+
assert fca(A1, A1B1C1) is A1
|
|
93
|
+
assert fca(A2B2, A2B2C2D2) is A2B2
|
|
94
|
+
assert fca(A2B2C2D2, A2B2) is A2B2
|
|
95
|
+
assert fca(int, object) is object
|
|
96
|
+
assert fca(object, int) is object
|
|
97
|
+
assert fca(int, bool) is int
|
|
98
|
+
assert fca(bool, int) is int
|
|
99
|
+
|
|
100
|
+
def test_nonliear_descent(self) -> None:
|
|
101
|
+
assert fca(A1B1, A1B2) is A1
|
|
102
|
+
assert fca(A1B2, A1B1) is A1
|
|
103
|
+
assert fca(A1B2C1, A1B2C2) is A1B2
|
|
104
|
+
assert fca(A1B2C2, A1B2C1) is A1B2
|
|
105
|
+
assert fca(A1B1C1, A1B2C2) is A1
|
|
106
|
+
assert fca(A1B2C2, A1B1C1) is A1
|
|
107
|
+
assert fca(A1B1C1, A1B2C1) is A1
|
|
108
|
+
assert fca(A1B2C1, A1B1C1) is A1
|
|
109
|
+
assert fca(A2B2C1, A2B2C2D2) is A2B2
|
|
110
|
+
assert fca(A2B2C2D2, A2B2C1) is A2B2
|
|
111
|
+
assert fca(A2B1C1, A2B2C2D2) is A2
|
|
112
|
+
assert fca(A2B2C2D2, A2B1C1) is A2
|
|
113
|
+
assert fca(A1, A2) is object
|
|
114
|
+
assert fca(A2B2C2D2, A1B2C2) is object
|
|
115
|
+
assert fca(A1B2C1, A2B1C1) is object
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pythonic_fp.gadgets.wrap import HWrap
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGadgetHWrapBuiltins:
|
|
19
|
+
def test_hwrap_simple(self) -> None:
|
|
20
|
+
w0 = HWrap(0)
|
|
21
|
+
w1 = HWrap(1)
|
|
22
|
+
x1 = HWrap(1)
|
|
23
|
+
|
|
24
|
+
assert w0 != w1
|
|
25
|
+
assert w1 == x1
|
|
26
|
+
assert w1 is w1
|
|
27
|
+
assert w1 is not x1
|
|
28
|
+
|
|
29
|
+
assert hash(w0) == hash(w0)
|
|
30
|
+
assert hash(w1) == hash(w1)
|
|
31
|
+
assert hash(w1) == hash(x1)
|
|
32
|
+
assert hash(w1) != hash(w0)
|
|
33
|
+
|
|
34
|
+
def test_map(self) -> None:
|
|
35
|
+
def cat(s: str) -> str:
|
|
36
|
+
return s + s
|
|
37
|
+
|
|
38
|
+
wa = HWrap('a')
|
|
39
|
+
waa = HWrap('aa')
|
|
40
|
+
waaaa = HWrap('aaaa')
|
|
41
|
+
|
|
42
|
+
xaa = wa.map(cat)
|
|
43
|
+
xaaaa = xaa.map(cat)
|
|
44
|
+
|
|
45
|
+
assert xaa == waa
|
|
46
|
+
assert xaa != waaaa
|
|
47
|
+
assert xaaaa == waaaa
|
|
48
|
+
assert waaaa == xaaaa
|
|
49
|
+
|
|
50
|
+
def test_bind(self) -> None:
|
|
51
|
+
type TS = tuple[str, ...]
|
|
52
|
+
type WLS = HWrap[TS]
|
|
53
|
+
type TI = tuple[int, ...]
|
|
54
|
+
type WTI = HWrap[TI]
|
|
55
|
+
|
|
56
|
+
def cnt(ts: TS) -> WTI:
|
|
57
|
+
ti: TI = ()
|
|
58
|
+
for s in ts:
|
|
59
|
+
ti += (len(s),)
|
|
60
|
+
return HWrap(ti)
|
|
61
|
+
|
|
62
|
+
hw: TS = ('hello', 'world', '!')
|
|
63
|
+
fb: TS = ('foobar',)
|
|
64
|
+
nil: TS = ()
|
|
65
|
+
|
|
66
|
+
wt_hw: WLS = HWrap(hw)
|
|
67
|
+
wt_fb: WLS = HWrap(fb)
|
|
68
|
+
wt_nil: WLS = HWrap(nil)
|
|
69
|
+
|
|
70
|
+
wi_hw: WTI = HWrap((5, 5, 1))
|
|
71
|
+
wi_fb: WTI = HWrap((6,))
|
|
72
|
+
wi_nil: WTI = HWrap(())
|
|
73
|
+
|
|
74
|
+
assert wi_hw == wt_hw.bind(cnt)
|
|
75
|
+
assert wi_fb == wt_fb.bind(cnt)
|
|
76
|
+
assert wi_nil == wt_nil.bind(cnt)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from pythonic_fp.circulararray.auto import ca, CA
|
|
16
|
-
from pythonic_fp.gadgets
|
|
16
|
+
from pythonic_fp.gadgets import iterate_over_arguments as ita
|
|
17
17
|
|
|
18
18
|
class TestGadgetIt:
|
|
19
19
|
"""Functionality testing"""
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from pythonic_fp.gadgets.wrap import Wrap
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestGadgetWrapBuiltins:
|
|
19
|
+
def test_wrap_simple(self) -> None:
|
|
20
|
+
w0 = Wrap(0)
|
|
21
|
+
w1 = Wrap(1)
|
|
22
|
+
x1 = Wrap(1)
|
|
23
|
+
|
|
24
|
+
assert w0 != w1
|
|
25
|
+
assert w1 == x1
|
|
26
|
+
assert w1 is w1
|
|
27
|
+
assert w1 is not x1
|
|
28
|
+
|
|
29
|
+
def test_map(self) -> None:
|
|
30
|
+
def cat(s: str) -> str:
|
|
31
|
+
return s + s
|
|
32
|
+
|
|
33
|
+
wa = Wrap('a')
|
|
34
|
+
waa = Wrap('aa')
|
|
35
|
+
waaaa = Wrap('aaaa')
|
|
36
|
+
|
|
37
|
+
xaa = wa.map(cat)
|
|
38
|
+
xaaaa = xaa.map(cat)
|
|
39
|
+
|
|
40
|
+
assert xaa == waa
|
|
41
|
+
assert xaa != waaaa
|
|
42
|
+
assert xaaaa == waaaa
|
|
43
|
+
assert waaaa == xaaaa
|
|
44
|
+
|
|
45
|
+
def test_bind(self) -> None:
|
|
46
|
+
type LS = list[str]
|
|
47
|
+
type WLS = Wrap[LS]
|
|
48
|
+
type LI = list[int]
|
|
49
|
+
type WLI = Wrap[LI]
|
|
50
|
+
|
|
51
|
+
def cnt(ls: LS) -> WLI:
|
|
52
|
+
li: LI = []
|
|
53
|
+
for s in ls:
|
|
54
|
+
li.append(len(s))
|
|
55
|
+
return Wrap(li)
|
|
56
|
+
|
|
57
|
+
hw: LS = ['hello', 'world', '!']
|
|
58
|
+
fb: LS = ['foobar',]
|
|
59
|
+
nil: LS = []
|
|
60
|
+
|
|
61
|
+
wt_hw: WLS = Wrap(hw)
|
|
62
|
+
wt_fb: WLS = Wrap(fb)
|
|
63
|
+
wt_nil: WLS = Wrap(nil)
|
|
64
|
+
|
|
65
|
+
wi_hw: WLI = Wrap([5, 5, 1])
|
|
66
|
+
wi_fb: WLI = Wrap([6,])
|
|
67
|
+
wi_nil: WLI = Wrap([])
|
|
68
|
+
|
|
69
|
+
assert wi_hw == wt_hw.bind(cnt)
|
|
70
|
+
assert wi_fb == wt_fb.bind(cnt)
|
|
71
|
+
assert wi_nil == wt_nil.bind(cnt)
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
"""
|
|
16
|
-
Simple Gadgets
|
|
17
|
-
==============
|
|
18
|
-
|
|
19
|
-
Library of simple, but useful, functions and classes with no external
|
|
20
|
-
dependencies besides the those from the Python standard Library. This
|
|
21
|
-
includes other Pythonic Functional Programming dependencies.
|
|
22
|
-
|
|
23
|
-
+------------------------------+----------------------+------------------------------------------------+
|
|
24
|
-
| Description | Gadget | Module |
|
|
25
|
-
+==============================+======================+================================================+
|
|
26
|
-
| Single item box | class ``Box`` | ``pythonic_fp.gadgets.box`` |
|
|
27
|
-
+------------------------------+----------------------+------------------------------------------------+
|
|
28
|
-
| Return Iterator of arguments | function ``ita`` | ``pythonic_fp.gadgets.iterate_arguments`` |
|
|
29
|
-
+------------------------------+----------------------+------------------------------------------------+
|
|
30
|
-
| Find least common base class | function ``lca`` | ``pythonic_fp.gadgets.latest_common_ancestor`` |
|
|
31
|
-
+------------------------------+----------------------+------------------------------------------------+
|
|
32
|
-
| sentinels values with extras | module ``sentinals`` | ``pythonic_fp.gadgets.sentinels`` |
|
|
33
|
-
+------------------------------+----------------------+------------------------------------------------+
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
__author__ = 'Geoffrey R. Scheller'
|
|
38
|
-
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
39
|
-
__license__ = 'Apache License 2.0'
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
"""Last Common Ancestor of Two Classes."""
|
|
16
|
-
|
|
17
|
-
from inspect import getmro
|
|
18
|
-
|
|
19
|
-
__all__ = ['lca']
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def lca(cls1: type, cls2: type) -> type:
|
|
23
|
-
"""Find the least upper bound in the inheritance graph
|
|
24
|
-
of two classes.
|
|
25
|
-
|
|
26
|
-
.. warning::
|
|
27
|
-
|
|
28
|
-
This function can fail with a TypeError. Some error messages
|
|
29
|
-
seen are
|
|
30
|
-
|
|
31
|
-
- multiple bases have instance lay-out conflict
|
|
32
|
-
- type 'bool' is not an acceptable base type
|
|
33
|
-
|
|
34
|
-
This happens frequently when the function is given
|
|
35
|
-
Python builtin types.
|
|
36
|
-
|
|
37
|
-
:param cls1: first class
|
|
38
|
-
:param cls2: second class
|
|
39
|
-
:returns: Least common ancestor base class of ``cls1`` and ``cls2``.
|
|
40
|
-
:raises TypeError: theoretically only from ``inspect.getmto``
|
|
41
|
-
|
|
42
|
-
"""
|
|
43
|
-
if issubclass(cls1, cls2):
|
|
44
|
-
return cls2
|
|
45
|
-
if issubclass(cls2, cls1):
|
|
46
|
-
return cls1
|
|
47
|
-
|
|
48
|
-
for common_ancestor in getmro(type('LcaDiamondClass', (cls1, cls2), {})):
|
|
49
|
-
if issubclass(cls1, common_ancestor) and issubclass(cls2, common_ancestor):
|
|
50
|
-
return common_ancestor
|
|
51
|
-
raise TypeError("latest_common_ancestor: no common ancestor found!!!")
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
"""
|
|
16
|
-
Sentinels with Extras
|
|
17
|
-
=====================
|
|
18
|
-
|
|
19
|
-
+-----------------------+---------------------------+-----------------------------------------------+
|
|
20
|
-
| Module | Class | Represents |
|
|
21
|
-
+=======================+===========================+===============================================+
|
|
22
|
-
| ``sentinel.novalue`` | ``NoValue`` | an actually , not potentially, missing value |
|
|
23
|
-
+-----------------------+---------------------------+-----------------------------------------------+
|
|
24
|
-
| ``sentinel.flavored`` | ``Sentinel[H: Hashable]`` | distinct sentinels labeled by hashable values |
|
|
25
|
-
+-----------------------+---------------------------+-----------------------------------------------+
|
|
26
|
-
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
__author__ = 'Geoffrey R. Scheller'
|
|
30
|
-
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
31
|
-
__license__ = 'Apache License 2.0'
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
"""
|
|
16
|
-
Missing Value Sentinel
|
|
17
|
-
======================
|
|
18
|
-
|
|
19
|
-
Singleton class representing a missing value.
|
|
20
|
-
|
|
21
|
-
In untyped Python, both ``None`` and ``()`` are often used by end users
|
|
22
|
-
and libraries as sentinel values. I prefer to think of them as
|
|
23
|
-
|
|
24
|
-
- ``None``: returned (or returns) no values
|
|
25
|
-
- ``()``: an empty, possibly typed, but still iterable collection
|
|
26
|
-
|
|
27
|
-
While ``NoValue()`` is a singleton object representing a missing value.
|
|
28
|
-
|
|
29
|
-
Given variables
|
|
30
|
-
|
|
31
|
-
.. code:: python
|
|
32
|
-
|
|
33
|
-
x: int | NoValue
|
|
34
|
-
y: int | NoValue
|
|
35
|
-
|
|
36
|
-
Equality between ``x`` and ``y`` means both values exist and compare as equal.
|
|
37
|
-
If one or both of theses values are missing, then what is there to compare?
|
|
38
|
-
|
|
39
|
-
.. table:: ``x == y``
|
|
40
|
-
|
|
41
|
-
+-----------+-----------+--------+--------+
|
|
42
|
-
| x∖y | NoValue() | 42 | 57 |
|
|
43
|
-
+===========+===========+========+========+
|
|
44
|
-
| NoValue() | false | false | false |
|
|
45
|
-
+-----------+-----------+--------+--------+
|
|
46
|
-
| 42 | false | true | false |
|
|
47
|
-
+-----------+-----------+--------+--------+
|
|
48
|
-
| 57 | false | false | true |
|
|
49
|
-
+-----------+-----------+--------+--------+
|
|
50
|
-
|
|
51
|
-
Similarly for not equals.
|
|
52
|
-
|
|
53
|
-
.. table:: ``x != y``
|
|
54
|
-
|
|
55
|
-
+-----------+-----------+--------+--------+
|
|
56
|
-
| x∖y | NoValue() | 42 | 57 |
|
|
57
|
-
+===========+===========+========+========+
|
|
58
|
-
| NoValue() | false | false | false |
|
|
59
|
-
+-----------+-----------+--------+--------+
|
|
60
|
-
| 42 | false | false | true |
|
|
61
|
-
+-----------+-----------+--------+--------+
|
|
62
|
-
| 57 | false | true | false |
|
|
63
|
-
+-----------+-----------+--------+--------+
|
|
64
|
-
|
|
65
|
-
.. note::
|
|
66
|
-
|
|
67
|
-
Threadsafe.
|
|
68
|
-
|
|
69
|
-
.. warning::
|
|
70
|
-
|
|
71
|
-
Do not use ``==`` or ``!=`` to identify ``NoValue()``, compare
|
|
72
|
-
directly by identity using ``is`` and ``is not``.
|
|
73
|
-
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
import threading
|
|
77
|
-
from typing import ClassVar, final
|
|
78
|
-
|
|
79
|
-
__all__ = ['NoValue']
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@final
|
|
83
|
-
class NoValue():
|
|
84
|
-
__slots__ = ()
|
|
85
|
-
|
|
86
|
-
_instance: 'ClassVar[NoValue | None]' = None
|
|
87
|
-
_lock: ClassVar[threading.Lock] = threading.Lock()
|
|
88
|
-
|
|
89
|
-
def __new__(cls) -> 'NoValue':
|
|
90
|
-
"""
|
|
91
|
-
:returns: The ``NoValue`` singleton instance.
|
|
92
|
-
"""
|
|
93
|
-
if cls._instance is None:
|
|
94
|
-
with cls._lock:
|
|
95
|
-
if cls._instance is None:
|
|
96
|
-
cls._instance = super().__new__(cls)
|
|
97
|
-
return cls._instance
|
|
98
|
-
|
|
99
|
-
def __repr__(self) -> str:
|
|
100
|
-
return 'NoValue()'
|
|
101
|
-
|
|
102
|
-
def __eq__(self, other: object) -> bool:
|
|
103
|
-
return False
|
|
104
|
-
|
|
105
|
-
def __ne__(self, other: object) -> bool:
|
|
106
|
-
return False
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
-
#
|
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
# you may not use this file except in compliance with the License.
|
|
5
|
-
# You may obtain a copy of the License at
|
|
6
|
-
#
|
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
#
|
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
# See the License for the specific language governing permissions and
|
|
13
|
-
# limitations under the License.
|
|
14
|
-
|
|
15
|
-
from pythonic_fp.gadgets.latest_common_ancestor import lca
|
|
16
|
-
|
|
17
|
-
class A1():
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
class A1B1(A1):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
class A1B1C1(A1B1):
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
class A1B1C2(A1B1):
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
class A1B2(A1):
|
|
30
|
-
pass
|
|
31
|
-
|
|
32
|
-
class A1B2C1(A1B2):
|
|
33
|
-
pass
|
|
34
|
-
|
|
35
|
-
class A1B2C2(A1B2):
|
|
36
|
-
pass
|
|
37
|
-
|
|
38
|
-
class A2():
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
class A2B1(A2):
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
class A2B1C1(A2B1):
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
class A2B1C2(A2B1):
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
class A2B2(A2):
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
class A2B2C1(A2B2):
|
|
54
|
-
pass
|
|
55
|
-
|
|
56
|
-
class A2B2C1D1(A2B2C1):
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
class A2B2C1D2(A2B2C1):
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
class A2B2C2(A2B2):
|
|
63
|
-
pass
|
|
64
|
-
|
|
65
|
-
class A2B2C2D1(A2B2C2):
|
|
66
|
-
pass
|
|
67
|
-
|
|
68
|
-
class A2B2C2D2(A2B2C2):
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
class TestLatestCommonAncestor:
|
|
72
|
-
"""Functionality testing"""
|
|
73
|
-
|
|
74
|
-
def test_self(self) -> None:
|
|
75
|
-
assert lca(A1, A1) is A1
|
|
76
|
-
assert lca(A1B1, A1B1) is A1B1
|
|
77
|
-
assert lca(A1B1C1, A1B1C1) is A1B1C1
|
|
78
|
-
assert lca(int, int) is int
|
|
79
|
-
assert lca(bool, bool) is bool
|
|
80
|
-
|
|
81
|
-
def test_linear_descent(self) -> None:
|
|
82
|
-
assert lca(A1, A1B1) is A1
|
|
83
|
-
assert lca(A1B1, A1) is A1
|
|
84
|
-
assert lca(A1B1C1, A1) is A1
|
|
85
|
-
assert lca(A1, A1B1C1) is A1
|
|
86
|
-
assert lca(A1B1C1, A1B1) is A1B1
|
|
87
|
-
assert lca(A1B1, A1B1C1) is A1B1
|
|
88
|
-
assert lca(A1B1C1, A1B1) is A1B1
|
|
89
|
-
assert lca(A1B1, A1B1C1) is A1B1
|
|
90
|
-
assert lca(A1B1C1, A1) is A1
|
|
91
|
-
assert lca(A1, A1B1C1) is A1
|
|
92
|
-
assert lca(A2B2, A2B2C2D2) is A2B2
|
|
93
|
-
assert lca(A2B2C2D2, A2B2) is A2B2
|
|
94
|
-
assert lca(int, object) is object
|
|
95
|
-
assert lca(object, int) is object
|
|
96
|
-
assert lca(int, bool) is int
|
|
97
|
-
assert lca(bool, int) is int
|
|
98
|
-
|
|
99
|
-
def test_nonliear_descent(self) -> None:
|
|
100
|
-
assert lca(A1B1, A1B2) is A1
|
|
101
|
-
assert lca(A1B2, A1B1) is A1
|
|
102
|
-
assert lca(A1B2C1, A1B2C2) is A1B2
|
|
103
|
-
assert lca(A1B2C2, A1B2C1) is A1B2
|
|
104
|
-
assert lca(A1B1C1, A1B2C2) is A1
|
|
105
|
-
assert lca(A1B2C2, A1B1C1) is A1
|
|
106
|
-
assert lca(A1B1C1, A1B2C1) is A1
|
|
107
|
-
assert lca(A1B2C1, A1B1C1) is A1
|
|
108
|
-
assert lca(A2B2C1, A2B2C2D2) is A2B2
|
|
109
|
-
assert lca(A2B2C2D2, A2B2C1) is A2B2
|
|
110
|
-
assert lca(A2B1C1, A2B2C2D2) is A2
|
|
111
|
-
assert lca(A2B2C2D2, A2B1C1) is A2
|
|
112
|
-
assert lca(A1, A2) is object
|
|
113
|
-
assert lca(A2B2C2D2, A1B2C2) is object
|
|
114
|
-
assert lca(A1B2C1, A2B1C1) is object
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|