Skip to content

Commit

Permalink
Merge pull request #196 from arusahni/feat/session-typing
Browse files Browse the repository at this point in the history
Add typings for Session
  • Loading branch information
tehkillerbee authored Nov 27, 2023
2 parents f84d043 + 45858f2 commit cbe8653
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 132 deletions.
3 changes: 1 addition & 2 deletions tidalapi/album.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ def page(self) -> "Page":
:return: A :class:`Page` containing the different categories, e.g. similar artists and albums
"""
page = self.session.page.get("pages/album", params={"albumId": self.id})
return cast("Page", page)
return self.session.page.get("pages/album", params={"albumId": self.id})

def similar(self) -> List["Album"]:
"""Retrieve albums similar to the current one.
Expand Down
15 changes: 6 additions & 9 deletions tidalapi/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing import TYPE_CHECKING, List, Mapping, Optional, Union, cast

import dateutil.parser
from typing_extensions import Never

from tidalapi.types import JsonObj

Expand Down Expand Up @@ -196,7 +197,7 @@ def get_radio(self) -> List["Track"]:
),
)

def items(self) -> list:
def items(self) -> List[Never]:
"""The artist page does not supply any items. This only exists for symmetry with
other model types.
Expand All @@ -222,14 +223,10 @@ def image(self, dimensions: int = 320) -> str:
if not self.picture:
raise ValueError("No image available")

return cast(
str,
self.session.config.image_url
% (
self.picture.replace("-", "/"),
dimensions,
dimensions,
),
return self.session.config.image_url % (
self.picture.replace("-", "/"),
dimensions,
dimensions,
)

def page(self) -> "Page":
Expand Down
6 changes: 4 additions & 2 deletions tidalapi/genre.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
""""""

import copy
from typing import TYPE_CHECKING, Any, List, Optional
from typing import TYPE_CHECKING, Any, List, Optional, cast

from tidalapi.types import JsonObj

Expand Down Expand Up @@ -73,5 +73,7 @@ def items(self, model: List[Optional[Any]]) -> List[Optional[Any]]:
parse = type_relations.parse
if getattr(self, name):
location = f"genres/{self.path}/{name}"
return self.requests.map_request(location, parse=parse)
return cast(
List[Optional[Any]], self.requests.map_request(location, parse=parse)
)
raise TypeError("This genre does not contain {0}".format(name))
24 changes: 11 additions & 13 deletions tidalapi/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,16 @@ class Media:
volume_num: int = 1
explicit: bool = False
popularity: int = -1
artist: Optional[tidalapi.Artist] = None
artist: Optional["tidalapi.artist.Artist"] = None
#: For the artist credit page
artist_roles = None
artists: Optional[List[tidalapi.Artist]] = None
album: Optional[tidalapi.album.Album] = None
artists: Optional[List["tidalapi.artist.Artist"]] = None
album: Optional["tidalapi.album.Album"] = None
type: Optional[str] = None

def __init__(self, session: tidalapi.Session, media_id: Optional[str] = None):
def __init__(
self, session: "tidalapi.session.Session", media_id: Optional[str] = None
):
self.session = session
self.requests = self.session.request
self.album = session.album()
Expand Down Expand Up @@ -216,7 +218,7 @@ def lyrics(self) -> "Lyrics":
assert not isinstance(lyrics, list)
return cast("Lyrics", lyrics)

def get_track_radio(self, limit=100) -> List["Track"]:
def get_track_radio(self, limit: int = 100) -> List["Track"]:
"""Queries TIDAL for the track radio, which is a mix of tracks that are similar
to this track.
Expand Down Expand Up @@ -341,12 +343,8 @@ def image(self, width: int = 1080, height: int = 720) -> str:
raise ValueError("Invalid resolution {} x {}".format(width, height))
if not self.cover:
raise AttributeError("No cover image")
return cast(
str,
self.session.config.image_url
% (
self.cover.replace("-", "/"),
width,
height,
),
return self.session.config.image_url % (
self.cover.replace("-", "/"),
width,
height,
)
34 changes: 15 additions & 19 deletions tidalapi/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,10 @@ def tracks(self, limit: Optional[int] = None, offset: int = 0) -> List["Track"]:
"GET", self._base_url % self.id + "/tracks", params=params
)
self._etag = request.headers["etag"]
return self.requests.map_json(
json_obj=request.json(), parse=self.session.parse_track
return list(
self.requests.map_json(
json_obj=request.json(), parse=self.session.parse_track
)
)

def items(self, limit: int = 100, offset: int = 0) -> List[Union["Track", "Video"]]:
Expand All @@ -163,7 +165,9 @@ def items(self, limit: int = 100, offset: int = 0) -> List[Union["Track", "Video
"GET", self._base_url % self.id + "/items", params=params
)
self._etag = request.headers["etag"]
return self.requests.map_json(request.json(), parse=self.session.parse_media)
return list(
self.requests.map_json(request.json(), parse=self.session.parse_media)
)

def image(self, dimensions: int = 480) -> str:
"""A URL to a playlist picture.
Expand All @@ -179,14 +183,10 @@ def image(self, dimensions: int = 480) -> str:
raise ValueError("Invalid resolution {0} x {0}".format(dimensions))
if self.square_picture is None:
raise AttributeError("No picture available")
return cast(
str,
self.session.config.image_url
% (
self.square_picture.replace("-", "/"),
dimensions,
dimensions,
),
return self.session.config.image_url % (
self.square_picture.replace("-", "/"),
dimensions,
dimensions,
)

def wide_image(self, width: int = 1080, height: int = 720) -> str:
Expand All @@ -203,14 +203,10 @@ def wide_image(self, width: int = 1080, height: int = 720) -> str:
raise ValueError("Invalid resolution {} x {}".format(width, height))
if self.picture is None:
raise AttributeError("No picture available")
return cast(
str,
self.session.config.image_url
% (
self.picture.replace("-", "/"),
width,
height,
),
return self.session.config.image_url % (
self.picture.replace("-", "/"),
width,
height,
)


Expand Down
37 changes: 25 additions & 12 deletions tidalapi/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,42 @@
List,
Literal,
Mapping,
MutableMapping,
Optional,
Union,
cast,
)
from urllib.parse import urljoin

import requests

from tidalapi.types import JsonObj

log = logging.getLogger(__name__)

Params = Mapping[str, Union[str, int, None]]

Methods = Literal["GET", "POST", "PUT", "DELETE"]

if TYPE_CHECKING:
from tidalapi.session import Session


class Requests(object):
"""A class for handling api requests to TIDAL."""

def __init__(self, session):
def __init__(self, session: "Session"):
self.session = session
self.config = session.config

def basic_request(self, method, path, params=None, data=None, headers=None):
def basic_request(
self,
method: Methods,
path: str,
params: Optional[Params] = None,
data: Optional[JsonObj] = None,
headers: Optional[MutableMapping[str, str]] = None,
) -> requests.Response:
request_params = {
"sessionId": self.session.session_id,
"countryCode": self.session.country_code,
Expand All @@ -64,7 +76,7 @@ def basic_request(self, method, path, params=None, data=None, headers=None):

if not headers:
headers = {}
if self.session.token_type:
if self.session.token_type and self.session.access_token is not None:
headers["authorization"] = (
self.session.token_type + " " + self.session.access_token
)
Expand Down Expand Up @@ -97,12 +109,12 @@ def basic_request(self, method, path, params=None, data=None, headers=None):

def request(
self,
method: Literal["GET", "POST", "PUT", "DELETE"],
method: Methods,
path: str,
params: Optional[Params] = None,
data: Optional[JsonObj] = None,
headers: Optional[Mapping[str, str]] = None,
):
headers: Optional[MutableMapping[str, str]] = None,
) -> requests.Response:
"""Method for tidal requests.
Not meant for use outside of this library.
Expand All @@ -126,8 +138,8 @@ def map_request(
self,
url: str,
params: Optional[Params] = None,
parse: Optional[Callable] = None,
):
parse: Optional[Callable[..., Any]] = None,
) -> Any:
"""Returns the data about object(s) at the specified url, with the method
specified in the parse argument.
Expand All @@ -147,12 +159,13 @@ def map_request(
def map_json(
cls,
json_obj: JsonObj,
parse: Optional[Callable] = None,
parse: Optional[Callable[..., Any]] = None,
session: Optional["Session"] = None,
) -> List[Any]:
) -> Any:
items = json_obj.get("items")

if items is None:
# Not a collection of items, so returning a single object
if parse is None:
raise ValueError("A parser must be supplied")
return parse(json_obj)
Expand All @@ -167,7 +180,7 @@ def map_json(
for item in items:
if session is not None:
parse = cast(
Callable,
Callable[..., Any],
session.convert_type(
cast(str, item["type"]).lower() + "s", output="parse"
),
Expand All @@ -181,7 +194,7 @@ def map_json(
raise ValueError("A parser must be supplied")
return list(map(parse, items))

def get_items(self, url, parse):
def get_items(self, url: str, parse: Callable[..., Any]) -> List[Any]:
"""Returns a list of items, used when there are over a 100 items, but TIDAL
doesn't always allow more specifying a higher limit.
Expand Down
Loading

0 comments on commit cbe8653

Please sign in to comment.