-
-
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
Conversation
…e of function to both "from_thread_run" functions.
I'm not really familiar with the whole CI thing, so just point me to some docs and I'll fix the test fails. Didn't expect a simple error message would break so many tests :P (in hindsight makes sense if they are output based) |
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.
Thank you for contributing! I'm realizing in hindsight that I might've been a little overzealous in putting "good first issue" on this one; the issues are subtler than I thought at first. If you're up for another round or two of review, though, I'd be delighted to see you continue this!
The tests are failing because your change broke some supported ways of calling trio.from_thread.run_sync
. If you click "Details" next to any of the failed runs, you should be able to see the error. On the details page, click on "Run tests" (the stage with the red X next to it, indicating failure) to see the output from that stage, then scroll down to see the output from pytest. One failure I see involves from_thread_run_sync(cancel_scope.cancel, trio_token=token)
raising an error. That's because cancel_scope.cancel
is an instance method (a method to be called on a particular object), not a function. Both are callable, but inspect
cares about little differences like that.
You'll probably have a happier development experience if you run the tests on your own computer before uploading a PR, since it's much faster to see the results. Then, at least in theory, you'll only get CI failures for issues that only show up on particular operating systems or Python versions. This is described in more detail in our contributing guide: https://trio.readthedocs.io/en/stable/contributing.html#tests
Other things to add:
- If you write "Closes In trio.to_thread.run, give a better error message if passed a sync function #1244" in your PR description, then merging the PR will automatically close that issue.
- Please add tests that exercise your new code, and a newsfragment describing the change for our release notes. (This is also described in more detail in the contributing guide; let me know if you have questions.)
trio/_threads.py
Outdated
@@ -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 comment
The 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 f"{afn!r} must be an asynchronous function"
to insert repr(afn)
into the exception message.
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.
Great! I knew the error message needed some work.
trio/_threads.py
Outdated
@@ -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): |
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 satisfy inspect.isfunction
, let alone inspect.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 use inspect.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 inside unprotected_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 a functools.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.
trio/_threads.py
Outdated
@@ -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 comment
The 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 inspect.iscoroutine
. (If it does, you'll also want to close()
it before returning, so you don't get an extra "coroutine was never awaited" warning.)
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.
Just saw your comment on #1504, should I look into Runner.spawn_impl()
for the "async fn or error" logic?
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.
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 [...]"
Thanks a lot for the feedback!, I'll work on it and send another PR! |
@guilledk Note that you don't have to actually create a new PR from scratch – you can just more commits to your |
@njsmith Ok good to know. Thanks a lot for the patience, you guys rock! |
…d it to trio._util. Used it in trio.from_thread_run.
So following what @oremanj said in #1504, I moved the "either get a coroutine from this async function+args or raise an error explaining why it's not in the form I wanted" logic to a function in Added the check to Also there is some code formatting issues I have to fix. |
Codecov Report
@@ Coverage Diff @@
## master #1513 +/- ##
========================================
Coverage 99.67% 99.67%
========================================
Files 107 108 +1
Lines 13254 13356 +102
Branches 1006 1012 +6
========================================
+ Hits 13211 13313 +102
Misses 28 28
Partials 15 15
|
Cool, all that's missing are the tests! |
@njsmith Hey so I could use some pointers to fix the code coverage issues, I check the codecov diff and I don't seem to get were the issue is. Thanks in advance! |
The coverage drop is in trio/socket.py: https://codecov.io/gh/python-trio/trio/pull/1513/src/trio/socket.py The issue is that the macOS 3.6 build was cancelled after 1m30s, I don't know why: https://github.com/python-trio/trio/pull/1513/checks?check_run_id=672946337 Closing/reopening to re-run all jobs |
Cool so this closes #1244 |
Great that the tests and coverage pass! @guilledk Note that you need to edit your first message to say that, otherwise GitHub won't close the issue when we merge this. |
@pquentin Done! |
trio/_threads.py
Outdated
:exc:`trio.Cancelled`, and this will propagate out into | ||
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 comment
The 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 comment
The 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 comment
The 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.
trio/_threads.py
Outdated
:exc:`trio.Cancelled`, and this will propagate out into | ||
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 comment
The 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.
trio/_core/tests/test_run.py
Outdated
async def f(): # pragma: no cover | ||
pass | ||
with ignore_coroutine_never_awaited_warnings(): | ||
with pytest.raises(TypeError, match="expecting an async function"): | ||
|
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.
You marked this as resolved but didn't change anything in your PR. Do you feel like the extra blank lines here are important for readability in a way that's related to the changes you're making here? It seems like you've added a number of them in this diff after block introducers (for, with, if, etc). That's not consistent with the style in the rest of the codebase.
Ok now I think I got all the formatting issues, sorry guys very used to my code style. Thanks again for the patience. |
Thank you for your patience! I removed a few more of the extraneous blank lines, and will merge this once the CI finishes. |
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.
Actually, my bad -- I was just giving this another look and I don't think you added a newsfragment describing your change. I mentioned this in my initial comment but failed to follow up. See https://trio.readthedocs.io/en/latest/contributing.html#release-notes for more details. You want to create a file called newsfragments/1244.bugfix.rst
that describes what changed and (briefly) why, from a user-visible perspective. Each bullet point in our release notes comes from one of these, so you can get a sense of the style by reading the release notes: https://trio.readthedocs.io/en/latest/history.html
Also, since I made some trivial commits above, you'll want to pull from your PR branch before you make additional changes. Sorry for the trouble! |
No trouble at all! Now I understand the workflow a lot more, next time I'll take less reviews I swear haha |
Co-authored-by: Joshua Oreman <[email protected]>
Hey @guilledk, it looks like that was the first time we merged one of your PRs! Thanks so much! 🎉 🎂 If you want to keep contributing, we'd love to have you. So, I just sent you an invitation to join the python-trio organization on Github! If you accept, then here's what will happen:
If you want to read more, here's the relevant section in our contributing guide. Alternatively, you're free to decline or ignore the invitation. You'll still be able to contribute as much or as little as you like, and I won't hassle you about joining again. But if you ever change your mind, just let us know and we'll send another invitation. We'd love to have you, but more importantly we want you to do whatever's best for you. If you have any questions, well... I am just a humble Python script, so I probably can't help. But please do post a comment here, or in our chat, or on our forum, whatever's easiest, and someone will help you out! |
Thanks @pquentin!, Awsome experience, to be honest never wrote a test before this PR, now all my projects have tests, so I learned a thing already. I was actually looking at #1532 to do next, I really like the |
Added helpful error message in the case the user passed incorrect type of function to both "from_thread_run" functions.
Closes #1244