Skip to content

Commit f0c4981

Browse files
committed
Fixes CI
1 parent c0d4a22 commit f0c4981

21 files changed

+467
-34
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,13 @@ Make sure you know how to get started, [check out our docs](https://returns.read
6060
`None` is called the [worst mistake in the history of Computer Science](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/).
6161

6262
So, what can we do to check for `None` in our programs?
63-
You can use `Optional` and write a lot of `if some is not None:` conditions.
63+
You can use builtin [Optional](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#optional-types-and-the-none-type) type
64+
and write a lot of `if some is not None:` conditions.
6465
But, having them here and there makes your code unreadable.
6566

6667
```python
68+
user: Optional[User]
69+
6770
if user is not None:
6871
balance = user.get_balance()
6972
if balance is not None:
@@ -100,7 +103,9 @@ Forget about `None`-related errors forever!
100103
And that's how your initial refactored code will look like:
101104

102105
```python
103-
can_buy_stuff = Maybe.new(user).map( # will have type: Maybe[bool]
106+
user: Optional[User]
107+
108+
can_buy_stuff: Maybe[bool] = Maybe.new(user).map( # type hint is not required
104109
lambda real_user: real_user.get_balance(),
105110
).map(
106111
lambda balance: balance.credit_amount(),

poetry.lock

Lines changed: 14 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

returns/io.py

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,31 @@ class IOResult(BaseContainer, Generic[_ValueType, _ErrorType]):
214214
Note, that even methods like :meth:`~IOResult.unwrap``
215215
and :meth:`~IOResult.value_or` return values wrapped in ``IO``.
216216
217+
``IOResult`` is a complex compound value that consists of:
218+
219+
- raw value
220+
- ``Result``
221+
- ``IO``
222+
223+
This is why it has so many helper and factory methods:
224+
225+
- You can construct ``IOResult`` from raw value
226+
with :func:`~IOSuccess` and :func:`~IOFailure` public type constructors
227+
- You can construct ``IOResult`` from ``IO`` values
228+
with :meth:`~IOResult.from_failed_io`
229+
and :meth:`IOResult.from_successful_io`
230+
- You can construct ``IOResult`` from ``Result`` values
231+
with :meth:`~IOResult.__init__`
232+
233+
We also have a lot of utility methods for better function composition like:
234+
235+
- :meth:`~IOResult.bind_result` to work
236+
with functions which return ``Result``
237+
- :meth:`~IOResult.from_typecast` to work with ``IO[Result[...]]`` values
238+
- :meth:`~IOResult.lift` and `~IOResult.lift_result` to allow
239+
indirect function composition
240+
with regular and ``Result`` based functions.
241+
217242
See also:
218243
https://github.com/gcanti/fp-ts/blob/master/docs/modules/IOEither.ts.md
219244
@@ -289,7 +314,7 @@ def bind_result(
289314
290315
.. code:: python
291316
292-
>>> from returns.io import IOResult, IOFailure, IOSuccess
317+
>>> from returns.io import IOFailure, IOSuccess
293318
>>> from returns.result import Result, Success
294319
295320
>>> def bindable(string: str) -> Result[str, str]:
@@ -460,9 +485,21 @@ def lift_result(
460485
"""
461486
Lifts function from ``Result`` to ``IOResult`` for better composition.
462487
463-
>>> assert False
488+
Similar to :meth:`~IOResult.lift`, but works with other types.
489+
490+
.. code:: python
491+
492+
>>> from returns.io import IOResult, IOSuccess
493+
>>> from returns.result import Result, Success
494+
495+
>>> def returns_result(arg: int) -> Result[int, str]:
496+
... return Success(arg + 1)
497+
...
498+
>>> returns_ioresult = IOResult.lift_result(returns_result)
499+
>>> assert returns_ioresult(IOSuccess(1)) == IOSuccess(2)
500+
464501
"""
465-
...
502+
return lambda container: container.bind_result(function)
466503

467504
@classmethod
468505
def from_typecast(
@@ -481,6 +518,38 @@ def from_typecast(
481518
"""
482519
return cls(container._inner_value) # noqa: WPS437
483520

521+
@classmethod
522+
def from_failed_io(
523+
cls, container: IO[_ErrorType],
524+
) -> 'IOResult[NoReturn, _ErrorType]':
525+
"""
526+
Creates new ``IOResult`` from "failed" ``IO`` container.
527+
528+
.. code:: python
529+
530+
>>> from returns.io import IO, IOResult, IOFailure
531+
>>> container = IO(1)
532+
>>> assert IOResult.from_failed_io(container) == IOFailure(1)
533+
534+
"""
535+
return IOFailure(container._inner_value) # noqa: WPS437
536+
537+
@classmethod
538+
def from_successful_io(
539+
cls, container: IO[_ValueType],
540+
) -> 'IOResult[_ValueType, NoReturn]':
541+
"""
542+
Creates new ``IOResult`` from "successful" ``IO`` container.
543+
544+
.. code:: python
545+
546+
>>> from returns.io import IO, IOResult, IOSuccess
547+
>>> container = IO(1)
548+
>>> assert IOResult.from_successful_io(container) == IOSuccess(1)
549+
550+
"""
551+
return IOSuccess(container._inner_value) # noqa: WPS437
552+
484553

485554
def IOSuccess( # noqa: N802
486555
inner_value: _NewValueType,

returns/result.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Result(
4545
4646
See also:
4747
https://bit.ly/361qQhi
48+
https://hackernoon.com/the-throw-keyword-was-a-mistake-l9e532di
4849
4950
"""
5051

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ exclude =
3939
ignore = D100, D104, D401, W504, X100, WPS202, WPS214, WPS320, WPS436, WPS440, TAE002, RST303, RST304, DAR103, DAR203
4040

4141
per-file-ignores =
42-
# Disable some pydocstyle checks for package:
43-
returns/*.py: D104
42+
# Disable some quality checks for the most heavy parts:
43+
returns/io.py: WPS402
4444
# Disable imports in `__init__.py`:
4545
returns/__init__.py: F401, WPS412
4646
# There are multiple assert's in tests:

tests/test_io/test_ioresult_container/test_ioresult_bind.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
from returns.io import IOFailure, IOResult, IOSuccess
4+
from returns.result import Failure, Result, Success
45

56

67
def test_bind():
@@ -45,6 +46,18 @@ def factory(inner_value: int) -> IOResult[int, int]:
4546
assert bound.bind(factory) == IOFailure(input_value)
4647

4748

49+
def test_bind_regular_result():
50+
"""Ensures that regular ``Result`` can be bound to ``IOResult``."""
51+
def factory(inner_value: int) -> Result[int, str]:
52+
if inner_value > 0:
53+
return Success(inner_value + 1)
54+
return Failure('nope')
55+
56+
assert IOSuccess(1).bind_result(factory) == IOSuccess(2)
57+
assert IOSuccess(0).bind_result(factory) == IOFailure('nope')
58+
assert IOFailure('a').bind_result(factory) == IOFailure('a')
59+
60+
4861
def test_rescue_success():
4962
"""Ensures that rescue works for IOSuccess container."""
5063
def factory(inner_value) -> IOResult[int, str]:
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from returns.io import IOFailure, IOSuccess
4+
5+
6+
def test_map_iosuccess():
7+
"""Ensures that IOSuccess is mappable."""
8+
assert IOSuccess(5).map(str) == IOSuccess('5')
9+
10+
11+
def test_alt_iofailure():
12+
"""Ensures that IOFailure is mappable."""
13+
assert IOFailure(5).map(str) == IOFailure(5)
14+
assert IOFailure(5).alt(str) == IOFailure('5')
15+
16+
17+
def test_fix_iosuccess():
18+
"""Ensures that IOSuccess.fix is NoOp."""
19+
assert IOSuccess(5).fix(str) == IOSuccess(5)
20+
assert IOSuccess(5).alt(str) == IOSuccess(5)
21+
22+
23+
def test_fix_iofailure():
24+
"""Ensures that IOFailure.fix produces the IOSuccess."""
25+
assert IOFailure(5).fix(str) == IOSuccess('5')
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import pytest
4+
5+
from returns.io import IO, IOFailure, IOSuccess
6+
from returns.primitives.exceptions import UnwrapFailedError
7+
8+
9+
def test_ioresult_value_or():
10+
"""Ensures that ``value_or`` works correctly."""
11+
assert IOSuccess(1).value_or(0) == IO(1)
12+
assert IOFailure(1).value_or(0) == IO(0)
13+
14+
15+
def test_unwrap_iosuccess():
16+
"""Ensures that unwrap works for IOSuccess container."""
17+
assert IOSuccess(5).unwrap() == IO(5)
18+
19+
20+
def test_unwrap_iofailure():
21+
"""Ensures that unwrap works for IOFailure container."""
22+
with pytest.raises(UnwrapFailedError):
23+
assert IOFailure(5).unwrap()
24+
25+
26+
def test_unwrap_iofailure_with_exception():
27+
"""Ensures that unwrap raises from the original exception."""
28+
expected_exception = ValueError('error')
29+
with pytest.raises(UnwrapFailedError) as excinfo:
30+
IOFailure(expected_exception).unwrap()
31+
32+
assert 'ValueError: error' in str( # type: ignore
33+
excinfo.getrepr(), # noqa: WPS441
34+
)
35+
36+
37+
def test_failure_iosuccess():
38+
"""Ensures that failure works for IOSuccess container."""
39+
with pytest.raises(UnwrapFailedError):
40+
IOSuccess(5).failure()
41+
42+
43+
def test_failure_iofailure():
44+
"""Ensures that failure works for IOFailure container."""
45+
assert IOFailure(5).failure() == IO(5)

tests/test_result/test_result_map.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def test_map_success():
88
assert Success(5).map(str) == Success('5')
99

1010

11-
def test_alt():
11+
def test_alt_failure():
1212
"""Ensures that Failure is mappable."""
1313
assert Failure(5).map(str) == Failure(5)
1414
assert Failure(5).alt(str) == Failure('5')

typesafety/test_converters/test_fold.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
container: IOResult[int, str]
4646
fold_ioresult(first, second)(container)
4747
out: |
48-
main:11: error: Argument 1 has incompatible type "Callable[[int], float]"; expected "Callable[[int], IO[<nothing>]]" (diff)
48+
main:11: error: Argument 1 has incompatible type "Callable[[int], float]"; expected "Callable[[int], IO[<nothing>]]"
4949
main:11: error: Argument 2 has incompatible type "Callable[[str], float]"; expected "Callable[[str], IO[<nothing>]]"
5050
5151

0 commit comments

Comments
 (0)