py-maybetype 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maybetype/__init__.py +51 -20
- maybetype/const.py +1 -1
- {py_maybetype-0.5.0.dist-info → py_maybetype-0.7.0.dist-info}/METADATA +45 -18
- py_maybetype-0.7.0.dist-info/RECORD +6 -0
- py_maybetype-0.5.0.dist-info/RECORD +0 -6
- {py_maybetype-0.5.0.dist-info → py_maybetype-0.7.0.dist-info}/WHEEL +0 -0
- {py_maybetype-0.5.0.dist-info → py_maybetype-0.7.0.dist-info}/licenses/LICENSE +0 -0
maybetype/__init__.py
CHANGED
|
@@ -12,7 +12,8 @@ class Maybe[T]:
|
|
|
12
12
|
|
|
13
13
|
def __init__(self, val: T | None) -> None:
|
|
14
14
|
warnings.warn(
|
|
15
|
-
'Direct instancing of Maybe()
|
|
15
|
+
'Direct instancing of Maybe() not intended and may cause unexpected behavior,'
|
|
16
|
+
+ ' use the maybe() function instead',
|
|
16
17
|
stacklevel=2,
|
|
17
18
|
)
|
|
18
19
|
self.val = val
|
|
@@ -32,7 +33,7 @@ class Maybe[T]:
|
|
|
32
33
|
return self.val == other.val
|
|
33
34
|
|
|
34
35
|
def __hash__(self) -> int:
|
|
35
|
-
return self.val
|
|
36
|
+
return hash(self.val)
|
|
36
37
|
|
|
37
38
|
@staticmethod
|
|
38
39
|
def cat(vals: 'Iterable[Maybe[T]]') -> list[T]:
|
|
@@ -46,7 +47,28 @@ class Maybe[T]:
|
|
|
46
47
|
return [i.unwrap() for i in vals if i]
|
|
47
48
|
|
|
48
49
|
@staticmethod
|
|
49
|
-
def
|
|
50
|
+
def map[A, B](fn: 'Callable[[A], Maybe[B]]', vals: Iterable[A]) -> list[B]:
|
|
51
|
+
"""Maps ``fn`` onto ``vals``, taking the unwrapped values of ``Some``s and discarding ``Nothing``s."""
|
|
52
|
+
return [i.unwrap() for i in map(fn, vals) if i]
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def sequence(vals: 'Iterable[Maybe[T]]') -> 'Maybe[list[T]]':
|
|
56
|
+
"""
|
|
57
|
+
Returns ``Nothing`` if any of ``vals`` is ``Nothing``, otherwise returns a ``Some`` of a list of unwrapped
|
|
58
|
+
items of ``vals``.
|
|
59
|
+
|
|
60
|
+
>>> assert Maybe.sequence([Some(1), Some(2), Some(3)]) == Some([1, 2, 3])
|
|
61
|
+
>>> assert Maybe.sequence([Some(1), Nothing, Some(3)]) is Nothing
|
|
62
|
+
"""
|
|
63
|
+
unwrapped: list[T] = []
|
|
64
|
+
for i in vals:
|
|
65
|
+
if i is Nothing:
|
|
66
|
+
return Nothing
|
|
67
|
+
unwrapped.append(i.unwrap())
|
|
68
|
+
return Some(unwrapped)
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def try_int(val: Any) -> 'Maybe[int]': # noqa: ANN401
|
|
50
72
|
"""
|
|
51
73
|
Attempts to convert ``val`` to an ``int``, returning a ``Some``-wrapped ``int`` if successful, or
|
|
52
74
|
``Nothing`` on failure.
|
|
@@ -57,12 +79,14 @@ class Maybe[T]:
|
|
|
57
79
|
except ValueError:
|
|
58
80
|
return Nothing
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
def and_then[R](self, func: Callable[[T], R]) -> 'Maybe[R]':
|
|
83
|
+
"""
|
|
84
|
+
Like :py:meth:`~maybetype.Maybe.then`, but returns a ``Maybe`` instance instead—``Nothing`` if this instance
|
|
85
|
+
is a ``Nothing``, ``Some(R)`` if the instance is ``Some``, where ``R`` is the returned value of ``func``.
|
|
86
|
+
"""
|
|
87
|
+
return Some(func(self.val)) if self.val is not None else Nothing
|
|
64
88
|
|
|
65
|
-
def attr[
|
|
89
|
+
def attr[U](self, name: str, typ: type[U] | None = None, *, err: bool = False) -> 'Maybe[U]':
|
|
66
90
|
"""
|
|
67
91
|
Attempts to access an attribute ``name`` on the wrapped object, returning a ``Some`` instance wrapping the
|
|
68
92
|
the value if it exists, or ``Nothing`` otherwise.
|
|
@@ -74,7 +98,7 @@ class Maybe[T]:
|
|
|
74
98
|
"""
|
|
75
99
|
return Some(getattr(self.val, name)) if err else maybe(getattr(self.val, name, None))
|
|
76
100
|
|
|
77
|
-
def attr_or[
|
|
101
|
+
def attr_or[U](self, name: str, default: U) -> U:
|
|
78
102
|
"""
|
|
79
103
|
Similar to the ``attr`` method, but unwraps the result if the attribute exists or returns the required default
|
|
80
104
|
value otherwise.
|
|
@@ -84,13 +108,13 @@ class Maybe[T]:
|
|
|
84
108
|
except AttributeError:
|
|
85
109
|
return default
|
|
86
110
|
|
|
87
|
-
def get[
|
|
111
|
+
def get[U](self,
|
|
88
112
|
accessor: Any, # noqa: ANN401
|
|
89
|
-
_typ: type[
|
|
113
|
+
_typ: type[U] | None = None,
|
|
90
114
|
*,
|
|
91
115
|
err: bool = False,
|
|
92
|
-
default:
|
|
93
|
-
) -> 'Maybe[
|
|
116
|
+
default: U | None = None,
|
|
117
|
+
) -> 'Maybe[U]':
|
|
94
118
|
"""
|
|
95
119
|
Attempts to access an item by ``accessor`` on the wrapped object, assuming the wrapped value implements
|
|
96
120
|
``__getitem__``. If it does not, or if the value does not exist (list index out of range, key does not exist on
|
|
@@ -112,20 +136,27 @@ class Maybe[T]:
|
|
|
112
136
|
raise
|
|
113
137
|
return maybe(default)
|
|
114
138
|
|
|
115
|
-
def
|
|
139
|
+
def test(self, predicate: Callable[[T], bool]) -> 'Maybe[T]':
|
|
140
|
+
"""
|
|
141
|
+
Returns ``Nothing`` if the wrapped value does not return ``True`` when passed to ``predicate``, otherwise
|
|
142
|
+
returns the instance the method was called from. When called from a ``Nothing`` instance, ``Nothing`` is always
|
|
143
|
+
returned.
|
|
116
144
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
match self:
|
|
146
|
+
case Some(val):
|
|
147
|
+
return self if predicate(val) else Nothing
|
|
148
|
+
case _:
|
|
149
|
+
return Nothing
|
|
150
|
+
|
|
151
|
+
def then[U](self, func: Callable[[T], U]) -> U | None:
|
|
152
|
+
"""
|
|
153
|
+
Returns ``func`` called with this instance's wrapped value if ``Some``, otherwise returns ``None``.
|
|
119
154
|
|
|
120
155
|
:param func: A ``Callable`` which takes a type of the possible wrapped value (``T``) and can return any type
|
|
121
156
|
(``R``).
|
|
122
157
|
"""
|
|
123
158
|
return func(self.val) if self.val is not None else None
|
|
124
159
|
|
|
125
|
-
def this_or(self, other: T) -> 'Maybe[T]':
|
|
126
|
-
"""Returns the original wrapped value if not ``None``, otherwise returns a ``Some``-wrapped ``other``."""
|
|
127
|
-
return self if self else Some(other)
|
|
128
|
-
|
|
129
160
|
def unwrap(self,
|
|
130
161
|
exc: Exception | Callable[..., Never] | None = None,
|
|
131
162
|
*exc_args: object,
|
maybetype/const.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-maybetype
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A basic implementation of a maybe/option type in Python, largely inspired by Rust's Option.
|
|
5
5
|
Project-URL: Homepage, https://github.com/svioletg/py-maybetype
|
|
6
6
|
Project-URL: Repository, https://github.com/svioletg/py-maybetype
|
|
@@ -16,10 +16,13 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Requires-Python: >=3.12
|
|
20
21
|
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: ipython>=9.10.0; extra == 'dev'
|
|
21
23
|
Requires-Dist: pytest>=9.0.2; extra == 'dev'
|
|
22
|
-
Requires-Dist: ruff>=0.
|
|
24
|
+
Requires-Dist: ruff>=0.15.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ty>=0.0.15; extra == 'dev'
|
|
23
26
|
Provides-Extra: docs
|
|
24
27
|
Requires-Dist: furo>=2025.12.19; extra == 'docs'
|
|
25
28
|
Requires-Dist: myst-parser>=4.0.1; extra == 'docs'
|
|
@@ -29,20 +32,28 @@ Description-Content-Type: text/markdown
|
|
|
29
32
|
|
|
30
33
|
# py-maybetype
|
|
31
34
|
|
|
35
|
+

|
|
36
|
+
|
|
32
37
|
Documentation: <https://py-maybetype.readthedocs.io/en/latest/>
|
|
33
38
|
|
|
39
|
+
PyPI: <https://pypi.org/project/py-maybetype/>
|
|
40
|
+
|
|
34
41
|
A basic implementation of a maybe/option type in Python, largely inspired by Rust's `Option`.
|
|
35
42
|
This was created as part of a separate project I had been working on, but I decided to make it into
|
|
36
43
|
its own package as I wanted to use it elsewhere and its scope grew. This is not meant to be a 1:1
|
|
37
44
|
replication or replacement for Rust's `Option` or Haskell's `Maybe`, but rather just an
|
|
38
45
|
interperetation of the idea that I feel works for Python.
|
|
39
46
|
|
|
47
|
+
> [!WARNING]
|
|
48
|
+
> Breaking changes are likely each update in the 0.x phase. Please check the changelog for these
|
|
49
|
+
> changes before updating to a new version.
|
|
50
|
+
|
|
40
51
|
## Usage
|
|
41
52
|
|
|
42
|
-
Install
|
|
53
|
+
Install with `pip`:
|
|
43
54
|
|
|
44
55
|
```bash
|
|
45
|
-
pip install
|
|
56
|
+
pip install py-maybetype
|
|
46
57
|
```
|
|
47
58
|
|
|
48
59
|
Call the `maybe()` function with a `T | None` value to return a `Maybe[T]`—either a `Some` instance
|
|
@@ -73,17 +84,20 @@ assert bool(num1) is True
|
|
|
73
84
|
assert bool(num2) is False
|
|
74
85
|
```
|
|
75
86
|
|
|
76
|
-
This example in particular can also be done with `Maybe
|
|
87
|
+
This example in particular can also be done with the `Maybe.try_int()` class method:
|
|
77
88
|
|
|
78
89
|
```python
|
|
79
|
-
num1: Maybe[int] = Maybe.
|
|
80
|
-
num2: Maybe[int] = Maybe.
|
|
90
|
+
num1: Maybe[int] = Maybe.try_int('5')
|
|
91
|
+
num2: Maybe[int] = Maybe.try_int('five')
|
|
81
92
|
```
|
|
82
93
|
|
|
83
94
|
The `maybe` constructor can be given an optional predicate argument to specify a custom condition
|
|
84
95
|
for which `Some(value)` is returned. This argument must be a `Callable` that returns `bool`,
|
|
85
96
|
where returning `False` causes the constructor to return `Nothing`.
|
|
86
97
|
|
|
98
|
+
> [!NOTE]
|
|
99
|
+
> `maybe(None)` will always returning `Nothing`, even if `predicate(None)` would return `True`
|
|
100
|
+
|
|
87
101
|
```python
|
|
88
102
|
import re
|
|
89
103
|
import uuid
|
|
@@ -108,29 +122,42 @@ from maybetype import maybe, Some
|
|
|
108
122
|
match maybe(1):
|
|
109
123
|
case Some(val):
|
|
110
124
|
print('Value: ', val)
|
|
111
|
-
case _:
|
|
125
|
+
case _: # "case Nothing:" also works, but just matching else in this case will be identical
|
|
112
126
|
print('No value')
|
|
113
127
|
```
|
|
114
128
|
|
|
115
129
|
## Other examples
|
|
116
130
|
|
|
117
|
-
Converting a `str | None` timestamp into a `datetime` object if not `None`, otherwise returning
|
|
131
|
+
Converting a `str | None` timestamp into a `datetime` object if not `None`, otherwise returning
|
|
132
|
+
`None`:
|
|
118
133
|
|
|
119
134
|
```python
|
|
120
135
|
from datetime import datetime
|
|
121
136
|
from maybetype import maybe
|
|
122
137
|
|
|
123
|
-
|
|
124
|
-
date = maybe('2025-09-06T030000').then(datetime.fromisoformat)
|
|
125
|
-
# date == datetime.datetime(2025, 9, 6, 3, 0)
|
|
138
|
+
assert maybe('2025-09-06T030000').then(datetime.fromisoformat) == datetime(2025, 9, 6, 3, 0)
|
|
126
139
|
|
|
127
|
-
|
|
128
|
-
date = maybe(date_str).then(datetime.fromisoformat)
|
|
129
|
-
# date == None
|
|
140
|
+
assert maybe(None).then(datetime.fromisoformat) is None
|
|
130
141
|
|
|
131
|
-
|
|
132
|
-
date = maybe(date_str or None).then(datetime.fromisoformat)
|
|
133
|
-
# date == None
|
|
142
|
+
assert maybe('' or None).then(datetime.fromisoformat) is None
|
|
134
143
|
# Maybe does not treat falsy values as None, only strictly x-is-None values
|
|
135
144
|
# Without `or None` here, datetime.fromisoformat would have raised a ValueError
|
|
136
145
|
```
|
|
146
|
+
|
|
147
|
+
Converting a `str | None` timestamp into a `datetime` object if not `None`, then ensuring that date
|
|
148
|
+
meets certain criteria:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from datetime import datetime
|
|
152
|
+
from maybetype import maybe
|
|
153
|
+
|
|
154
|
+
assert maybe('2025-09-06T030000').and_then(datetime.fromisoformat).test(lambda dt: dt.year > 2024)
|
|
155
|
+
|
|
156
|
+
assert not maybe('2024-09-06T030000').and_then(datetime.fromisoformat).test(lambda dt: dt.year > 2024)
|
|
157
|
+
|
|
158
|
+
match maybe('2025-09-06T030000').and_then(datetime.fromisoformat).test(lambda dt: dt.year > 2024):
|
|
159
|
+
case Some(date):
|
|
160
|
+
... # Do something with the date
|
|
161
|
+
case _:
|
|
162
|
+
... # Do something else
|
|
163
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
maybetype/__init__.py,sha256=Ehj90apIBpn0_lUuN_h-wp6JCk22cqYm4FUbC8Q54No,8973
|
|
2
|
+
maybetype/const.py,sha256=_Lc2ZS-9j5n0TcSTEXGWqQ6SO4hyARBcVZ_JNleNKYU,93
|
|
3
|
+
py_maybetype-0.7.0.dist-info/METADATA,sha256=rD3UtHoMBCklaxnkakh6oW2-qhdKWN5RzPubJTiAYgE,5648
|
|
4
|
+
py_maybetype-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
+
py_maybetype-0.7.0.dist-info/licenses/LICENSE,sha256=eMbi_IczZg-O56zEg00k6bmfExkQTpx01DN1xLY2w9I,1076
|
|
6
|
+
py_maybetype-0.7.0.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
maybetype/__init__.py,sha256=CQFFyrpwovLYg_17QDcxY8jssh9CISi6UMlsilL-3kc,7696
|
|
2
|
-
maybetype/const.py,sha256=Vu_oNFgMuJFf4wGqh2ywIO7I0rA0zlrJb4KdPeoupns,90
|
|
3
|
-
py_maybetype-0.5.0.dist-info/METADATA,sha256=jxcQQqsJTYiZ_qhQFrMqJ_d6lpMJosG-JrdHzW39KEc,4562
|
|
4
|
-
py_maybetype-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
5
|
-
py_maybetype-0.5.0.dist-info/licenses/LICENSE,sha256=eMbi_IczZg-O56zEg00k6bmfExkQTpx01DN1xLY2w9I,1076
|
|
6
|
-
py_maybetype-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|