pythonic-fp-gadgets 4.0.4__tar.gz → 4.2.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.
Files changed (49) hide show
  1. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/PKG-INFO +22 -31
  2. pythonic_fp_gadgets-4.0.4/README.rst → pythonic_fp_gadgets-4.2.0/README.md +13 -21
  3. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/pyproject.toml +30 -25
  4. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/__init__.py +1 -0
  5. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/box.py +43 -27
  6. pythonic_fp_gadgets-4.2.0/src/pythonic_fp/gadgets/wrap.py +270 -0
  7. pythonic_fp_gadgets-4.0.4/.github/workflows/static.yml +0 -64
  8. pythonic_fp_gadgets-4.0.4/.gitignore +0 -9
  9. pythonic_fp_gadgets-4.0.4/CHANGELOG.rst +0 -110
  10. pythonic_fp_gadgets-4.0.4/docs/Makefile +0 -78
  11. pythonic_fp_gadgets-4.0.4/docs/gen_conf.py +0 -110
  12. pythonic_fp_gadgets-4.0.4/docs/requirements.txt +0 -2
  13. pythonic_fp_gadgets-4.0.4/docs/source/_static/.gitkeep +0 -0
  14. pythonic_fp_gadgets-4.0.4/docs/source/_static/custom.css +0 -3
  15. pythonic_fp_gadgets-4.0.4/docs/source/_templates/.gitkeep +0 -0
  16. pythonic_fp_gadgets-4.0.4/docs/source/api/box.rst +0 -6
  17. pythonic_fp_gadgets-4.0.4/docs/source/api/functions/first_common_ancestor.rst +0 -4
  18. pythonic_fp_gadgets-4.0.4/docs/source/api/functions/index.rst +0 -8
  19. pythonic_fp_gadgets-4.0.4/docs/source/api/functions/iterate_over_arguments.rst +0 -4
  20. pythonic_fp_gadgets-4.0.4/docs/source/api/index.rst +0 -31
  21. pythonic_fp_gadgets-4.0.4/docs/source/api/sentinels/flavored.rst +0 -8
  22. pythonic_fp_gadgets-4.0.4/docs/source/api/sentinels/index.rst +0 -14
  23. pythonic_fp_gadgets-4.0.4/docs/source/api/sentinels/novalue.rst +0 -11
  24. pythonic_fp_gadgets-4.0.4/docs/source/api/wrap.rst +0 -6
  25. pythonic_fp_gadgets-4.0.4/docs/source/changelog.rst +0 -7
  26. pythonic_fp_gadgets-4.0.4/docs/source/description.rst +0 -8
  27. pythonic_fp_gadgets-4.0.4/docs/source/index.rst +0 -30
  28. pythonic_fp_gadgets-4.0.4/docs/source/releases.rst +0 -28
  29. pythonic_fp_gadgets-4.0.4/docs/source/usage.rst +0 -160
  30. pythonic_fp_gadgets-4.0.4/src/pythonic_fp/gadgets/wrap.py +0 -219
  31. pythonic_fp_gadgets-4.0.4/tests/sentinels/test_novalue.py +0 -83
  32. pythonic_fp_gadgets-4.0.4/tests/sentinels/test_sentinel_hidden_implemetation_detail.py +0 -70
  33. pythonic_fp_gadgets-4.0.4/tests/sentinels/test_sentinel_with_functions.py +0 -31
  34. pythonic_fp_gadgets-4.0.4/tests/test_box.py +0 -95
  35. pythonic_fp_gadgets-4.0.4/tests/test_first_common_ancestor.py +0 -115
  36. pythonic_fp_gadgets-4.0.4/tests/test_hwrap.py +0 -76
  37. pythonic_fp_gadgets-4.0.4/tests/test_iterate_over_arguments.py +0 -46
  38. pythonic_fp_gadgets-4.0.4/tests/test_wrap.py +0 -71
  39. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/LICENSE +0 -0
  40. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/__init__.pyi +0 -0
  41. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/box.pyi +0 -0
  42. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/py.typed +0 -0
  43. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/__init__.py +0 -0
  44. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/__init__.pyi +0 -0
  45. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/flavored.py +0 -0
  46. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/flavored.pyi +0 -0
  47. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/novalue.py +0 -0
  48. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/sentinels/novalue.pyi +0 -0
  49. {pythonic_fp_gadgets-4.0.4 → pythonic_fp_gadgets-4.2.0}/src/pythonic_fp/gadgets/wrap.pyi +0 -0
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pythonic-fp-gadgets
3
- Version: 4.0.4
4
- Summary: Gadgets
3
+ Version: 4.2.0
4
+ Summary: Gadgets without external Pythonic FP dependencies.
5
5
  Keywords: gadgets
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
7
  Requires-Python: >=3.13
8
- Description-Content-Type: text/x-rst
9
- Classifier: Development Status :: 3 - Alpha
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 4 - Beta
10
10
  Classifier: Framework :: Pytest
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
@@ -14,46 +14,37 @@ Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Typing :: Typed
16
16
  License-File: LICENSE
17
- Requires-Dist: pytest>=8.4.1 ; extra == "test"
18
- Requires-Dist: pythonic-fp-circulararray>=6.0.4 ; extra == "test"
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/projects/gadgets.html
17
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-gadgets/blob/main/CHANGELOG.md
18
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-gadgets/release/html/
21
19
  Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
22
20
  Project-URL: Source, https://github.com/grscheller/pythonic-fp-gadgets
23
- Provides-Extra: test
21
+ Import-Name: pythonic_fp.gadgets
22
+ Import-Namespace: pythonic_fp
24
23
 
25
- Pythonic FP - Gadgets
26
- =====================
27
-
28
- PyPI project
29
- `pythonic-fp-gadgets
30
- <https://pypi.org/project/pythonic-fp-gadgets>`_.
24
+ # Pythonic FP - Gadgets
31
25
 
32
26
  Library of simple, but useful, classes and functions with no dependencies
33
27
  outside the Python Standard Library.
34
28
 
35
- - Gadgets
29
+ PyPI project
30
+ [pythonic-fp-gadgets](https://pypi.org/project/pythonic-fp-gadgets).
31
+ Part of the
32
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
33
+ PyPI projects.
34
+
35
+ ## Documentation
36
+
37
+ Documentation and other links for this project are hosted on
38
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/gadgets.html).
36
39
 
40
+ - Gadgets
37
41
  - single item box
38
42
  - immutable wrapped (hashable) references
39
43
  - function returning iterator of its arguments
40
44
  - function to find the latest common ancestor of two classes
41
45
  - sentinel values
42
46
 
43
- Part of the
44
- `pythonic-fp
45
- <https://grscheller.github.io/pythonic-fp/>`_
46
- PyPI projects.
47
-
48
- Documentation
49
- -------------
50
-
51
- Documentation and other links for this project are hosted on
52
- `GitHub Pages
53
- <https://grscheller.github.io/pythonic-fp/projects/gadgets.html>`_.
54
-
55
- Copyright and License
56
- ---------------------
47
+ ## Copyright and License
57
48
 
58
49
  Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
59
50
  License, Version 2.0. See the LICENSE file for details.
@@ -1,35 +1,27 @@
1
- Pythonic FP - Gadgets
2
- =====================
3
-
4
- PyPI project
5
- `pythonic-fp-gadgets
6
- <https://pypi.org/project/pythonic-fp-gadgets>`_.
1
+ # Pythonic FP - Gadgets
7
2
 
8
3
  Library of simple, but useful, classes and functions with no dependencies
9
4
  outside the Python Standard Library.
10
5
 
11
- - Gadgets
6
+ PyPI project
7
+ [pythonic-fp-gadgets](https://pypi.org/project/pythonic-fp-gadgets).
8
+ Part of the
9
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
10
+ PyPI projects.
11
+
12
+ ## Documentation
13
+
14
+ Documentation and other links for this project are hosted on
15
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/gadgets.html).
12
16
 
17
+ - Gadgets
13
18
  - single item box
14
19
  - immutable wrapped (hashable) references
15
20
  - function returning iterator of its arguments
16
21
  - function to find the latest common ancestor of two classes
17
22
  - sentinel values
18
23
 
19
- Part of the
20
- `pythonic-fp
21
- <https://grscheller.github.io/pythonic-fp/>`_
22
- PyPI projects.
23
-
24
- Documentation
25
- -------------
26
-
27
- Documentation and other links for this project are hosted on
28
- `GitHub Pages
29
- <https://grscheller.github.io/pythonic-fp/projects/gadgets.html>`_.
30
-
31
- Copyright and License
32
- ---------------------
24
+ ## Copyright and License
33
25
 
34
26
  Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
35
27
  License, Version 2.0. See the LICENSE file for details.
@@ -4,9 +4,10 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "pythonic-fp-gadgets"
7
- version = "4.0.4"
8
- readme = "README.rst"
9
- requires-python = ">=3.13"
7
+ version = "4.2.0"
8
+ description = "Gadgets without external Pythonic FP dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.13" # syntactic floor: known to SyntaxError below this
10
11
  authors = [
11
12
  { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
12
13
  ]
@@ -14,32 +15,48 @@ keywords = [
14
15
  "gadgets",
15
16
  ]
16
17
  classifiers = [
17
- "Development Status :: 3 - Alpha",
18
+ "Development Status :: 4 - Beta",
18
19
  "Framework :: Pytest",
19
20
  "Intended Audience :: Developers",
20
21
  "License :: OSI Approved :: Apache Software License",
21
22
  "Operating System :: OS Independent",
22
- "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: Python :: 3.14", # tested: pytest test suite successful
23
24
  "Typing :: Typed",
24
25
  ]
25
26
  dependencies = []
26
- dynamic = ["description"]
27
27
 
28
28
  [project.urls]
29
- Changelog = "https://github.com/grscheller/pythonic-fp-gadgets/blob/main/CHANGELOG.rst"
30
- Documentation = "https://grscheller.github.io/pythonic-fp/projects/gadgets.html"
29
+ Changelog = "https://github.com/grscheller/pythonic-fp-gadgets/blob/main/CHANGELOG.md"
30
+ Documentation ="https://grscheller.github.io/pythonic-fp-gadgets/release/html/"
31
31
  Homepage = "https://grscheller.github.io/pythonic-fp/"
32
32
  Source = "https://github.com/grscheller/pythonic-fp-gadgets"
33
33
 
34
- [project.optional-dependencies]
34
+ [dependency-groups]
35
+ docs = [
36
+ "furo>=2025.12.19",
37
+ "sphinx>=9.1",
38
+ ]
39
+ pub = [
40
+ "flit",
41
+ ]
35
42
  test = [
36
43
  "pytest>=8.4.1",
37
- "pythonic-fp-circulararray>=6.0.4",
38
44
  ]
39
45
 
40
46
  [tool.flit.module]
41
47
  name = "pythonic_fp.gadgets"
42
48
 
49
+ [tool.flit.sdist]
50
+ exclude = [
51
+ "docs/",
52
+ "tests/",
53
+ ]
54
+
55
+ [tool.pytest]
56
+ addopts = ["-ra"]
57
+ consider_namespace_packages = true
58
+ testpaths = ["tests/"]
59
+
43
60
  [tool.mypy]
44
61
  explicit_package_bases = true
45
62
  local_partial_types = true
@@ -48,23 +65,12 @@ warn_redundant_casts = true
48
65
  warn_return_any = true
49
66
  warn_unused_configs = true
50
67
 
51
- [tool.pylsp-mypy]
52
- enabled = true
53
- live-mode = true
54
- dmypy = false
55
- strict = true
56
- report_progress = true
57
-
58
- [tool.pytest]
59
- addopts = [
60
- "-ra",
61
- ]
62
- consider_namespace_packages = true
63
- testpaths = ["tests/"]
64
-
65
68
  [tool.ruff]
66
69
  target-version = "py314"
67
70
 
71
+ [tool.ruff.lint]
72
+ ignore = ["RUF022", "E741"]
73
+
68
74
  [tool.ruff.lint.flake8-quotes]
69
75
  docstring-quotes = "double"
70
76
 
@@ -73,7 +79,6 @@ docstring-quotes = "double"
73
79
  "E731",
74
80
  "C901",
75
81
  ]
76
- "**/*.py" = ["E741"]
77
82
 
78
83
  [tool.ruff.format]
79
84
  quote-style = "single"
@@ -59,6 +59,7 @@ def first_common_ancestor(cls1: type, cls2: type) -> type:
59
59
 
60
60
  This happens frequently when the function is given
61
61
  Python builtin types or in multiple inheritance situations.
62
+
62
63
  """
63
64
  if issubclass(cls1, cls2):
64
65
  return cls2
@@ -52,7 +52,7 @@ class Box[T]:
52
52
 
53
53
  Initialize ``Box`` with 0 or 1 items.
54
54
 
55
- :param item: An optional initial ``item`` for the ``Box``.
55
+ :param item: An optional initial item for the ``Box``.
56
56
 
57
57
  """
58
58
  self._item = item
@@ -68,9 +68,9 @@ class Box[T]:
68
68
 
69
69
  def __iter__(self) -> Iterator[T]:
70
70
  """
71
- .. admonition:: Iterability
71
+ .. admonition:: iter
72
72
 
73
- Iterates boxed item.
73
+ :yields: Boxed item.
74
74
 
75
75
  """
76
76
  if self:
@@ -83,7 +83,7 @@ class Box[T]:
83
83
  - 1 if ``Box`` contains an item
84
84
  - 0 if ``Box`` is empty
85
85
 
86
- :returns: The number of items currently in the ``Box``.
86
+ :returns: The number of items currently in the ``Box``.
87
87
 
88
88
  """
89
89
  return 1 if self else 0
@@ -94,10 +94,10 @@ class Box[T]:
94
94
 
95
95
  Efficiently compare ``Box`` to another object.
96
96
 
97
- :param other: The object to be compared.
98
- :returns: ``True`` if ``other`` is another ``Box`` and contains
99
- an object which compares as equal to the object
100
- 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``.
101
101
 
102
102
  """
103
103
  if not isinstance(other, type(self)):
@@ -111,19 +111,35 @@ class Box[T]:
111
111
 
112
112
  def __repr__(self) -> str:
113
113
  """
114
- .. admonition:: Representation string
114
+ .. admonition:: repr string
115
115
 
116
116
  Construct string 'Box()' if empty, otherwise 'Box(item_repr)'
117
117
  where ``item_repr = repr(item)`` for the currently contained
118
118
  item.
119
119
 
120
- :returns: A string to reproduce the current state of the ``Box``.
120
+ :returns: A string to reproduce the current state of the ``Box``.
121
121
 
122
122
  """
123
123
  if self:
124
124
  return 'Box(' + repr(self._item) + ')'
125
125
  return 'Box()'
126
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
+
127
143
  @overload
128
144
  def get(self) -> T: ...
129
145
  @overload
@@ -136,12 +152,12 @@ class Box[T]:
136
152
  Return the boxed item, if it exists, otherwise
137
153
  an alternate item, if given.
138
154
 
139
- :param alt: An optional item of type ``T`` to return
140
- if the ``Box`` is empty.
141
- :returns: Contents of ``Box`` or an alternate item, if given,
142
- when the ``Box`` is empty.
143
- :raises ValueError: When the ``alt`` item is not provided
144
- 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.
145
161
 
146
162
  """
147
163
  if self._item is not _sentinel:
@@ -157,8 +173,8 @@ class Box[T]:
157
173
 
158
174
  Pop item from ``Box`` if not empty.
159
175
 
160
- :returns: The item contained in the ``Box``.
161
- :raises ValueError: If Box is empty.
176
+ :returns: The item contained in the ``Box``.
177
+ :raises ValueError: If Box is empty.
162
178
 
163
179
  """
164
180
  if self._item is _sentinel:
@@ -174,8 +190,8 @@ class Box[T]:
174
190
 
175
191
  Push an item into ``Box`` if empty.
176
192
 
177
- :param item: Item to push into the empty ``Box``.
178
- :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.
179
195
 
180
196
  """
181
197
  if self._item is _sentinel:
@@ -200,9 +216,9 @@ class Box[T]:
200
216
 
201
217
  Exchange an item with what is in the Box.
202
218
 
203
- :param ``new_item``: New item to exchange for current item.
204
- :returns: Original contents of the ``Box``.
205
- :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.
206
222
 
207
223
  """
208
224
  if self._item is _sentinel:
@@ -219,8 +235,8 @@ class Box[T]:
219
235
  Map function ``f`` over contents. We need to return a new
220
236
  instance since the type of Box can change.
221
237
 
222
- :param f: Mapping function.
223
- :returns: New instance.
238
+ :param f: Mapping function.
239
+ :returns: New instance.
224
240
 
225
241
  """
226
242
  if self._item is _sentinel:
@@ -233,8 +249,8 @@ class Box[T]:
233
249
 
234
250
  Flatmap ``Box`` with function ``f``.
235
251
 
236
- :param f: Binding function.
237
- :returns: New instance.
252
+ :param f: Binding function.
253
+ :returns: New instance.
238
254
 
239
255
  """
240
256
  if self._item is _sentinel:
@@ -0,0 +1,270 @@
1
+ # Copyright 2023-2026 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
+ """
22
+ .. admonition:: Wrap
23
+
24
+ Immutablely wrap exactly one item.
25
+
26
+ .. tip::
27
+
28
+ ``Wrap`` objects are matchable.
29
+
30
+ """
31
+ __slots__ = ('_item',)
32
+ __match_args__ = ('_item',)
33
+
34
+ def __init__(self, item: T) -> None:
35
+ """
36
+ .. admonition:: init
37
+
38
+ Initialize ``Wrap`` with 1 required item.
39
+
40
+ :param item: Item to be wrapped.
41
+
42
+ """
43
+ self._item = item
44
+
45
+ def __bool__(self) -> bool:
46
+ """
47
+ .. admonition:: bool
48
+
49
+ Truthiness same as wrapped object.
50
+
51
+ """
52
+ return bool(self._item)
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
+
65
+ def __iter__(self) -> Iterator[T]:
66
+ """
67
+ .. admonition:: iter
68
+
69
+ :yields: A reference to the wrapped item.
70
+
71
+ """
72
+ if self:
73
+ yield self._item
74
+
75
+ def __eq__(self, other: object) -> bool:
76
+ """
77
+ .. admonition:: equality comparison
78
+
79
+ Compare ``Wrap`` to another object.
80
+
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``.
85
+
86
+ """
87
+ if not isinstance(other, type(self)):
88
+ return False
89
+
90
+ if self._item is other._item:
91
+ return True
92
+ return self._item == other._item
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
+
118
+ def map[U](self, f: Callable[[T], U]) -> 'Wrap[U]':
119
+ """
120
+ .. admonition:: map
121
+
122
+ Map function ``f`` over contents.
123
+
124
+ :param f: Mapping function.
125
+ :returns: New instance.
126
+
127
+ """
128
+ return Wrap(f(self._item))
129
+
130
+ def bind[U](self, f: Callable[[T], 'Wrap[U]']) -> 'Wrap[U]':
131
+ """
132
+ .. admonition:: bind
133
+
134
+ Flatmap wrapped object with function ``f``.
135
+
136
+ :param f: Binding function.
137
+ :returns: New instance.
138
+
139
+ """
140
+ return f(self._item)
141
+
142
+
143
+ class HWrap[T: Hashable](Hashable):
144
+ """
145
+ .. admonition:: HWrap
146
+
147
+ Immutablely wrap exactly one hashable item.
148
+
149
+ .. tip::
150
+
151
+ ``HWrap`` objects are hashable and matchable.
152
+
153
+ """
154
+
155
+ __slots__ = ('_item', '_hash')
156
+ __match_args__ = ('_item',)
157
+
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
+ """
167
+ self._item, self._hash = item, hash(item)
168
+
169
+ def __hash__(self) -> int:
170
+ return self._hash
171
+
172
+ def __bool__(self) -> bool:
173
+ """
174
+ .. admonition:: bool
175
+
176
+ Truthiness same as wrapped object.
177
+
178
+ """
179
+ return bool(self._item)
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
+
192
+ def __iter__(self) -> Iterator[T]:
193
+ """
194
+ .. admonition:: iter
195
+
196
+ :yields: A reference to the wrapped item.
197
+
198
+ """
199
+ if self:
200
+ yield self._item
201
+
202
+ def __eq__(self, other: object) -> bool:
203
+ """
204
+ .. admonition:: equality comparison
205
+
206
+ Compare ``HWrap`` to another object.
207
+
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``.
212
+
213
+ """
214
+ if not isinstance(other, type(self)):
215
+ return False
216
+
217
+ if self._hash != other._hash:
218
+ return False
219
+ if self._item is other._item:
220
+ return True
221
+ return self._item == other._item
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
+
247
+ def map[U](self, f: Callable[[T], U]) -> 'HWrap[U]':
248
+ """
249
+ .. admonition:: map
250
+
251
+ Map function ``f`` over wrapped the wrapped object
252
+ returning a new ``HWrap`` instance.
253
+
254
+ :param f: Mapping function.
255
+ :returns: New instance.
256
+
257
+ """
258
+ return HWrap(f(self._item))
259
+
260
+ def bind[U](self, f: Callable[[T], 'HWrap[U]']) -> 'HWrap[U]':
261
+ """
262
+ .. admonition:: bind
263
+
264
+ Flatmap ``Box`` with function ``f``.
265
+
266
+ :param f: Binding function.
267
+ :returns: New instance.
268
+
269
+ """
270
+ return f(self._item)