Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into ki-in-new-task
Browse files Browse the repository at this point in the history
  • Loading branch information
oremanj committed May 22, 2020
2 parents b18396c + 4a05d9f commit 6b76c5c
Show file tree
Hide file tree
Showing 14 changed files with 263 additions and 185 deletions.
2 changes: 1 addition & 1 deletion docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pygments==2.6.1 # via sphinx
pyparsing==2.4.7 # via packaging
pytz==2020.1 # via babel
requests==2.23.0 # via sphinx
six==1.14.0 # via packaging
six==1.15.0 # via packaging
sniffio==1.1.0 # via -r docs-requirements.in
snowballstemmer==2.0.0 # via sphinx
sortedcontainers==2.1.0 # via -r docs-requirements.in
Expand Down
4 changes: 4 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ def setup(app):
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# This avoids a warning by the epub builder that it can't figure out
# the MIME type for our favicon.
suppress_warnings = ["epub.unknown_project_files"]


# -- Options for HTML output ----------------------------------------------

Expand Down
12 changes: 12 additions & 0 deletions docs/source/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ Release history

.. towncrier release notes start
Trio 0.15.1 (2020-05-22)
------------------------

Bugfixes
~~~~~~~~

- Fix documentation build. (This must be a new release tag to get readthedocs
"stable" to include the changes from 0.15.0.)

- Added a helpful error message if an async function is passed to `trio.from_thread.run_sync` or a sync function to `trio.from_thread.run`. (`#1244 <https://github.com/python-trio/trio/issues/1244>`__)


Trio 0.15.0 (2020-05-19)
------------------------

Expand Down
29 changes: 9 additions & 20 deletions docs/source/releasing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Things to do for releasing:

* Do the actual release changeset

+ update version number
+ bump version number

- increment as per Semantic Versioning rules

Expand All @@ -37,31 +37,20 @@ Things to do for releasing:

* create pull request to ``python-trio/trio``'s "master" branch

* announce PR on gitter

+ wait for feedback

+ fix problems, if any

* verify that all checks succeeded

* acknowledge the release PR
* tag with vVERSION, push tag

+ or rather, somebody else should do that
* push to PyPI::

* tag with vVERSION
git clean -xdf # maybe run 'git clean -xdn' first to see what it will delete
python3 setup.py sdist bdist_wheel
twine upload dist/*

* push to PyPI

+ ``python3 setup.py sdist bdist_wheel upload``

* announce on gitter

* update version number
* update version number in the same pull request

+ add ``+dev`` tag to the end

* prepare another pull request to "master"

+ acknowledge it
* merge the release pull request

* announce on gitter
2 changes: 1 addition & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pyopenssl==19.1.0 # via -r test-requirements.in
pyparsing==2.4.7 # via packaging
pytest-cov==2.8.1 # via -r test-requirements.in
pytest==5.4.2 # via -r test-requirements.in, pytest-cov
six==1.14.0 # via astroid, cryptography, packaging, pyopenssl, traitlets
six==1.15.0 # via astroid, cryptography, packaging, pyopenssl, traitlets
sniffio==1.1.0 # via -r test-requirements.in
sortedcontainers==2.1.0 # via -r test-requirements.in
toml==0.10.1 # via pylint
Expand Down
87 changes: 5 additions & 82 deletions trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from sniffio import current_async_library_cvar

import attr
from async_generator import isasyncgen
from sortedcontainers import SortedDict
from outcome import Error, Value, capture

Expand All @@ -38,7 +37,7 @@
)
from .. import _core
from .._deprecate import deprecated
from .._util import Final, NoPublicConstructor
from .._util import Final, NoPublicConstructor, coroutine_or_error

_NO_SEND = object()

Expand Down Expand Up @@ -1234,86 +1233,7 @@ def spawn_impl(self, async_fn, args, nursery, name, *, system_task=False):
# Call the function and get the coroutine object, while giving helpful
# errors for common mistakes.
######

def _return_value_looks_like_wrong_library(value):
# Returned by legacy @asyncio.coroutine functions, which includes
# a surprising proportion of asyncio builtins.
if isinstance(value, collections.abc.Generator):
return True
# The protocol for detecting an asyncio Future-like object
if getattr(value, "_asyncio_future_blocking", None) is not None:
return True
# This janky check catches tornado Futures and twisted Deferreds.
# By the time we're calling this function, we already know
# something has gone wrong, so a heuristic is pretty safe.
if value.__class__.__name__ in ("Future", "Deferred"):
return True
return False

try:
coro = async_fn(*args)
except TypeError:
# Give good error for: nursery.start_soon(trio.sleep(1))
if isinstance(async_fn, collections.abc.Coroutine):
raise TypeError(
"Trio was expecting an async function, but instead it got "
"a coroutine object {async_fn!r}\n"
"\n"
"Probably you did something like:\n"
"\n"
" trio.run({async_fn.__name__}(...)) # incorrect!\n"
" nursery.start_soon({async_fn.__name__}(...)) # incorrect!\n"
"\n"
"Instead, you want (notice the parentheses!):\n"
"\n"
" trio.run({async_fn.__name__}, ...) # correct!\n"
" nursery.start_soon({async_fn.__name__}, ...) # correct!"
.format(async_fn=async_fn)
) from None

# Give good error for: nursery.start_soon(future)
if _return_value_looks_like_wrong_library(async_fn):
raise TypeError(
"Trio was expecting an async function, but instead it got "
"{!r} – are you trying to use a library written for "
"asyncio/twisted/tornado or similar? That won't work "
"without some sort of compatibility shim."
.format(async_fn)
) from None

raise

# We can't check iscoroutinefunction(async_fn), because that will fail
# for things like functools.partial objects wrapping an async
# function. So we have to just call it and then check whether the
# return value is a coroutine object.
if not isinstance(coro, collections.abc.Coroutine):
# Give good error for: nursery.start_soon(func_returning_future)
if _return_value_looks_like_wrong_library(coro):
raise TypeError(
"start_soon got unexpected {!r} – are you trying to use a "
"library written for asyncio/twisted/tornado or similar? "
"That won't work without some sort of compatibility shim."
.format(coro)
)

if isasyncgen(coro):
raise TypeError(
"start_soon expected an async function but got an async "
"generator {!r}".format(coro)
)

# Give good error for: nursery.start_soon(some_sync_fn)
raise TypeError(
"Trio expected an async function, but {!r} appears to be "
"synchronous".format(
getattr(async_fn, "__qualname__", async_fn)
)
)

######
# Set up the Task object
######
coro = coroutine_or_error(async_fn, *args)

if name is None:
name = async_fn
Expand All @@ -1340,6 +1260,9 @@ async def python_wrapper(orig_coro):
LOCALS_KEY_KI_PROTECTION_ENABLED, system_task
)

######
# Set up the Task object
######
task = Task._create(
coro=coro,
parent_nursery=nursery,
Expand Down
87 changes: 17 additions & 70 deletions trio/_core/tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
import sniffio
import pytest

from .tutil import slow, check_sequence_matches, gc_collect_harder
from .tutil import (
slow, check_sequence_matches, gc_collect_harder,
ignore_coroutine_never_awaited_warnings
)

from ... import _core
from ..._deprecate import TrioDeprecationWarning
from ..._threads import to_thread_run_sync
Expand All @@ -34,24 +38,6 @@ async def sleep_forever():
return await _core.wait_task_rescheduled(lambda _: _core.Abort.SUCCEEDED)


# Some of our tests need to leak coroutines, and thus trigger the
# "RuntimeWarning: coroutine '...' was never awaited" message. This context
# manager should be used anywhere this happens to hide those messages, because
# when expected they're clutter.
@contextmanager
def ignore_coroutine_never_awaited_warnings():
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="coroutine '.*' was never awaited"
)
try:
yield
finally:
# Make sure to trigger any coroutine __del__ methods now, before
# we leave the context manager.
gc_collect_harder()


def test_basic():
async def trivial(x):
return x
Expand Down Expand Up @@ -1711,8 +1697,6 @@ async def test_current_effective_deadline(mock_clock):
assert _core.current_effective_deadline() == inf


# @coroutine is deprecated since python 3.8, which is fine with us.
@pytest.mark.filterwarnings("ignore:.*@coroutine.*:DeprecationWarning")
def test_nice_error_on_bad_calls_to_run_or_spawn():
def bad_call_run(*args):
_core.run(*args)
Expand All @@ -1724,59 +1708,22 @@ async def main():

_core.run(main)

class Deferred:
"Just kidding"

with ignore_coroutine_never_awaited_warnings():
for bad_call in bad_call_run, bad_call_spawn:

async def f(): # pragma: no cover
pass

with pytest.raises(TypeError) as excinfo:
bad_call(f())
assert "expecting an async function" in str(excinfo.value)

import asyncio

@asyncio.coroutine
def generator_based_coro(): # pragma: no cover
yield from asyncio.sleep(1)

with pytest.raises(TypeError) as excinfo:
bad_call(generator_based_coro())
assert "asyncio" in str(excinfo.value)
for bad_call in bad_call_run, bad_call_spawn:

with pytest.raises(TypeError) as excinfo:
bad_call(asyncio.Future())
assert "asyncio" in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
bad_call(lambda: asyncio.Future())
assert "asyncio" in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
bad_call(Deferred())
assert "twisted" in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
bad_call(lambda: Deferred())
assert "twisted" in str(excinfo.value)

with pytest.raises(TypeError) as excinfo:
bad_call(len, [1, 2, 3])
assert "appears to be synchronous" in str(excinfo.value)
async def f(): # pragma: no cover
pass

async def async_gen(arg): # pragma: no cover
yield
with pytest.raises(TypeError, match="expecting an async function"):
bad_call(f())

with pytest.raises(TypeError) as excinfo:
bad_call(async_gen, 0)
msg = "expected an async function but got an async generator"
assert msg in str(excinfo.value)
async def async_gen(arg): # pragma: no cover
yield arg

# Make sure no references are kept around to keep anything alive
del excinfo
with pytest.raises(
TypeError,
match="expected an async function but got an async generator"
):
bad_call(async_gen, 0)


def test_calling_asyncio_function_gives_nice_error():
Expand Down
20 changes: 20 additions & 0 deletions trio/_core/tests/tutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os

import pytest
import warnings
from contextlib import contextmanager

import gc

Expand Down Expand Up @@ -52,6 +54,24 @@ def gc_collect_harder():
gc.collect()


# Some of our tests need to leak coroutines, and thus trigger the
# "RuntimeWarning: coroutine '...' was never awaited" message. This context
# manager should be used anywhere this happens to hide those messages, because
# when expected they're clutter.
@contextmanager
def ignore_coroutine_never_awaited_warnings():
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore", message="coroutine '.*' was never awaited"
)
try:
yield
finally:
# Make sure to trigger any coroutine __del__ methods now, before
# we leave the context manager.
gc_collect_harder()


# template is like:
# [1, {2.1, 2.2}, 3] -> matches [1, 2.1, 2.2, 3] or [1, 2.2, 2.1, 3]
def check_sequence_matches(seq, template):
Expand Down
Loading

0 comments on commit 6b76c5c

Please sign in to comment.