py-maybetype 0.6.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 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() is not recommended as of v0.5.0, use the maybe() function instead',
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.__hash__()
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 int(val: Any) -> 'Maybe[int]': # noqa: ANN401
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
- @staticmethod
61
- def map[A, B](fn: 'Callable[[A], Maybe[B]]', vals: Iterable[A]) -> list[B]:
62
- """Maps ``fn`` onto ``vals``, taking the unwrapped values of ``Some``s and discarding ``Nothing``s."""
63
- return [i.unwrap() for i in map(fn, vals) if i]
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[V](self, name: str, typ: type[V] | None = None, *, err: bool = False) -> 'Maybe[V]':
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[V](self, name: str, default: V) -> V:
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[V](self,
111
+ def get[U](self,
88
112
  accessor: Any, # noqa: ANN401
89
- _typ: type[V] | None = None,
113
+ _typ: type[U] | None = None,
90
114
  *,
91
115
  err: bool = False,
92
- default: V | None = None,
93
- ) -> 'Maybe[V]':
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 then[R](self, func: Callable[[T], R]) -> R | None:
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
- Calls ``func`` with the wrapped value as the argument and returns its value, or returns ``None`` if the wrapped
118
- value is ``None``.
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,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-maybetype
3
- Version: 0.6.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.14.8; extra == 'dev'
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'
@@ -41,6 +44,10 @@ its own package as I wanted to use it elsewhere and its scope grew. This is not
41
44
  replication or replacement for Rust's `Option` or Haskell's `Maybe`, but rather just an
42
45
  interperetation of the idea that I feel works for Python.
43
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
+
44
51
  ## Usage
45
52
 
46
53
  Install with `pip`:
@@ -77,17 +84,20 @@ assert bool(num1) is True
77
84
  assert bool(num2) is False
78
85
  ```
79
86
 
80
- This example in particular can also be done with `Maybe`'s built-in `int()` class method:
87
+ This example in particular can also be done with the `Maybe.try_int()` class method:
81
88
 
82
89
  ```python
83
- num1: Maybe[int] = Maybe.int('5')
84
- num2: Maybe[int] = Maybe.int('five')
90
+ num1: Maybe[int] = Maybe.try_int('5')
91
+ num2: Maybe[int] = Maybe.try_int('five')
85
92
  ```
86
93
 
87
94
  The `maybe` constructor can be given an optional predicate argument to specify a custom condition
88
95
  for which `Some(value)` is returned. This argument must be a `Callable` that returns `bool`,
89
96
  where returning `False` causes the constructor to return `Nothing`.
90
97
 
98
+ > [!NOTE]
99
+ > `maybe(None)` will always returning `Nothing`, even if `predicate(None)` would return `True`
100
+
91
101
  ```python
92
102
  import re
93
103
  import uuid
@@ -112,29 +122,42 @@ from maybetype import maybe, Some
112
122
  match maybe(1):
113
123
  case Some(val):
114
124
  print('Value: ', val)
115
- case _:
125
+ case _: # "case Nothing:" also works, but just matching else in this case will be identical
116
126
  print('No value')
117
127
  ```
118
128
 
119
129
  ## Other examples
120
130
 
121
- Converting a `str | None` timestamp into a `datetime` object if not `None`, otherwise returning `None`:
131
+ Converting a `str | None` timestamp into a `datetime` object if not `None`, otherwise returning
132
+ `None`:
122
133
 
123
134
  ```python
124
135
  from datetime import datetime
125
136
  from maybetype import maybe
126
137
 
127
- date_str = '2025-09-06T030000'
128
- date = maybe('2025-09-06T030000').then(datetime.fromisoformat)
129
- # date == datetime.datetime(2025, 9, 6, 3, 0)
138
+ assert maybe('2025-09-06T030000').then(datetime.fromisoformat) == datetime(2025, 9, 6, 3, 0)
130
139
 
131
- date_str = None
132
- date = maybe(date_str).then(datetime.fromisoformat)
133
- # date == None
140
+ assert maybe(None).then(datetime.fromisoformat) is None
134
141
 
135
- date_str = ''
136
- date = maybe(date_str or None).then(datetime.fromisoformat)
137
- # date == None
142
+ assert maybe('' or None).then(datetime.fromisoformat) is None
138
143
  # Maybe does not treat falsy values as None, only strictly x-is-None values
139
144
  # Without `or None` here, datetime.fromisoformat would have raised a ValueError
140
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=_Lc2ZS-9j5n0TcSTEXGWqQ6SO4hyARBcVZ_JNleNKYU,93
3
- py_maybetype-0.6.0.dist-info/METADATA,sha256=VvranIlATjyYL-kvb0pdygONwYjgT-RNwcituMMgHLo,4653
4
- py_maybetype-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
- py_maybetype-0.6.0.dist-info/licenses/LICENSE,sha256=eMbi_IczZg-O56zEg00k6bmfExkQTpx01DN1xLY2w9I,1076
6
- py_maybetype-0.6.0.dist-info/RECORD,,