Skip to content

Commit 4e9ab7b

Browse files
committed
Version 0.5.0 pre-release
1 parent fffa3d6 commit 4e9ab7b

File tree

7 files changed

+132
-3
lines changed

7 files changed

+132
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ We follow Semantic Versions since the `0.1.0` release.
77

88
### Features
99

10-
- Adds `compose` function
10+
- Adds `compose` helper function
11+
- Adds public API to `import returns`
12+
- Adds `raise_exception` helper function
13+
- Adds full traceback to `.unwrap()`
1114

1215

1316
### Misc

docs/pages/functions.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,44 @@ Composition is also type-safe.
221221
The only limitation is that we only support
222222
functions with one argument and one return to be composed.
223223

224+
225+
raise_exception
226+
---------------
227+
228+
Sometimes you really want to reraise an exception from ``Failure[Exception]``
229+
due to some existing API (or a dirty hack).
230+
231+
We allow you to do that with ease!
232+
233+
.. code:: python
234+
235+
from returns.functions import raise_exception
236+
237+
class CreateAccountAndUser(object):
238+
"""Creates new Account-User pair."""
239+
240+
@pipeline
241+
def __call__(self, username: str) -> ...:
242+
"""Imagine, that you need to reraise ValidationErrors due to API."""
243+
user_schema = self._validate_user(
244+
username,
245+
).fix(
246+
# What happens here is interesting, since you do not let your
247+
# unwrap to fail with UnwrapFailedError, but instead
248+
# allows you to reraise a wrapped exception.
249+
# In this case `ValidationError()` will be thrown
250+
# before `UnwrapFailedError`
251+
raise_exception,
252+
).unwrap()
253+
254+
def _validate_user(
255+
self, username: str,
256+
) -> Result['User', ValidationError]:
257+
...
258+
259+
Use this with caution. We try to remove exceptions from our code base.
260+
Original proposal is `here <https://github.com/dry-python/returns/issues/56>`_.
261+
224262
API Reference
225263
-------------
226264

returns/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
11
# -*- coding: utf-8 -*-
2+
3+
"""
4+
We define public API here.
5+
6+
So, later our code can be used like so:
7+
8+
.. code:: python
9+
10+
import returns
11+
result: returns.Result[int, str]
12+
13+
See: https://github.com/dry-python/returns/issues/73
14+
"""
15+
16+
from returns.functions import compose, safe, pipeline
17+
from returns.result import Failure, Result, Success
18+
from returns.primitives.exceptions import UnwrapFailedError
19+
20+
__all__ = ( # noqa: Z410
21+
'compose',
22+
'safe',
23+
'pipeline',
24+
'Failure',
25+
'Result',
26+
'Success',
27+
'UnwrapFailedError',
28+
)

returns/functions.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,29 @@ def compose(first, second):
5656
"""
5757
Allows function composition.
5858
59-
Works as: second . first
59+
Works as: ``second . first``
6060
You can read it as "second after first".
6161
6262
We can only compose functions with one argument and one return.
6363
"""
6464
return lambda argument: second(first(argument))
65+
66+
67+
def raise_exception(exception):
68+
"""
69+
Helper function to raise exceptions as a function.
70+
71+
That's how it can be used:
72+
73+
.. code:: python
74+
75+
from returns.functions import raise_exception
76+
77+
# Some operation result:
78+
user: Failure[UserDoesNotExistError]
79+
# Here we unwrap internal exception and raise it:
80+
user.fix(raise_exception)
81+
82+
See: https://github.com/dry-python/returns/issues/56
83+
"""
84+
raise exception

returns/functions.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import Callable, TypeVar
3+
from typing import Callable, NoReturn, TypeVar
44

55
from returns.primitives.container import Container
66
from returns.result import Result
@@ -39,3 +39,7 @@ def compose(
3939
second: Callable[[_SecondType], _ThirdType],
4040
) -> Callable[[_FirstType], _ThirdType]:
4141
...
42+
43+
44+
def raise_exception(exception: Exception) -> NoReturn:
45+
...

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ max-methods = 8
2525
per-file-ignores =
2626
# Disable some pydocstyle checks for package:
2727
returns/**/*.py: D104
28+
# Disable imports in `__init__.py`:
29+
returns/__init__.py: F401, Z412
2830
# There are multiple assert's in tests:
2931
tests/**/test_*.py: S101
3032
# Disable some pydocstyle checks globally:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from typing import Type
4+
5+
import pytest
6+
7+
from returns.functions import raise_exception
8+
from returns.result import Failure, Success
9+
10+
11+
class _CustomException(Exception):
12+
"""Just for the test."""
13+
14+
15+
@pytest.mark.parametrize('exception_type', [
16+
TypeError,
17+
ValueError,
18+
_CustomException,
19+
])
20+
def test_raise_regular_exception(exception_type: Type[Exception]):
21+
"""Ensures that regular exception can be thrown."""
22+
with pytest.raises(exception_type):
23+
raise_exception(exception_type())
24+
25+
26+
def test_failure_can_be_fixed():
27+
"""Ensures that exceptions can work with Failures."""
28+
failure = Failure(ValueError('Message'))
29+
with pytest.raises(ValueError):
30+
failure.fix(raise_exception)
31+
32+
33+
def test_success_is_not_touched():
34+
"""Ensures that exceptions can work with Success."""
35+
assert Success(1).fix(raise_exception) == Success(1)

0 commit comments

Comments
 (0)