Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change order of generic Result types to [T, E] #8

Merged
merged 4 commits into from
Feb 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[run]
source =
result
omit =
result/typetests.py

[report]
# Regexes for lines to exclude from consideration
Expand Down
8 changes: 7 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ install:
script:
- pip install -e .
- py.test
- mypy result/result.py
- if [ $(python -c 'import sys; print(sys.version_info.minor)') -gt 4 ]; then
mypy result/result.py;
mypy result/typetests.py;
echo "Mypy tests done";
else
echo "Skipping mypy";
fi
after_success:
- coveralls
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ Possible log types:
- `[fixed]` for any bug fixes.
- `[security]` to invite users to upgrade in case of vulnerabilities.

## Unreleased

- [changed] Type annotations: Change parameter order
from `Result[E, T]` to `Result[T, E]` to match Rust/OCaml/F# (#7)
dbrgn marked this conversation as resolved.
Show resolved Hide resolved

## [0.4.1] - 2020-02-17

- [added] Add `py.typed` for PEP561 package compliance (#16)
Expand Down
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Result
:alt: Coverage
:target: https://coveralls.io/github/dbrgn/result

A simple Result type for Python 3 `inspired by Rust <https://doc.rust-lang.org/std/result/>`__.
A simple Result type for Python 3 `inspired by Rust
<https://doc.rust-lang.org/std/result/>`__, fully type annotated.

The idea is that a ``Result`` value can be either ``Ok(value)`` or ``Err(error)``,
with a way to differentiate between the two. It will change code like this:
Expand Down Expand Up @@ -62,8 +63,8 @@ side, you don't have to return semantically unclear tuples anymore.

Not all methods (https://doc.rust-lang.org/std/result/enum.Result.html) have
been implemented, only the ones that make sense in the Python context. You still
don't get any type safety, but some easier handling of types that can be OK or
not, without resorting to custom exceptions.
don't get any type safety at runtime, but some easier handling of types that can
be OK or not, without resorting to custom exceptions.


API
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest==2.8.5
pytest-cov==2.2.0
pytest-pep8==1.0.6
-e git+https://github.com/python/mypy@005fd6b357b995f13e3d90293cabf3c8c8c2a952#egg=mypy
mypy==0.720
44 changes: 24 additions & 20 deletions result/result.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
from typing import Callable, Generic, TypeVar, Union, Any, Optional, cast, overload


E = TypeVar("E")
T = TypeVar("T") # Success type
E = TypeVar("E") # Error type
F = TypeVar("F")
T = TypeVar("T")
U = TypeVar("U")


class Result(Generic[E, T]):
class Result(Generic[T, E]):
"""
A simple `Result` type inspired by Rust.

Not all methods (https://doc.rust-lang.org/std/result/enum.Result.html)
have been implemented, only the ones that make sense in the Python context.
"""
def __init__(self, is_ok: bool, value: Union[E, T], force: bool = False) -> None:
def __init__(self, is_ok: bool, value: Union[T, E], force: bool = False) -> None:
"""Do not call this constructor, use the Ok or Err class methods instead.

There are no type guarantees on the value if this is called directly.

Args:
is_ok: If this represents an ok result
value: The value inside the result
force: Force creation of the object. This is false by default to prevent
is_ok:
If this represents an ok result
value:
The value inside the result
force:
Force creation of the object. This is false by default to prevent
accidentally creating instance of a Result in an unsafe way.

dbrgn marked this conversation as resolved.
Show resolved Hide resolved
"""
if force is not True:
raise RuntimeError("Don't instantiate a Result directly. "
Expand Down Expand Up @@ -51,20 +55,20 @@ def __repr__(self) -> str:

@classmethod
@overload
def Ok(cls) -> 'Result[E, bool]':
def Ok(cls) -> 'Result[bool, Any]':
dbrgn marked this conversation as resolved.
Show resolved Hide resolved
pass

@classmethod
@overload
def Ok(cls, value: T) -> 'Result[E, T]':
def Ok(cls, value: T) -> 'Result[T, Any]':
pass

@classmethod
def Ok(cls, value: Any = True) -> 'Result[E, Any]':
def Ok(cls, value: Any = True) -> 'Result[Any, Any]':
return cls(is_ok=True, value=value, force=True)

@classmethod
def Err(cls, error: E) -> 'Result[E, T]':
def Err(cls, error: E) -> 'Result[Any, E]':
return cls(is_ok=False, value=error, force=True)

def is_ok(self) -> bool:
Expand All @@ -87,7 +91,7 @@ def err(self) -> Optional[E]:
return cast(E, self._value) if self.is_err() else None

@property
def value(self) -> Union[E, T]:
def value(self) -> Union[T, E]:
"""
Return the inner value. This might be either the ok or the error type.
"""
Expand Down Expand Up @@ -120,14 +124,14 @@ def unwrap_or(self, default: T) -> T:
else:
return default

def map(self, op: Callable[[T], U]) -> 'Result[E, U]':
def map(self, op: Callable[[T], U]) -> 'Result[U, E]':
"""
If contained result is `Ok`, return `Ok` with original value mapped to
a new value using the passed in function. Otherwise return `Err` with
same value.
"""
if not self._is_ok:
return cast(Result[E, U], self)
return cast(Result[U, E], self)
return Ok(op(cast(T, self._value)))

def map_or(self, default: U, op: Callable[[T], U]) -> U:
Expand All @@ -153,35 +157,35 @@ def map_or_else(
return default_op()
return op(cast(T, self._value))

def map_err(self, op: Callable[[E], F]) -> 'Result[F, T]':
def map_err(self, op: Callable[[E], F]) -> 'Result[T, F]':
"""
If contained result is `Err`, return `Err` with original value mapped
to a new value using the passed in `op` function. Otherwise return `Ok`
with the same value.
"""
if self._is_ok:
return cast(Result[F, T], self)
return cast(Result[T, F], self)
return Err(op(cast(E, self._value)))


@overload
def Ok() -> Result[E, bool]:
def Ok() -> Result[bool, Any]:
pass


@overload
def Ok(value: T) -> Result[E, T]:
def Ok(value: T) -> Result[T, Any]:
dbrgn marked this conversation as resolved.
Show resolved Hide resolved
pass


def Ok(value: Any = True) -> Result[E, Any]:
def Ok(value: Any = True) -> Result[Any, Any]:
"""
Shortcut function to create a new Result.
"""
return Result.Ok(value)


def Err(error: E) -> Result[E, T]:
def Err(error: E) -> Result[Any, E]:
"""
Shortcut function to create a new Result.
"""
Expand Down
20 changes: 20 additions & 0 deletions result/typetests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List, Optional
dbrgn marked this conversation as resolved.
Show resolved Hide resolved

from .result import Result, Ok, Err


res1 = Result.Ok('hello') # type: Result[str, int]
if res1.is_ok():
ok = res1.ok() # type: Optional[str]
mapped_to_float = res1.map_or(1.0, lambda s: len(s) * 1.5) # type: float
else:
err = res1.err() # type: Optional[int]
mapped_to_list = res1.map_err(lambda e: [e]).err() # type: Optional[List[int]]

# Test constructor functions
res2 = Ok()
res3 = Result.Ok()
res4 = Ok(42)
res5 = Result.Ok(23)
res6 = Err(1)
res7 = Result.Err(2)