Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ v0.9.0
* Bugfix: Non-JSON error responses and `Retry-After` given as a date no longer break error handling - tehkillerbee_
* Bugfix: Parsing tracks and albums no longer fails when TIDAL returns no or empty artist information - tehkillerbee_
* Parsed `Track` flags (`pay_to_stream`, `premium_streaming_only`, `editable`, `upload`, `spotlighted`) are now `False` instead of `None` when TIDAL omits them, and `url` is an empty string - tehkillerbee_
* Bugfix: `load_oauth_session` accepts the expiry time as an ISO 8601 string, as applications that store it as text pass it. Since the token is now refreshed before it expires, such a string made every request fail - Petru101_

v0.8.11
--------
Expand Down Expand Up @@ -323,4 +324,5 @@ v0.6.2
.. _nilathedragon: https://github.com/nilathedragon
.. _semohr: https://github.com/semohr
.. _rafrombrc: https://github.com/rafrombrc
.. _Petru101: https://github.com/Petru101

39 changes: 39 additions & 0 deletions tests/test_token_refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,45 @@ def test_failed_proactive_refresh_does_not_fail_the_request():
assert session.request.basic_request("GET", "tracks/1").ok


def _load_oauth_session(expiry_time):
session = _refreshing_session(None)
session.request_session.request.return_value = fake_response(
200, {"sessionId": "sid", "countryCode": "US", "userId": 1}
)
with mock.patch.object(tidalapi.user.User, "factory"):
assert session.load_oauth_session(
"Bearer", "old-access", "old-refresh", expiry_time, is_pkce=True
)
return session


def test_load_oauth_session_accepts_expiry_time_string():
expired = datetime.datetime.utcnow() - datetime.timedelta(days=1)

session = _load_oauth_session(str(expired))

# The expired token is refreshed up front, which sets a new expiry time
session.request_session.post.assert_called_once()
assert session.access_token == "new-access"
assert session.expiry_time > datetime.datetime.utcnow()


def test_load_oauth_session_accepts_iso_format_expiry_time():
expiry_time = datetime.datetime(2030, 1, 2, 3, 4, 5)

session = _load_oauth_session(expiry_time.isoformat())

assert session.expiry_time == expiry_time
session.request_session.post.assert_not_called()


def test_load_oauth_session_ignores_invalid_expiry_time_string():
session = _load_oauth_session("None")

assert session.expiry_time is None
session.request_session.post.assert_not_called()


def test_session_file_round_trips_expiry_time(tmp_path):
session_file = tmp_path / "session.json"
expiry_time = datetime.datetime(2030, 1, 2, 3, 4, 5)
Expand Down
13 changes: 11 additions & 2 deletions tidalapi/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def load_oauth_session(
token_type: str,
access_token: str,
refresh_token: Optional[str] = None,
expiry_time: Optional[datetime.datetime] = None,
expiry_time: Optional[Union[datetime.datetime, str]] = None,
is_pkce: Optional[bool] = False,
) -> bool:
"""Login to TIDAL using details from a previous OAuth login, automatically
Expand All @@ -398,10 +398,19 @@ def load_oauth_session(
:param access_token: The access token received from an oauth login or refresh
:param refresh_token: (Optional) A refresh token that lets you get a new access
token after it has expired
:param expiry_time: (Optional) The datetime the access token will expire
:param expiry_time: (Optional) The datetime the access token will expire. A
string in ISO 8601 format (e.g. ``str(datetime)``) is accepted too
:param is_pkce: (Optional) Is session pkce?
:return: True if we believe the login was successful, otherwise false.
"""
if isinstance(expiry_time, str):
# Applications often store the expiry time as text
try:
expiry_time = datetime.datetime.fromisoformat(expiry_time)
except ValueError:
log.info("Ignoring invalid expiry_time %r", expiry_time)
expiry_time = None

self.token_type = token_type
self.access_token = access_token
self.refresh_token = refresh_token
Expand Down
Loading