yutipy 2.2.3__py3-none-any.whl → 2.2.5__py3-none-any.whl

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/deezer.py CHANGED
@@ -9,11 +9,10 @@ from yutipy.exceptions import (
9
9
  DeezerException,
10
10
  InvalidResponseException,
11
11
  InvalidValueException,
12
- NetworkException,
13
12
  )
13
+ from yutipy.logger import logger
14
14
  from yutipy.models import MusicInfo
15
15
  from yutipy.utils.helpers import are_strings_similar, is_valid_string
16
- from yutipy.logger import logger
17
16
 
18
17
 
19
18
  class Deezer:
@@ -97,17 +96,17 @@ class Deezer:
97
96
  logger.debug(f"Response status code: {response.status_code}")
98
97
  response.raise_for_status()
99
98
  except requests.RequestException as e:
100
- logger.error(f"Network error while fetching music info: {e}")
101
- raise NetworkException(f"Network error occurred: {e}")
99
+ logger.warning(f"Network error while fetching music info: {e}")
100
+ return None
102
101
  except Exception as e:
103
- logger.exception(f"Unexpected error while searching Deezer: {e}")
102
+ logger.warning(f"Unexpected error while searching Deezer: {e}")
104
103
  raise DeezerException(f"An error occurred while searching Deezer: {e}")
105
104
 
106
105
  try:
107
106
  logger.debug(f"Parsing response JSON: {response.json()}")
108
107
  result = response.json()["data"]
109
108
  except (IndexError, KeyError, ValueError) as e:
110
- logger.error(f"Invalid response structure from Deezer: {e}")
109
+ logger.warning(f"Invalid response structure from Deezer: {e}")
111
110
  raise InvalidResponseException(f"Invalid response received: {e}")
112
111
 
113
112
  music_info = self._parse_results(artist, song, result)
@@ -164,17 +163,17 @@ class Deezer:
164
163
  logger.debug(f"Response status code: {response.status_code}")
165
164
  response.raise_for_status()
166
165
  except requests.RequestException as e:
167
- logger.error(f"Error fetching track info: {e}")
168
- raise NetworkException(f"Network error occurred: {e}")
166
+ logger.warning(f"Error fetching track info: {e}")
167
+ return None
169
168
  except Exception as e:
170
- logger.error(f"Error fetching track info: {e}")
169
+ logger.warning(f"Error fetching track info: {e}")
171
170
  raise DeezerException(f"An error occurred while fetching track info: {e}")
172
171
 
173
172
  try:
174
173
  logger.debug(f"Response JSON: {response.json()}")
175
174
  result = response.json()
176
175
  except ValueError as e:
177
- logger.error(f"Invalid response received from Deezer: {e}")
176
+ logger.warning(f"Invalid response received from Deezer: {e}")
178
177
  raise InvalidResponseException(f"Invalid response received: {e}")
179
178
 
180
179
  return {
@@ -205,17 +204,17 @@ class Deezer:
205
204
  logger.info(f"Response status code: {response.status_code}")
206
205
  response.raise_for_status()
207
206
  except requests.RequestException as e:
208
- logger.error(f"Error fetching album info: {e}")
209
- raise NetworkException(f"Network error occurred: {e}")
207
+ logger.warning(f"Error fetching album info: {e}")
208
+ return None
210
209
  except Exception as e:
211
- logger.error(f"Error fetching album info: {e}")
210
+ logger.warning(f"Error fetching album info: {e}")
212
211
  raise DeezerException(f"An error occurred while fetching album info: {e}")
213
212
 
214
213
  try:
215
214
  logger.debug(f"Response JSON: {response.json()}")
216
215
  result = response.json()
217
216
  except ValueError as e:
218
- logger.error(f"Invalid response received from Deezer: {e}")
217
+ logger.warning(f"Invalid response received from Deezer: {e}")
219
218
  raise InvalidResponseException(f"Invalid response received: {e}")
220
219
 
221
220
  return {
@@ -323,6 +322,7 @@ class Deezer:
323
322
 
324
323
  if __name__ == "__main__":
325
324
  import logging
325
+
326
326
  from yutipy.logger import enable_logging
327
327
 
328
328
  enable_logging(level=logging.DEBUG)
yutipy/exceptions.py CHANGED
@@ -2,7 +2,6 @@ __all__ = [
2
2
  "AuthenticationException",
3
3
  "InvalidResponseException",
4
4
  "InvalidValueException",
5
- "NetworkException",
6
5
  "YutipyException",
7
6
  ]
8
7
 
@@ -25,10 +24,6 @@ class InvalidValueException(YutipyException):
25
24
  """Exception raised for invalid values."""
26
25
 
27
26
 
28
- class NetworkException(YutipyException):
29
- """Exception raised for network-related errors."""
30
-
31
-
32
27
  # Service Exceptions
33
28
  class DeezerException(YutipyException):
34
29
  """Exception raised for errors related to the Deezer API."""
yutipy/itunes.py CHANGED
@@ -9,8 +9,7 @@ import requests
9
9
  from yutipy.exceptions import (
10
10
  InvalidResponseException,
11
11
  InvalidValueException,
12
- ItunesException,
13
- NetworkException,
12
+ ItunesException
14
13
  )
15
14
  from yutipy.models import MusicInfo
16
15
  from yutipy.utils.helpers import (
@@ -100,8 +99,8 @@ class Itunes:
100
99
  logger.debug(f"Response status code: {response.status_code}")
101
100
  response.raise_for_status()
102
101
  except requests.RequestException as e:
103
- logger.error(f"Network error while searching iTunes: {e}")
104
- raise NetworkException(f"Network error occurred: {e}")
102
+ logger.warning(f"Network error while searching iTunes: {e}")
103
+ return None
105
104
  except Exception as e:
106
105
  logger.exception(f"Unexpected error while searching iTunes: {e}")
107
106
  raise ItunesException(f"An error occurred while searching iTunes: {e}")
@@ -110,7 +109,7 @@ class Itunes:
110
109
  logger.debug(f"Parsing response JSON: {response.json()}")
111
110
  result = response.json()["results"]
112
111
  except (IndexError, KeyError, ValueError) as e:
113
- logger.error(f"Invalid response structure from iTunes: {e}")
112
+ logger.warning(f"Invalid response structure from iTunes: {e}")
114
113
  raise InvalidResponseException(f"Invalid response received: {e}")
115
114
 
116
115
  music_info = self._parse_result(artist, song, result)
yutipy/kkbox.py CHANGED
@@ -12,10 +12,8 @@ from dotenv import load_dotenv
12
12
 
13
13
  from yutipy.exceptions import (
14
14
  AuthenticationException,
15
- InvalidResponseException,
16
15
  InvalidValueException,
17
16
  KKBoxException,
18
- NetworkException,
19
17
  )
20
18
  from yutipy.logger import logger
21
19
  from yutipy.models import MusicInfo
@@ -187,15 +185,15 @@ class KKBox:
187
185
  logger.debug(f"Authentication response status code: {response.status_code}")
188
186
  response.raise_for_status()
189
187
  except requests.RequestException as e:
190
- logger.error(f"Network error during KKBOX authentication: {e}")
191
- raise NetworkException(f"Network error occurred: {e}")
188
+ logger.warning(f"Network error during KKBOX authentication: {e}")
189
+ return None
192
190
 
193
191
  if response.status_code == 200:
194
192
  response_json = response.json()
195
193
  response_json["requested_at"] = time()
196
194
  return response_json
197
195
  else:
198
- raise InvalidResponseException(
196
+ raise AuthenticationException(
199
197
  f"Invalid response received: {response.json()}"
200
198
  )
201
199
 
@@ -319,7 +317,7 @@ class KKBox:
319
317
  logger.debug(f"Parsing response JSON: {response.json()}")
320
318
  response.raise_for_status()
321
319
  except requests.RequestException as e:
322
- raise NetworkException(f"Network error occurred: {e}")
320
+ return None
323
321
 
324
322
  if response.status_code != 200:
325
323
  raise KKBoxException(f"Failed to search for music: {response.json()}")
yutipy/lastfm.py CHANGED
@@ -73,6 +73,46 @@ class LastFm:
73
73
  """Checks if the session is closed."""
74
74
  return self._is_session_closed
75
75
 
76
+ def get_user_profile(self, username: str):
77
+ """
78
+ Fetches the user profile information for the provided username.
79
+
80
+ Returns
81
+ -------
82
+ dict
83
+ A dictionary containing the user's profile information or error is username does not exist.
84
+ """
85
+ query = (
86
+ f"?method=user.getinfo&user={username}&api_key={self.api_key}&format=json"
87
+ )
88
+ query_url = self.__api_url + query
89
+
90
+ try:
91
+ response = self.__session.get(query_url, timeout=30)
92
+ except requests.RequestException as e:
93
+ logger.warning(f"Failed to fetch user profile: {e}")
94
+ return None
95
+
96
+ response_json = response.json()
97
+ result = response_json.get("user")
98
+ error = response_json.get("message")
99
+ if result:
100
+ images = [
101
+ {"size": image.get("size"), "url": image.get("#text")}
102
+ for image in result.get("image", [])
103
+ ]
104
+ return {
105
+ "name": result.get("realname"),
106
+ "username": result.get("name"),
107
+ "type": result.get("type"),
108
+ "url": result.get("url"),
109
+ "images": images,
110
+ }
111
+ elif error:
112
+ return {"error": error}
113
+ else:
114
+ return None
115
+
76
116
  def get_currently_playing(self, username: str) -> Optional[UserPlaying]:
77
117
  """
78
118
  Fetches information about the currently playing or most recent track for a user.
@@ -95,33 +135,38 @@ class LastFm:
95
135
  response = self.__session.get(query_url, timeout=30)
96
136
  response.raise_for_status()
97
137
  except requests.RequestException as e:
98
- logger.error(f"Failed to fetch user profile: {e}")
138
+ logger.warning(f"Failed to fetch user profile: {e}")
99
139
  return None
100
140
 
101
141
  response_json = response.json()
102
142
  result = response_json.get("recenttracks", {}).get("track", [])[0]
103
- album_art = [
104
- img.get("#text")
105
- for img in result.get("image", [])
106
- if img.get("size") == "extralarge"
107
- ]
108
- return UserPlaying(
109
- album_art="".join(album_art),
110
- album_title=result.get("album", {}).get("#text"),
111
- artists=", ".join(separate_artists(result.get("artist", {}).get("#text"))),
112
- id=result.get("mbid"),
113
- title=result.get("name"),
114
- url=result.get("url"),
115
- is_playing=result.get("@attr", {}).get("nowplaying", False),
116
- )
143
+ if result:
144
+ album_art = [
145
+ img.get("#text")
146
+ for img in result.get("image", [])
147
+ if img.get("size") == "extralarge"
148
+ ]
149
+ return UserPlaying(
150
+ album_art="".join(album_art),
151
+ album_title=result.get("album", {}).get("#text"),
152
+ artists=", ".join(
153
+ separate_artists(result.get("artist", {}).get("#text"))
154
+ ),
155
+ id=result.get("mbid"),
156
+ timestamp=result.get("date", {}).get("uts"),
157
+ title=result.get("name"),
158
+ url=result.get("url"),
159
+ is_playing=result.get("@attr", {}).get("nowplaying", False),
160
+ )
161
+ return None
117
162
 
118
163
 
119
164
  if __name__ == "__main__":
120
165
  with LastFm() as lastfm:
121
166
  username = input("Enter Lasfm Username: ").strip()
122
- result = lastfm.get_currently_playing(username=username, limit=5)
167
+ result = lastfm.get_user_profile(username=username)
123
168
 
124
169
  if result:
125
- pprint(asdict(result))
170
+ pprint(result)
126
171
  else:
127
172
  print("No result was found. Make sure the username is correct!")
yutipy/logger.py CHANGED
@@ -2,7 +2,7 @@ import logging
2
2
 
3
3
  # Create a logger for the library
4
4
  logger = logging.getLogger("yutipy")
5
- logger.setLevel(logging.NOTSET)
5
+ logger.setLevel(logging.CRITICAL)
6
6
 
7
7
 
8
8
  def enable_logging(level=logging.INFO, handler=None):
yutipy/models.py CHANGED
@@ -74,8 +74,11 @@ class UserPlaying(MusicInfo):
74
74
 
75
75
  Attributes
76
76
  ----------
77
+ timetamp : Optional[int]
78
+ Unix Timestamp (in seconds) when playback was started.
77
79
  is_playing : Optional[bool]
78
80
  Whether the music is currently playing or paused.
79
81
  """
80
82
 
83
+ timestamp: Optional[int] = None
81
84
  is_playing: Optional[bool] = None
yutipy/musicyt.py CHANGED
@@ -11,9 +11,9 @@ from yutipy.exceptions import (
11
11
  InvalidValueException,
12
12
  MusicYTException,
13
13
  )
14
+ from yutipy.logger import logger
14
15
  from yutipy.models import MusicInfo
15
16
  from yutipy.utils.helpers import are_strings_similar, is_valid_string
16
- from yutipy.logger import logger
17
17
 
18
18
 
19
19
  class MusicYT:
@@ -87,8 +87,8 @@ class MusicYT:
87
87
  try:
88
88
  results = self.ytmusic.search(query=query, limit=limit)
89
89
  except exceptions.YTMusicServerError as e:
90
- logger.error(f"Something went wrong while searching YTMusic: {e}")
91
- raise MusicYTException(f"Something went wrong while searching YTMusic: {e}")
90
+ logger.warning(f"Something went wrong while searching YTMusic: {e}")
91
+ return None
92
92
 
93
93
  for result in results:
94
94
  if self._is_relevant_result(artist, song, result):
@@ -181,9 +181,15 @@ class MusicYT:
181
181
  The extracted music information.
182
182
  """
183
183
  if result["resultType"] in ["song", "video"]:
184
- return self._get_song(result)
184
+ try:
185
+ return self._get_song(result)
186
+ except InvalidResponseException:
187
+ return None
185
188
  else:
186
- return self._get_album(result)
189
+ try:
190
+ return self._get_album(result)
191
+ except InvalidResponseException:
192
+ return None
187
193
 
188
194
  def _get_song(self, result: dict) -> MusicInfo:
189
195
  """
@@ -200,7 +206,9 @@ class MusicYT:
200
206
  The extracted music information.
201
207
  """
202
208
  title = result.get("title")
203
- artist_names = ", ".join([artist.get("name") for artist in result.get("artists", [])])
209
+ artist_names = ", ".join(
210
+ [artist.get("name") for artist in result.get("artists", [])]
211
+ )
204
212
  video_id = result.get("videoId")
205
213
  song_url = f"https://music.youtube.com/watch?v={video_id}"
206
214
  lyrics_id = self.ytmusic.get_watch_playlist(video_id)
yutipy/spotify.py CHANGED
@@ -14,9 +14,7 @@ from dotenv import load_dotenv
14
14
 
15
15
  from yutipy.exceptions import (
16
16
  AuthenticationException,
17
- InvalidResponseException,
18
17
  InvalidValueException,
19
- NetworkException,
20
18
  SpotifyAuthException,
21
19
  SpotifyException,
22
20
  )
@@ -198,8 +196,9 @@ class Spotify:
198
196
  logger.debug(f"Authentication response status code: {response.status_code}")
199
197
  response.raise_for_status()
200
198
  except requests.RequestException as e:
201
- logger.error(f"Network error during Spotify authentication: {e}")
202
- raise NetworkException(f"Network error occurred: {e}")
199
+ raise requests.RequestException(
200
+ f"Network error during Spotify authentication: {e}"
201
+ )
203
202
 
204
203
  if response.status_code == 200:
205
204
  response_json = response.json()
@@ -212,19 +211,34 @@ class Spotify:
212
211
 
213
212
  def __refresh_access_token(self):
214
213
  """Refreshes the token if it has expired."""
215
- if time() - self.__token_requested_at >= self.__token_expires_in:
216
- token_info = self.__get_access_token()
214
+ if not self.__access_token:
215
+ raise SpotifyAuthException("No access token was found.")
217
216
 
218
- try:
219
- self.save_access_token(token_info)
220
- except NotImplementedError as e:
221
- logger.warning(e)
217
+ try:
218
+ if time() - self.__token_requested_at >= self.__token_expires_in:
219
+ token_info = self.__get_access_token()
222
220
 
223
- self.__access_token = token_info.get("access_token")
224
- self.__token_expires_in = token_info.get("expires_in")
225
- self.__token_requested_at = token_info.get("requested_at")
221
+ try:
222
+ self.save_access_token(token_info)
223
+ except NotImplementedError as e:
224
+ logger.warning(e)
226
225
 
227
- logger.info("The access token is still valid, no need to refresh.")
226
+ self.__access_token = token_info.get("access_token")
227
+ self.__token_expires_in = token_info.get("expires_in")
228
+ self.__token_requested_at = token_info.get("requested_at")
229
+
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
+ )
235
+ except TypeError:
236
+ logger.debug(
237
+ f"token requested at: {self.__token_requested_at} | token expires in: {self.__token_expires_in}"
238
+ )
239
+ logger.info(
240
+ "Something went wrong while trying to refresh the access token. Set logging level to `DEBUG` to see the issue."
241
+ )
228
242
 
229
243
  def save_access_token(self, token_info: dict) -> None:
230
244
  """
@@ -335,7 +349,8 @@ class Spotify:
335
349
  )
336
350
  response.raise_for_status()
337
351
  except requests.RequestException as e:
338
- raise NetworkException(f"Network error occurred: {e}")
352
+ logger.warning(f"Network error during Spotify search: {e}")
353
+ return None
339
354
 
340
355
  if response.status_code != 200:
341
356
  raise SpotifyException(f"Failed to search for music: {response.json()}")
@@ -402,7 +417,8 @@ class Spotify:
402
417
  )
403
418
  response.raise_for_status()
404
419
  except requests.RequestException as e:
405
- raise NetworkException(f"Network error occurred: {e}")
420
+ logger.warning(f"Network error during Spotify search (advanced): {e}")
421
+ return None
406
422
 
407
423
  if response.status_code != 200:
408
424
  raise SpotifyException(
@@ -435,7 +451,8 @@ class Spotify:
435
451
  )
436
452
  response.raise_for_status()
437
453
  except requests.RequestException as e:
438
- raise NetworkException(f"Network error occurred: {e}")
454
+ logger.warning(f"Network error during Spotify get artist ids: {e}")
455
+ return None
439
456
 
440
457
  if response.status_code != 200:
441
458
  return None
@@ -814,15 +831,16 @@ class SpotifyAuth:
814
831
  logger.debug(f"Authentication response status code: {response.status_code}")
815
832
  response.raise_for_status()
816
833
  except requests.RequestException as e:
817
- logger.error(f"Network error during Spotify authentication: {e}")
818
- raise NetworkException(f"Network error occurred: {e}")
834
+ raise requests.RequestException(
835
+ f"Network error during Spotify authentication: {e}"
836
+ )
819
837
 
820
838
  if response.status_code == 200:
821
839
  response_json = response.json()
822
840
  response_json["requested_at"] = time()
823
841
  return response_json
824
842
  else:
825
- raise InvalidResponseException(
843
+ raise AuthenticationException(
826
844
  f"Invalid response received: {response.json()}"
827
845
  )
828
846
 
@@ -831,20 +849,30 @@ class SpotifyAuth:
831
849
  if not self.__access_token:
832
850
  raise SpotifyAuthException("No access token was found.")
833
851
 
834
- if time() - self.__token_requested_at >= self.__token_expires_in:
835
- token_info = self.__get_access_token(refresh_token=self.__refresh_token)
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)
836
855
 
837
- try:
838
- self.save_access_token(token_info)
839
- except NotImplementedError as e:
840
- logger.warning(e)
856
+ try:
857
+ self.save_access_token(token_info)
858
+ except NotImplementedError as e:
859
+ logger.warning(e)
841
860
 
842
- self.__access_token = token_info.get("access_token")
843
- self.__refresh_token = token_info.get("refresh_token")
844
- self.__token_expires_in = token_info.get("expires_in")
845
- self.__token_requested_at = token_info.get("requested_at")
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")
846
865
 
847
- logger.info("The access token is still valid, no need to refresh.")
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
+ )
848
876
 
849
877
  @staticmethod
850
878
  def generate_state() -> str:
@@ -1022,7 +1050,7 @@ class SpotifyAuth:
1022
1050
  except NotImplementedError as e:
1023
1051
  logger.warning(e)
1024
1052
 
1025
- def get_user_profile(self):
1053
+ def get_user_profile(self) -> Optional[dict]:
1026
1054
  """
1027
1055
  Fetches the user's display name and profile images.
1028
1056
 
@@ -1050,17 +1078,18 @@ class SpotifyAuth:
1050
1078
  response = self.__session.get(query_url, headers=header, timeout=30)
1051
1079
  response.raise_for_status()
1052
1080
  except requests.RequestException as e:
1053
- logger.error(f"Failed to fetch user profile: {e}")
1081
+ logger.warning(f"Failed to fetch user profile: {e}")
1054
1082
  return None
1055
1083
 
1056
1084
  if response.status_code != 200:
1057
- logger.error(f"Unexpected response: {response.json()}")
1085
+ logger.warning(f"Unexpected response: {response.json()}")
1058
1086
  return None
1059
1087
 
1060
- response_json = response.json()
1088
+ result = response.json()
1061
1089
  return {
1062
- "display_name": response_json.get("display_name"),
1063
- "images": response_json.get("images", []),
1090
+ "display_name": result.get("display_name"),
1091
+ "images": result.get("images", []),
1092
+ "url": result.get("external_urls", {}).get("spotify")
1064
1093
  }
1065
1094
 
1066
1095
  def get_currently_playing(self) -> Optional[UserPlaying]:
@@ -1099,16 +1128,17 @@ class SpotifyAuth:
1099
1128
  response = self.__session.get(query_url, headers=header, timeout=30)
1100
1129
  response.raise_for_status()
1101
1130
  except requests.RequestException as e:
1102
- raise NetworkException(f"Network error occurred: {e}")
1131
+ logger.warning(f"Error while getting Spotify user activity: {e}")
1132
+ return None
1103
1133
 
1104
1134
  if response.status_code == 204:
1105
1135
  logger.info("Requested user is currently not listening to any music.")
1106
1136
  return None
1107
1137
  if response.status_code != 200:
1108
1138
  try:
1109
- logger.error(f"Unexpected response: {response.json()}")
1139
+ logger.warning(f"Unexpected response: {response.json()}")
1110
1140
  except requests.exceptions.JSONDecodeError:
1111
- logger.error(
1141
+ logger.warning(
1112
1142
  f"Response Code: {response.status_code}, Reason: {response.reason}"
1113
1143
  )
1114
1144
  return None
@@ -1122,6 +1152,8 @@ class SpotifyAuth:
1122
1152
  guess,
1123
1153
  use_translation=False,
1124
1154
  )
1155
+ # Spotify returns timestamp in milliseconds, so convert milliseconds to seconds:
1156
+ timestamp = response_json.get("timestamp") / 1000.0
1125
1157
  return UserPlaying(
1126
1158
  album_art=result.get("album", {}).get("images", [])[0].get("url"),
1127
1159
  album_title=result.get("album", {}).get("name"),
@@ -1138,6 +1170,7 @@ class SpotifyAuth:
1138
1170
  lyrics=None,
1139
1171
  release_date=result.get("album", {}).get("release_date"),
1140
1172
  tempo=None,
1173
+ timestamp=timestamp,
1141
1174
  title=result.get("name"),
1142
1175
  type=result.get("type"),
1143
1176
  upc=result.get("external_ids", {}).get("upc"),
yutipy/yutipy_music.py CHANGED
@@ -22,8 +22,23 @@ class YutipyMusic:
22
22
  Instead of calling each service separately, you can use this class to get the information from all services at once.
23
23
  """
24
24
 
25
- def __init__(self) -> None:
26
- """Initializes the YutipyMusic class."""
25
+ def __init__(
26
+ self,
27
+ custom_kkbox_class = KKBox,
28
+ custom_spotify_class = Spotify,
29
+ ) -> None:
30
+ """
31
+ Initializes the YutipyMusic class.
32
+
33
+ Parameters
34
+ ----------
35
+ custom_kkbox_class : Optional[type], optional
36
+ A custom class inherited from ``KKBox`` to override the default KKBox implementation.
37
+ This class should implement ``load_access_token()`` and ``save_access_token()`` methods. Default is ``KKBox``.
38
+ custom_spotify_class : Optional[type], optional
39
+ A custom class inherited from ``Spotify`` to override the default Spotify implementation.
40
+ This class should implement ``load_access_token()`` and ``save_access_token()`` methods. Default is ``Spotify``.
41
+ """
27
42
  self.music_info = MusicInfos()
28
43
  self.normalize_non_english = True
29
44
  self.album_art_priority = ["deezer", "ytmusic", "itunes"]
@@ -34,8 +49,8 @@ class YutipyMusic:
34
49
  }
35
50
 
36
51
  try:
37
- self.services["kkbox"] = KKBox()
38
- except (KKBoxException) as e:
52
+ self.services["kkbox"] = custom_kkbox_class()
53
+ except KKBoxException as e:
39
54
  logger.warning(
40
55
  f"{self.__class__.__name__}: Skipping KKBox due to KKBoxException: {e}"
41
56
  )
@@ -44,8 +59,8 @@ class YutipyMusic:
44
59
  self.album_art_priority.insert(idx, "kkbox")
45
60
 
46
61
  try:
47
- self.services["spotify"] = Spotify()
48
- except (SpotifyException) as e:
62
+ self.services["spotify"] = custom_spotify_class()
63
+ except SpotifyException as e:
49
64
  logger.warning(
50
65
  f"{self.__class__.__name__}: Skipping Spotify due to SpotifyException: {e}"
51
66
  )
@@ -114,7 +129,7 @@ class YutipyMusic:
114
129
  result = future.result()
115
130
  self._combine_results(result, service_name)
116
131
  except Exception as e:
117
- logger.error(
132
+ logger.warning(
118
133
  f"Error occurred while searching with {service_name}: {e}"
119
134
  )
120
135
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yutipy
3
- Version: 2.2.3
3
+ Version: 2.2.5
4
4
  Summary: A simple package for retrieving music information from various music platforms APIs.
5
5
  Author: Cheap Nightbot
6
6
  Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
@@ -0,0 +1,22 @@
1
+ yutipy/__init__.py,sha256=Zrw3cr_6khXp1IgQdZxGcUM9A64GYgPs-6rlqSukW5Q,294
2
+ yutipy/deezer.py,sha256=xI7ZDoPHES64_uRN4aYA-kj56V4tFU8xYkZYPXidl_I,11266
3
+ yutipy/exceptions.py,sha256=zz0XyyZr5xRcmRyw3hdTGaVRcwRn_RSYZdmwmuO0sEM,1379
4
+ yutipy/itunes.py,sha256=3YBLoWPhIx6ODE2G-msRppCHo0DNDGZADj-XvL_f5H0,7867
5
+ yutipy/kkbox.py,sha256=ZOIW8rQuiYEWmsXW3Dw9Q5mw71Jh_tMRAULvEgAuDew,19310
6
+ yutipy/lastfm.py,sha256=qv834EdJH5We90JME06dvUQBc0JLDphJOnbbzMfEQAQ,5797
7
+ yutipy/logger.py,sha256=GyLBlfQZ6pLNJ5MbyQSvcD_PkxmFdX41DPq5aeG1z68,1316
8
+ yutipy/models.py,sha256=45M-bNHusaAan_Ta_E9DyvsWujsT-ivbJqIfy2-i3R8,2343
9
+ yutipy/musicyt.py,sha256=WKR4J4ru1uqNa7ORSZKlet1FxXQlHzlXTg9CIUqnsoc,9462
10
+ yutipy/spotify.py,sha256=aKOntF5uBh_X8A779sDlRUAqihJid5NCSmEGOxTIQF0,45708
11
+ yutipy/yutipy_music.py,sha256=MNNh2WT-7GTAykAabLF6p4-0uXiIIbuogswmb-_QqtQ,7272
12
+ yutipy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ yutipy/cli/config.py,sha256=e5RIq6RxVxxzx30nKVMa06gwyQ258s7U0WA1xvJuR_0,4543
14
+ yutipy/cli/search.py,sha256=8SQw0bjRzRqAg-FuVz9aWjB2KBZqmCf38SyKAQ3rx5E,3025
15
+ yutipy/utils/__init__.py,sha256=AZaqvs6AJwnqwJuodbGnHu702WSUqc8plVC16SppOcU,239
16
+ yutipy/utils/helpers.py,sha256=W3g9iqoSygcFFCKCp2sk0NQrZOEG26wI2XuNi9pgAXE,5207
17
+ yutipy-2.2.5.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
18
+ yutipy-2.2.5.dist-info/METADATA,sha256=fTeNY_lf4jWyeVv1uhY74Ak1G58R3SOr-uFJGh0hGeI,6522
19
+ yutipy-2.2.5.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
20
+ yutipy-2.2.5.dist-info/entry_points.txt,sha256=BrgmanaPjQqKQ3Ip76JLcsPgGANtrBSURf5CNIxl1HA,106
21
+ yutipy-2.2.5.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
22
+ yutipy-2.2.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,22 +0,0 @@
1
- yutipy/__init__.py,sha256=Zrw3cr_6khXp1IgQdZxGcUM9A64GYgPs-6rlqSukW5Q,294
2
- yutipy/deezer.py,sha256=PTTTfeORh1HZ_ta7_Uu4YARouSknUnAxO9AQJPFm4v0,11402
3
- yutipy/exceptions.py,sha256=oMuhNfDJ2AFsM_fJn6sayxMqIJRY_ihHRmL0U2IK6qQ,1501
4
- yutipy/itunes.py,sha256=fV7KLsXWvfM_97KwVwn_KfnWM7j0cVGE7RytvnDGlZM,7929
5
- yutipy/kkbox.py,sha256=7kgq0ZliiA16CeJa49faSUS8_SdqQPp6M-cKdnMFlaM,19447
6
- yutipy/lastfm.py,sha256=0adVGigS8Kqnu52k-ry5eqHR6koktgKBhCNI1riUMfk,4302
7
- yutipy/logger.py,sha256=4jXB-Qt2r75kpuAdxSGIwGrcpUyugxs9ROmBru9NtTw,1314
8
- yutipy/models.py,sha256=_92e54uXXCw53oWZiNLBBai6C0InOZMJL7r8GJ5smbM,2215
9
- yutipy/musicyt.py,sha256=6Vz8bI8hDNFoDKRh6GK90dGMRbn_d5d6eGPsaYogb_Y,9315
10
- yutipy/spotify.py,sha256=pf_vaQXMxExMjIGuId4eTHaSO112hAqf-6YgWLSa5uQ,44108
11
- yutipy/yutipy_music.py,sha256=cHJ95HxGILweVrnEacj8tTlU0NPxMpuDVMpngdX0mZQ,6558
12
- yutipy/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- yutipy/cli/config.py,sha256=e5RIq6RxVxxzx30nKVMa06gwyQ258s7U0WA1xvJuR_0,4543
14
- yutipy/cli/search.py,sha256=8SQw0bjRzRqAg-FuVz9aWjB2KBZqmCf38SyKAQ3rx5E,3025
15
- yutipy/utils/__init__.py,sha256=AZaqvs6AJwnqwJuodbGnHu702WSUqc8plVC16SppOcU,239
16
- yutipy/utils/helpers.py,sha256=W3g9iqoSygcFFCKCp2sk0NQrZOEG26wI2XuNi9pgAXE,5207
17
- yutipy-2.2.3.dist-info/licenses/LICENSE,sha256=_89JsS2QnBG8tAb5-VWbJDj_uJ002zPJAYBJJdh3DPY,1071
18
- yutipy-2.2.3.dist-info/METADATA,sha256=GU0nDx1yzsjArFuBvjNw9LdFVmktbOFyFCjUFMklD-o,6522
19
- yutipy-2.2.3.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
20
- yutipy-2.2.3.dist-info/entry_points.txt,sha256=BrgmanaPjQqKQ3Ip76JLcsPgGANtrBSURf5CNIxl1HA,106
21
- yutipy-2.2.3.dist-info/top_level.txt,sha256=t2A5V2_mUcfnHkbCy6tAQlb3909jDYU5GQgXtA4756I,7
22
- yutipy-2.2.3.dist-info/RECORD,,