Skip to content

Commit

Permalink
Version 0.5.0 pre-release
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed May 31, 2019
1 parent fffa3d6 commit 4e9ab7b
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 3 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ We follow Semantic Versions since the `0.1.0` release.

### Features

- Adds `compose` function
- Adds `compose` helper function
- Adds public API to `import returns`
- Adds `raise_exception` helper function
- Adds full traceback to `.unwrap()`


### Misc
Expand Down
38 changes: 38 additions & 0 deletions docs/pages/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,44 @@ Composition is also type-safe.
The only limitation is that we only support
functions with one argument and one return to be composed.


raise_exception
---------------

Sometimes you really want to reraise an exception from ``Failure[Exception]``
due to some existing API (or a dirty hack).

We allow you to do that with ease!

.. code:: python
from returns.functions import raise_exception
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
@pipeline
def __call__(self, username: str) -> ...:
"""Imagine, that you need to reraise ValidationErrors due to API."""
user_schema = self._validate_user(
username,
).fix(
# What happens here is interesting, since you do not let your
# unwrap to fail with UnwrapFailedError, but instead
# allows you to reraise a wrapped exception.
# In this case `ValidationError()` will be thrown
# before `UnwrapFailedError`
raise_exception,
).unwrap()
def _validate_user(
self, username: str,
) -> Result['User', ValidationError]:
...
Use this with caution. We try to remove exceptions from our code base.
Original proposal is `here <https://github.com/dry-python/returns/issues/56>`_.

API Reference
-------------

Expand Down
27 changes: 27 additions & 0 deletions returns/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,28 @@
# -*- coding: utf-8 -*-

"""
We define public API here.
So, later our code can be used like so:
.. code:: python
import returns
result: returns.Result[int, str]
See: https://github.com/dry-python/returns/issues/73
"""

from returns.functions import compose, safe, pipeline
from returns.result import Failure, Result, Success
from returns.primitives.exceptions import UnwrapFailedError

__all__ = ( # noqa: Z410
'compose',
'safe',
'pipeline',
'Failure',
'Result',
'Success',
'UnwrapFailedError',
)
22 changes: 21 additions & 1 deletion returns/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,29 @@ def compose(first, second):
"""
Allows function composition.
Works as: second . first
Works as: ``second . first``
You can read it as "second after first".
We can only compose functions with one argument and one return.
"""
return lambda argument: second(first(argument))


def raise_exception(exception):
"""
Helper function to raise exceptions as a function.
That's how it can be used:
.. code:: python
from returns.functions import raise_exception
# Some operation result:
user: Failure[UserDoesNotExistError]
# Here we unwrap internal exception and raise it:
user.fix(raise_exception)
See: https://github.com/dry-python/returns/issues/56
"""
raise exception
6 changes: 5 additions & 1 deletion returns/functions.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

from typing import Callable, TypeVar
from typing import Callable, NoReturn, TypeVar

from returns.primitives.container import Container
from returns.result import Result
Expand Down Expand Up @@ -39,3 +39,7 @@ def compose(
second: Callable[[_SecondType], _ThirdType],
) -> Callable[[_FirstType], _ThirdType]:
...


def raise_exception(exception: Exception) -> NoReturn:
...
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ max-methods = 8
per-file-ignores =
# Disable some pydocstyle checks for package:
returns/**/*.py: D104
# Disable imports in `__init__.py`:
returns/__init__.py: F401, Z412
# There are multiple assert's in tests:
tests/**/test_*.py: S101
# Disable some pydocstyle checks globally:
Expand Down
35 changes: 35 additions & 0 deletions tests/test_functions/test_raise_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-

from typing import Type

import pytest

from returns.functions import raise_exception
from returns.result import Failure, Success


class _CustomException(Exception):
"""Just for the test."""


@pytest.mark.parametrize('exception_type', [
TypeError,
ValueError,
_CustomException,
])
def test_raise_regular_exception(exception_type: Type[Exception]):
"""Ensures that regular exception can be thrown."""
with pytest.raises(exception_type):
raise_exception(exception_type())


def test_failure_can_be_fixed():
"""Ensures that exceptions can work with Failures."""
failure = Failure(ValueError('Message'))
with pytest.raises(ValueError):
failure.fix(raise_exception)


def test_success_is_not_touched():
"""Ensures that exceptions can work with Success."""
assert Success(1).fix(raise_exception) == Success(1)

0 comments on commit 4e9ab7b

Please sign in to comment.