Skip to content

Commit

Permalink
#28: Add a possibility to use event hooks of aiohttp and requests lib…
Browse files Browse the repository at this point in the history
…raries
  • Loading branch information
Antti Nykänen committed Mar 16, 2020
1 parent f85a70d commit 52ed3f6
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 4 deletions.
27 changes: 27 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,33 @@ Session history for debugging
s.history.latest.headers
Event hooks
------------------

.. code-block:: python
# Another way to implement debugging is to use event hooks.
# The event hooks of the underlaying aiohttp or requests libraries can
# be used as such by passing them as event_hooks argument as a dict.
# For example if you want to print all the sent data on console at async mode, you can use the
# 'on_request_chunk_sent' event hook https://docs.aiohttp.org/en/stable/tracing_reference.html#aiohttp.TraceConfig.on_request_chunk_sent
import asyncio
async def sent(session, context, params):
print(f'sent {params.chunk}')
s = Session(
'http://0.0.0.0:8090/api',
enable_async=True,
schema=models_as_jsonschema,
event_hooks={'on_request_chunk_sent': sent}
)
await s.get('some-collection')
await s.close()
# On sychronous mode the available event hooks are listed here https://requests.readthedocs.io/en/master/user/advanced/#event-hooks
Credits
=======

Expand Down
23 changes: 21 additions & 2 deletions src/jsonapi_client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ def __init__(self, server_url: str=None,
request_kwargs: dict=None,
loop: 'AbstractEventLoop'=None,
use_relationship_iterator: bool=False,
enable_history: bool=False) -> None:
enable_history: bool=False,
event_hooks: dict=None) -> None:
self._server: ParseResult
self.enable_async = enable_async

Expand All @@ -214,11 +215,29 @@ def __init__(self, server_url: str=None,
self.schema: Schema = Schema(schema)
if enable_async:
import aiohttp
self._aiohttp_session = aiohttp.ClientSession(loop=loop)
self._prepare_async_event_hooks(event_hooks)
self._aiohttp_session = aiohttp.ClientSession(
loop=loop,
trace_configs=[self.trace_config]
)
else:
if event_hooks is not None:
hooks = self._request_kwargs.get('hooks', {})
hooks.update(**event_hooks)
self._request_kwargs['hooks'] = hooks
self.use_relationship_iterator = use_relationship_iterator
self.enable_history = enable_history
self.history = SessionHistory()

def _prepare_async_event_hooks(self, event_hooks: dict=None) -> None:
import aiohttp
self.trace_config = aiohttp.TraceConfig()
if event_hooks is None:
return

for event, hook in event_hooks.items():
getattr(self.trace_config, event).append(hook)

def add_resources(self, *resources: 'ResourceObject') -> None:
"""
Add resources to session cache.
Expand Down
79 changes: 77 additions & 2 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from yarl import URL

from aiohttp import ClientResponse
from aiohttp.client_exceptions import ServerDisconnectedError
from aiohttp.helpers import TimerNoop
from aiohttp.test_utils import make_mocked_coro
import jsonschema
import pytest
from requests import Response
Expand Down Expand Up @@ -1908,7 +1910,7 @@ async def test_history_async_post(loop, session):
'http://localhost:8080',
schema=api_schema_all,
enable_async=True,
enable_history=True
enable_history=True,
)
request_mock.return_value = response
s.create('leases')
Expand All @@ -1923,7 +1925,6 @@ async def test_history_async_post(loop, session):

assert len(s.history) == 1
latest = s.history.latest
print(latest)
assert latest.url == 'http://localhost:8080/leases'
assert latest.http_method == 'POST'
assert latest.send_json == {
Expand All @@ -1943,3 +1944,77 @@ async def test_history_async_post(loop, session):
latest.payload
assert latest.content_length == len(response._body)
assert latest.status_code == 500


@pytest.mark.asyncio
async def test_on_request_chunk_sent_async_hook():
data_sent = make_mocked_coro()
s = Session(
'http://0.0.0.0:8080/api',
schema=api_schema_all,
enable_async=True,
enable_history=True,
event_hooks={'on_request_chunk_sent': data_sent}
)

s.create('leases')
a = s.create('leases')
assert a.is_dirty
a.lease_id = '1'
a.active_status = 'pending'
a.reference_number = 'test'
with pytest.raises(ServerDisconnectedError):
await a.commit()
assert data_sent.called
assert json.loads(data_sent.call_args[0][2].chunk) == {
'data': {
'attributes': {
'active-status': 'pending',
'lease-id': '1',
'reference-number': 'test'
},
'relationships': {},
'type': 'leases'
}
}


def test_set_event_hooks_for_requests():
"""Event hooks for requests library is a keyword argument
so this test only tests that the request_kwargs are updated correctly.
"""
# Hooks not set at all
s = Session(
'http://0.0.0.0:8080/api',
schema=api_schema_all
)
assert 'hooks' not in s._request_kwargs

# Hooks can be set from event_hooks
response_hook = Mock()
s = Session(
'http://0.0.0.0:8080/api',
schema=api_schema_all,
event_hooks={'response': response_hook}
)
assert 'hooks' in s._request_kwargs
assert s._request_kwargs.get('hooks') == {'response': response_hook}

# Hooks can be set also from kwargs
s = Session(
'http://0.0.0.0:8080/api',
schema=api_schema_all,
request_kwargs={'hooks': {'test': None}},
event_hooks={'response': response_hook}
)
assert 'hooks' in s._request_kwargs
assert s._request_kwargs.get('hooks') == {'response': response_hook, 'test': None}

# Hooks set only at kwargs
s = Session(
'http://0.0.0.0:8080/api',
schema=api_schema_all,
request_kwargs={'hooks': {'test': None}}
)
assert 'hooks' in s._request_kwargs
assert s._request_kwargs.get('hooks') == {'test': None}

0 comments on commit 52ed3f6

Please sign in to comment.