pythonic-fp-booleans 3.0.2__tar.gz → 4.1.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_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/PKG-INFO +23 -28
  2. pythonic_fp_booleans-4.1.0/README.md +24 -0
  3. {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/pyproject.toml +34 -25
  4. {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/src/pythonic_fp/booleans/__init__.py +7 -20
  5. pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/flavored.py +194 -0
  6. pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/subtypable.py +246 -0
  7. pythonic_fp_booleans-4.1.0/src/pythonic_fp/booleans/truthy_falsy.py +229 -0
  8. pythonic_fp_booleans-3.0.2/.github/workflows/static.yml +0 -59
  9. pythonic_fp_booleans-3.0.2/.gitignore +0 -11
  10. pythonic_fp_booleans-3.0.2/CHANGELOG.rst +0 -104
  11. pythonic_fp_booleans-3.0.2/README.rst +0 -29
  12. pythonic_fp_booleans-3.0.2/docs/Makefile +0 -69
  13. pythonic_fp_booleans-3.0.2/docs/build/.nojekyll +0 -0
  14. pythonic_fp_booleans-3.0.2/docs/build/index.html +0 -44
  15. pythonic_fp_booleans-3.0.2/docs/build/style.css +0 -44
  16. pythonic_fp_booleans-3.0.2/docs/conf_custom.py +0 -68
  17. pythonic_fp_booleans-3.0.2/docs/conf_devel.py +0 -68
  18. pythonic_fp_booleans-3.0.2/docs/conf_release.py +0 -68
  19. pythonic_fp_booleans-3.0.2/docs/index_custom.rst +0 -30
  20. pythonic_fp_booleans-3.0.2/docs/index_devel.rst +0 -30
  21. pythonic_fp_booleans-3.0.2/docs/index_release.rst +0 -30
  22. pythonic_fp_booleans-3.0.2/docs/requirements.txt +0 -2
  23. pythonic_fp_booleans-3.0.2/docs/source/changelog.rst +0 -7
  24. pythonic_fp_booleans-3.0.2/docs/source/description.rst +0 -7
  25. pythonic_fp_booleans-3.0.2/docs/source/docs/flavored.rst +0 -4
  26. pythonic_fp_booleans-3.0.2/docs/source/docs/index.rst +0 -15
  27. pythonic_fp_booleans-3.0.2/docs/source/docs/subtypable.rst +0 -8
  28. pythonic_fp_booleans-3.0.2/docs/source/docs/truthy_falsy.rst +0 -4
  29. pythonic_fp_booleans-3.0.2/docs/source/releases.rst +0 -24
  30. pythonic_fp_booleans-3.0.2/docs/source/usage.rst +0 -63
  31. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/__init__.pyi +0 -0
  32. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/flavored.py +0 -140
  33. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/flavored.pyi +0 -19
  34. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/subtypable.py +0 -166
  35. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/subtypable.pyi +0 -18
  36. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/truthy_falsy.py +0 -168
  37. pythonic_fp_booleans-3.0.2/src/pythonic_fp/booleans/truthy_falsy.pyi +0 -21
  38. pythonic_fp_booleans-3.0.2/tests/test_bool_vs_fbool.py +0 -195
  39. pythonic_fp_booleans-3.0.2/tests/test_bool_vs_sbool.py +0 -160
  40. pythonic_fp_booleans-3.0.2/tests/test_flavored.py +0 -167
  41. pythonic_fp_booleans-3.0.2/tests/test_flavored_non_shortcut_logic.py +0 -112
  42. pythonic_fp_booleans-3.0.2/tests/test_flavored_sbool.py +0 -162
  43. pythonic_fp_booleans-3.0.2/tests/test_subtypable_non_shortcut_logic.py +0 -83
  44. pythonic_fp_booleans-3.0.2/tests/test_true_false.py +0 -189
  45. pythonic_fp_booleans-3.0.2/tests/test_true_false_non_shortcut_logic.py +0 -157
  46. pythonic_fp_booleans-3.0.2/tests/test_typing.py +0 -98
  47. pythonic_fp_booleans-3.0.2/tests/test_variance.py +0 -187
  48. {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/LICENSE +0 -0
  49. {pythonic_fp_booleans-3.0.2 → pythonic_fp_booleans-4.1.0}/src/pythonic_fp/booleans/py.typed +0 -0
@@ -1,53 +1,48 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pythonic-fp-booleans
3
- Version: 3.0.2
4
- Summary: Subtypable Boolean Class Hierarchy
3
+ Version: 4.1.0
4
+ Summary: True Booleans which are subtypable.
5
5
  Keywords: booleans,subtypable
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
7
  Requires-Python: >=3.13
8
- Description-Content-Type: text/x-rst
8
+ Description-Content-Type: text/markdown
9
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
13
13
  Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Typing :: Typed
16
16
  License-File: LICENSE
17
17
  Requires-Dist: pythonic-fp-gadgets>=4.0.2
18
- Requires-Dist: pytest>=8.4.1 ; extra == "test"
19
- Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.rst
20
- Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-booleans/
21
- Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/html/
18
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.md
19
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-booleans/release/html/
20
+ Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
22
21
  Project-URL: Source, https://github.com/grscheller/pythonic-fp-booleans
23
- Provides-Extra: test
22
+ Import-Name: pythonic_fp.booleans
23
+ Import-Namespace: pythonic_fp
24
24
 
25
- Pythonic FP - Booleans
26
- ======================
27
-
28
- PyPI project
29
- `pythonic-fp-booleans
30
- <https://pypi.org/project/pythonic-fp-booleans>`_.
25
+ # Pythonic FP - Booleans
31
26
 
32
27
  Subtypable Boolean like classes.
33
28
 
34
- While still compatible with Python shortcut logic, these singleton classes
35
- can be non-shortcut logically composed with Python’s bitwise operators.
36
-
29
+ PyPI project
30
+ [pythonic-fp-booleans](https://pypi.org/project/pythonic-fp-booleans).
37
31
  Part of the
38
- `pythonic-fp
39
- <https://grscheller.github.io/pythonic-fp>`_
32
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
40
33
  PyPI projects.
41
34
 
42
- Documentation
43
- -------------
35
+ ## Documentation
36
+
37
+ Documentation and other links for this project are hosted on
38
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/booleans.html).
44
39
 
45
- Documentation for this project is hosted on
46
- `GitHub Pages
47
- <https://grscheller.github.io/pythonic-fp-booleans>`_.
40
+ While still compatible with Python shortcut logic, these singleton
41
+ classes can be non-shortcut logically composed with Python’s bitwise
42
+ operators. Unlike Python booleans which bitwise compose as integers,
43
+ these compose as true Booleans in a contravariant way.
48
44
 
49
- Copyright and License
50
- ---------------------
45
+ ## Copyright and License
51
46
 
52
47
  Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
53
48
  License, Version 2.0. See the LICENSE file for details.
@@ -0,0 +1,24 @@
1
+ # Pythonic FP - Booleans
2
+
3
+ Subtypable Boolean like classes.
4
+
5
+ PyPI project
6
+ [pythonic-fp-booleans](https://pypi.org/project/pythonic-fp-booleans).
7
+ Part of the
8
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
9
+ PyPI projects.
10
+
11
+ ## Documentation
12
+
13
+ Documentation and other links for this project are hosted on
14
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/booleans.html).
15
+
16
+ While still compatible with Python shortcut logic, these singleton
17
+ classes can be non-shortcut logically composed with Python’s bitwise
18
+ operators. Unlike Python booleans which bitwise compose as integers,
19
+ these compose as true Booleans in a contravariant way.
20
+
21
+ ## Copyright and License
22
+
23
+ Copyright (c) 2025-2026 Geoffrey R. Scheller. Licensed under the Apache
24
+ License, Version 2.0. See the LICENSE file for details.
@@ -1,10 +1,15 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.12,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
1
5
  [project]
2
6
  name = "pythonic-fp-booleans"
3
- version = "3.0.2"
4
- readme = "README.rst"
7
+ version = "4.1.0"
8
+ readme = "README.md"
5
9
  requires-python = ">=3.13"
6
- license = { file = "LICENSE" }
7
- authors = [{ name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" }]
10
+ authors = [
11
+ { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
12
+ ]
8
13
  keywords = [
9
14
  "booleans",
10
15
  "subtypable",
@@ -15,32 +20,41 @@ classifiers = [
15
20
  "Intended Audience :: Developers",
16
21
  "License :: OSI Approved :: Apache Software License",
17
22
  "Operating System :: OS Independent",
18
- "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
19
24
  "Typing :: Typed",
20
25
  ]
21
26
  dependencies = [
22
27
  "pythonic-fp-gadgets>=4.0.2",
23
28
  ]
24
- dynamic = ["description"]
29
+ description = "True Booleans which are subtypable."
25
30
 
26
31
  [project.urls]
27
- Changelog = "https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.rst"
28
- Documentation = "https://grscheller.github.io/pythonic-fp-booleans/"
29
- Homepage = "https://grscheller.github.io/pythonic-fp/homepage/html/"
32
+ Changelog = "https://github.com/grscheller/pythonic-fp-booleans/blob/main/CHANGELOG.md"
33
+ Documentation ="https://grscheller.github.io/pythonic-fp-booleans/release/html/"
34
+ Homepage = "https://grscheller.github.io/pythonic-fp/"
30
35
  Source = "https://github.com/grscheller/pythonic-fp-booleans"
31
36
 
32
- [project.optional-dependencies]
37
+ [dependency-groups]
38
+ docs = [
39
+ "furo>=2025.12.19",
40
+ "sphinx>=9.1",
41
+ ]
42
+ pub = [
43
+ "flit",
44
+ ]
33
45
  test = [
34
46
  "pytest>=8.4.1",
35
47
  ]
36
48
 
37
- [build-system]
38
- requires = ["flit_core>=3.12,<4"]
39
- build-backend = "flit_core.buildapi"
40
-
41
49
  [tool.flit.module]
42
50
  name = "pythonic_fp.booleans"
43
51
 
52
+ [tool.flit.sdist]
53
+ exclude = [
54
+ "docs/",
55
+ "tests/",
56
+ ]
57
+
44
58
  [tool.mypy]
45
59
  explicit_package_bases = true
46
60
  local_partial_types = true
@@ -49,20 +63,16 @@ warn_redundant_casts = true
49
63
  warn_return_any = true
50
64
  warn_unused_configs = true
51
65
 
52
- [tool.pylsp-mypy]
53
- enabled = true
54
- live-mode = true
55
- dmypy = false
56
- strict = true
57
- report_progress = true
58
-
59
- [tool.pytest.ini_options]
60
- addopts = "-ra"
66
+ [tool.pytest]
67
+ addopts = ["-ra"]
61
68
  consider_namespace_packages = true
62
69
  testpaths = ["tests/"]
63
70
 
64
71
  [tool.ruff]
65
- target-version = "py313"
72
+ target-version = "py314"
73
+
74
+ [tool.ruff.lint]
75
+ ignore = ["RUF022", "E741"]
66
76
 
67
77
  [tool.ruff.lint.flake8-quotes]
68
78
  docstring-quotes = "double"
@@ -72,7 +82,6 @@ docstring-quotes = "double"
72
82
  "E731",
73
83
  "C901",
74
84
  ]
75
- "**/*.py" = ["E741"]
76
85
 
77
86
  [tool.ruff.format]
78
87
  quote-style = "single"
@@ -13,28 +13,15 @@
13
13
  # limitations under the License.
14
14
 
15
15
  """
16
- Subtypable Boolean Class Hierarchy
17
- ----------------------------------
16
+ Booleans
17
+ ========
18
18
 
19
- .. graphviz::
19
+ .. admonition:: Subtypable/subtyped Boolean like classes
20
20
 
21
- digraph Booleans {
22
- bgcolor="#957fb8";
23
- node [style=filled, fillcolor="#181616", fontcolor="#dcd7ba"];
24
- edge [color="#181616", fontcolor="#dcd7ba"];
25
- int -> bool;
26
- int -> SBool;
27
- SBool -> "FBool(h1)";
28
- SBool -> "FBool(h2)";
29
- SBool -> "FBool(h3)";
30
- SBool -> TF_Bool;
31
- TF_Bool -> T_Bool;
32
- TF_Bool -> F_Bool;
33
- }
34
-
35
- While still compatible with Python shortcut logic, ``SBool`` and its
36
- subclasses can be non-shortcut logically composed with Python's bitwise
37
- operators.
21
+ - Like Python's builtin ``bool``, ``SBool`` and its subclasses
22
+ are threadsafe singletons.
23
+ - While still compatible with Python shortcut logic, these can be
24
+ non-shortcut logically composed with Python's bitwise operators.
38
25
 
39
26
  """
40
27
 
@@ -0,0 +1,194 @@
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
+ from collections.abc import Hashable
16
+ from threading import Lock
17
+ from typing import ClassVar, Self, final
18
+
19
+ from .subtypable import SBool
20
+
21
+ __all__ = ['FBool', 'truthy', 'falsy']
22
+
23
+
24
+ @final
25
+ class FBool(SBool):
26
+ """
27
+ .. admonition:: Favored Booleans
28
+
29
+ When different flavors of the truth matter. Each ``FBool`` is
30
+ an ``SBool`` subtype corresponding to a hashable flavor.
31
+
32
+ .. warning::
33
+
34
+ Combining FBool instances of different flavors with
35
+ bitwise operators will result in a runtime ValueError
36
+ exception.
37
+
38
+ """
39
+
40
+ _falsy_dict: ClassVar[dict[Hashable, FBool]] = {}
41
+ _falsy_dict_lock: ClassVar[Lock] = Lock()
42
+
43
+ _truthy_dict: ClassVar[dict[Hashable, FBool]] = {}
44
+ _truthy_dict_lock: ClassVar[Lock] = Lock()
45
+
46
+ def __new__(cls, witness: object, flavor: Hashable) -> Self:
47
+ """
48
+ .. admonition:: new
49
+
50
+ Traditional singleton pattern but with a ClassVar dict
51
+ to store the for truthy or falsy singleton for each
52
+ hashable flavor.
53
+
54
+ :param witness: Determines truthiness of the FBool instance returned.
55
+ :param flavor: The flavor of FBool to created.
56
+ :returns: The truthy or falsy FBool instance of a particular flavor.
57
+
58
+ """
59
+ if witness:
60
+ if flavor not in cls._truthy_dict:
61
+ with cls._truthy_dict_lock:
62
+ if flavor not in cls._truthy_dict:
63
+ cls._truthy_dict[flavor] = super(SBool, cls).__new__(cls, 1)
64
+ return cls._truthy_dict[flavor]
65
+ else:
66
+ if flavor not in cls._falsy_dict:
67
+ with cls._falsy_dict_lock:
68
+ if flavor not in cls._falsy_dict:
69
+ cls._falsy_dict[flavor] = super(SBool, cls).__new__(cls, 0)
70
+ return cls._falsy_dict[flavor]
71
+
72
+ def __init__(self, witness: object, flavor: Hashable) -> None:
73
+ """
74
+ .. admonition:: initialize
75
+
76
+ Let the flavored boolean know its flavor.
77
+
78
+ :param witness: Determines truthiness of the FBool instance returned.
79
+ :param flavor: The flavor of FBool to created.
80
+ :type flavor: H: Hashable
81
+ :returns: The truthy or falsy FBool instance of a particular flavor.
82
+ :raises ValueError: If different flavors compared with bitwise operators.
83
+
84
+ """
85
+ if not hasattr(self, '_flavor'):
86
+ self._flavor = flavor
87
+
88
+ def __and__(self, other: int) -> int:
89
+ if type(other) is type(self):
90
+ if other.flavor() != self._flavor:
91
+ msg = 'Error: diffent flavored booleans compared with & operator'
92
+ raise ValueError(msg)
93
+ if self and other:
94
+ return FBool(1, self._flavor)
95
+ else:
96
+ return FBool(0, self._flavor)
97
+ return super().__and__(other)
98
+
99
+ def __or__(self, other: int) -> int:
100
+ if type(other) is type(self):
101
+ if other.flavor() != self.flavor():
102
+ msg = 'Error: diffent flavored booleans compared with | operator'
103
+ raise ValueError(msg)
104
+ if self or other:
105
+ return FBool(1, self._flavor)
106
+ else:
107
+ return FBool(0, self._flavor)
108
+ return super().__or__(other)
109
+
110
+ def __xor__(self, other: int) -> int:
111
+ if type(other) is type(self):
112
+ if other.flavor() != self._flavor:
113
+ msg = 'Error: diffent flavored booleans compared with ^ operator'
114
+ raise ValueError(msg)
115
+ if (self or other) and not (self and other):
116
+ return FBool(1, self._flavor)
117
+ else:
118
+ return FBool(0, self._flavor)
119
+ return super().__xor__(other)
120
+
121
+ def __repr__(self) -> str:
122
+ """
123
+ .. admonition:: repr string
124
+
125
+ Create strings of the form
126
+
127
+ - ``FBool(True, repr_flavor)``
128
+ - ``FBool(False, repr_flavor)``
129
+
130
+ Where ``repr_flavor = repr(self.flavor())``
131
+
132
+ :returns: A String to reproduce the flavored boolean.
133
+
134
+ """
135
+ if self:
136
+ return f'FBool(True, {self._flavor!r})'
137
+ return f'FBool(False, {self._flavor!r})'
138
+
139
+ def __str__(self) -> str:
140
+ """
141
+ .. admonition:: user string
142
+
143
+ Create strings of the form
144
+
145
+ - ``FBool(True, str_flavor)``
146
+ - ``FBool(False, str_flavor)``
147
+
148
+ Where ``str_flavor = str(self.flavor())``
149
+
150
+ :returns: A String meaningful to an end user.
151
+
152
+ """
153
+ if self:
154
+ return f'FBool(True, {self._flavor!s})'
155
+ return f'FBool(False, {self._flavor!s})'
156
+
157
+ def flavor(self) -> Hashable:
158
+ """
159
+ .. admonition:: flavor
160
+
161
+ Get the flavor of the ``FBool``, a hashable value.
162
+
163
+ :returns: The flavor.
164
+
165
+ """
166
+ return self._flavor
167
+
168
+
169
+ def truthy(flavor: Hashable) -> FBool:
170
+ """
171
+ .. admonition:: function truthy
172
+
173
+ Returns the truthy singleton ``FBool`` of a particular flavor.
174
+
175
+ :param flavor: Hashable value to determine which
176
+ singleton flavor to return.
177
+ :returns: The truthy singleton of a particular flavor.
178
+
179
+ """
180
+ return FBool(True, flavor)
181
+
182
+
183
+ def falsy(flavor: Hashable) -> FBool:
184
+ """
185
+ .. admonition:: Function falsy
186
+
187
+ Returns the falsy singleton ``FBool`` of a particular flavor.
188
+
189
+ :param flavor: Hashable value to determine which
190
+ singleton flavor to return.
191
+ :returns: The falsy singleton of a particular flavor.
192
+
193
+ """
194
+ return FBool(False, flavor)
@@ -0,0 +1,246 @@
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 collections.abc import Hashable
16
+ from threading import Lock
17
+ from typing import ClassVar, Final, Self, cast, overload
18
+
19
+ from pythonic_fp.gadgets import first_common_ancestor as fca
20
+ from pythonic_fp.gadgets.sentinels.novalue import NoValue
21
+
22
+ __all__ = ['SBool', 'TRUTH', 'LIE']
23
+
24
+ no_value = NoValue()
25
+
26
+ class SBool(int):
27
+ """
28
+ .. admonition:: Subtypable Boolean
29
+
30
+ Like Python's built in ``bool``, class ``SBool`` is a singleton
31
+ subclass of ``int``. Unlike ``bool``, it can be further subclassed.
32
+
33
+ ``SBool`` and its subtypes can also do (non-shortcut) Boolean logic
34
+ using Python bitwise operators.
35
+
36
+ +-------------------+--------+----------------+
37
+ | Boolean operation | symbol | dunder |
38
+ +===================+========+================+
39
+ | not | ``~`` | ``__invert__`` |
40
+ +-------------------+--------+----------------+
41
+ | and | ``&`` | ``__and__`` |
42
+ +-------------------+--------+----------------+
43
+ | or | ``|`` | ``__or__`` |
44
+ +-------------------+--------+----------------+
45
+ | xor | ``^`` | ``__xor__`` |
46
+ +-------------------+--------+----------------+
47
+
48
+ While compatible with Python short-cut logic, , the not
49
+ operator unfortunately always returns a bool.
50
+
51
+ .. tip::
52
+
53
+ Use the bitwise ~ operator to return an opposite SBool
54
+ instance or subclass instance.
55
+
56
+ .. note::
57
+
58
+ These operators are contravariant, that is they will return
59
+ the instance of the latest common ancestor of their
60
+ arguments. More specifically, the instance returned will
61
+ have the type of the least upper bound in the inheritance
62
+ graph of the classes of the two arguments.
63
+
64
+ .. warning::
65
+
66
+ The "bitwise" operators can raise TypeError
67
+ exceptions when applied against an SBool and
68
+ objects not descended from int.
69
+
70
+ """
71
+
72
+ _falsy: ClassVar[SBool | NoValue] = NoValue()
73
+ _falsy_lock: ClassVar[Lock] = Lock()
74
+
75
+ _truthy: ClassVar[SBool | NoValue] = NoValue()
76
+ _truthy_lock: ClassVar[Lock] = Lock()
77
+
78
+ @overload
79
+ def __new__(cls, witness: object) -> Self: ...
80
+ @overload
81
+ def __new__(
82
+ cls, witness: object, flavor: Hashable | NoValue = no_value
83
+ ) -> Self: ...
84
+
85
+ def __new__(
86
+ cls,
87
+ witness: object,
88
+ flavor: Hashable | NoValue = no_value,
89
+ ) -> Self:
90
+ """
91
+ .. admonition:: new
92
+
93
+ :param witness: Determines truthiness of the ``SBool``.
94
+ :param flavor: Ignored
95
+ :returns: The truthy or falsy ``SBool`` class instance.
96
+
97
+ """
98
+ if witness:
99
+ if cls._truthy is NoValue():
100
+ with cls._truthy_lock:
101
+ if cls._truthy is NoValue():
102
+ cls._truthy = super().__new__(cls, 1)
103
+ return cast(Self, cls._truthy)
104
+ else:
105
+ if cls._falsy is NoValue():
106
+ with cls._falsy_lock:
107
+ if cls._falsy is NoValue():
108
+ cls._falsy = super().__new__(cls, 0)
109
+ return cast(Self, cls._falsy)
110
+
111
+ @overload
112
+ def __init__(self, witness: object) -> None: ...
113
+ @overload
114
+ def __init__(self, witness: object, flavor: Hashable) -> None: ...
115
+
116
+ def __init__(
117
+ self,
118
+ witness: object = False,
119
+ flavor: Hashable | NoValue = no_value,
120
+ ) -> None:
121
+ """
122
+ .. admonition:: init
123
+
124
+ :param witness: Determines the truthiness of the ``SBool``.
125
+ :param flavor: Ignored by ``SBool``, here only to support
126
+ the Liskov Substitution Principle.
127
+ """
128
+ self._flavor: Hashable | NoValue = NoValue()
129
+
130
+ def __invert__(self) -> int:
131
+ if self:
132
+ return type(self)(False, self._flavor)
133
+ return type(self)(True, self._flavor)
134
+
135
+ def __and__(self, other: int) -> int:
136
+ try:
137
+ base_class = fca(type(self), type(other))
138
+ except TypeError:
139
+ if type(other) is bool:
140
+ base_class = int
141
+ else:
142
+ msg = f"unsupported operand type(s) for &: '{type(self)}' and '{type(other)}'"
143
+ raise TypeError(msg)
144
+
145
+ if issubclass(base_class, SBool):
146
+ if self._flavor == cast(SBool, other)._flavor:
147
+ flavor = self._flavor
148
+ else:
149
+ flavor = NoValue()
150
+
151
+ if self and other:
152
+ return base_class(True, flavor)
153
+ return base_class(False, flavor)
154
+ else:
155
+ return int(self) & int(other)
156
+
157
+ def __or__(self, other: int) -> int:
158
+ try:
159
+ base_class = fca(type(self), type(other))
160
+ except TypeError:
161
+ if type(other) is bool:
162
+ base_class = int
163
+ else:
164
+ msg = f"unsupported operand type(s) for |: '{type(self)}' and '{type(other)}'"
165
+ raise TypeError(msg)
166
+
167
+ if issubclass(base_class, SBool):
168
+ if self._flavor == cast(SBool, other)._flavor:
169
+ flavor = self._flavor
170
+ else:
171
+ flavor = NoValue()
172
+
173
+ if self or other:
174
+ return base_class(True, flavor)
175
+ return base_class(False, flavor)
176
+ else:
177
+ return int(self) | int(other)
178
+
179
+ def __xor__(self, other: int) -> int:
180
+ try:
181
+ base_class = fca(type(self), type(other))
182
+ except TypeError:
183
+ if type(other) is bool:
184
+ base_class = int
185
+ else:
186
+ msg = f"unsupported operand type(s) for ^: '{type(self)}' and '{type(other)}'"
187
+ raise TypeError(msg)
188
+
189
+ if issubclass(base_class, SBool):
190
+ if self._flavor == cast(SBool, other)._flavor:
191
+ flavor = self._flavor
192
+ else:
193
+ flavor = NoValue()
194
+
195
+ if self and not other or other and not self:
196
+ return base_class(True, flavor)
197
+ return base_class(False, flavor)
198
+ else:
199
+ return int(self) ^ int(other)
200
+
201
+ # override in derived classes
202
+ def __repr__(self) -> str:
203
+ """
204
+ .. admonition:: repr string
205
+
206
+ - 'SBool(True)' if truthy
207
+ - 'SBool(False)' if falsy
208
+
209
+ :returns: A string to reproduce the ``SBool``.
210
+
211
+ """
212
+ if self:
213
+ return 'SBool(True)'
214
+ return 'SBool(False)'
215
+
216
+ # override in derived classes
217
+ def __str__(self) -> str:
218
+ """
219
+ .. admonition:: user string
220
+
221
+ - 'TRUTH' if truthy
222
+ - 'LIE' if falsy
223
+
224
+ :returns: A string meaningful to an end user.
225
+
226
+ """
227
+ if self:
228
+ return 'TRUTH'
229
+ return 'LIE'
230
+
231
+
232
+ TRUTH: Final[SBool] = SBool(True)
233
+ """
234
+ .. admonition:: TRUTH
235
+
236
+ :var TRUTH: The truthy singleton of type ``SBool``.
237
+
238
+ """
239
+
240
+ LIE: Final[SBool] = SBool(False)
241
+ """
242
+ .. admonition:: LIE
243
+
244
+ :var LIE: The falsy singleton of type SBool.
245
+
246
+ """