-
-
Notifications
You must be signed in to change notification settings - Fork 861
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
Unasync'ed HTTPX #722
Unasync'ed HTTPX #722
Conversation
ad1cfd0
to
634bcd9
Compare
634bcd9
to
a12bbe1
Compare
Amazing stuff! Ace bit of work. Crackin stuff on the SyncBackend too!
|
Yes, doing things the raw way for anything above dispatchers still sounds pretty valid. I think it's a viable path, though I'm still worried that some of the renaming hoops I had to go through to get this working result in issues down the line. Surely we'll need (and be able to?) to extend Anyway, I pushed a last commit demonstrating how we could push the madness even further and have unasync generate the tests for the I don't see this going anywhere near |
I wanted to get an actual feeling for what switching to
unasync
would look like and require, so…This PR provides a working^1 implementation for an unasync'ed HTTPX, from concurrency backends to
SyncClient
… 🎉Uses a sync backend adapted from #525, and a patched version of
unasync
waiting for python-trio/unasync#53 to be released. Also adds atest_sync_client.py
module.^1: the sync UDS implementation is still TODO, which is why CI fails.Below some notes about what I learnt. I'll start with…
So what?
There are no major blockers to unasync'ing, however:
Dispatcher
,HTTPConnection
, etc. to theAsync
-prefixed variant. So:AsyncDispatcher
,AsyncHTTPConnection
, etc. (Or we could makeunasync
more clever than it needs to be, but we definitely don't want it to be too clever/configurable.)In terms of doing this incrementally after adding an urllib3-backed
Achievements
scripts/compile
step whenever we want to use the sync code (e.g. for tests or local experimentation).Workflow
My main workflow for this PR was to move code to the
_async
folder, generate the_sync
one, and run mypy to see what's wrong at least from a typing perspective. Eg. we don't wantSyncConnectionPool
to be importing the currentHTTPConnection
, because that's an async component. (And that hints us at unasync'ingHTTPConnection
too. In general unasync'ing componentX
requires to unasync all its dependencies, which isn't surprising.)Typing support
I'm very pleased to say that once the
_sync
version is compiled, mypy is 100% happy with the output. Editor support is great too, e.g. when importing code from._sync
we get autocompletions on modules and then on the imported components.Top-level API
The top-level API here is switched to sync (in line with #715). (Not using unasync here since we wouldn't be using the async top-level API.)
Renaming stuff
Besides handling conversion for builtins, the only mechanism that unasync provides for renaming stuff is in the form of
AsyncXYZ -> SyncXYZ
.(I think this is nice, because it brings a lot of consistency w.r.t. to "how do I unasync this component.")
But it doesn't handle (1)
aread() -> read()
/aiter_xxx() -> iter_xxx()
, (2a)AsyncIteratorStream -> IteratorStream
, (2b)func(aiterator=...) -> func(iterator=...)
, certainly not (2)ASGIDispatch -> WSGIDispatch
.To get things working, I solved (1) by defining the mappings manually via the
AsyncFixes
andSyncFixes
classes, that e.g. haveread_response()
, which is async onAsyncFixes
, but sync onSyncFixes
. Then the_async
part of the code importsAsyncFixes
, which gets unasync'ed toSyncFixes
, and everything works great. Another approach would be to unasync the response, i.e. drop our recent approach of "let's put both sync and async methods onResponse
", and rename.aiter_xxx()
to.iter_xxx()
so that we expose.iter_xxx()
in all cases. (That doesn't make it clear that you'd need toasync for
the result though, does it?)For (2a),
(Async)IteratorStream
doesn't have to be defined in the_(a)sync
part of the code (it's incontent_streams.py
right now), so I left it there but just renamedIteratorStream
to the more standardSyncIteratorStream
. This allowsunasync
to seamlessly rename references toAsyncIteratorStream
toSyncIteratorStream
.(2b) is similar: internal API, so I went ahead and standardized the
aiterator
parameter name toiterator
.For (3), I renamed
ASGIDispatch
toAsyncAppDispatch
, so that it gets unasync'ed toSyncAppDispatch
. We can then easily re-export things toASGIDispatch
andWSGIDispatch
.Proxy modules
There are several "proxy modules" left (i.e. modules that import stuff from
_async
and_sync
to re-export them), mostly because I didn't want to break the import paths in the tests. We'd probably need to handle that eventually, but at least this is a technique for breaking things up a bit.To commit or not to commit the sync code?
This PR git-ignores the
_sync
part of the code, which means it won't show up in the source tree in version control, andscripts/compile
is a prerequisite to even running the tests locally. But the developer experience feels cleaner that way. Eg. it makes it clearer that_sync
should never be edited manually, and re-compiles don't trigger git highlighting in the explorer.