dry-python / returns Goto Github PK
View Code? Open in Web Editor NEWMake your functions return something meaningful, typed, and safe!
Home Page: https://returns.rtfd.io
License: BSD 2-Clause "Simplified" License
Make your functions return something meaningful, typed, and safe!
Home Page: https://returns.rtfd.io
License: BSD 2-Clause "Simplified" License
(crypy-PletnYfd)alexv@pop-os:~/Projects/crypy$ python3
Python 3.7.2 (default, Jan 8 2019, 16:12:09)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import returns
>>> returns.result
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'returns' has no attribute 'result'
>>> returns.__file__
'/home/alexv/.local/share/virtualenvs/crypy-PletnYfd/lib/python3.7/site-packages/returns/__init__.py'
>>> returns.__version__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'returns' has no attribute '__version__'
I believe __init__.py
should contain the modules or not be there at all, for both usecases to work.
The usage given in example from returns.result import Result
works because python import finds result.py
as part of the package, but the module is not loaded as part of the super/parent package.
bind
causes too many associations with monads. Which is not a good thing.
But, bind
is also not so understandable. then
is just like in Promise
. So, it is way more readable.
We need to compose Result
and IO
monad.
In Haskell we have:
lift
liftIO
Maybe we should use something similar as well?
Related #16
After a review done by @astynax it was revealed that many types are incorrect.
We have privately discussed all the problems and solutions.
Here's the short version:
Result
itself, Success
and Failure
should return Result
instancesSuccess
and Failure
directly, since there's no such thing, only Result
Result
functions should always return Result
changing only the type signatureBtw, @astynax is now a core-dev of returns
library. Congrats ๐
Hi,
I faced the following issue in 0.7.0: mypy gives an error when is_successful
is given a Result
type. Given the following code (adapted from the docs) in main.py using 0.7.0
from returns.result import safe, is_successful
@safe
def divide(number: int) -> float:
return number / number
is_successful(divide(1))
mypy main.py
(mypy 0.701) fails with
main.py:7: error: No overload variant of "is_successful" matches argument type "Result[float, Exception]"
main.py:7: note: Possible overload variants:
main.py:7: note: def is_successful(container: Success[Any]) -> Literal[True]
main.py:7: note: def is_successful(container: Failure[Any]) -> Literal[False]
The typecheck passes with 0.6.0 (importing the functions from returns.functions
rather than returns.result
) and with the is_successful
examples in the docs where it's given a known Success
or Failure
. The type error aside, the function works.
Thanks for your work on the project.
When we do something like this:
class FetchUserProfile(object):
"""Single responsibility callable object that fetches user profile."""
#: You can later use dependency injection to replace `requests`
#: with any other http library (or even a custom service).
_http = requests
@pipeline
def __call__(self, user_id: int) -> Result['UserProfile', Exception]:
"""Fetches UserProfile dict from foreign API."""
response = self._make_request(user_id).unwrap()
return self._parse_json(response)
@safe
def _make_request(self, user_id: int) -> requests.Response:
response = self._http.get('/api/users/{0}'.format(user_id))
response.raise_for_status()
return response
@safe
def _parse_json(self, response: requests.Response) -> 'UserProfile':
return response.json()
In this example self._make_request(user_id)
may return Failure[Exception]
and calling unwrap will create new exception, so we might lose the traceback from the original exception.
We need to be sure that it does not happen at all.
We can add a special value that will track the execution of the following snippet:
tracked: Tracked[int]
Success(tracked)
.map(lambda x: x * 2)
.fix(lambda x: -x)
.bind(lambda x: Success(x) if x > 10 else Failure(x))
.fix(lambda x: abs(x))
It is hard to tell from the start. But, we can use Writer
monad for that. And it will allow us to build nice tracebacks.
We can sync the format with the one stories output. I would say that json
is the best option for the output.
Related typlog/sphinx-typlog-theme#16
Left
-> Failure
Right
-> Success
Either
-> Result
fmap
-> map
ebind
-> rescue
efmap
-> ? fix
, repair
do_notation
-> pipeline
We need to make ._inner_value
immutable.
Currently it is impossible to use this monad due to this bug in typing
:
x: Optional[int]
Monad.new(x) # ?
Type will be: Some[Union[int, None]]
, not Union[Some[int], Nothing]
Related:
Lines 10 to 24 in edb8ae2
It was an accident.
We should provide a utility way to compose any amount of functions that works with single input and single output.
@proofit404 where did you get your logos for dependencies and stories?
It looks like some custom font, maybe you can create a new one for this project as well?
I can also publish a task on https://github.com/opensourcedesign as a plan B.
Otherwise mypy
won't know the type of any imports.
See: https://mypy.readthedocs.io/en/latest/running_mypy.html
Hello!
This library looks interesting as a "ROP" approach to exception handling. Can you provide more real-life examples with usage of rescue
for example?
As i understand, currently rescue
function is only usable for transforming exception message into some response, because when exception happens, Failure
holds no context but exception instance.
My case:
task = get_next_task_from_db()
try:
result = process_task(task) # result is a dataclass with success: bool, payload: dict
except Exception as e:
task.set_exception(e)
else:
if result.success:
task.set_complete(result.payload)
else:
task.set_failed(result.payload)
put_task_into_db(task)
I'm not sure of how to rewrite this code in a functional manner.
Rewrite the same code with exceptions and nulls:
class CreateAccountAndUser(object):
"""Creates new Account-User pair."""
@do_notation
def __call__(self, username: str, email: str) -> Result['User', str]:
"""Can return a Success(user) or Failure(str_reason)."""
user_schema = self._validate_user(username, email).unwrap()
account = self._create_account(user_schema).unwrap()
return self._create_user(account)
It is a common practice to put possible errors in enum
(or Sum Type
).
So, we can later use them like so:
from enum import Enum, unique
@unique
class APIExceptions(Enum):
_value: Exception
ServerTimeoutError = exceptions.ServerTimeoutError
OtherError = exceptions.OtherError
def call_api() -> Result[int, APIExceptions]: ...
We can also try Union
and not enum
.
[![Python Version](https://img.shields.io/pypi/pyversions/wemake-python-styleguide.svg)](https://pypi.org/project/wemake-python-styleguide/)
[![Dependencies Status](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen.svg)](https://github.com/wemake-services/wemake-python-styleguide/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Aapp%2Fdependabot)
[![wemake-python-styleguide](https://img.shields.io/badge/style-wemake-000000.svg)](https://github.com/wemake-services/wemake-python-styleguide)
Can be taken from here.
I am talking about these two types: https://github.com/dry-python/returns/blob/master/returns/primitives/monad.py#L8-L9
It will be released soon.
We need to support it before 1.0.0
release.
Dependabot can't evaluate your Python dependency files.
As a result, Dependabot couldn't check whether any of your dependencies are out-of-date.
The error Dependabot encountered was:
Illformed requirement ["==3.7.2."]
You can mention @dependabot in the comments below to contact the Dependabot team.
Let's say I unwrap a failure and want to catch it using try-except.
The only way to do so currently is:
from returns.result import Result
from returns.functions import safe
from returns.primitives.exceptions import UnwrapFailedError
@safe
def foo(i) -> Result[int, Exception]:
if i == 0:
raise ValueError("problem")
return i + 5
try:
result = foo('a') # Will raise TypeError
# do stuff
result.unwrap()
except UnwrapFailedError as e:
try:
raise e.halted_container.failure() from e
except ValueError:
print("Don't use zero")
except TypeError:
print("Dont use strings")
This makes exception handling code more complex than it should be.
Naturally you could have also used:
@safe
def handle_err(e) -> Result[None, Exception]:
if isinstance(e, ValueError):
print("Don't use zero")
elif isinstance(e, TypeError):
print("Dont use strings")
else:
raise e from e
try:
foo('a').rescue(handle_err).unwrap()
except UnwrapFailedError:
print("Unhandled exception!!!")
But the error handling code above simply feels unpythonic and awkward.
Rescuing from an error fits the usecase where you want to log the error and proceed or any other generic error handling situation.
You could make rescue()
accept a mapping between types to callables and call the appropriate error handler when an exception occured but I don't see why you'd have to do so.
It seems to me that the UnwrapFailedError
exception is not necessary and hinders code readability.
The try-except clause was created for such situations. We should use it.
I guess we need to modify these lines:
And add await
there. And it should be working.
I am also not sure about types and .bind
methods that expects callables. Will it work with asyncio
?
It might be a good idea to promote https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell idea in IO[]
type.
Let's see an example:
from returns.io import IO, impure
@impure
def get_username() -> str:
return input('Hi, what is your name?')
reveal_type(get_username()) # => IO[str]
get_username().split('@') # nope! type error!
get_username().map(lambda name: name.split('@')) # => IO[List[str]]
It is important to restrict any access to the internal state of this object.
Related: typlog/sphinx-typlog-theme#15
from typing import TYPE_CHECKING
from returns import Failure, pipeline, Result
@pipeline
def test(x: int) -> Result[int, str]:
res = Failure(bool).unwrap()
return Failure('a')
if TYPE_CHECKING:
reveal_type(test(1))
# => Revealed type is 'returns.result.Result[builtins.int, builtins.str]'
print(test(1))
# => <Failure: <class 'bool'>>
I am pretty sure, that we will have to change how @pipeline
works.
This is also related to #89
Dependabot can't evaluate your Python dependency files.
As a result, Dependabot couldn't check whether any of your dependencies are out-of-date.
The error Dependabot encountered was:
Illformed requirement ["==3.7.2."]
You can mention @dependabot in the comments below to contact the Dependabot team.
https://github.com/agronholm/typeguard
It is quite similar to contracts.
Current implementation causes a lot of confusion.
We have two identical type declaration in both source files and .pyi
.
I will try to reduce it to just .pyi
removing all types from the source code.
Typing should still be working for both local development and end users.
Problem: https://returns.readthedocs.io/en/latest/pages/result.html#safe
from returns.result import safe
@safe
def function(param: int) -> int:
return param
reveal_type(function)
# Actual => def (*Any, **Any) -> builtins.int
# Expected => def (int) -> builtins.int
We need to change the return type, while leaving the input types untouched.
Original issue: python/mypy#3157
We need to map failures to new failures without fixing them.
That's where map_failure()
comes into play.
Scope:
FixableContainer
definitionprimitives/container.pyi
and result.pyi
)containers.rst
and result.rst
)We possibly can create a .contrib
package with some popular use-case like:
Result
-> django
request converterResult
-> transaction rollbackOr maybe documentation will be just fine.
I invite @proofit404 to give some ideas.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.