From bbfaa946a2ddb26c3fadb1146fafa9c316d247ac Mon Sep 17 00:00:00 2001 From: geo-martino Date: Wed, 10 Jul 2024 19:07:08 -0400 Subject: [PATCH] update aiorequestful version --- README.md | 2 +- README.template.md | 2 +- musify/libraries/remote/core/api.py | 9 ++++++- musify/libraries/remote/spotify/api/base.py | 2 +- musify/libraries/remote/spotify/api/cache.py | 9 ++++--- pyproject.toml | 4 ++-- tests/libraries/remote/core/api.py | 24 ++++++++++++++++++- tests/libraries/remote/spotify/api/testers.py | 10 -------- 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 961dd7e6..a21dc55c 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ pip install musify python -m pip install musify ``` -There are alo optional dependencies that you may install for optional functionality. +There are optional dependencies that you may install for optional functionality. For the current list of optional dependency groups, [read the docs](https://geo-martino.github.io/musify/howto.install.html) diff --git a/README.template.md b/README.template.md index e99a91da..f800f90e 100644 --- a/README.template.md +++ b/README.template.md @@ -49,7 +49,7 @@ pip install {program_name_lower} python -m pip install {program_name_lower} ``` -There are alo optional dependencies that you may install for optional functionality. +There are optional dependencies that you may install for optional functionality. For the current list of optional dependency groups, [read the docs](https://{program_owner_user}.github.io/{program_name_lower}/howto.install.html) diff --git a/musify/libraries/remote/core/api.py b/musify/libraries/remote/core/api.py index d7595070..2627faed 100644 --- a/musify/libraries/remote/core/api.py +++ b/musify/libraries/remote/core/api.py @@ -11,6 +11,7 @@ from aiorequestful.auth import Authoriser from aiorequestful.cache.backend.base import ResponseCache from aiorequestful.cache.exception import CacheError +from aiorequestful.cache.session import CachedSession from aiorequestful.request.handler import RequestHandler from aiorequestful.response.payload import JSONPayloadHandler from aiorequestful.types import ImmutableJSON, JSON @@ -87,7 +88,7 @@ def __init__(self, authoriser: A, wrangler: RemoteDataWrangler, cache: ResponseC #: The :py:class:`RequestHandler` for handling authorised requests to the API self.handler: RequestHandler[A, JSON] = RequestHandler.create( - authoriser=authoriser, cache=cache, payload_handler=JSONPayloadHandler(), + authoriser=authoriser, cache=cache, payload_handler=JSONPayloadHandler(indent=2), ) #: Stores the loaded user data for the currently authorised user @@ -103,6 +104,12 @@ async def __aenter__(self) -> Self: except CacheError: pass + if isinstance(self.handler.session, CachedSession): + for repository in self.handler.session.cache.values(): + # all repositories must use the same payload handler as the request handler + # for it to function correctly + repository.settings.payload_handler = self.handler.payload_handler + await self.load_user() await self.load_user_playlists() diff --git a/musify/libraries/remote/spotify/api/base.py b/musify/libraries/remote/spotify/api/base.py index 0ac6b5e0..490acde2 100644 --- a/musify/libraries/remote/spotify/api/base.py +++ b/musify/libraries/remote/spotify/api/base.py @@ -144,4 +144,4 @@ async def _cache_responses(self, method: str, responses: Iterable[dict[str, Any] url=url, message=f"Caching {len(results_mapped)} to {repository.settings.name!r} repository", ) - await repository.save_responses({k: repository.serialize(v) for k, v in results_mapped.items()}) + await repository.save_responses({k: await repository.serialize(v) for k, v in results_mapped.items()}) diff --git a/musify/libraries/remote/spotify/api/cache.py b/musify/libraries/remote/spotify/api/cache.py index 208e93cd..ebc18be0 100644 --- a/musify/libraries/remote/spotify/api/cache.py +++ b/musify/libraries/remote/spotify/api/cache.py @@ -26,11 +26,10 @@ def get_key(self, method: MethodInput, url: URLInput, **__) -> tuple[str | None, pass return (None,) - @staticmethod - def get_name(response: dict[str, Any]) -> str | None: - if response.get("type") == "user": - return response["display_name"] - return response.get("name") + def get_name(self, payload: dict[str, Any]) -> str | None: + if payload.get("type") == "user": + return payload["display_name"] + return payload.get("name") class SpotifyPaginatedRepositorySettings(SpotifyRepositorySettings): diff --git a/pyproject.toml b/pyproject.toml index d6594fec..c83f4691 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] dependencies = [ "mutagen~=1.47", - "aiorequestful~=0.4", + "aiorequestful~=0.5", "python-dateutil~=2.9", "Pillow~=10.3", ] @@ -43,7 +43,7 @@ musicbee = [ "lxml~=5.2", ] sqlite = [ - "aiorequestful[sqlite]~=0.4", + "aiorequestful[sqlite]~=0.5", ] # dev dependencies diff --git a/tests/libraries/remote/core/api.py b/tests/libraries/remote/core/api.py index e275a350..08794a3f 100644 --- a/tests/libraries/remote/core/api.py +++ b/tests/libraries/remote/core/api.py @@ -6,6 +6,7 @@ from urllib.parse import unquote import pytest +from aiorequestful.cache.backend import ResponseCache from yarl import URL from musify.libraries.remote.core.api import RemoteAPI @@ -39,6 +40,17 @@ def object_factory(self) -> RemoteObjectFactory: """Yield the object factory for objects of this remote service type as a pytest.fixture.""" raise NotImplementedError + @pytest.fixture + async def api_cache(self, api: RemoteAPI, cache: ResponseCache, api_mock: RemoteMock) -> RemoteAPI: + """Yield an authorised :py:class:`RemoteAPI` object with a :py:class:`ResponseCache` configured.""" + api_cache = api.__class__(cache=cache) + api_cache.handler.authoriser.response_handler = api.handler.authoriser.response_handler + + async with api_cache as a: + # entering context sometimes makes HTTP calls, reset to avoid issues asserting request counts + api_mock.reset() + yield a + @pytest.fixture def _responses(self, object_type: RemoteObjectType, api_mock: RemoteMock) -> dict[str, dict[str, Any]]: """Yields valid responses mapped by ID for a given ``object_type`` as a pytest.fixture.""" @@ -73,7 +85,7 @@ def key(self, object_type: RemoteObjectType, extend: bool, api: RemoteAPI) -> st return api.collection_item_map[object_type].name.lower() + "s" if extend else None -class RemoteAPIItemTester(metaclass=ABCMeta): +class RemoteAPIItemTester(RemoteAPIFixtures, metaclass=ABCMeta): """Run generic tests for item methods of :py:class:`RemoteAPI` implementations.""" ########################################################################### ## Assertions @@ -102,6 +114,16 @@ def assert_params(requests: Iterable[URL], params: dict[str, Any] | list[dict[st assert k in url.query assert unquote(url.query[k]) == params[k] + def test_context_management(self, api_cache: RemoteAPI): + session = api_cache.handler.session + + assert session.cache.values() + for repository in session.cache.values(): + assert repository.settings.payload_handler == api_cache.handler.payload_handler + + assert api_cache.user_data + assert api_cache.user_playlist_data + class RemoteAPIPlaylistTester(metaclass=ABCMeta): """Run generic tests for playlist methods of :py:class:`RemoteAPI` implementations.""" diff --git a/tests/libraries/remote/spotify/api/testers.py b/tests/libraries/remote/spotify/api/testers.py index 9f674905..f8685e4a 100644 --- a/tests/libraries/remote/spotify/api/testers.py +++ b/tests/libraries/remote/spotify/api/testers.py @@ -37,16 +37,6 @@ async def repository( """Yields a valid :py:class:`ResponseCache` to use throughout tests in this suite as a pytest.fixture.""" return cache.get_repository_from_url(response[self.url_key]) - @pytest.fixture - async def api_cache(self, api: SpotifyAPI, cache: ResponseCache, api_mock: SpotifyMock) -> SpotifyAPI: - """Yield an authorised :py:class:`SpotifyAPI` object with a :py:class:`ResponseCache` configured.""" - api_cache = SpotifyAPI(cache=cache) - api_cache.handler.authoriser.response_handler = api.handler.authoriser.response_handler - - async with api_cache as a: - api_mock.reset() # entering context calls '/me' endpoint, reset to avoid issues asserting request counts - yield a - @pytest.fixture def responses(self, _responses: dict[str, dict[str, Any]], key: str) -> dict[str, dict[str, Any]]: return {id_: response for id_, response in _responses.items() if key is None or response[key]["total"] > 3}