dry-python / returns

Make your functions return something meaningful, typed, and safe!
https://returns.rtfd.io
BSD 2-Clause "Simplified" License
3.54k stars 116 forks source link

future_safe & RuntimeWarning: coroutine '...' was never awaited #565

Open juntatalor opened 4 years ago

juntatalor commented 4 years ago

Hi! I'm running sample code from https://returns.readthedocs.io/en/latest/pages/future.html#futureresult on python3.7 I've set timeout for AsyncClient to 0.01, expecting the function to always fail. That works correct, the result is <IOResult: > But there are lots of RuntimeWarnings:

/.../returns/_generated/futures/_future_result.py:44: RuntimeWarning: coroutine 'async_map' was never awaited
  container = await inner_value
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/.../returns/_generated/futures/_future_result.py:44: RuntimeWarning: coroutine 'future_safe.<locals>.factory' was never awaited
  container = await inner_value
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/.../future.py:594: RuntimeWarning: coroutine 'async_map' was never awaited
  return IOResult.from_result(await self._inner_value)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/.../returns/future.py:594: RuntimeWarning: coroutine 'future_safe.<locals>.factory' was never awaited
  return IOResult.from_result(await self._inner_value)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

It is not a good idea to disable RuntimeWarnings, and i suspect this behaviour to be incorrect.

I think that the problem is inside the FutureResultE.from_iterable, the minimal working example is like this:

@future_safe
async def f1(x: float) -> float:
    return x / 0

def f2() -> FutureResultE[Sequence[float]]:
    return FutureResultE.from_iterable(
        # changing range(3) -> range(1) removes RuntimeWarnings
        [f1(x).map(lambda k: k + 1) for x in range(3)]
    )

print(anyio.run(f2().awaitable))
sobolevn commented 4 years ago

Ok, this highlights an important problem. We don't have any API for coroutine cancelation. We also don't really clean things up after some coroutines do not succeed.

I propose several things here:

sobolevn commented 4 years ago

Ok, we also have a problem with using gather with ReaderFutureResult and some examples of FutureResult. We would need to work on this in the future versions.

kyprifog commented 3 years ago

I ran into the same issue when running this test code:

import asyncio
import sys
from returns.future import future, future_safe
import anyio
from returns.result import safe
from returns.unsafe import unsafe_perform_io

def main():
    args = sys.argv[1:]
    input = args[0]
    get_sync("1")
    result = get_result_1(input) \
        .bind(get_sync)

    print(unsafe_perform_io(anyio.run(result.awaitable)).unwrap())

@safe
def get_result_1(a: str) -> str:
    if a == "1":
        return a
    else:
        raise(Exception("ERROR"))

@future_safe
async def get_sync(input: str) -> str:
    print("SYNC FUNCTION")
    result = await get_async(input + "2")
    return result

async def get_async(input: str) -> str:
    print("ASYNC FUNCTION")
    print("SLEEPING")
    await asyncio.sleep(1)
    print("DONE SLEEPING")
    return input + "3"

if __name__ == "__main__":
    main()

which resulted in

async_test.py:12: RuntimeWarning: coroutine 'future_safe.<locals>.factory' was never awaited
  get_sync("1")
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
SYNC FUNCTION
ASYNC FUNCTION
SLEEPING
DONE SLEEPING
123

Stylistically is there a better way or is the Runtime Warning still unavoidable at this point?