diff --git a/examples/find_albums.py b/examples/find_albums.py index b1d43de..7b92da4 100755 --- a/examples/find_albums.py +++ b/examples/find_albums.py @@ -4,6 +4,7 @@ Find an album using the `spotify_actions` API and echo its contents. from argparse import ArgumentParser from asyncio import get_event_loop +from logging import basicConfig, DEBUG from spotify_actions.echo import echo_album from spotify_actions.search import search_album @@ -11,6 +12,8 @@ from spotify_actions.util import read_credentials def main(): # pylint: disable=missing-function-docstring + basicConfig(level=DEBUG) + parser = ArgumentParser() parser.add_argument("-f", "--config", required=True) parser.add_argument("search_string") diff --git a/spotify_actions/search.py b/spotify_actions/search.py index 7cf961a..9313cd9 100644 --- a/spotify_actions/search.py +++ b/spotify_actions/search.py @@ -6,8 +6,8 @@ from typing import AsyncIterable from spotify import Album, Client -async def search_album(client: Client, name: str) -> AsyncIterable[Album]: +async def search_album(client: Client, search_str: str) -> AsyncIterable[Album]: "Search for a specific album by name" - results = await client.search(name, types=["album"]) + results = await client.search(search_str, types=["album"]) for album in results.albums: yield album diff --git a/spotify_actions/util.py b/spotify_actions/util.py index c2b2339..1ef8424 100644 --- a/spotify_actions/util.py +++ b/spotify_actions/util.py @@ -3,13 +3,74 @@ Utility methods for working with the Spotify API """ from asyncio import AbstractEventLoop from pathlib import Path +from typing import Optional, Awaitable, Dict, Any +from logging import getLogger import yaml -from spotify import Client +from aiohttp import TraceConfig, ClientSession, TraceRequestEndParams, TraceResponseChunkReceivedParams +from spotify import Client, HTTPClient -def read_credentials(path: Path, loop: AbstractEventLoop) -> Client: +LOGGER = getLogger(__name__) + + +class UnescapeHTTPClient(HTTPClient): + "Disable URL-escaping queries" + + def search( + self, + q: str, + query_type: str = "track,playlist,artist,album", + market: str = "US", + limit: int = 20, + offset: int = 0, + include_external: Optional[str] = None, + ) -> Awaitable: + route = self.route("GET", "/search") + payload: Dict[str, Any] = { + # The important bit is not quoting here + "q": q, + "type": query_type, + "limit": limit, + "offset": offset, + } + + if market: + payload["market"] = market + + if include_external is not None: + payload["include_external"] = include_external + + return self.request(route, params=payload) + + +class UnescapeClient(Client): + + _default_http_client = UnescapeHTTPClient + + +def read_credentials(path: Path, loop: AbstractEventLoop, trace_config: Optional[TraceConfig] = None) -> Client: "Read credentials from a YAML file and construct a Spotify client" + + if trace_config: + class TracingHTTPClient(UnescapeHTTPClient): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + prev_session = self._session + self._session = ClientSession(loop=loop, trace_configs=[trace_config]) + loop.run_until_complete(prev_session.close()) + + class TracingClient(Client): + + _default_http_client = TracingHTTPClient + + client_cls = TracingClient + + else: + client_cls = Client + with open(path, "r") as credentials_file: credentials = yaml.safe_load(credentials_file) - return Client(credentials["client_id"], credentials["client_secret"], loop=loop) + return client_cls(credentials["client_id"], credentials["client_secret"], loop=loop)