Skip to content

Commit

Permalink
Provide a close method to senders, fixes #220
Browse files Browse the repository at this point in the history
  • Loading branch information
felix-hilden committed Oct 16, 2020
1 parent 27c6e75 commit 44cf000
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/src/reference/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ Non-endpoint methods
Spotify.max_limits
Spotify.token_as
Spotify.send
Spotify.close

.. automethod:: Spotify.chunked
.. automethod:: Spotify.max_limits
.. automethod:: Spotify.token_as
.. automethod:: Spotify.send
.. automethod:: Spotify.close

.. _client-paging:

Expand Down
2 changes: 2 additions & 0 deletions docs/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Added
(:issue:`216`)
- Error messages for :func:`parse_code_from_url`, :func:`parse_state_from_url`
and :meth:`Credentials.pkce_user_authorisation` were improved (:issue:`218`)
- :class:`Spotify` and :ref:`senders`, both synchronous and asynchronous,
can now be closed directly with :meth:`close <Spotify.close>` (:issue:`220`)

3.1.0 (2020-09-13)
------------------
Expand Down
4 changes: 4 additions & 0 deletions tekore/_sender/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ def send(
@abstractmethod
def is_async(self) -> bool:
"""Sender asynchronicity mode."""

@abstractmethod
def close(self) -> Union[None, Coroutine[None, None, None]]:
"""Close underlying client."""
10 changes: 9 additions & 1 deletion tekore/_sender/concrete.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Coroutine
from httpx import Client, AsyncClient, Response as HTTPXResponse

from .base import Sender, Request, Response
Expand Down Expand Up @@ -52,6 +52,10 @@ def is_async(self) -> bool:
"""Sender asynchronicity, always :class:`False`."""
return False

def close(self) -> None:
"""Close the underlying synchronous client."""
return self.client.close()


class AsyncSender(Sender):
"""
Expand Down Expand Up @@ -92,3 +96,7 @@ async def send(self, request: Request) -> Response:
def is_async(self) -> bool:
"""Sender asynchronicity, always :class:`True`."""
return True

async def close(self) -> None:
"""Close the underlying asynchronous client."""
return await self.client.aclose()
9 changes: 9 additions & 0 deletions tekore/_sender/extending.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ def is_async(self) -> bool:
"""Sender asynchronicity, delegated to the underlying sender."""
return self.sender.is_async

def close(self) -> Union[None, Coroutine[None, None, None]]:
"""
Close the underlying sender.
To close synchronous senders, call :meth:`close`.
To close asynchronous senders, await :meth:`close`.
"""
return self.sender.close()


class RetryingSender(ExtendingSender):
"""
Expand Down
2 changes: 2 additions & 0 deletions tests/auth/expiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ async def test_async_request_client_token(self, app_env):
c = Credentials(app_env[0], app_env[1], asynchronous=True)
token = await c.request_client_token()
assert token.refresh_token is None
await c.close()

def test_refresh_user_token(self, app_env, user_refresh):
c = Credentials(app_env[0], app_env[1])
Expand All @@ -114,6 +115,7 @@ async def test_async_refresh_user_token(self, app_env, user_refresh):
c = Credentials(app_env[0], app_env[1], asynchronous=True)
token = await c.refresh_user_token(user_refresh)
assert token.refresh_token is not None
await c.close()

def test_bad_arguments_raises_error(self):
c = Credentials('id', 'secret')
Expand Down
15 changes: 15 additions & 0 deletions tests/client/full.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def test_all_endpoints_have_scope_attributes(self, client):
# Skip non-endpoint functions
skips = {
'send',
'close',
'next',
'previous',
'all_pages',
Expand All @@ -56,6 +57,19 @@ def test_all_endpoints_have_scope_attributes(self, client):
assert isinstance(method.optional_scope, Scope)
assert method.scope == method.required_scope + method.optional_scope

def test_request_with_closed_client_raises(self):
client = Spotify()
client.close()
with pytest.raises(RuntimeError):
client.track('id')

@pytest.mark.asyncio
async def test_request_with_closed_async_client_raises(self):
client = Spotify(asynchronous=True)
await client.close()
with pytest.raises(RuntimeError):
await client.track('id')


class TestSpotifyMaxLimits:
def test_turning_on_max_limits_returns_more(self, app_token):
Expand Down Expand Up @@ -122,6 +136,7 @@ async def test_async_too_many_chunked_succeeds(self, app_token, track_ids):
client = Spotify(app_token, chunked_on=True, asynchronous=True)
tracks = await client.tracks(track_ids)
assert len(track_ids) == len(tracks)
await client.close()

def test_returns_model_list(self, app_token, track_ids):
client = Spotify(app_token, chunked_on=True)
Expand Down
19 changes: 12 additions & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def app_client(app_token):
Provides a client with an application token.
"""
sender = tk.RetryingSender(sender=tk.SyncSender())
return tk.Spotify(app_token, sender=sender)
yield tk.Spotify(app_token, sender=sender)
sender.close()


@pytest.fixture(scope='function')
Expand All @@ -96,7 +97,8 @@ def user_client(user_token):
Provides a client with a user token.
"""
sender = tk.RetryingSender(sender=tk.SyncSender())
return tk.Spotify(user_token, sender=sender)
yield tk.Spotify(user_token, sender=sender)
sender.close()


@pytest.fixture(scope='class')
Expand All @@ -105,25 +107,28 @@ def data_client(user_token):
Provides a client with a user token.
"""
sender = tk.RetryingSender(sender=tk.SyncSender())
return tk.Spotify(user_token, sender=sender)
yield tk.Spotify(user_token, sender=sender)
sender.close()


@pytest.fixture(scope='function')
def app_aclient(app_token):
async def app_aclient(app_token):
"""
Provides an asynchronous client with an application token.
"""
sender = tk.RetryingSender(sender=tk.AsyncSender())
return tk.Spotify(app_token, sender=sender)
yield tk.Spotify(app_token, sender=sender)
await sender.close()


@pytest.fixture(scope='function')
def user_aclient(user_token):
async def user_aclient(user_token):
"""
Provides an asynchronous client with a user token.
"""
sender = tk.RetryingSender(sender=tk.AsyncSender())
return tk.Spotify(user_token, sender=sender)
yield tk.Spotify(user_token, sender=sender)
await sender.close()


@pytest.fixture(scope='class')
Expand Down

0 comments on commit 44cf000

Please sign in to comment.