-
-
Notifications
You must be signed in to change notification settings - Fork 845
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
Event hooks #1215
Event hooks #1215
Conversation
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.
💯 👍
@@ -35,6 +35,7 @@ def request( | |||
verify: VerifyTypes = True, | |||
cert: CertTypes = None, | |||
trust_env: bool = True, | |||
event_hooks: dict = None, |
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.
I think I'd like us to prefer a stricter typing here, and a more straightforward style that requires using a list of callables:
client = httpx.Client(event_hooks={"request": [on_request], "response": [on_response])
It would make it clearer that client.event_hooks
is really just always a dict of lists, rather than "a dict of sometimes a callable, sometimes a list of callables, depending on what you've set" (not even sure that's what this PR intends, actually?).
Hence this suggestion (which would need to be applied across _client.py
as well):
event_hooks: dict = None, | |
event_hooks: Dict[str, List[Callable]] = None, |
@@ -410,6 +411,48 @@ def __repr__(self) -> str: | |||
) | |||
|
|||
|
|||
class EventHooks(MutableMapping): |
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.
I think if we apply my suggestion of using a plain Dict[str, List[Callable]]
always, we can mostly drop this custom data structure, correct?
I'm also not sure we need to enforce that the only possible keys be request
and response
. Currently we do this with a bit of a surprising behavior ("don't actually store items for unknown keys"), while I think making it so that event_hooks
behaves just like a normal dict would be beneficial.
So perhaps we could replace this with a simple factory helper…
def create_event_hooks(initial: Dict[str, List[Callable]] = None) -> Dict[str, List[Callable]]):
event_hooks = {"request": [], "response": []}
event_hooks.update(initial or {})
return event_hooks
@@ -166,3 +166,59 @@ async def test_100_continue(server): | |||
|
|||
assert response.status_code == 200 | |||
assert response.content == data | |||
|
|||
|
|||
@pytest.mark.usefixtures("async_environment") |
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.
What do we think of putting these tests (and those for the sync client) in a dedicated tests/client/test_event_hooks.py
file?
|
||
|
||
def test_client_base_url(): | ||
client = AsyncClient() | ||
client = Client() |
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.
I think these changes belong to a separate PR? Or most likely, this PR isn't up to date with master
. :-)
pass # pragma: nocover | ||
|
||
client = Client() | ||
client.event_hooks = {"request": on_request} # type: ignore |
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.
Another reason why I'm advocating for a straightforward "dict of lists of callables" is that we wouldn't need any # type: ignore
…
client.event_hooks = {"request": on_request} # type: ignore | |
client.event_hooks = {"request": [on_request]} |
pass # pragma: nocover | ||
|
||
|
||
def test_event_hooks_init(): |
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.
These tests would be simplified if we adopted a simple create_event_hooks()
helper that returns a plain dict.
How can we get the response body in async def raise_with_response(cls, res: Response) -> None:
if res.status_code >= 300:
# await res.aread(). # if no this code, will occurs an error
print(res.text). # error occurs: httpx.ResponseNotRead: Attempted to access streaming response content, without having called `read()`. For read text, I need run |
I'm not sure what the question is? If you want to access the response body inside an event hook, then as you've said, you need to call |
Thank you, got it. |
Closes #790, based on the design outlined in #790 (comment)
I've narrowed this down to only
request
andresponse
initially. I think thatauth
andredirect
also make sense, but let's treat that separately. (I want to be careful, since it could be a bit fiddly making sure that raised exceptions within those hooks don't leave responses open.)Also worth noting that we're not exposing the
EventHooks
data structure as public API. Event hooks are always set using a regular dictionary, but have smart behaviour when the property is updated, so that they always return lists of installed handlers, but can be set either from a list or from a single callable.For example:
Rendered docs: