-
-
Notifications
You must be signed in to change notification settings - Fork 344
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
Fixed unhelpful error message in from_thread_run functions. #1513
Changes from 1 commit
96cdeb2
015b220
126f7f4
417ba5b
dc06cad
dc1e5b8
5e4145d
c74a829
ad27338
57c1080
f4c11e9
28b08d1
0f809d0
7a71a85
7de4c84
f77c200
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
import attr | ||
import outcome | ||
|
||
import inspect | ||
|
||
import trio | ||
|
||
from ._sync import CapacityLimiter | ||
|
@@ -364,7 +366,7 @@ def from_thread_run(afn, *args, trio_token=None): | |
RuntimeError: if you try calling this from inside the Trio thread, | ||
which would otherwise cause a deadlock. | ||
AttributeError: if no ``trio_token`` was provided, and we can't infer | ||
one from context. | ||
one from context. Also if ``afn`` is not an async function. | ||
|
||
**Locating a Trio Token**: There are two ways to specify which | ||
`trio.run` loop to reenter: | ||
|
@@ -377,6 +379,9 @@ def from_thread_run(afn, *args, trio_token=None): | |
"foreign" thread, spawned using some other framework, and still want | ||
to enter Trio. | ||
""" | ||
if not inspect.iscoroutinefunction(afn): | ||
raise AttributeError("afn must be an asynchronous function") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user passed an argument of the wrong type, so you should raise TypeError. In errors like this, it's also nice to mention in the message what specific thing the user sent you. For example, you could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great! I knew the error message needed some work. |
||
|
||
def callback(q, afn, args): | ||
@disable_ki_protection | ||
async def unprotected_afn(): | ||
|
@@ -409,7 +414,7 @@ def from_thread_run_sync(fn, *args, trio_token=None): | |
RuntimeError: if you try calling this from inside the Trio thread, | ||
which would otherwise cause a deadlock. | ||
AttributeError: if no ``trio_token`` was provided, and we can't infer | ||
one from context. | ||
one from context. Also if ``fn`` is not a sync function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is leftover from your previous use of AttributeError for this condition, but you're now using TypeError. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You marked this as resolved but you didn't update the docstring. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You still have not updated the docstring despite having marked this as resolved again. The request is to remove the text "Also if fn is not a sync function" from the description for AttributeError, because you no longer raise AttributeError in that case. |
||
|
||
**Locating a Trio Token**: There are two ways to specify which | ||
`trio.run` loop to reenter: | ||
|
@@ -422,6 +427,9 @@ def from_thread_run_sync(fn, *args, trio_token=None): | |
"foreign" thread, spawned using some other framework, and still want | ||
to enter Trio. | ||
""" | ||
if not (inspect.isfunction(fn) and not inspect.iscoroutinefunction(fn)): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed above, these won't tell you what you want. Better to wait and raise an error if the result of calling the function satisfies There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just saw your comment on #1504, should I look into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be a useful extension if you feel like it! The relevant code is toward the top of spawn_impl(), with the comments about "Give good error for [...]" |
||
raise AttributeError("fn must be a synchronous function") | ||
|
||
def callback(q, fn, args): | ||
@disable_ki_protection | ||
def unprotected_fn(): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, identifying async functions is rather subtler than just
inspect.iscoroutinefunction
, because async functions can be wrapped in a way that doesn't satisfy that test. (For example, Trio functions decorated with@enable_ki_protection
are implemented using a wrapper that looks synchronous.functools.partial
objects don't even satisfyinspect.isfunction
, let aloneinspect.iscoroutinefunction
. All of these are perfectly valid to pass as an "async function" as long as the function being wrapped is async.)The best way to see if something is an async function is to call it without
await
and useinspect.iscoroutine
on the result. Of course, if it wasn't an async function, you just executed it, so it's best to do this only in the context where you were planning to call it anyway. In this case, that would be insideunprotected_afn
below.If you're not willing to call the function, you have to get a lot more special-cased to see if it's async or not. You can look inside the
.func
attribute of afunctools.partial
object, follow the.__wrapped__
attributes generally added by function wrappers, etc, but there's no approach that will definitely succeed for any async callable.