Skip to content

Commit 9163394

Browse files
committed
✨ Methods for device authorization have been renamed, so they are not considered private. LinkLogin is used instead of JsonObj. Docstrings are added.
1 parent c543cca commit 9163394

File tree

1 file changed

+70
-21
lines changed

1 file changed

+70
-21
lines changed

tidalapi/session.py

+70-21
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,21 @@ class LinkLogin:
8181
verification_uri: str
8282
#: The link the user has to visit, with the code already included
8383
verification_uri_complete: str
84+
#: After how much time the uri expires.
85+
expires_in: float
86+
#: The interval for authorization checks against the backend.
87+
interval: float
88+
#: The unique device code necessary for authorization.
89+
device_code: str
8490

8591
def __init__(self, json: JsonObj):
8692
self.expires_in = int(json["expiresIn"])
8793
self.user_code = str(json["userCode"])
8894
self.verification_uri = str(json["verificationUri"])
8995
self.verification_uri_complete = str(json["verificationUriComplete"])
96+
self.expires_in = float(json["expiresIn"])
97+
self.interval = float(json["interval"])
98+
self.device_code = str(json["deviceCode"])
9099

91100

92101
class Config:
@@ -600,16 +609,17 @@ def login_oauth_simple(self, fn_print: Callable[[str], None] = print) -> None:
600609
def login_oauth(self) -> Tuple[LinkLogin, concurrent.futures.Future[Any]]:
601610
"""Login to TIDAL with a remote link for limited input devices. The function
602611
will return everything you need to log in through a web browser, and will return
603-
an future that will run until login.
612+
a future that will run until login.
604613
605614
:return: A :class:`LinkLogin` object containing all the data needed to log in remotely, and
606-
a :class:`concurrent.futures.Future` that will poll until the login is completed, or until the link expires.
615+
a :class:`concurrent.futures.Future` object that will poll until the login is completed, or until the link expires.
616+
:rtype: :class:`LinkLogin`
607617
:raises: TimeoutError: If the login takes too long
608618
"""
609-
json_obj: JsonObj = self._login_with_link()
619+
link_login: LinkLogin = self.get_link_login()
610620
executor = concurrent.futures.ThreadPoolExecutor()
611621

612-
return LinkLogin(json_obj), executor.submit(self._process_link_login, json_obj)
622+
return link_login, executor.submit(self.process_link_login, link_login)
613623

614624
def save_session_to_file(self, session_file: Path):
615625
# create a new session
@@ -646,7 +656,13 @@ def load_session_from_file(self, session_file: Path):
646656

647657
return self.load_oauth_session(**args)
648658

649-
def _login_with_link(self) -> JsonObj:
659+
def get_link_login(self) -> LinkLogin:
660+
"""Return information required to login into TIDAL using a device authorization
661+
link.
662+
663+
:return: Login information for device authorization retrieved from the TIDAL backend.
664+
:rtype: :class:`LinkLogin`
665+
"""
650666
url = "https://auth.tidal.com/v1/oauth2/device_authorization"
651667
params = {"client_id": self.config.client_id, "scope": "r_usr w_usr w_sub"}
652668

@@ -658,23 +674,39 @@ def _login_with_link(self) -> JsonObj:
658674

659675
json = request.json()
660676

661-
return json
677+
return LinkLogin(json)
678+
679+
def process_link_login(
680+
self, link_login: LinkLogin, until_expiry: bool = True
681+
) -> bool:
682+
"""Checks if device authorization was successful and processes the retrieved
683+
OAuth token from the Backend.
684+
685+
:param link_login: Link login information containing the necessary device authorization information.
686+
:type link_login: :class:`LinkLogin`
687+
:param until_expiry: If `True` device authorization check is running until the link expires. If `False`check is running only once.
688+
:type until_expiry: :class:`bool`
689+
690+
:return: `True` if login was successful.
691+
:rtype: bool
692+
"""
693+
result: JsonObj = self._check_link_login(link_login, until_expiry)
694+
result_process: bool = self.process_auth_token(result, is_pkce_token=False)
662695

663-
def _process_link_login(self, json: JsonObj) -> None:
664-
json = self._wait_for_link_login(json)
665-
self.process_auth_token(json, is_pkce_token=False)
696+
return result_process
666697

667698
def process_auth_token(
668699
self, json: dict[str, Union[str, int]], is_pkce_token: bool = True
669-
) -> None:
700+
) -> bool:
670701
"""Parses the authorization response and sets the token values to the specific
671702
variables for further usage.
672703
673704
:param json: Parsed JSON response after login / authorization.
674705
:type json: dict[str, str | int]
675706
:param is_pkce_token: Set true if current token is obtained using PKCE
676707
:type is_pkce_token: bool
677-
:return: None
708+
:return: `True` if no error occurs.
709+
:rtype: bool
678710
"""
679711
self.access_token = json["access_token"]
680712
self.expiry_time = datetime.datetime.utcnow() + datetime.timedelta(
@@ -689,28 +721,45 @@ def process_auth_token(
689721
self.user = user.User(self, user_id=json["userId"]).factory()
690722
self.is_pkce = is_pkce_token
691723

692-
def _wait_for_link_login(self, json: JsonObj) -> Any:
693-
expiry = float(json["expiresIn"])
694-
interval = float(json["interval"])
695-
device_code = json["deviceCode"]
724+
return True
725+
726+
def _check_link_login(
727+
self, link_login: LinkLogin, until_expiry: bool = True
728+
) -> TimeoutError | JsonObj:
729+
"""Checks if device authorization was successful and retrieves OAuth data. Can
730+
check the backend for successful device authrization until the link expires
731+
(with the given interval) or just once.
732+
733+
:param link_login: Link login information containing the necessary device authorization information.
734+
:type link_login: :class:`LinkLogin`
735+
:param until_expiry: If `True` device authorization check is running until the link expires. If `False`check is running only once.
736+
:type until_expiry: :class:`bool`
737+
:return: Raise :class:`TimeoutError` if the link has expired otherwise returns retrieved OAuth information.
738+
:rtype: :class:`TimeoutError` | :class:`JsonObj`
739+
"""
740+
expiry: float = link_login.expires_in if until_expiry else 1
696741
url = self.config.api_oauth2_token
697742
params = {
698743
"client_id": self.config.client_id,
699744
"client_secret": self.config.client_secret,
700-
"device_code": device_code,
745+
"device_code": link_login.device_code,
701746
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
702747
"scope": "r_usr w_usr w_sub",
703748
}
749+
704750
while expiry > 0:
705751
request = self.request_session.post(url, params)
706-
json = request.json()
752+
result: JsonObj = request.json()
753+
707754
if request.ok:
708-
return json
755+
return result
756+
709757
# Because the requests take time, the expiry variable won't be accurate, so stop if TIDAL says it's expired
710-
if json["error"] == "expired_token":
758+
if result["error"] == "expired_token":
711759
break
712-
time.sleep(interval)
713-
expiry = expiry - interval
760+
761+
time.sleep(link_login.interval)
762+
expiry = expiry - link_login.interval
714763

715764
raise TimeoutError("You took too long to log in")
716765

0 commit comments

Comments
 (0)