yutipy 2.2.4__tar.gz → 2.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of yutipy might be problematic. Click here for more details.
- {yutipy-2.2.4 → yutipy-2.2.6}/PKG-INFO +1 -1
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/api_reference.rst +0 -6
- yutipy-2.2.6/tests/test_lastfm.py +118 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/lastfm.py +63 -17
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/spotify.py +44 -20
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/PKG-INFO +1 -1
- yutipy-2.2.4/tests/test_lastfm.py +0 -60
- {yutipy-2.2.4 → yutipy-2.2.6}/.gitattributes +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/FUNDING.yml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/dependabot.yml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/workflows/pytest-unit-testing.yml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.github/workflows/release.yml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.gitignore +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/.readthedocs.yaml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/LICENSE +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/MANIFEST.in +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/README.md +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/Makefile +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/_static/yutipy_header.png +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/_static/yutipy_logo.png +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/available_platforms.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/cli.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/conf.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/faq.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/index.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/installation.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/make.bat +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/requirements.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/docs/usage_examples.rst +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/pyproject.toml +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/requirements-dev.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/requirements.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/setup.cfg +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/__init__.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_deezer.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_itunes.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_kkbox.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_models.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_musicyt.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_spotify.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/tests/test_utils.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/__init__.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/cli/__init__.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/cli/config.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/cli/search.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/deezer.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/exceptions.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/itunes.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/kkbox.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/logger.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/models.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/musicyt.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/utils/__init__.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/utils/helpers.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy/yutipy_music.py +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/SOURCES.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/dependency_links.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/entry_points.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/requires.txt +0 -0
- {yutipy-2.2.4 → yutipy-2.2.6}/yutipy.egg-info/top_level.txt +0 -0
|
@@ -132,12 +132,6 @@ Generic Exceptions
|
|
|
132
132
|
:noindex:
|
|
133
133
|
:exclude-members: add_note, args, with_traceback
|
|
134
134
|
|
|
135
|
-
.. autoclass:: yutipy.exceptions.NetworkException
|
|
136
|
-
:members:
|
|
137
|
-
:inherited-members:
|
|
138
|
-
:noindex:
|
|
139
|
-
:exclude-members: add_note, args, with_traceback
|
|
140
|
-
|
|
141
135
|
Service Exceptions
|
|
142
136
|
------------------
|
|
143
137
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from yutipy.lastfm import LastFm
|
|
4
|
+
from yutipy.models import UserPlaying
|
|
5
|
+
from tests import BaseResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def lastfm():
|
|
10
|
+
return LastFm(api_key="test_api_key")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MockResponseActivity(BaseResponse):
|
|
14
|
+
@staticmethod
|
|
15
|
+
def json():
|
|
16
|
+
return {
|
|
17
|
+
"recenttracks": {
|
|
18
|
+
"track": [
|
|
19
|
+
{
|
|
20
|
+
"artist": {"mbid": "", "#text": "Test Artist"},
|
|
21
|
+
"image": [
|
|
22
|
+
{
|
|
23
|
+
"size": "small",
|
|
24
|
+
"#text": "https://example.com/image/small.jpg",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"size": "extralarge",
|
|
28
|
+
"#text": "https://example.com/image/extralarge.jpg",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
"mbid": "",
|
|
32
|
+
"album": {
|
|
33
|
+
"mbid": "",
|
|
34
|
+
"#text": "Test Album",
|
|
35
|
+
},
|
|
36
|
+
"name": "Test Track",
|
|
37
|
+
"@attr": {"nowplaying": True},
|
|
38
|
+
"url": "https://www.last.fm/music/test+track",
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MockResponseProfile(BaseResponse):
|
|
46
|
+
@staticmethod
|
|
47
|
+
def json():
|
|
48
|
+
return {
|
|
49
|
+
"user": {
|
|
50
|
+
"name": "john",
|
|
51
|
+
"realname": "Real John",
|
|
52
|
+
"image": [
|
|
53
|
+
{
|
|
54
|
+
"size": "small",
|
|
55
|
+
"#text": "https://example.com/image/john",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"size": "extralarge",
|
|
59
|
+
"#text": "https://example.com/image/john",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
"url": "https://example.com/john",
|
|
63
|
+
"type": "user",
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def mock_response_activity(lastfm, monkeypatch):
|
|
70
|
+
def mock_get(*args, **kwargs):
|
|
71
|
+
return MockResponseActivity()
|
|
72
|
+
|
|
73
|
+
monkeypatch.setattr(lastfm._LastFm__session, "get", mock_get)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def mock_response_profile(lastfm, monkeypatch):
|
|
78
|
+
def mock_get(*args, **kwargs):
|
|
79
|
+
return MockResponseProfile()
|
|
80
|
+
|
|
81
|
+
monkeypatch.setattr(lastfm._LastFm__session, "get", mock_get)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_get_currently_playing(lastfm, mock_response_activity):
|
|
85
|
+
username = "bob"
|
|
86
|
+
currently_playing = lastfm.get_currently_playing(username=username)
|
|
87
|
+
assert currently_playing is not None
|
|
88
|
+
assert isinstance(currently_playing, UserPlaying)
|
|
89
|
+
assert currently_playing.title == "Test Track"
|
|
90
|
+
assert currently_playing.album_title == "Test Album"
|
|
91
|
+
assert "extralarge" in currently_playing.album_art
|
|
92
|
+
assert currently_playing.is_playing is True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_get_user_profile(lastfm, mock_response_profile):
|
|
96
|
+
username = "john"
|
|
97
|
+
profile = lastfm.get_user_profile(username=username)
|
|
98
|
+
assert profile is not None
|
|
99
|
+
assert profile["username"] == username
|
|
100
|
+
assert profile["name"] == "Real John"
|
|
101
|
+
assert profile["type"] == "user"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_invalid_username(lastfm, monkeypatch):
|
|
105
|
+
def mock_get(*args, **kwargs):
|
|
106
|
+
class MockResponse(BaseResponse):
|
|
107
|
+
@staticmethod
|
|
108
|
+
def json():
|
|
109
|
+
return {"message": "User not found", "error": 6}
|
|
110
|
+
|
|
111
|
+
return MockResponse()
|
|
112
|
+
|
|
113
|
+
monkeypatch.setattr(lastfm._LastFm__session, "get", mock_get)
|
|
114
|
+
|
|
115
|
+
username = "alksdjfalsjdfweurppqoweiuwu"
|
|
116
|
+
profile = lastfm.get_user_profile(username=username)
|
|
117
|
+
assert profile is not None
|
|
118
|
+
assert "error" in profile
|
|
@@ -2,6 +2,7 @@ __all__ = ["LastFm", "LastFmException"]
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import asdict
|
|
5
|
+
from time import time
|
|
5
6
|
from pprint import pprint
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -73,6 +74,46 @@ class LastFm:
|
|
|
73
74
|
"""Checks if the session is closed."""
|
|
74
75
|
return self._is_session_closed
|
|
75
76
|
|
|
77
|
+
def get_user_profile(self, username: str):
|
|
78
|
+
"""
|
|
79
|
+
Fetches the user profile information for the provided username.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
dict
|
|
84
|
+
A dictionary containing the user's profile information or error is username does not exist.
|
|
85
|
+
"""
|
|
86
|
+
query = (
|
|
87
|
+
f"?method=user.getinfo&user={username}&api_key={self.api_key}&format=json"
|
|
88
|
+
)
|
|
89
|
+
query_url = self.__api_url + query
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
response = self.__session.get(query_url, timeout=30)
|
|
93
|
+
except requests.RequestException as e:
|
|
94
|
+
logger.warning(f"Failed to fetch user profile: {e}")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
response_json = response.json()
|
|
98
|
+
result = response_json.get("user")
|
|
99
|
+
error = response_json.get("message")
|
|
100
|
+
if result:
|
|
101
|
+
images = [
|
|
102
|
+
{"size": image.get("size"), "url": image.get("#text")}
|
|
103
|
+
for image in result.get("image", [])
|
|
104
|
+
]
|
|
105
|
+
return {
|
|
106
|
+
"name": result.get("realname"),
|
|
107
|
+
"username": result.get("name"),
|
|
108
|
+
"type": result.get("type"),
|
|
109
|
+
"url": result.get("url"),
|
|
110
|
+
"images": images,
|
|
111
|
+
}
|
|
112
|
+
elif error:
|
|
113
|
+
return {"error": error}
|
|
114
|
+
else:
|
|
115
|
+
return None
|
|
116
|
+
|
|
76
117
|
def get_currently_playing(self, username: str) -> Optional[UserPlaying]:
|
|
77
118
|
"""
|
|
78
119
|
Fetches information about the currently playing or most recent track for a user.
|
|
@@ -100,29 +141,34 @@ class LastFm:
|
|
|
100
141
|
|
|
101
142
|
response_json = response.json()
|
|
102
143
|
result = response_json.get("recenttracks", {}).get("track", [])[0]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
144
|
+
is_playing = result.get("@attr", {}).get("nowplaying", False)
|
|
145
|
+
if result and is_playing:
|
|
146
|
+
album_art = [
|
|
147
|
+
img.get("#text")
|
|
148
|
+
for img in result.get("image", [])
|
|
149
|
+
if img.get("size") == "extralarge"
|
|
150
|
+
]
|
|
151
|
+
return UserPlaying(
|
|
152
|
+
album_art="".join(album_art),
|
|
153
|
+
album_title=result.get("album", {}).get("#text"),
|
|
154
|
+
artists=", ".join(
|
|
155
|
+
separate_artists(result.get("artist", {}).get("#text"))
|
|
156
|
+
),
|
|
157
|
+
id=result.get("mbid"),
|
|
158
|
+
timestamp=result.get("date", {}).get("uts") or time(),
|
|
159
|
+
title=result.get("name"),
|
|
160
|
+
url=result.get("url"),
|
|
161
|
+
is_playing=is_playing,
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
118
164
|
|
|
119
165
|
|
|
120
166
|
if __name__ == "__main__":
|
|
121
167
|
with LastFm() as lastfm:
|
|
122
168
|
username = input("Enter Lasfm Username: ").strip()
|
|
123
|
-
result = lastfm.
|
|
169
|
+
result = lastfm.get_user_profile(username=username)
|
|
124
170
|
|
|
125
171
|
if result:
|
|
126
|
-
pprint(
|
|
172
|
+
pprint(result)
|
|
127
173
|
else:
|
|
128
174
|
print("No result was found. Make sure the username is correct!")
|
|
@@ -196,8 +196,9 @@ class Spotify:
|
|
|
196
196
|
logger.debug(f"Authentication response status code: {response.status_code}")
|
|
197
197
|
response.raise_for_status()
|
|
198
198
|
except requests.RequestException as e:
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
raise requests.RequestException(
|
|
200
|
+
f"Network error during Spotify authentication: {e}"
|
|
201
|
+
)
|
|
201
202
|
|
|
202
203
|
if response.status_code == 200:
|
|
203
204
|
response_json = response.json()
|
|
@@ -210,6 +211,9 @@ class Spotify:
|
|
|
210
211
|
|
|
211
212
|
def __refresh_access_token(self):
|
|
212
213
|
"""Refreshes the token if it has expired."""
|
|
214
|
+
if not self.__access_token:
|
|
215
|
+
raise SpotifyAuthException("No access token was found.")
|
|
216
|
+
|
|
213
217
|
try:
|
|
214
218
|
if time() - self.__token_requested_at >= self.__token_expires_in:
|
|
215
219
|
token_info = self.__get_access_token()
|
|
@@ -224,12 +228,16 @@ class Spotify:
|
|
|
224
228
|
self.__token_requested_at = token_info.get("requested_at")
|
|
225
229
|
|
|
226
230
|
logger.info("The access token is still valid, no need to refresh.")
|
|
231
|
+
except (AuthenticationException, requests.RequestException) as e:
|
|
232
|
+
logger.warning(
|
|
233
|
+
f"Failed to refresh the access toke due to following error: {e}"
|
|
234
|
+
)
|
|
227
235
|
except TypeError:
|
|
228
236
|
logger.debug(
|
|
229
237
|
f"token requested at: {self.__token_requested_at} | token expires in: {self.__token_expires_in}"
|
|
230
238
|
)
|
|
231
239
|
logger.info(
|
|
232
|
-
"Something went wrong while trying to refresh the token. Set logging level to `DEBUG` to see the issue."
|
|
240
|
+
"Something went wrong while trying to refresh the access token. Set logging level to `DEBUG` to see the issue."
|
|
233
241
|
)
|
|
234
242
|
|
|
235
243
|
def save_access_token(self, token_info: dict) -> None:
|
|
@@ -341,6 +349,7 @@ class Spotify:
|
|
|
341
349
|
)
|
|
342
350
|
response.raise_for_status()
|
|
343
351
|
except requests.RequestException as e:
|
|
352
|
+
logger.warning(f"Network error during Spotify search: {e}")
|
|
344
353
|
return None
|
|
345
354
|
|
|
346
355
|
if response.status_code != 200:
|
|
@@ -408,6 +417,7 @@ class Spotify:
|
|
|
408
417
|
)
|
|
409
418
|
response.raise_for_status()
|
|
410
419
|
except requests.RequestException as e:
|
|
420
|
+
logger.warning(f"Network error during Spotify search (advanced): {e}")
|
|
411
421
|
return None
|
|
412
422
|
|
|
413
423
|
if response.status_code != 200:
|
|
@@ -441,6 +451,7 @@ class Spotify:
|
|
|
441
451
|
)
|
|
442
452
|
response.raise_for_status()
|
|
443
453
|
except requests.RequestException as e:
|
|
454
|
+
logger.warning(f"Network error during Spotify get artist ids: {e}")
|
|
444
455
|
return None
|
|
445
456
|
|
|
446
457
|
if response.status_code != 200:
|
|
@@ -820,8 +831,9 @@ class SpotifyAuth:
|
|
|
820
831
|
logger.debug(f"Authentication response status code: {response.status_code}")
|
|
821
832
|
response.raise_for_status()
|
|
822
833
|
except requests.RequestException as e:
|
|
823
|
-
|
|
824
|
-
|
|
834
|
+
raise requests.RequestException(
|
|
835
|
+
f"Network error during Spotify authentication: {e}"
|
|
836
|
+
)
|
|
825
837
|
|
|
826
838
|
if response.status_code == 200:
|
|
827
839
|
response_json = response.json()
|
|
@@ -837,20 +849,30 @@ class SpotifyAuth:
|
|
|
837
849
|
if not self.__access_token:
|
|
838
850
|
raise SpotifyAuthException("No access token was found.")
|
|
839
851
|
|
|
840
|
-
|
|
841
|
-
|
|
852
|
+
try:
|
|
853
|
+
if time() - self.__token_requested_at >= self.__token_expires_in:
|
|
854
|
+
token_info = self.__get_access_token(refresh_token=self.__refresh_token)
|
|
842
855
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
856
|
+
try:
|
|
857
|
+
self.save_access_token(token_info)
|
|
858
|
+
except NotImplementedError as e:
|
|
859
|
+
logger.warning(e)
|
|
847
860
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
861
|
+
self.__access_token = token_info.get("access_token")
|
|
862
|
+
self.__refresh_token = token_info.get("refresh_token")
|
|
863
|
+
self.__token_expires_in = token_info.get("expires_in")
|
|
864
|
+
self.__token_requested_at = token_info.get("requested_at")
|
|
852
865
|
|
|
853
|
-
|
|
866
|
+
logger.info("The access token is still valid, no need to refresh.")
|
|
867
|
+
except (AuthenticationException, requests.RequestException) as e:
|
|
868
|
+
logger.warning(f"Failed to refresh the access toke due to following error: {e}")
|
|
869
|
+
except TypeError:
|
|
870
|
+
logger.debug(
|
|
871
|
+
f"token requested at: {self.__token_requested_at} | token expires in: {self.__token_expires_in}"
|
|
872
|
+
)
|
|
873
|
+
logger.warning(
|
|
874
|
+
"Something went wrong while trying to refresh the access token. Set logging level to `DEBUG` to see the issue."
|
|
875
|
+
)
|
|
854
876
|
|
|
855
877
|
@staticmethod
|
|
856
878
|
def generate_state() -> str:
|
|
@@ -1028,7 +1050,7 @@ class SpotifyAuth:
|
|
|
1028
1050
|
except NotImplementedError as e:
|
|
1029
1051
|
logger.warning(e)
|
|
1030
1052
|
|
|
1031
|
-
def get_user_profile(self):
|
|
1053
|
+
def get_user_profile(self) -> Optional[dict]:
|
|
1032
1054
|
"""
|
|
1033
1055
|
Fetches the user's display name and profile images.
|
|
1034
1056
|
|
|
@@ -1063,10 +1085,11 @@ class SpotifyAuth:
|
|
|
1063
1085
|
logger.warning(f"Unexpected response: {response.json()}")
|
|
1064
1086
|
return None
|
|
1065
1087
|
|
|
1066
|
-
|
|
1088
|
+
result = response.json()
|
|
1067
1089
|
return {
|
|
1068
|
-
"display_name":
|
|
1069
|
-
"images":
|
|
1090
|
+
"display_name": result.get("display_name"),
|
|
1091
|
+
"images": result.get("images", []),
|
|
1092
|
+
"url": result.get("external_urls", {}).get("spotify")
|
|
1070
1093
|
}
|
|
1071
1094
|
|
|
1072
1095
|
def get_currently_playing(self) -> Optional[UserPlaying]:
|
|
@@ -1105,6 +1128,7 @@ class SpotifyAuth:
|
|
|
1105
1128
|
response = self.__session.get(query_url, headers=header, timeout=30)
|
|
1106
1129
|
response.raise_for_status()
|
|
1107
1130
|
except requests.RequestException as e:
|
|
1131
|
+
logger.warning(f"Error while getting Spotify user activity: {e}")
|
|
1108
1132
|
return None
|
|
1109
1133
|
|
|
1110
1134
|
if response.status_code == 204:
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from yutipy.lastfm import LastFm
|
|
4
|
-
from yutipy.models import UserPlaying
|
|
5
|
-
from tests import BaseResponse
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture
|
|
9
|
-
def lastfm():
|
|
10
|
-
return LastFm(api_key="test_api_key")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class MockResponse(BaseResponse):
|
|
14
|
-
@staticmethod
|
|
15
|
-
def json():
|
|
16
|
-
return {
|
|
17
|
-
"recenttracks": {
|
|
18
|
-
"track": [
|
|
19
|
-
{
|
|
20
|
-
"artist": {"mbid": "", "#text": "Test Artist"},
|
|
21
|
-
"image": [
|
|
22
|
-
{
|
|
23
|
-
"size": "small",
|
|
24
|
-
"#text": "https://example.com/image/small.jpg",
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"size": "extralarge",
|
|
28
|
-
"#text": "https://example.com/image/extralarge.jpg",
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
"mbid": "",
|
|
32
|
-
"album": {
|
|
33
|
-
"mbid": "",
|
|
34
|
-
"#text": "Test Album",
|
|
35
|
-
},
|
|
36
|
-
"name": "Test Track",
|
|
37
|
-
"url": "https://www.last.fm/music/test+track",
|
|
38
|
-
}
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@pytest.fixture
|
|
45
|
-
def mock_response(lastfm, monkeypatch):
|
|
46
|
-
def mock_get(*args, **kwargs):
|
|
47
|
-
return MockResponse()
|
|
48
|
-
|
|
49
|
-
monkeypatch.setattr(lastfm._LastFm__session, "get", mock_get)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def test_get_currently_playing(lastfm, mock_response):
|
|
53
|
-
username = "bob"
|
|
54
|
-
currently_playing = lastfm.get_currently_playing(username=username)
|
|
55
|
-
assert currently_playing is not None
|
|
56
|
-
assert isinstance(currently_playing, UserPlaying)
|
|
57
|
-
assert currently_playing.title == "Test Track"
|
|
58
|
-
assert currently_playing.album_title == "Test Album"
|
|
59
|
-
assert "extralarge" in currently_playing.album_art
|
|
60
|
-
assert currently_playing.is_playing is False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|